From 875bd20a7327b7fd03d1b020e25054755e42d5ad Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 16 Jul 2010 13:52:02 +0300 Subject: [PATCH 01/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation 1. Changed the lazy optimization for subqueries that can be materialized into bottom-up optimization during the optimization of the main query. The main change is implemented by the method Item_in_subselect::setup_engine. All other changes were required to correct problems resulting from changing the order of optimization. Most of these problems followed the same pattern - there are some shared structures between a subquery and its parent query. Depending on which one is optimized first (parent or child query), these shared strucutres may get different values, thus resulting in an inconsistent query plan. 2. Changed the code-generation for subquery materialization to be performed in runtime memory for each (re)execution, instead of in statement memory (once per prepared statement). - Item_in_subselect::setup_engine() no longer creates materialization related objects in statement memory. - Merged subselect_hash_sj_engine::init_permanent and subselect_hash_sj_engine::init_runtime into subselect_hash_sj_engine::init, which is called for each (re)execution. - Fixed deletion of the temp table accordingly. mysql-test/r/subselect_mat.result: Adjusted changed EXPLAIN because of earlier optimization of subqueries. --- mysql-test/r/subselect_mat.result | 4 +- sql/item_subselect.cc | 205 +++++++++++++++--------------- sql/item_subselect.h | 5 +- sql/sql_class.cc | 1 + sql/sql_class.h | 9 +- sql/sql_select.cc | 43 +++++-- 6 files changed, 146 insertions(+), 121 deletions(-) diff --git a/mysql-test/r/subselect_mat.result b/mysql-test/r/subselect_mat.result index 41531e95db8..c8ebfc9b5d4 100644 --- a/mysql-test/r/subselect_mat.result +++ b/mysql-test/r/subselect_mat.result @@ -1139,7 +1139,7 @@ insert into t1 values (5); explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away -2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL no matching row in const table select min(a1) from t1 where 7 in (select b1 from t2 group by b1); min(a1) set @@optimizer_switch='default,materialization=off'; @@ -1153,7 +1153,7 @@ set @@optimizer_switch='default,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away -2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL no matching row in const table select min(a1) from t1 where 7 in (select b1 from t2); min(a1) set @@optimizer_switch='default,materialization=off'; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 454a3929f99..fd9da843f31 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -166,6 +166,7 @@ void Item_in_subselect::cleanup() Item_subselect::~Item_subselect() { delete engine; + engine= NULL; } Item_subselect::trans_res @@ -2220,73 +2221,73 @@ void Item_in_subselect::update_used_tables() bool Item_in_subselect::setup_engine() { - subselect_hash_sj_engine *new_engine= NULL; - bool res= FALSE; + subselect_hash_sj_engine *mat_engine= NULL; + subselect_single_select_engine *select_engine; DBUG_ENTER("Item_in_subselect::setup_engine"); - if (engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE) - { - /* Create/initialize objects in permanent memory. */ - subselect_single_select_engine *old_engine; - Query_arena *arena= thd->stmt_arena, backup; - - old_engine= (subselect_single_select_engine*) engine; - - if (arena->is_conventional()) - arena= 0; - else - thd->set_n_backup_active_arena(arena, &backup); - - if (!(new_engine= new subselect_hash_sj_engine(thd, this, - old_engine)) || - new_engine->init_permanent(unit->get_unit_column_types())) - { - Item_subselect::trans_res trans_res; - /* - If for some reason we cannot use materialization for this IN predicate, - delete all materialization-related objects, and apply the IN=>EXISTS - transformation. - */ - delete new_engine; - new_engine= NULL; - exec_method= NOT_TRANSFORMED; - if (left_expr->cols() == 1) - trans_res= single_value_in_to_exists_transformer(old_engine->join, - &eq_creator); - else - trans_res= row_value_in_to_exists_transformer(old_engine->join); - res= (trans_res != Item_subselect::RES_OK); - } - if (new_engine) - engine= new_engine; - - if (arena) - thd->restore_active_arena(arena, &backup); - } - else - { - DBUG_ASSERT(engine->engine_type() == subselect_engine::HASH_SJ_ENGINE); - new_engine= (subselect_hash_sj_engine*) engine; - } - - /* Initilizations done in runtime memory, repeated for each execution. */ - if (new_engine) + + SELECT_LEX *save_select= thd->lex->current_select; + thd->lex->current_select= get_select_lex(); + int res= thd->lex->current_select->join->optimize(); + thd->lex->current_select= save_select; + if (res) + DBUG_RETURN(TRUE); + + /* + The select_engine (that executes transformed IN=>EXISTS subselects) is + pre-created at parse time, and is stored in statment memory (preserved + across PS executions). + */ + DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE); + select_engine= (subselect_single_select_engine*) engine; + + /* Create/initialize execution objects. */ + if (!(mat_engine= new subselect_hash_sj_engine(thd, this, select_engine))) + DBUG_RETURN(TRUE); + + if (mat_engine->init(&select_engine->join->fields_list)) { + Item_subselect::trans_res trans_res; /* - Reset the LIMIT 1 set in Item_exists_subselect::fix_length_and_dec. - TODO: - Currently we set the subquery LIMIT to infinity, and this is correct - because we forbid at parse time LIMIT inside IN subqueries (see - Item_in_subselect::test_limit). However, once we allow this, here - we should set the correct limit if given in the query. + If for some reason we cannot use materialization for this IN predicate, + delete all materialization-related objects, and apply the IN=>EXISTS + transformation. */ - unit->global_parameters->select_limit= NULL; - if ((res= new_engine->init_runtime())) - DBUG_RETURN(res); + delete mat_engine; + mat_engine= NULL; + exec_method= NOT_TRANSFORMED; + + if (left_expr->cols() == 1) + trans_res= single_value_in_to_exists_transformer(select_engine->join, + &eq_creator); + else + trans_res= row_value_in_to_exists_transformer(select_engine->join); + + /* + The IN=>EXISTS transformation above injects new predicates into the + WHERE and HAVING clauses. Since the subquery was already optimized, + below we force its reoptimization with the new injected conditions + by the first call to subselect_single_select_engine::exec(). + This is the only case of lazy subquery optimization in the server. + */ + DBUG_ASSERT(select_engine->join->optimized); + select_engine->join->optimized= false; + DBUG_RETURN(trans_res != Item_subselect::RES_OK); } - DBUG_RETURN(res); + /* + Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec. + TODO: + Currently we set the subquery LIMIT to infinity, and this is correct + because we forbid at parse time LIMIT inside IN subqueries (see + Item_in_subselect::test_limit). However, once we allow this, here + we should set the correct limit if given in the query. + */ + unit->global_parameters->select_limit= NULL; + + engine= mat_engine; + DBUG_RETURN(FALSE); } @@ -3787,13 +3788,14 @@ bitmap_init_memroot(MY_BITMAP *map, uint n_bits, MEM_ROOT *mem_root) @retval FALSE otherwise */ -bool subselect_hash_sj_engine::init_permanent(List *tmp_columns) +bool subselect_hash_sj_engine::init(List *tmp_columns) { + select_union *result_sink; /* Options to create_tmp_table. */ ulonglong tmp_create_options= thd->options | TMP_TABLE_ALL_COLUMNS; /* | TMP_TABLE_FORCE_MYISAM; TIMOUR: force MYISAM */ - DBUG_ENTER("subselect_hash_sj_engine::init_permanent"); + DBUG_ENTER("subselect_hash_sj_engine::init"); if (bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements, thd->mem_root) || @@ -3822,15 +3824,16 @@ bool subselect_hash_sj_engine::init_permanent(List *tmp_columns) DBUG_RETURN(TRUE); } */ - if (!(result= new select_materialize_with_stats)) + if (!(result_sink= new select_materialize_with_stats)) + DBUG_RETURN(TRUE); + result_sink->get_tmp_table_param()->materialized_subquery= true; + if (result_sink->create_result_table(thd, tmp_columns, TRUE, + tmp_create_options, + "materialized subselect", TRUE)) DBUG_RETURN(TRUE); - if (((select_union*) result)->create_result_table( - thd, tmp_columns, TRUE, tmp_create_options, - "materialized subselect", TRUE)) - DBUG_RETURN(TRUE); - - tmp_table= ((select_union*) result)->table; + tmp_table= result_sink->table; + result= result_sink; /* If the subquery has blobs, or the total key lenght is bigger than @@ -3867,6 +3870,17 @@ bool subselect_hash_sj_engine::init_permanent(List *tmp_columns) !(lookup_engine= make_unique_engine())) DBUG_RETURN(TRUE); + /* + Repeat name resolution for 'cond' since cond is not part of any + clause of the query, and it is not 'fixed' during JOIN::prepare. + */ + if (semi_join_conds && !semi_join_conds->fixed && + semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds)) + DBUG_RETURN(TRUE); + /* Let our engine reuse this query plan for materialization. */ + materialize_join= materialize_engine->join; + materialize_join->change_result(result); + DBUG_RETURN(FALSE); } @@ -3957,8 +3971,6 @@ subselect_hash_sj_engine::make_unique_engine() Item_iterator_row it(item_in->left_expr); /* The only index on the temporary table. */ KEY *tmp_key= tmp_table->key_info; - /* Number of keyparts in tmp_key. */ - uint tmp_key_parts= tmp_key->key_parts; JOIN_TAB *tab; DBUG_ENTER("subselect_hash_sj_engine::make_unique_engine"); @@ -3981,35 +3993,6 @@ subselect_hash_sj_engine::make_unique_engine() } -/** - Initialize members of the engine that need to be re-initilized at each - execution. - - @retval TRUE if a memory allocation error occurred - @retval FALSE if success -*/ - -bool subselect_hash_sj_engine::init_runtime() -{ - /* - Create and optimize the JOIN that will be used to materialize - the subquery if not yet created. - */ - materialize_engine->prepare(); - /* - Repeat name resolution for 'cond' since cond is not part of any - clause of the query, and it is not 'fixed' during JOIN::prepare. - */ - if (semi_join_conds && !semi_join_conds->fixed && - semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds)) - return TRUE; - /* Let our engine reuse this query plan for materialization. */ - materialize_join= materialize_engine->join; - materialize_join->change_result(result); - return FALSE; -} - - subselect_hash_sj_engine::~subselect_hash_sj_engine() { delete lookup_engine; @@ -4019,6 +4002,16 @@ subselect_hash_sj_engine::~subselect_hash_sj_engine() } +int subselect_hash_sj_engine::prepare() +{ + /* + Create and optimize the JOIN that will be used to materialize + the subquery if not yet created. + */ + return materialize_engine->prepare(); +} + + /** Cleanup performed after each PS execution. @@ -4036,6 +4029,12 @@ void subselect_hash_sj_engine::cleanup() count_null_only_columns= 0; strategy= UNDEFINED; materialize_engine->cleanup(); + /* + Restore the original Item_in_subselect engine. This engine is created once + at parse time and stored across executions, while all other materialization + related engines are created and chosen for each execution. + */ + ((Item_in_subselect *) item)->engine= materialize_engine; if (lookup_engine_type == TABLE_SCAN_ENGINE || lookup_engine_type == ROWID_MERGE_ENGINE) { @@ -4052,6 +4051,9 @@ void subselect_hash_sj_engine::cleanup() DBUG_ASSERT(lookup_engine->engine_type() == UNIQUESUBQUERY_ENGINE); lookup_engine->cleanup(); result->cleanup(); /* Resets the temp table as well. */ + DBUG_ASSERT(tmp_table); + free_tmp_table(thd, tmp_table); + tmp_table= NULL; } @@ -4080,9 +4082,8 @@ int subselect_hash_sj_engine::exec() the subquery predicate. */ thd->lex->current_select= materialize_engine->select_lex; - if ((res= materialize_join->optimize())) - goto err; /* purecov: inspected */ - DBUG_ASSERT(!is_materialized); /* We should materialize only once. */ + /* The subquery should be optimized, and materialized only once. */ + DBUG_ASSERT(materialize_join->optimized && !is_materialized); materialize_join->exec(); if ((res= test(materialize_join->error || thd->is_fatal_error))) goto err; diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 72f73d58867..15b2dc8448f 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -817,10 +817,9 @@ public: } ~subselect_hash_sj_engine(); - bool init_permanent(List *tmp_columns); - bool init_runtime(); + bool init(List *tmp_columns); void cleanup(); - int prepare() { return 0; } /* Override virtual function in base class. */ + int prepare(); int exec(); virtual void print(String *str, enum_query_type query_type); uint cols() diff --git a/sql/sql_class.cc b/sql/sql_class.cc index c85ea76c70d..8fd73fec960 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -3052,6 +3052,7 @@ void TMP_TABLE_PARAM::init() table_charset= 0; precomputed_group_by= 0; bit_fields_as_long= 0; + materialized_subquery= 0; skip_create_table= 0; DBUG_VOID_RETURN; } diff --git a/sql/sql_class.h b/sql/sql_class.h index 6ef330e8ee6..9784fe01536 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -2852,6 +2852,8 @@ public: uint convert_blob_length; CHARSET_INFO *table_charset; bool schema_table; + /* TRUE if the temp table is created for subquery materialization. */ + bool materialized_subquery; /* True if GROUP BY and its aggregate functions are already computed by a table access method (e.g. by loose index scan). In this case @@ -2875,8 +2877,8 @@ public: TMP_TABLE_PARAM() :copy_field(0), group_parts(0), group_length(0), group_null_parts(0), convert_blob_length(0), - schema_table(0), precomputed_group_by(0), force_copy_fields(0), - bit_fields_as_long(0), skip_create_table(0) + schema_table(0), materialized_subquery(0), precomputed_group_by(0), + force_copy_fields(0), bit_fields_as_long(0), skip_create_table(0) {} ~TMP_TABLE_PARAM() { @@ -2905,6 +2907,7 @@ public: bool send_data(List &items); bool send_eof(); bool flush(); + TMP_TABLE_PARAM *get_tmp_table_param() { return &tmp_table_param; } virtual bool create_result_table(THD *thd, List *column_types, bool is_distinct, ulonglong options, @@ -2969,7 +2972,7 @@ protected: ha_rows count_rows; public: - select_materialize_with_stats() {} + select_materialize_with_stats() { tmp_table_param.init(); } virtual bool create_result_table(THD *thd, List *column_types, bool is_distinct, ulonglong options, const char *alias, bool bit_fields_as_long); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 887196fed9b..a5baa60fe32 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2586,14 +2586,13 @@ err: Setup for execution all subqueries of a query, for which the optimizer chose hash semi-join. - @details Iterate over all subqueries of the query, and if they are under an - IN predicate, and the optimizer chose to compute it via hash semi-join: - - try to initialize all data structures needed for the materialized execution - of the IN predicate, - - if this fails, then perform the IN=>EXISTS transformation which was - previously blocked during JOIN::prepare. - - This method is part of the "code generation" query processing phase. + @details Iterate over all immediate child subqueries of the query, and if + they are under an IN predicate, and the optimizer chose to compute it via + materialization: + - optimize each subquery, + - choose an optimial execution strategy for the IN predicate - either + materialization, or an IN=>EXISTS transformation with an approriate + engine. This phase must be called after substitute_for_best_equal_field() because that function may replace items with other items from a multiple equality, @@ -7925,7 +7924,7 @@ bool TABLE_REF::tmp_table_index_lookup_init(THD *thd, use that information instead. */ cur_ref_buff + null_count, - null_count ? key_buff : 0, + null_count ? cur_ref_buff : 0, cur_key_part->length, items[i], value); cur_ref_buff+= cur_key_part->store_length; } @@ -11408,10 +11407,30 @@ create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List &fields, { if (thd->is_fatal_error) goto err; // Got OOM - continue; // Some kindf of const item + continue; // Some kind of const item } if (type == Item::SUM_FUNC_ITEM) - ((Item_sum *) item)->result_field= new_field; + { + Item_sum *agg_item= (Item_sum *) item; + /* + Update the result field only if it has never been set, or if the + created temporary table is not to be used for subquery + materialization. + + The reason is that for subqueries that require materialization as part + of their plan, we create the 'external' temporary table needed for IN + execution, after the 'internal' temporary table needed for grouping. + Since both the external and the internal temporary tables are created + for the same list of SELECT fields of the subquery, setting + 'result_field' for each invocation of create_tmp_table overrides the + previous value of 'result_field'. + + The condition below prevents the creation of the external temp table + to override the 'result_field' that was set for the internal temp table. + */ + if (!agg_item->result_field || !param->materialized_subquery) + agg_item->result_field= new_field; + } tmp_from_field++; reclength+=new_field->pack_length(); if (!(new_field->flags & NOT_NULL_FLAG)) @@ -19240,6 +19259,8 @@ bool JOIN::change_result(select_result *res) { DBUG_ENTER("JOIN::change_result"); result= res; + if (tmp_join) + tmp_join->result= res; if (!procedure && (result->prepare(fields_list, select_lex->master_unit()) || result->prepare2())) { From 78ddd9ffe621697a8518bf0a82821032d4ff48fe Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 18 Jul 2010 14:46:08 +0300 Subject: [PATCH 02/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Step1 in the separation of the creation of IN->EXISTS equi-join conditions from their injection. The goal of this separation is to make it possible that the IN->EXISTS conditions can be used for cost estimation without actually modifying the subquery. This patch separates single_value_in_to_exists_transformer() into two methods: - create_single_value_in_to_exists_cond(), and - inject_single_value_in_to_exists_cond() The patch performs minimal refactoring of the code so that it is easier to solve problems resulting from the separation. There is a lot to be simplified in this code, but this will be done separately. --- sql/item_subselect.cc | 228 +++++++++++++++++++++++++++++------------- sql/item_subselect.h | 10 ++ 2 files changed, 167 insertions(+), 71 deletions(-) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index f9a9e7dfdd7..8218f927ad3 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1521,16 +1521,35 @@ Item_in_subselect::single_value_transformer(JOIN *join, */ Item_subselect::trans_res -Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creator *func) +Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, + Comp_creator *func) +{ + Item *where_term; + Item *having_term; + Item_subselect::trans_res res; + + res= create_single_value_in_to_exists_cond(join, func, + &where_term, &having_term); + if (res != RES_OK) + return res; + res= inject_single_value_in_to_exists_cond(join, func, + where_term, having_term); + return res; +} + + +Item_subselect::trans_res +Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, + Comp_creator *func, + Item **where_term, + Item **having_term) { SELECT_LEX *select_lex= join->select_lex; - DBUG_ENTER("Item_in_subselect::single_value_in_to_exists_transformer"); + DBUG_ENTER("Item_in_subselect::create_single_value_in_to_exists_cond"); - select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; if (join->having || select_lex->with_sum_func || select_lex->group_list.elements) { - bool tmp; Item *item= func->create(expr, new Item_ref_null_helper(&select_lex->context, this, @@ -1546,24 +1565,12 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat */ item= new Item_func_trig_cond(item, get_cond_guard(0)); } - - /* - AND and comparison functions can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - select_lex->having= join->having= and_items(join->having, item); - if (join->having == item) - item->name= (char*)in_having_cond; - select_lex->having_fix_field= 1; - /* - we do not check join->having->fixed, because Item_and (from and_items) - or comparison function (from func->create) can't be fixed after creation - */ - tmp= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (tmp) + + if (item->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); + + *having_term= item; + *where_term= NULL; } else { @@ -1571,13 +1578,8 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat if (select_lex->table_list.elements) { - bool tmp; - Item *having= item, *orig_item= item; - select_lex->item_list.empty(); - select_lex->item_list.push_back(new Item_int("Not_used", - (longlong) 1, - MY_INT64_NUM_DECIMAL_DIGITS)); - select_lex->ref_pointer_array[0]= select_lex->item_list.head(); + Item *having= item; + Item *orig_item= item; item= func->create(expr, item); if (!abort_on_null && orig_item->maybe_null) @@ -1589,23 +1591,12 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat get_cond_guard(0)))) DBUG_RETURN(RES_ERROR); } - /* - Item_is_not_null_test can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - having->name= (char*)in_having_cond; - select_lex->having= join->having= having; - select_lex->having_fix_field= 1; - /* - we do not check join->having->fixed, because Item_and (from - and_items) or comparison function (from func->create) can't be - fixed after creation - */ - tmp= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (tmp) + + if (having->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); + + *having_term= having; + item= new Item_cond_or(item, new Item_func_isnull(orig_item)); } @@ -1618,30 +1609,14 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat if (!(item= new Item_func_trig_cond(item, get_cond_guard(0)))) DBUG_RETURN(RES_ERROR); } - /* - TODO: figure out why the following is done here in - single_value_transformer but there is no corresponding action in - row_value_transformer? - */ - item->name= (char *)in_additional_cond; - /* - AND can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - select_lex->where= join->conds= and_items(join->conds, item); - select_lex->where->top_level_item(); - /* - we do not check join->conds->fixed, because Item_and can't be fixed - after creation - */ - if (join->conds->fix_fields(thd, 0)) + if (item->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); + + *where_term= item; } else { - bool tmp; if (select_lex->master_unit()->is_union()) { /* @@ -1661,17 +1636,128 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat get_cond_guard(0)))) DBUG_RETURN(RES_ERROR); } - new_having->name= (char*)in_having_cond; - select_lex->having= join->having= new_having; + + if (new_having->fix_fields(thd, 0)) + DBUG_RETURN(RES_ERROR); + + *having_term= new_having; + *where_term= NULL; + } + else + { + *having_term= NULL; + *where_term= (Item*) select_lex->item_list.head(); + } + } + } + + DBUG_RETURN(RES_OK); +} + + + +Item_subselect::trans_res +Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, + Comp_creator *func, + Item *where_term, + Item *having_term) +{ + SELECT_LEX *select_lex= join->select_lex; + bool fix_res; + DBUG_ENTER("Item_in_subselect::single_value_in_to_exists_transformer"); + + select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + if (join->having || select_lex->with_sum_func || + select_lex->group_list.elements) + { + /* + AND and comparison functions can't be changed during fix_fields() + we can assign select_lex->having here, and pass 0 as last + argument (reference) to fix_fields() + */ + select_lex->having= join->having= and_items(join->having, having_term); + if (join->having == having_term) + having_term->name= (char*)in_having_cond; + select_lex->having_fix_field= 1; + /* + we do not check join->having->fixed, because Item_and (from and_items) + or comparison function (from func->create) can't be fixed after creation + */ + if (!join->having->fixed) + fix_res= join->having->fix_fields(thd, 0); + select_lex->having_fix_field= 0; + if (fix_res) + DBUG_RETURN(RES_ERROR); + } + else + { + if (select_lex->table_list.elements) + { + Item *orig_item= (Item*) select_lex->item_list.head(); + select_lex->item_list.empty(); + select_lex->item_list.push_back(new Item_int("Not_used", + (longlong) 1, + MY_INT64_NUM_DECIMAL_DIGITS)); + select_lex->ref_pointer_array[0]= select_lex->item_list.head(); + + if (!abort_on_null && orig_item->maybe_null) + { + /* + Item_is_not_null_test can't be changed during fix_fields() + we can assign select_lex->having here, and pass 0 as last + argument (reference) to fix_fields() + */ + having_term->name= (char*)in_having_cond; + select_lex->having= join->having= having_term; + select_lex->having_fix_field= 1; + /* + we do not check join->having->fixed, because Item_and (from + and_items) or comparison function (from func->create) can't be + fixed after creation + */ + if (!join->having->fixed) + fix_res= join->having->fix_fields(thd, 0); + select_lex->having_fix_field= 0; + if (fix_res) + DBUG_RETURN(RES_ERROR); + } + /* + TODO: figure out why the following is done here in + single_value_transformer but there is no corresponding action in + row_value_transformer? + */ + where_term->name= (char *)in_additional_cond; + + /* + AND can't be changed during fix_fields() + we can assign select_lex->having here, and pass 0 as last + argument (reference) to fix_fields() + */ + select_lex->where= join->conds= and_items(join->conds, where_term); + select_lex->where->top_level_item(); + /* + we do not check join->conds->fixed, because Item_and can't be fixed + after creation + */ + if (!join->conds->fixed && join->conds->fix_fields(thd, 0)) + DBUG_RETURN(RES_ERROR); + } + else + { + if (select_lex->master_unit()->is_union()) + { + having_term->name= (char*)in_having_cond; + select_lex->having= join->having= having_term; select_lex->having_fix_field= 1; /* we do not check join->having->fixed, because comparison function (from func->create) can't be fixed after creation */ - tmp= join->having->fix_fields(thd, 0); + if (!join->having->fixed) + fix_res= join->having->fix_fields(thd, 0); select_lex->having_fix_field= 0; - if (tmp) + if (fix_res) DBUG_RETURN(RES_ERROR); } else @@ -1679,11 +1765,11 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creat // it is single select without tables => possible optimization // remove the dependence mark since the item is moved to upper // select and is not outer anymore. - item->walk(&Item::remove_dependence_processor, 0, - (uchar *) select_lex->outer_select()); - item= func->create(left_expr, item); + where_term->walk(&Item::remove_dependence_processor, 0, + (uchar *) select_lex->outer_select()); + where_term= func->create(left_expr, where_term); // fix_field of item will be done in time of substituting - substitution= item; + substitution= where_term; have_to_be_excluded= 1; if (thd->lex->describe) { diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 15b2dc8448f..0a02dd3423b 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -425,8 +425,18 @@ public: trans_res select_in_like_transformer(JOIN *join, Comp_creator *func); trans_res single_value_transformer(JOIN *join, Comp_creator *func); trans_res row_value_transformer(JOIN * join); + trans_res single_value_in_to_exists_transformer(JOIN * join, Comp_creator *func); + trans_res create_single_value_in_to_exists_cond(JOIN * join, + Comp_creator *func, + Item **where_term, + Item **having_term); + trans_res inject_single_value_in_to_exists_cond(JOIN * join, + Comp_creator *func, + Item *where_term, + Item *having_term); + trans_res row_value_in_to_exists_transformer(JOIN * join); virtual bool exec(); longlong val_int(); From aa195b25704b4e67423654422ce0b601b54f809d Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 18 Jul 2010 15:59:24 +0300 Subject: [PATCH 03/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Step2 in the separation of the creation of IN->EXISTS equi-join conditions from their injection. The goal of this separation is to make it possible that the IN->EXISTS conditions can be used for cost estimation without actually modifying the subquery. This patch separates row_value_in_to_exists_transformer() into two methods: - create_row_value_in_to_exists_cond(), and - inject_row_value_in_to_exists_cond() The patch performs minimal refactoring of the code so that it is easier to solve problems resulting from the separation. There is a lot to be simplified in this code, but this will be done separately. --- sql/item_subselect.cc | 126 ++++++++++++++++++++++++++++-------------- sql/item_subselect.h | 7 +++ 2 files changed, 90 insertions(+), 43 deletions(-) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 8218f927ad3..2c00137d571 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1524,16 +1524,16 @@ Item_subselect::trans_res Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Comp_creator *func) { - Item *where_term; - Item *having_term; + Item *where_item; + Item *having_item; Item_subselect::trans_res res; res= create_single_value_in_to_exists_cond(join, func, - &where_term, &having_term); + &where_item, &having_item); if (res != RES_OK) return res; res= inject_single_value_in_to_exists_cond(join, func, - where_term, having_term); + where_item, having_item); return res; } @@ -1541,8 +1541,8 @@ Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, Item_subselect::trans_res Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, Comp_creator *func, - Item **where_term, - Item **having_term) + Item **where_item, + Item **having_item) { SELECT_LEX *select_lex= join->select_lex; DBUG_ENTER("Item_in_subselect::create_single_value_in_to_exists_cond"); @@ -1569,8 +1569,8 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, if (item->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); - *having_term= item; - *where_term= NULL; + *having_item= item; + *where_item= NULL; } else { @@ -1595,7 +1595,7 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, if (having->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); - *having_term= having; + *having_item= having; item= new Item_cond_or(item, new Item_func_isnull(orig_item)); @@ -1613,7 +1613,7 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, if (item->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); - *where_term= item; + *where_item= item; } else { @@ -1640,13 +1640,13 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, if (new_having->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); - *having_term= new_having; - *where_term= NULL; + *having_item= new_having; + *where_item= NULL; } else { - *having_term= NULL; - *where_term= (Item*) select_lex->item_list.head(); + *having_item= NULL; + *where_item= (Item*) select_lex->item_list.head(); } } } @@ -1659,8 +1659,8 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, Item_subselect::trans_res Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, Comp_creator *func, - Item *where_term, - Item *having_term) + Item *where_item, + Item *having_item) { SELECT_LEX *select_lex= join->select_lex; bool fix_res; @@ -1675,9 +1675,9 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, we can assign select_lex->having here, and pass 0 as last argument (reference) to fix_fields() */ - select_lex->having= join->having= and_items(join->having, having_term); - if (join->having == having_term) - having_term->name= (char*)in_having_cond; + select_lex->having= join->having= and_items(join->having, having_item); + if (join->having == having_item) + having_item->name= (char*)in_having_cond; select_lex->having_fix_field= 1; /* we do not check join->having->fixed, because Item_and (from and_items) @@ -1707,8 +1707,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, we can assign select_lex->having here, and pass 0 as last argument (reference) to fix_fields() */ - having_term->name= (char*)in_having_cond; - select_lex->having= join->having= having_term; + having_item->name= (char*)in_having_cond; + select_lex->having= join->having= having_item; select_lex->having_fix_field= 1; /* we do not check join->having->fixed, because Item_and (from @@ -1726,14 +1726,14 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, single_value_transformer but there is no corresponding action in row_value_transformer? */ - where_term->name= (char *)in_additional_cond; + where_item->name= (char *)in_additional_cond; /* AND can't be changed during fix_fields() we can assign select_lex->having here, and pass 0 as last argument (reference) to fix_fields() */ - select_lex->where= join->conds= and_items(join->conds, where_term); + select_lex->where= join->conds= and_items(join->conds, where_item); select_lex->where->top_level_item(); /* we do not check join->conds->fixed, because Item_and can't be fixed @@ -1746,8 +1746,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, { if (select_lex->master_unit()->is_union()) { - having_term->name= (char*)in_having_cond; - select_lex->having= join->having= having_term; + having_item->name= (char*)in_having_cond; + select_lex->having= join->having= having_item; select_lex->having_fix_field= 1; /* @@ -1765,11 +1765,11 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, // it is single select without tables => possible optimization // remove the dependence mark since the item is moved to upper // select and is not outer anymore. - where_term->walk(&Item::remove_dependence_processor, 0, + where_item->walk(&Item::remove_dependence_processor, 0, (uchar *) select_lex->outer_select()); - where_term= func->create(left_expr, where_term); + where_item= func->create(left_expr, where_item); // fix_field of item will be done in time of substituting - substitution= where_term; + substitution= where_item; have_to_be_excluded= 1; if (thd->lex->describe) { @@ -1866,20 +1866,37 @@ Item_in_subselect::row_value_transformer(JOIN *join) add the equi-join and the "is null" to WHERE add the is_not_null_test to HAVING */ - Item_subselect::trans_res Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) +{ + Item *where_item; + Item *having_item; + Item_subselect::trans_res res; + + res= create_row_value_in_to_exists_cond(join, &where_item, &having_item); + if (res != RES_OK) + return res; + res= inject_row_value_in_to_exists_cond(join, where_item, having_item); + return res; +} + + +Item_subselect::trans_res +Item_in_subselect::create_row_value_in_to_exists_cond(JOIN * join, + Item **where_item, + Item **having_item) { SELECT_LEX *select_lex= join->select_lex; - Item *having_item= 0; uint cols_num= left_expr->cols(); bool is_having_used= (join->having || select_lex->with_sum_func || select_lex->group_list.first || !select_lex->table_list.elements); - DBUG_ENTER("Item_in_subselect::row_value_in_to_exists_transformer"); + DBUG_ENTER("Item_in_subselect::create_row_value_in_to_exists_cond"); + + *where_item= NULL; + *having_item= NULL; - select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; if (is_having_used) { /* @@ -1899,6 +1916,7 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) for (uint i= 0; i < cols_num; i++) { DBUG_ASSERT((left_expr->fixed && + select_lex->ref_pointer_array[i]->fixed) || (select_lex->ref_pointer_array[i]->type() == REF_ITEM && ((Item_ref*)(select_lex->ref_pointer_array[i]))->ref_type() == @@ -1932,8 +1950,8 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) if (!(col_item= new Item_func_trig_cond(col_item, get_cond_guard(i)))) DBUG_RETURN(RES_ERROR); } - having_item= and_items(having_item, col_item); - + *having_item= and_items(*having_item, col_item); + Item *item_nnull_test= new Item_is_not_null_test(this, new Item_ref(&select_lex->context, @@ -1950,8 +1968,8 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) item_having_part2= and_items(item_having_part2, item_nnull_test); item_having_part2->top_level_item(); } - having_item= and_items(having_item, item_having_part2); - having_item->top_level_item(); + *having_item= and_items(*having_item, item_having_part2); + (*having_item)->top_level_item(); } else { @@ -1972,7 +1990,6 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) (l2 = v2) and (l3 = v3) */ - Item *where_item= 0; for (uint i= 0; i < cols_num; i++) { Item *item, *item_isnull; @@ -2030,10 +2047,33 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) new Item_func_trig_cond(having_col_item, get_cond_guard(i)))) DBUG_RETURN(RES_ERROR); } - having_item= and_items(having_item, having_col_item); + *having_item= and_items(*having_item, having_col_item); } - where_item= and_items(where_item, item); + *where_item= and_items(*where_item, item); } + (*where_item)->fix_fields(thd, 0); + } + + DBUG_RETURN(RES_OK); +} + + +Item_subselect::trans_res +Item_in_subselect::inject_row_value_in_to_exists_cond(JOIN * join, + Item *where_item, + Item *having_item) +{ + SELECT_LEX *select_lex= join->select_lex; + bool is_having_used= (join->having || select_lex->with_sum_func || + select_lex->group_list.first || + !select_lex->table_list.elements); + + DBUG_ENTER("Item_in_subselect::inject_row_value_in_to_exists_cond"); + + select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + + if (!is_having_used) + { /* AND can't be changed during fix_fields() we can assign select_lex->where here, and pass 0 as last @@ -2041,9 +2081,10 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) */ select_lex->where= join->conds= and_items(join->conds, where_item); select_lex->where->top_level_item(); - if (join->conds->fix_fields(thd, 0)) + if (!join->conds->fixed && join->conds->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); } + if (having_item) { bool res; @@ -2057,12 +2098,11 @@ Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) argument (reference) to fix_fields() */ select_lex->having_fix_field= 1; - res= join->having->fix_fields(thd, 0); + if (!join->having->fixed) + res= join->having->fix_fields(thd, 0); select_lex->having_fix_field= 0; if (res) - { DBUG_RETURN(RES_ERROR); - } } DBUG_RETURN(RES_OK); diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 0a02dd3423b..d6cca97c351 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -438,6 +438,13 @@ public: Item *having_term); trans_res row_value_in_to_exists_transformer(JOIN * join); + trans_res create_row_value_in_to_exists_cond(JOIN * join, + Item **where_term, + Item **having_term); + trans_res inject_row_value_in_to_exists_cond(JOIN * join, + Item *where_term, + Item *having_term); + virtual bool exec(); longlong val_int(); double val_real(); From 18ad3bdc2fa3bbe1bfd7e433adb0bc6b3dbce8c8 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 5 Sep 2010 18:43:47 +0300 Subject: [PATCH 04/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Fixes for multiple problems/bugs/test failures that resulted from moving subquery optimization from the execution phase to the optimization phase. --- sql/item.h | 2 + sql/item_cmpfunc.cc | 16 +- sql/item_cmpfunc.h | 2 + sql/item_subselect.cc | 424 +++++++++++++++++++++++------------------- sql/item_subselect.h | 65 ++++--- sql/opt_subselect.cc | 86 ++++++++- sql/sql_class.cc | 1 + sql/sql_delete.cc | 4 + sql/sql_lex.cc | 63 ++++++- sql/sql_lex.h | 8 + sql/sql_select.cc | 94 ++++++---- sql/sql_select.h | 14 +- sql/sql_union.cc | 14 ++ sql/sql_update.cc | 4 + 14 files changed, 528 insertions(+), 269 deletions(-) diff --git a/sql/item.h b/sql/item.h index b627729374d..313c7e4d8fd 100644 --- a/sql/item.h +++ b/sql/item.h @@ -947,6 +947,8 @@ public: virtual bool register_field_in_read_map(uchar *arg) { return 0; } virtual bool enumerate_field_refs_processor(uchar *arg) { return 0; } virtual bool mark_as_eliminated_processor(uchar *arg) { return 0; } + virtual bool eliminate_subselect_processor(uchar *arg) { return 0; } + virtual bool set_fake_select_as_master_processor(uchar *arg) { return 0; } /* The next function differs from the previous one that a bitmap to be updated is passed as uchar *arg. diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index d1ef5fb5264..a3fa1ecf748 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -1964,6 +1964,18 @@ Item *Item_in_optimizer::transform(Item_transformer transformer, uchar *argument } +bool Item_in_optimizer::is_expensive_processor(uchar *arg) +{ + return args[1]->is_expensive_processor(arg); +} + + +bool Item_in_optimizer::is_expensive() +{ + return args[1]->is_expensive(); +} + + longlong Item_func_eq::val_int() { DBUG_ASSERT(fixed == 1); @@ -5342,7 +5354,7 @@ Item *Item_func_nop_all::neg_transformer(THD *thd) /* "NOT (e $cmp$ ANY (SELECT ...)) -> e $rev_cmp$" ALL (SELECT ...) */ Item_func_not_all *new_item= new Item_func_not_all(args[0]); Item_allany_subselect *allany= (Item_allany_subselect*)args[0]; - allany->func= allany->func_creator(FALSE); + allany->create_comp_func(FALSE); allany->all= !allany->all; allany->upper_item= new_item; return new_item; @@ -5354,7 +5366,7 @@ Item *Item_func_not_all::neg_transformer(THD *thd) Item_func_nop_all *new_item= new Item_func_nop_all(args[0]); Item_allany_subselect *allany= (Item_allany_subselect*)args[0]; allany->all= !allany->all; - allany->func= allany->func_creator(TRUE); + allany->create_comp_func(TRUE); allany->upper_item= new_item; return new_item; } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 053a54349c2..e161c1baa84 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -265,6 +265,8 @@ public: void keep_top_level_cache(); Item *transform(Item_transformer transformer, uchar *arg); virtual Item *expr_cache_insert_transformer(uchar *thd_arg); + bool is_expensive_processor(uchar *arg); + bool is_expensive(); }; class Comp_creator diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 2c00137d571..aced2faaed5 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -61,7 +61,8 @@ void Item_subselect::init(st_select_lex *select_lex, DBUG_ENTER("Item_subselect::init"); DBUG_PRINT("enter", ("select_lex: 0x%lx", (long) select_lex)); unit= select_lex->master_unit(); - + thd= unit->thd; + is_min_max_optimized= FALSE; if (unit->item) { /* @@ -159,6 +160,9 @@ void Item_in_subselect::cleanup() } first_execution= TRUE; is_constant= FALSE; + if (exec_method == MATERIALIZATION) + exec_method= NOT_TRANSFORMED; + pushed_cond_guards= NULL; Item_subselect::cleanup(); DBUG_VOID_RETURN; } @@ -184,7 +188,8 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) bool res; DBUG_ASSERT(fixed == 0); - engine->set_thd((thd= thd_param)); + DBUG_ASSERT(thd == thd_param); + engine->set_thd(thd); if (!done_first_fix_fields) { done_first_fix_fields= TRUE; @@ -207,11 +212,7 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) { // all transformation is done (used by prepared statements) changed= 1; - inside_first_fix_fields= FALSE; - - - // all transformation is done (used by prepared statements) - changed= 1; + inside_first_fix_fields= FALSE; /* Substitute the current item with an Item_in_optimizer that was @@ -221,11 +222,14 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) */ if (substitution) { - // did we changed top item of WHERE condition + /* + If the top item of the WHERE/HAVING condition changed, + set correct WHERE/HAVING for PS. + */ if (unit->outer_select()->where == (*ref)) - unit->outer_select()->where= substitution; // correct WHERE for PS + thd->change_item_tree(&(unit->outer_select()->where), substitution); else if (unit->outer_select()->having == (*ref)) - unit->outer_select()->having= substitution; // correct HAVING for PS + thd->change_item_tree(&(unit->outer_select()->having), substitution); (*ref)= substitution; substitution->name= name; @@ -285,6 +289,61 @@ bool Item_subselect::mark_as_eliminated_processor(uchar *arg) } +bool Item_subselect::eliminate_subselect_processor(uchar *arg) +{ + unit->item= NULL; + unit->exclude_from_tree(); + eliminated= TRUE; + return FALSE; +} + + +/* + Adjust the master select of the subquery to be the fake_select which + represents the whole UNION right above the subquery, instead of the + last query of the UNION. +*/ + +bool Item_subselect::set_fake_select_as_master_processor(uchar *arg) +{ + SELECT_LEX *fake_select= (SELECT_LEX*) arg; + /* + Apply the substitution only for immediate child subqueries of a + UNION query. + */ + if (unit->outer_select()->master_unit()->fake_select_lex == fake_select) + { + /* + Include the st_select_lex_unit of a subquery from a global ORDER BY + clause as a direct child of the fake_select of a UNION. In this way + the ORDER BY is applied to the temporary table that contains the + result of the whole UNION, and all columns in the subquery are + resolved against this table. + */ + /* + Set the master of the subquery to be the fake select (i.e. the whole + UNION, instead of the last query in the UNION. + TODO: this is a hack, instead we should call: + unit->include_down(fake_select); + however, this call results in an infinite loop where + some_select_lex->master == some_select_lex. + */ + unit->set_master(fake_select); + /* Adjust the name resolution context hierarchy accordingly. */ + for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) + sl->context.outer_context= &(fake_select->context); + /* + Undo Item_subselect::eliminate_subselect_processor because at that + phase we don't know yet (or don't know how to figure it out) that + the ORDER clause will be moved to the fake select. + */ + unit->item= this; + eliminated= FALSE; + } + return FALSE; // return TRUE ? because we need to stop processing down +} + + bool Item_subselect::mark_as_dependent(THD *thd, st_select_lex *select, Item *item) { @@ -1017,6 +1076,7 @@ Item_in_subselect::Item_in_subselect(Item * left_exp, { DBUG_ENTER("Item_in_subselect::Item_in_subselect"); left_expr= left_exp; + func= &eq_creator; init(select_lex, new select_exists_subselect(this)); max_columns= UINT_MAX; maybe_null= 1; @@ -1333,8 +1393,7 @@ my_decimal *Item_in_subselect::val_decimal(my_decimal *decimal_value) */ Item_subselect::trans_res -Item_in_subselect::single_value_transformer(JOIN *join, - Comp_creator *func) +Item_in_subselect::single_value_transformer(JOIN *join) { SELECT_LEX *select_lex= join->select_lex; DBUG_ENTER("Item_in_subselect::single_value_transformer"); @@ -1429,6 +1488,33 @@ Item_in_subselect::single_value_transformer(JOIN *join, } /* fix fields is already called for left expression */ substitution= func->create(left_expr, subs); + is_min_max_optimized= TRUE; + DBUG_RETURN(RES_OK); + } + + Item* join_having= join->having ? join->having : join->tmp_having; + if (!(join_having || select_lex->with_sum_func || + select_lex->group_list.elements) && + select_lex->table_list.elements == 0 && + !select_lex->master_unit()->is_union()) + { + Item *where_item= (Item*) select_lex->item_list.head(); + /* + it is single select without tables => possible optimization + remove the dependence mark since the item is moved to upper + select and is not outer anymore. + */ + where_item->walk(&Item::remove_dependence_processor, 0, + (uchar *) select_lex->outer_select()); + substitution= func->create(left_expr, where_item); + have_to_be_excluded= 1; + if (thd->lex->describe) + { + char warn_buff[MYSQL_ERRMSG_SIZE]; + sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number); + push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, + ER_SELECT_REDUCED, warn_buff); + } DBUG_RETURN(RES_OK); } @@ -1462,25 +1548,10 @@ Item_in_subselect::single_value_transformer(JOIN *join, (char *)in_left_expr_name); master_unit->uncacheable|= UNCACHEABLE_DEPENDENT; - //psergey: placed then removed: select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + //select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; } - if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards) - { - if (!(pushed_cond_guards= (bool*)join->thd->alloc(sizeof(bool)))) - DBUG_RETURN(RES_ERROR); - pushed_cond_guards[0]= TRUE; - } - - /* - If this IN predicate can be computed via materialization, do not - perform the IN -> EXISTS transformation. - */ - if (exec_method == MATERIALIZATION) - DBUG_RETURN(RES_OK); - - /* Perform the IN=>EXISTS transformation. */ - DBUG_RETURN(single_value_in_to_exists_transformer(join, func)); + DBUG_RETURN(RES_OK); } @@ -1521,33 +1592,22 @@ Item_in_subselect::single_value_transformer(JOIN *join, */ Item_subselect::trans_res -Item_in_subselect::single_value_in_to_exists_transformer(JOIN * join, - Comp_creator *func) -{ - Item *where_item; - Item *having_item; - Item_subselect::trans_res res; - - res= create_single_value_in_to_exists_cond(join, func, - &where_item, &having_item); - if (res != RES_OK) - return res; - res= inject_single_value_in_to_exists_cond(join, func, - where_item, having_item); - return res; -} - - -Item_subselect::trans_res -Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, - Comp_creator *func, - Item **where_item, - Item **having_item) +Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, + Item **where_item, + Item **having_item) { SELECT_LEX *select_lex= join->select_lex; - DBUG_ENTER("Item_in_subselect::create_single_value_in_to_exists_cond"); + /* + The non-transformed HAVING clause of 'join' may be stored differently in + JOIN::optimize: + this->tmp_having= this->having + this->having= 0; + */ + Item* join_having= join->having ? join->having : join->tmp_having; - if (join->having || select_lex->with_sum_func || + DBUG_ENTER("Item_in_subselect::create_single_in_to_exists_cond"); + + if (join_having || select_lex->with_sum_func || select_lex->group_list.elements) { Item *item= func->create(expr, @@ -1645,6 +1705,8 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, } else { + DBUG_ASSERT(FALSE); + /* TIMOUR TODO */ *having_item= NULL; *where_item= (Item*) select_lex->item_list.head(); } @@ -1657,17 +1719,22 @@ Item_in_subselect::create_single_value_in_to_exists_cond(JOIN * join, Item_subselect::trans_res -Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, - Comp_creator *func, - Item *where_item, - Item *having_item) +Item_in_subselect::inject_single_in_to_exists_cond(JOIN * join, + Item *where_item, + Item *having_item) { SELECT_LEX *select_lex= join->select_lex; + /* + The non-transformed HAVING clause of 'join' may be stored differently in + JOIN::optimize: + this->tmp_having= this->having + this->having= 0; + */ + Item* join_having= join->having ? join->having : join->tmp_having; bool fix_res; - DBUG_ENTER("Item_in_subselect::single_value_in_to_exists_transformer"); + DBUG_ENTER("Item_in_subselect::inject_single_in_to_exists_cond"); - select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; - if (join->having || select_lex->with_sum_func || + if (join_having || select_lex->with_sum_func || select_lex->group_list.elements) { /* @@ -1675,7 +1742,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, we can assign select_lex->having here, and pass 0 as last argument (reference) to fix_fields() */ - select_lex->having= join->having= and_items(join->having, having_item); + thd->change_item_tree(&select_lex->having, and_items(join_having, having_item)); + join->having= select_lex->having; if (join->having == having_item) having_item->name= (char*)in_having_cond; select_lex->having_fix_field= 1; @@ -1693,14 +1761,7 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, { if (select_lex->table_list.elements) { - Item *orig_item= (Item*) select_lex->item_list.head(); - select_lex->item_list.empty(); - select_lex->item_list.push_back(new Item_int("Not_used", - (longlong) 1, - MY_INT64_NUM_DECIMAL_DIGITS)); - select_lex->ref_pointer_array[0]= select_lex->item_list.head(); - - if (!abort_on_null && orig_item->maybe_null) + if (!abort_on_null && select_lex->item_list.head()->maybe_null) { /* Item_is_not_null_test can't be changed during fix_fields() @@ -1708,7 +1769,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, argument (reference) to fix_fields() */ having_item->name= (char*)in_having_cond; - select_lex->having= join->having= having_item; + thd->change_item_tree(&select_lex->having, having_item); + join->having= select_lex->having; select_lex->having_fix_field= 1; /* we do not check join->having->fixed, because Item_and (from @@ -1733,7 +1795,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, we can assign select_lex->having here, and pass 0 as last argument (reference) to fix_fields() */ - select_lex->where= join->conds= and_items(join->conds, where_item); + thd->change_item_tree(&select_lex->where, and_items(join->conds, where_item)); + join->conds= select_lex->where; select_lex->where->top_level_item(); /* we do not check join->conds->fixed, because Item_and can't be fixed @@ -1747,7 +1810,8 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, if (select_lex->master_unit()->is_union()) { having_item->name= (char*)in_having_cond; - select_lex->having= join->having= having_item; + thd->change_item_tree(&select_lex->having, having_item); + join->having= select_lex->having; select_lex->having_fix_field= 1; /* @@ -1761,25 +1825,7 @@ Item_in_subselect::inject_single_value_in_to_exists_cond(JOIN * join, DBUG_RETURN(RES_ERROR); } else - { - // it is single select without tables => possible optimization - // remove the dependence mark since the item is moved to upper - // select and is not outer anymore. - where_item->walk(&Item::remove_dependence_processor, 0, - (uchar *) select_lex->outer_select()); - where_item= func->create(left_expr, where_item); - // fix_field of item will be done in time of substituting - substitution= where_item; - have_to_be_excluded= 1; - if (thd->lex->describe) - { - char warn_buff[MYSQL_ERRMSG_SIZE]; - sprintf(warn_buff, ER(ER_SELECT_REDUCED), select_lex->select_number); - push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, - ER_SELECT_REDUCED, warn_buff); - } - DBUG_RETURN(RES_REDUCE); - } + DBUG_ASSERT(FALSE); } } @@ -1826,26 +1872,10 @@ Item_in_subselect::row_value_transformer(JOIN *join) thd->lex->current_select= current; master_unit->uncacheable|= UNCACHEABLE_DEPENDENT; - - if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards) - { - if (!(pushed_cond_guards= (bool*)join->thd->alloc(sizeof(bool) * - left_expr->cols()))) - DBUG_RETURN(RES_ERROR); - for (uint i= 0; i < cols_num; i++) - pushed_cond_guards[i]= TRUE; - } + //select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; } - /* - If this IN predicate can be computed via materialization, do not - perform the IN -> EXISTS transformation. - */ - if (exec_method == MATERIALIZATION) - DBUG_RETURN(RES_OK); - - /* Perform the IN=>EXISTS transformation. */ - DBUG_RETURN(row_value_in_to_exists_transformer(join)); + DBUG_RETURN(RES_OK); } @@ -1866,33 +1896,26 @@ Item_in_subselect::row_value_transformer(JOIN *join) add the equi-join and the "is null" to WHERE add the is_not_null_test to HAVING */ -Item_subselect::trans_res -Item_in_subselect::row_value_in_to_exists_transformer(JOIN * join) -{ - Item *where_item; - Item *having_item; - Item_subselect::trans_res res; - - res= create_row_value_in_to_exists_cond(join, &where_item, &having_item); - if (res != RES_OK) - return res; - res= inject_row_value_in_to_exists_cond(join, where_item, having_item); - return res; -} - Item_subselect::trans_res -Item_in_subselect::create_row_value_in_to_exists_cond(JOIN * join, - Item **where_item, - Item **having_item) +Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, + Item **where_item, + Item **having_item) { SELECT_LEX *select_lex= join->select_lex; uint cols_num= left_expr->cols(); - bool is_having_used= (join->having || select_lex->with_sum_func || + /* + The non-transformed HAVING clause of 'join' may be stored differently in + JOIN::optimize: + this->tmp_having= this->having + this->having= 0; + */ + Item* join_having= join->having ? join->having : join->tmp_having; + bool is_having_used= (join_having || select_lex->with_sum_func || select_lex->group_list.first || !select_lex->table_list.elements); - DBUG_ENTER("Item_in_subselect::create_row_value_in_to_exists_cond"); + DBUG_ENTER("Item_in_subselect::create_row_in_to_exists_cond"); *where_item= NULL; *having_item= NULL; @@ -2059,18 +2082,23 @@ Item_in_subselect::create_row_value_in_to_exists_cond(JOIN * join, Item_subselect::trans_res -Item_in_subselect::inject_row_value_in_to_exists_cond(JOIN * join, - Item *where_item, - Item *having_item) +Item_in_subselect::inject_row_in_to_exists_cond(JOIN * join, + Item *where_item, + Item *having_item) { SELECT_LEX *select_lex= join->select_lex; - bool is_having_used= (join->having || select_lex->with_sum_func || + /* + The non-transformed HAVING clause of 'join' may be stored differently in + JOIN::optimize: + this->tmp_having= this->having + this->having= 0; + */ + Item* join_having= join->having ? join->having : join->tmp_having; + bool is_having_used= (join_having || select_lex->with_sum_func || select_lex->group_list.first || !select_lex->table_list.elements); - DBUG_ENTER("Item_in_subselect::inject_row_value_in_to_exists_cond"); - - select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + DBUG_ENTER("Item_in_subselect::inject_row_in_to_exists_cond"); if (!is_having_used) { @@ -2079,7 +2107,8 @@ Item_in_subselect::inject_row_value_in_to_exists_cond(JOIN * join, we can assign select_lex->where here, and pass 0 as last argument (reference) to fix_fields() */ - select_lex->where= join->conds= and_items(join->conds, where_item); + thd->change_item_tree(&select_lex->where, and_items(join->conds, where_item)); + join->conds= select_lex->where; select_lex->where->top_level_item(); if (!join->conds->fixed && join->conds->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); @@ -2088,7 +2117,9 @@ Item_in_subselect::inject_row_value_in_to_exists_cond(JOIN * join, if (having_item) { bool res; - select_lex->having= join->having= and_items(join->having, having_item); + thd->change_item_tree(&select_lex->having, + and_items(join_having, having_item)); + join->having= select_lex->having; if (having_item == select_lex->having) having_item->name= (char*)in_having_cond; select_lex->having->top_level_item(); @@ -2112,7 +2143,52 @@ Item_in_subselect::inject_row_value_in_to_exists_cond(JOIN * join, Item_subselect::trans_res Item_in_subselect::select_transformer(JOIN *join) { - return select_in_like_transformer(join, &eq_creator); + return select_in_like_transformer(join); +} + + +bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg) +{ + Item_subselect::trans_res res; + + DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE || + engine->engine_type() == subselect_engine::UNION_ENGINE); + /* + TIMOUR TODO: the call to init_cond_guards allocates and initializes an + array of booleans that may not be used later because we may choose + materialization. + The two calls below to create_XYZ_cond depend on this boolean array. + This dependency can be easily removed, and the call moved to a later + phase. + */ + init_cond_guards(); + join_arg->select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + if (left_expr->cols() == 1) + res= create_single_in_to_exists_cond(join_arg, + &(join_arg->in_to_exists_where), + &(join_arg->in_to_exists_having)); + else + res= create_row_in_to_exists_cond(join_arg, + &(join_arg->in_to_exists_where), + &(join_arg->in_to_exists_having)); + return (res != RES_OK); +} + + +bool Item_in_subselect::inject_in_to_exists_cond(JOIN * join_arg) +{ + Item_subselect::trans_res res; + + if (left_expr->cols() == 1) + res= inject_single_in_to_exists_cond(join_arg, + join_arg->in_to_exists_where, + join_arg->in_to_exists_having); + else + res= inject_row_in_to_exists_cond(join_arg, + join_arg->in_to_exists_where, + join_arg->in_to_exists_having); + + return (res != RES_OK && res != RES_REDUCE); } @@ -2138,7 +2214,7 @@ Item_in_subselect::select_transformer(JOIN *join) */ Item_subselect::trans_res -Item_in_subselect::select_in_like_transformer(JOIN *join, Comp_creator *func) +Item_in_subselect::select_in_like_transformer(JOIN *join) { Query_arena *arena, backup; SELECT_LEX *current= thd->lex->current_select; @@ -2193,14 +2269,6 @@ Item_in_subselect::select_in_like_transformer(JOIN *join, Comp_creator *func) if (result) goto err; - /* - If we didn't choose an execution method up to this point, we choose - the IN=>EXISTS transformation. - */ - if (exec_method == NOT_TRANSFORMED) - exec_method= IN_TO_EXISTS; - arena= thd->activate_stmt_arena_if_needed(&backup); - /* Both transformers call fix_fields() only for Items created inside them, and all that items do not make permanent changes in current item arena @@ -2208,8 +2276,9 @@ Item_in_subselect::select_in_like_transformer(JOIN *join, Comp_creator *func) of Item, we have to call fix_fields() for it only with original arena to avoid memory leack) */ + arena= thd->activate_stmt_arena_if_needed(&backup); if (left_expr->cols() == 1) - res= single_value_transformer(join, func); + res= single_value_transformer(join); else { /* we do not support row operation for ALL/ANY/SOME */ @@ -2345,20 +2414,12 @@ void Item_in_subselect::update_used_tables() @retval FALSE an execution method was chosen successfully */ -bool Item_in_subselect::setup_engine() +bool Item_in_subselect::setup_mat_engine() { subselect_hash_sj_engine *mat_engine= NULL; subselect_single_select_engine *select_engine; - DBUG_ENTER("Item_in_subselect::setup_engine"); - - - SELECT_LEX *save_select= thd->lex->current_select; - thd->lex->current_select= get_select_lex(); - int res= thd->lex->current_select->join->optimize(); - thd->lex->current_select= save_select; - if (res) - DBUG_RETURN(TRUE); + DBUG_ENTER("Item_in_subselect::setup_mat_engine"); /* The select_engine (that executes transformed IN=>EXISTS subselects) is @@ -2373,34 +2434,7 @@ bool Item_in_subselect::setup_engine() DBUG_RETURN(TRUE); if (mat_engine->init(&select_engine->join->fields_list)) - { - Item_subselect::trans_res trans_res; - /* - If for some reason we cannot use materialization for this IN predicate, - delete all materialization-related objects, and apply the IN=>EXISTS - transformation. - */ - delete mat_engine; - mat_engine= NULL; - exec_method= NOT_TRANSFORMED; - - if (left_expr->cols() == 1) - trans_res= single_value_in_to_exists_transformer(select_engine->join, - &eq_creator); - else - trans_res= row_value_in_to_exists_transformer(select_engine->join); - - /* - The IN=>EXISTS transformation above injects new predicates into the - WHERE and HAVING clauses. Since the subquery was already optimized, - below we force its reoptimization with the new injected conditions - by the first call to subselect_single_select_engine::exec(). - This is the only case of lazy subquery optimization in the server. - */ - DBUG_ASSERT(select_engine->join->optimized); - select_engine->join->optimized= false; - DBUG_RETURN(trans_res != Item_subselect::RES_OK); - } + DBUG_RETURN(TRUE); /* Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec. @@ -2456,6 +2490,20 @@ bool Item_in_subselect::init_left_expr_cache() } +bool Item_in_subselect::init_cond_guards() +{ + uint cols_num= left_expr->cols(); + if (!abort_on_null && left_expr->maybe_null && !pushed_cond_guards) + { + if (!(pushed_cond_guards= (bool*)thd->alloc(sizeof(bool) * cols_num))) + return TRUE; + for (uint i= 0; i < cols_num; i++) + pushed_cond_guards[i]= TRUE; + } + return FALSE; +} + + /* Callback to test if an IN predicate is expensive. @@ -2471,7 +2519,7 @@ bool Item_in_subselect::init_left_expr_cache() bool Item_in_subselect::is_expensive_processor(uchar *arg) { - return exec_method == MATERIALIZATION; + return TRUE; // exec_method == MATERIALIZATION; } @@ -2482,7 +2530,7 @@ Item_allany_subselect::select_transformer(JOIN *join) exec_method= IN_TO_EXISTS; if (upper_item) upper_item->show= 1; - DBUG_RETURN(select_in_like_transformer(join, func)); + DBUG_RETURN(select_in_like_transformer(join)); } diff --git a/sql/item_subselect.h b/sql/item_subselect.h index d6cca97c351..d35d036e9ea 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -108,6 +108,9 @@ public: /* subquery is transformed */ bool changed; + /* TIMOUR: this is temporary, remove it. */ + bool is_min_max_optimized; + /* TRUE <=> The underlying SELECT is correlated w.r.t some ancestor select */ bool is_correlated; @@ -180,6 +183,8 @@ public: enum_parsing_place place() { return parsing_place; } bool walk(Item_processor processor, bool walk_subquery, uchar *arg); bool mark_as_eliminated_processor(uchar *arg); + bool eliminate_subselect_processor(uchar *arg); + bool set_fake_select_as_master_processor(uchar *arg); bool enumerate_field_refs_processor(uchar *arg); bool check_vcol_func_processor(uchar *int_arg) { @@ -326,8 +331,6 @@ public: class Item_in_subselect :public Item_exists_subselect { -public: - Item *left_expr; protected: /* Cache of the left operand of the subquery predicate. Allocated in the @@ -350,10 +353,30 @@ protected: Item_in_optimizer *optimizer; bool was_null; bool abort_on_null; -public: /* Used to trigger on/off conditions that were pushed down to subselect */ bool *pushed_cond_guards; - + Comp_creator *func; + +protected: + bool init_cond_guards(); + trans_res select_in_like_transformer(JOIN *join); + trans_res single_value_transformer(JOIN *join); + trans_res row_value_transformer(JOIN * join); + trans_res create_single_in_to_exists_cond(JOIN * join, + Item **where_item, + Item **having_item); + trans_res inject_single_in_to_exists_cond(JOIN * join, + Item *where_item, + Item *having_item); + + trans_res create_row_in_to_exists_cond(JOIN * join, + Item **where_item, + Item **having_item); + trans_res inject_row_in_to_exists_cond(JOIN * join, + Item *where_item, + Item *having_item); +public: + Item *left_expr; /* Priority of this predicate in the convert-to-semi-join-nest process. */ int sj_convert_priority; /* @@ -410,8 +433,9 @@ public: Item_in_subselect() :Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), is_constant(FALSE), optimizer(0), abort_on_null(0), - pushed_cond_guards(NULL), exec_method(NOT_TRANSFORMED), upper_item(0) - {} + pushed_cond_guards(NULL), func(NULL), exec_method(NOT_TRANSFORMED), + upper_item(0) + {} void cleanup(); subs_type substype() { return IN_SUBS; } void reset() @@ -422,28 +446,8 @@ public: was_null= 0; } trans_res select_transformer(JOIN *join); - trans_res select_in_like_transformer(JOIN *join, Comp_creator *func); - trans_res single_value_transformer(JOIN *join, Comp_creator *func); - trans_res row_value_transformer(JOIN * join); - - trans_res single_value_in_to_exists_transformer(JOIN * join, - Comp_creator *func); - trans_res create_single_value_in_to_exists_cond(JOIN * join, - Comp_creator *func, - Item **where_term, - Item **having_term); - trans_res inject_single_value_in_to_exists_cond(JOIN * join, - Comp_creator *func, - Item *where_term, - Item *having_term); - - trans_res row_value_in_to_exists_transformer(JOIN * join); - trans_res create_row_value_in_to_exists_cond(JOIN * join, - Item **where_term, - Item **having_term); - trans_res inject_row_value_in_to_exists_cond(JOIN * join, - Item *where_term, - Item *having_term); + bool create_in_to_exists_cond(JOIN * join_arg); + bool inject_in_to_exists_cond(JOIN * join_arg); virtual bool exec(); longlong val_int(); @@ -459,11 +463,12 @@ public: bool fix_fields(THD *thd, Item **ref); void fix_after_pullout(st_select_lex *new_parent, Item **ref); void update_used_tables(); - bool setup_engine(); + bool setup_mat_engine(); bool init_left_expr_cache(); /* Inform 'this' that it was computed, and contains a valid result. */ void set_first_execution() { if (first_execution) first_execution= FALSE; } bool is_expensive_processor(uchar *arg); + bool is_expensive() { return TRUE; } bool expr_cache_is_needed(THD *thd); /* @@ -485,7 +490,6 @@ class Item_allany_subselect :public Item_in_subselect { public: chooser_compare_func_creator func_creator; - Comp_creator *func; bool all; Item_allany_subselect(Item * left_expr, chooser_compare_func_creator fc, @@ -494,6 +498,7 @@ public: // only ALL subquery has upper not subs_type substype() { return all?ALL_SUBS:ANY_SUBS; } trans_res select_transformer(JOIN *join); + void create_comp_func(bool invert) { func= func_creator(invert); } virtual void print(String *str, enum_query_type query_type); }; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 8567d291341..13b8bb40ea7 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -67,12 +67,15 @@ int check_and_do_in_subquery_rewrites(JOIN *join) { THD *thd=join->thd; st_select_lex *select_lex= join->select_lex; + st_select_lex_unit* parent_unit= select_lex->master_unit(); DBUG_ENTER("check_and_do_in_subquery_rewrites"); /* If 1) this join is inside a subquery (of any type except FROM-clause subquery) and 2) we aren't just normalizing a VIEW + 3) The join and its select_lex object do not represent the 'fake' + select used to compute the result of a UNION. Then perform early unconditional subquery transformations: - Convert subquery predicate into semi-join, or @@ -84,8 +87,9 @@ int check_and_do_in_subquery_rewrites(JOIN *join) TODO: for PS, make the whole block execute only on the first execution */ Item_subselect *subselect; - if (!thd->lex->view_prepare_mode && // (1) - (subselect= select_lex->master_unit()->item)) // (2) + if (!thd->lex->view_prepare_mode && // (1) + (subselect= parent_unit->item))// && // (2) +// select_lex == parent_unit->fake_select_lex) // (3) { Item_in_subselect *in_subs= NULL; if (subselect->substype() == Item_subselect::IN_SUBS) @@ -129,6 +133,9 @@ int check_and_do_in_subquery_rewrites(JOIN *join) if (failure) DBUG_RETURN(-1); /* purecov: deadcode */ } + if (select_lex == parent_unit->fake_select_lex) + DBUG_RETURN(0); + DBUG_PRINT("info", ("Checking if subq can be converted to semi-join")); /* Check if we're in subquery that is a candidate for flattening into a @@ -154,7 +161,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join) !join->having && !select_lex->with_sum_func && // 4 thd->thd_marker.emb_on_expr_nest && // 5 select_lex->outer_select()->join && // 6 - select_lex->master_unit()->first_select()->leaf_tables && // 7 + parent_unit->first_select()->leaf_tables && // 7 in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED && // 8 select_lex->outer_select()->leaf_tables && // 9 !((join->select_options | // 10 @@ -212,7 +219,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join) if (optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION) && in_subs && // 1 !select_lex->is_part_of_union() && // 2 - select_lex->master_unit()->first_select()->leaf_tables && // 3 + parent_unit->first_select()->leaf_tables && // 3 thd->lex->sql_command == SQLCOM_SELECT && // * select_lex->outer_select()->leaf_tables && // 3A subquery_types_allow_materialization(in_subs) && @@ -223,15 +230,27 @@ int check_and_do_in_subquery_rewrites(JOIN *join) !in_subs->is_correlated && // 5 in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6 { - in_subs->exec_method= Item_in_subselect::MATERIALIZATION; + /* + Materialization is possible, later the optimize phase of each + subquery will choose either materialization or in-to-exists based + on cost. + */ + in_subs->exec_method= Item_in_subselect::MATERIALIZATION; + } + else if (in_subs) + { + /* Materialization is not possible at all. */ + in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; } + /* + Transform each subquery predicate according to its overloaded + transformer. + */ Item_subselect::trans_res trans_res; if ((trans_res= subselect->select_transformer(join)) != Item_subselect::RES_OK) - { DBUG_RETURN((trans_res == Item_subselect::RES_ERROR)); - } } } DBUG_RETURN(0); @@ -3505,3 +3524,56 @@ static void remove_subq_pushed_predicates(JOIN *join, Item **where) } +bool JOIN::choose_subquery_plan() +{ + double mat_strategy_cost; /* The cost to compute IN via materialization. */ + double in_exists_strategy_cost; /* The cost of the IN->EXISTS strategy. */ + bool res; + + DBUG_ASSERT(in_to_exists_where || in_to_exists_having); + DBUG_ASSERT(select_lex->master_unit()->item && + (select_lex->master_unit()->item->substype() == + Item_subselect::IN_SUBS || + select_lex->master_unit()->item->substype() == + Item_subselect::ALL_SUBS || + select_lex->master_unit()->item->substype() == + Item_subselect::ANY_SUBS)); + + Item_in_subselect *in_subs= (Item_in_subselect*) + select_lex->master_unit()->item; + + /* Always revert to IN->EXISTS. */ + mat_strategy_cost= 1; + in_exists_strategy_cost= 0; + + if (mat_strategy_cost < in_exists_strategy_cost) + { + in_subs->exec_method = Item_in_subselect::MATERIALIZATION; + if (in_subs->setup_mat_engine()) + { + /* + In some cases it is not possible to create usable indexes for the + materialization strategy, so fall back to IN->EXISTS. + */ + in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; + } + } + else + in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; + + if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) + { + + // TODO: should we unset the UNCACHEABLE_DEPENDENT flag fro + // select_lex->uncacheable; ? + // This affects how we execute JOIN::join_free - full or not. + // inner_join->restore_plan (keyuse, best_positions, best_read) + ; + } + else if (in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS) + res= in_subs->inject_in_to_exists_cond(this); + else + DBUG_ASSERT(FALSE); + + return res; +} diff --git a/sql/sql_class.cc b/sql/sql_class.cc index a4e8c84401b..930cf2393f7 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2630,6 +2630,7 @@ void Query_arena::free_items() for (; free_list; free_list= next) { next= free_list->next; + DBUG_ASSERT(free_list != next); free_list->delete_self(); } /* Postcondition: free_list is 0 */ diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index ddb6af97865..862d3326c2a 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -92,6 +92,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } } + /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */ + if (select_lex->optimize_unflattened_subqueries()) + DBUG_RETURN(TRUE); + const_cond= (!conds || conds->const_item()); safe_update=test(thd->options & OPTION_SAFE_UPDATES); if (safe_update && const_cond) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 7b19b4874b1..69a0e9c6e14 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -1734,17 +1734,29 @@ void st_select_lex_node::fast_exclude() } + /* - excluding select_lex structure (except first (first select can't be + Exclude a node from the tree lex structure, but leave it in the global + list of nodes. +*/ + +void st_select_lex_node::exclude_from_tree() +{ + if ((*prev= next)) + next->prev= prev; +} + + +/* + Exclude select_lex structure (except first (first select can't be deleted, because it is most upper select)) */ void st_select_lex_node::exclude() { - //exclude from global list + /* exclude from global list */ fast_exclude(); - //exclude from other structures - if ((*prev= next)) - next->prev= prev; + /* exclude from other structures */ + exclude_from_tree(); /* We do not need following statements, because prev pointer of first list element point to master->slave @@ -3076,6 +3088,46 @@ bool st_select_lex::add_index_hint (THD *thd, char *str, uint length) str, length)); } + +bool st_select_lex::optimize_unflattened_subqueries() +{ + for (SELECT_LEX_UNIT *un= first_inner_unit(); un; un= un->next_unit()) + { + Item_subselect *subquery_predicate= un->item; + if (subquery_predicate) + { + Item_in_subselect *item_in= NULL; + if (subquery_predicate->substype() == Item_subselect::IN_SUBS || + subquery_predicate->substype() == Item_subselect::ALL_SUBS || + subquery_predicate->substype() == Item_subselect::ANY_SUBS) + item_in= (Item_in_subselect*) subquery_predicate; + for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) + { + JOIN *inner_join= sl->join; + SELECT_LEX *save_select= un->thd->lex->current_select; + int res; + + /* + Make sure that we do not create IN->EXISTS conditions for + subquery predicates that were substituted by Item_maxmin_subselect + or by Item_singlerow_subselect. + */ + DBUG_ASSERT(!item_in || (item_in && !item_in->is_min_max_optimized)); + if (item_in && item_in->create_in_to_exists_cond(inner_join)) + return TRUE; + un->set_limit(un->global_parameters); + un->thd->lex->current_select= sl; + res= inner_join->optimize(); + un->thd->lex->current_select= save_select; + if (res) + return TRUE; + } + } + } + return FALSE; +} + + /** A routine used by the parser to decide whether we are specifying a full partitioning or if only partitions to add or to split. @@ -3093,4 +3145,3 @@ bool st_lex::is_partition_management() const (alter_info.flags == ALTER_ADD_PARTITION || alter_info.flags == ALTER_REORGANIZE_PARTITION)); } - diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 335aa7f4ed4..d106bfd55fd 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -439,6 +439,7 @@ public: st_select_lex_node(): linkage(UNSPECIFIED_TYPE) {} virtual ~st_select_lex_node() {} inline st_select_lex_node* get_master() { return master; } + inline void set_master(st_select_lex_node* master_arg) { master= master_arg; } virtual void init_query(); virtual void init_select(); void include_down(st_select_lex_node *upper); @@ -446,6 +447,7 @@ public: void include_standalone(st_select_lex_node *sel, st_select_lex_node **ref); void include_global(st_select_lex_node **plink); void exclude(); + void exclude_from_tree(); virtual st_select_lex_unit* master_unit()= 0; virtual st_select_lex* outer_select()= 0; @@ -839,6 +841,12 @@ public: void clear_index_hints(void) { index_hints= NULL; } bool is_part_of_union() { return master_unit()->is_union(); } + /* + Optimize all subqueries that have not been flattened into semi-joins. + This functionality is a method of SELECT_LEX instead of JOIN because + some SQL statements as DELETE do not have a corresponding JOIN object. + */ + bool optimize_unflattened_subqueries(); private: /* current index hint kind. used in filling up index_hints */ enum index_hint_type current_index_hint_type; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a5baa60fe32..574353897de 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -926,9 +926,27 @@ JOIN::optimize() { DBUG_PRINT("info",("No tables")); error= 0; - /* Create all structures needed for materialized subquery execution. */ - if (setup_subquery_materialization()) + if (optimize_unflattened_subqueries()) DBUG_RETURN(1); + if (in_to_exists_where || in_to_exists_having) + { + /* + TIMOUR: TODO: refactor this block and JOIN::choose_subquery_plan + */ + Item_in_subselect *in_subs= (Item_in_subselect*) + select_lex->master_unit()->item; + + if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) + ; // setup materialized execution structures + else if (in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS) + { + if (in_subs->inject_in_to_exists_cond(this)) + DBUG_RETURN(1); + tmp_having= having; + } + else + DBUG_ASSERT(FALSE); + } DBUG_RETURN(0); } error= -1; // Error is sent to client @@ -1286,7 +1304,7 @@ JOIN::optimize() init_ftfuncs(thd, select_lex, test(order)); /* Create all structures needed for materialized subquery execution. */ - if (setup_subquery_materialization()) + if (optimize_unflattened_subqueries()) DBUG_RETURN(1); int res; @@ -1381,6 +1399,34 @@ JOIN::optimize() if (join_tab->is_using_loose_index_scan()) tmp_table_param.precomputed_group_by= TRUE; + error= 0; + DBUG_RETURN(0); + +setup_subq_exit: + /* + Even with zero matching rows, subqueries in the HAVING clause may + need to be evaluated if there are aggregate functions in the query. + If we planned to materialize the subquery, we need to set it up + properly before prematurely leaving optimize(). + */ + if (optimize_unflattened_subqueries()) + DBUG_RETURN(1); + error= 0; + DBUG_RETURN(0); +} + + +/** + Create and initialize objects neeed for the execution of a query plan. +*/ + +int JOIN::init_execution() +{ + DBUG_ENTER("JOIN::init_execution"); + + DBUG_ASSERT(optimized); + initialized= true; + /* Create a tmp table if distinct or if the sort is too complicated */ if (need_tmp) { @@ -1413,7 +1459,7 @@ JOIN::optimize() select_options, tmp_rows_limit, (char *) ""))) - { + { DBUG_RETURN(1); } @@ -1499,19 +1545,6 @@ JOIN::optimize() DBUG_RETURN(-1); /* purecov: inspected */ } - error= 0; - DBUG_RETURN(0); - -setup_subq_exit: - /* - Even with zero matching rows, subqueries in the HAVING clause may - need to be evaluated if there are aggregate functions in the - query. If we have planned to materialize the subquery, we need to - set it up properly before prematurely leaving optimize(). - */ - if (setup_subquery_materialization()) - DBUG_RETURN(1); - error= 0; DBUG_RETURN(0); } @@ -1775,6 +1808,9 @@ JOIN::exec() int tmp_error; DBUG_ENTER("JOIN::exec"); + if (!initialized && init_execution()) + DBUG_VOID_RETURN; + thd_proc_info(thd, "executing"); error= 0; if (procedure) @@ -2604,25 +2640,9 @@ err: @retval TRUE error occurred. */ -bool JOIN::setup_subquery_materialization() +bool JOIN::optimize_unflattened_subqueries() { - for (SELECT_LEX_UNIT *un= select_lex->first_inner_unit(); un; - un= un->next_unit()) - { - for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) - { - Item_subselect *subquery_predicate= sl->master_unit()->item; - if (subquery_predicate && - subquery_predicate->substype() == Item_subselect::IN_SUBS) - { - Item_in_subselect *in_subs= (Item_in_subselect*) subquery_predicate; - if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION && - in_subs->setup_engine()) - return TRUE; - } - } - } - return FALSE; + return select_lex->optimize_unflattened_subqueries(); } @@ -3143,6 +3163,10 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, sizeof(POSITION)*join->const_tables); join->best_read=1.0; } + if ((join->in_to_exists_where || join->in_to_exists_having) + && join->choose_subquery_plan()) + goto error; + /* Generate an execution plan from the found optimal join order. */ DBUG_RETURN(join->thd->killed || get_best_combination(join)); diff --git a/sql/sql_select.h b/sql/sql_select.h index 2b7fc8fd47c..c87147f6106 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1573,8 +1573,15 @@ public: bool union_part; ///< this subselect is part of union bool optimized; ///< flag to avoid double optimization in EXPLAIN + bool initialized; ///< flag to avoid double init_execution calls Array sj_subselects; + /* + Additional WHERE and HAVING predicates to be considered for IN=>EXISTS + subquery transformation of a JOIN object. + */ + Item *in_to_exists_where; + Item *in_to_exists_having; /* Temporary tables used to weed-out semi-join duplicates */ List sj_tmp_tables; @@ -1649,6 +1656,7 @@ public: ref_pointer_array_size= 0; zero_result_cause= 0; optimized= 0; + initialized= 0; cond_equal= 0; group_optimized_away= 0; @@ -1662,6 +1670,8 @@ public: no_const_tables= FALSE; first_select= sub_select; + in_to_exists_where= NULL; + in_to_exists_having= NULL; } int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num, @@ -1670,12 +1680,13 @@ public: SELECT_LEX_UNIT *unit); int optimize(); int reinit(); + int init_execution(); void exec(); int destroy(); void restore_tmp(); bool alloc_func_list(); bool flatten_subqueries(); - bool setup_subquery_materialization(); + bool optimize_unflattened_subqueries(); bool make_sum_func_list(List &all_fields, List &send_fields, bool before_group_by, bool recompute= FALSE); @@ -1735,6 +1746,7 @@ public: NULL : join_tab+const_tables; } bool setup_subquery_caches(); + bool choose_subquery_plan(); private: /** TRUE if the query contains an aggregate function but has no GROUP diff --git a/sql/sql_union.cc b/sql/sql_union.cc index 374e92c6a52..d91bdc069e8 100644 --- a/sql/sql_union.cc +++ b/sql/sql_union.cc @@ -186,6 +186,8 @@ st_select_lex_unit::init_prepare_fake_select_lex(THD *thd_arg) { (*order->item)->walk(&Item::change_context_processor, 0, (uchar*) &fake_select_lex->context); + (*order->item)->walk(&Item::set_fake_select_as_master_processor, 0, + (uchar*) fake_select_lex); } } @@ -271,6 +273,18 @@ bool st_select_lex_unit::prepare(THD *thd_arg, select_result *sel_result, can_skip_order_by= is_union_select && !(sl->braces && sl->explicit_limit); + /* + Remove all references from the select_lex_units to the subqueries that + are inside the ORDER BY clause. + */ + if (can_skip_order_by) + { + for (ORDER *ord= (ORDER *)sl->order_list.first; ord; ord= ord->next) + { + (*ord->item)->walk(&Item::eliminate_subselect_processor, FALSE, NULL); + } + } + saved_error= join->prepare(&sl->ref_pointer_array, (TABLE_LIST*) sl->table_list.first, sl->with_wild, diff --git a/sql/sql_update.cc b/sql/sql_update.cc index a7d12ce6efd..da744bf83a4 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -290,6 +290,10 @@ int mysql_update(THD *thd, DBUG_RETURN(1); /* purecov: inspected */ } + /* Apply the IN=>EXISTS transformation to all subqueries and optimize them. */ + if (select_lex->optimize_unflattened_subqueries()) + DBUG_RETURN(TRUE); + if (select_lex->inner_refs_list.elements && fix_inner_refs(thd, all_fields, select_lex, select_lex->ref_pointer_array)) DBUG_RETURN(1); From ccc8dabc9cb24b0bbe7544bbc57e4cdcbb1cb82f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 6 Sep 2010 13:19:47 +0300 Subject: [PATCH 05/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Fixed a crash in Item_equal::fix_length_and_dec that was a result of the architecture of the MWL#89 subquery optimization. The injection of IN->EXISTS predicates for subqueries happened inside make_join_statistics, after constant substitution, but before multiple equality substitution done by substitute_for_best_equal_field. As a result, when we called fix_fields for the WHERE clause after the injection of IN->EXISTS predicates, Item_equal was not fixed, and it was in a state not anticipated by Item_equal::fix_length_and_dec - the Item_equal containted only a constant, and no fields at all. The fix takes into account this new possible state when calling fix_fields. --- sql/item_cmpfunc.cc | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index a3fa1ecf748..75ffdf6f400 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -5649,6 +5649,9 @@ longlong Item_equal::val_int() Item_field *item_field; if (cond_false) return 0; + /* If there is a single constant and no fields, the equality is TRUE. */ + if (const_item && !fields.elements) + return 1; List_iterator_fast it(fields); Item *item= const_item ? const_item : it++; if ((null_value= item->null_value)) @@ -5669,6 +5672,15 @@ longlong Item_equal::val_int() void Item_equal::fix_length_and_dec() { Item *item= get_first(NULL); + if (!item) + { + /* + If there are no fields, there must be at least a constant, in which + case Item_equal::val_int evaluates to TRUE. + */ + DBUG_ASSERT(const_item); + return; + } eval_item= cmp_item::get_comparator(item->result_type(), item->collation.collation); } From 22a2cce37790fbbd7f847407013ce15d7aa8ed86 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 6 Sep 2010 14:26:30 +0300 Subject: [PATCH 06/16] Fixed Valgrind warning about uninitialized variable --- sql/item_subselect.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index aced2faaed5..5f941939fa2 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -1731,7 +1731,7 @@ Item_in_subselect::inject_single_in_to_exists_cond(JOIN * join, this->having= 0; */ Item* join_having= join->having ? join->having : join->tmp_having; - bool fix_res; + bool fix_res= 0; DBUG_ENTER("Item_in_subselect::inject_single_in_to_exists_cond"); if (join_having || select_lex->with_sum_func || From 35f0faf4c7cd5d0b6270855f274027f85c5aca72 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Sep 2010 16:49:20 +0300 Subject: [PATCH 07/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation - Corrected a wrong result that was recorded by the MySQL fix for BUG#39069. - Removed Item_func_isnull::cached_value and all the logic around this custom-made caching of the NULL result because MWL#89 optimizes subqueries before the outer query is being executed, and this cache cannot be made easily to work for all kinds of Items (specifically Item_sum_sum, but others too). --- mysql-test/r/subselect3.result | 4 ++-- sql/item_cmpfunc.cc | 22 ---------------------- sql/item_cmpfunc.h | 11 ----------- sql/opt_subselect.cc | 13 +++++++++---- 4 files changed, 11 insertions(+), 39 deletions(-) diff --git a/mysql-test/r/subselect3.result b/mysql-test/r/subselect3.result index 665bb9a2bde..cfc18413ee0 100644 --- a/mysql-test/r/subselect3.result +++ b/mysql-test/r/subselect3.result @@ -843,8 +843,8 @@ x ROW(11, 12) = (SELECT MAX(x), 22) ROW(11, 12) IN (SELECT MAX(x), 22) # 2nd and 3rd columns should be same for x == 11 only SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; x ROW(11, 12) = (SELECT MAX(x), 12) ROW(11, 12) IN (SELECT MAX(x), 12) -1 0 1 -2 0 1 +1 0 0 +2 0 0 11 1 1 DROP TABLE t1; # both columns should be same diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 75ffdf6f400..f8c46420035 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -4631,12 +4631,6 @@ Item *and_expressions(Item *a, Item *b, Item **org_item) longlong Item_func_isnull::val_int() { DBUG_ASSERT(fixed == 1); - /* - Handle optimization if the argument can't be null - This has to be here because of the test in update_used_tables(). - */ - if (!used_tables_cache && !with_subselect) - return cached_value; return args[0]->is_null() ? 1: 0; } @@ -4644,12 +4638,6 @@ longlong Item_is_not_null_test::val_int() { DBUG_ASSERT(fixed == 1); DBUG_ENTER("Item_is_not_null_test::val_int"); - if (!used_tables_cache && !with_subselect) - { - owner->was_null|= (!cached_value); - DBUG_PRINT("info", ("cached: %ld", (long) cached_value)); - DBUG_RETURN(cached_value); - } if (args[0]->is_null()) { DBUG_PRINT("info", ("null")); @@ -4666,19 +4654,9 @@ longlong Item_is_not_null_test::val_int() void Item_is_not_null_test::update_used_tables() { if (!args[0]->maybe_null) - { used_tables_cache= 0; /* is always true */ - cached_value= (longlong) 1; - } else - { args[0]->update_used_tables(); - if (!(used_tables_cache=args[0]->used_tables()) && !with_subselect) - { - /* Remember if the value is always NULL or never NULL */ - cached_value= (longlong) !args[0]->is_null(); - } - } } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index e161c1baa84..33407a161b8 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -1304,8 +1304,6 @@ public: class Item_func_isnull :public Item_bool_func { -protected: - longlong cached_value; public: Item_func_isnull(Item *a) :Item_bool_func(a) {} longlong val_int(); @@ -1323,18 +1321,9 @@ public: { used_tables_cache= 0; /* is always false */ const_item_cache= 1; - cached_value= (longlong) 0; } else - { args[0]->update_used_tables(); - if ((const_item_cache= !(used_tables_cache= args[0]->used_tables())) && - !with_subselect) - { - /* Remember if the value is always NULL or never NULL */ - cached_value= (longlong) args[0]->is_null(); - } - } } table_map not_null_tables() const { return 0; } optimize_type select_optimize() const { return OPTIMIZE_NULL; } diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 13b8bb40ea7..46d8591aaf1 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -3543,12 +3543,17 @@ bool JOIN::choose_subquery_plan() select_lex->master_unit()->item; /* Always revert to IN->EXISTS. */ - mat_strategy_cost= 1; - in_exists_strategy_cost= 0; + mat_strategy_cost= 0; + in_exists_strategy_cost= 1; - if (mat_strategy_cost < in_exists_strategy_cost) + /* + If (1) materialization is a possible strategy based on static analysis, and + (2) it is cheaper strategy than the IN->EXISTS transformation, then compute + in_subs via the materialization trategy. + */ + if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION && // 1 + mat_strategy_cost < in_exists_strategy_cost) // 2 { - in_subs->exec_method = Item_in_subselect::MATERIALIZATION; if (in_subs->setup_mat_engine()) { /* From 2c81150590cbb850e7e9f45e761f5ae7bc4f4701 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 Sep 2010 17:06:58 +0300 Subject: [PATCH 08/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Reset correctly the LIMIT and the uncacheable flag of a subquery when executing it via materialization. --- sql/item_subselect.cc | 10 ---------- sql/opt_subselect.cc | 27 +++++++++++++++++++++------ sql/sql_lex.cc | 1 + 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 5f941939fa2..38d1526f917 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -2436,16 +2436,6 @@ bool Item_in_subselect::setup_mat_engine() if (mat_engine->init(&select_engine->join->fields_list)) DBUG_RETURN(TRUE); - /* - Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec. - TODO: - Currently we set the subquery LIMIT to infinity, and this is correct - because we forbid at parse time LIMIT inside IN subqueries (see - Item_in_subselect::test_limit). However, once we allow this, here - we should set the correct limit if given in the query. - */ - unit->global_parameters->select_limit= NULL; - engine= mat_engine; DBUG_RETURN(FALSE); } diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 46d8591aaf1..e5e21976d84 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -3566,14 +3566,29 @@ bool JOIN::choose_subquery_plan() else in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; - if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) + if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) { + /* TODO: should we set/unset this flag for both select_lex and its unit? */ + in_subs->unit->uncacheable&= ~UNCACHEABLE_DEPENDENT; + select_lex->uncacheable&= ~UNCACHEABLE_DEPENDENT; - // TODO: should we unset the UNCACHEABLE_DEPENDENT flag fro - // select_lex->uncacheable; ? - // This affects how we execute JOIN::join_free - full or not. - // inner_join->restore_plan (keyuse, best_positions, best_read) - ; + /* + Reset the "LIMIT 1" set in Item_exists_subselect::fix_length_and_dec. + TODO: + Currently we set the subquery LIMIT to infinity, and this is correct + because we forbid at parse time LIMIT inside IN subqueries (see + Item_in_subselect::test_limit). However, once we allow this, here + we should set the correct limit if given in the query. + */ + in_subs->unit->global_parameters->select_limit= NULL; + in_subs->unit->set_limit(unit->global_parameters); + /* + Set the limit of this JOIN object as well, because normally its being + set in the beginning of JOIN::optimize, which was already done. + */ + select_limit= in_subs->unit->select_limit_cnt; + + // TODO: inner_join->restore_plan (keyuse, best_positions, best_read) } else if (in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS) res= in_subs->inject_in_to_exists_cond(this); diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 69a0e9c6e14..d9ceb431bb3 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3115,6 +3115,7 @@ bool st_select_lex::optimize_unflattened_subqueries() DBUG_ASSERT(!item_in || (item_in && !item_in->is_min_max_optimized)); if (item_in && item_in->create_in_to_exists_cond(inner_join)) return TRUE; + /* We need only 1 row to determine existence */ un->set_limit(un->global_parameters); un->thd->lex->current_select= sl; res= inner_join->optimize(); From 50888477af5d9f08088e2009dcef2f52d71f1f8e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 Sep 2010 13:17:27 +0300 Subject: [PATCH 09/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation * Fixed a "crack" between semijoin analysis and materialization analysis where semijoin didn't set the correct strategy for the IN predicate. * Cosmetic changes in the code/comments. --- sql/opt_subselect.cc | 58 +++++++++++++++++++++++++++++++++++++++----- sql/sql_select.cc | 28 --------------------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index e5e21976d84..0c7746ed72a 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -74,8 +74,6 @@ int check_and_do_in_subquery_rewrites(JOIN *join) 1) this join is inside a subquery (of any type except FROM-clause subquery) and 2) we aren't just normalizing a VIEW - 3) The join and its select_lex object do not represent the 'fake' - select used to compute the result of a UNION. Then perform early unconditional subquery transformations: - Convert subquery predicate into semi-join, or @@ -87,9 +85,8 @@ int check_and_do_in_subquery_rewrites(JOIN *join) TODO: for PS, make the whole block execute only on the first execution */ Item_subselect *subselect; - if (!thd->lex->view_prepare_mode && // (1) - (subselect= parent_unit->item))// && // (2) -// select_lex == parent_unit->fake_select_lex) // (3) + if (!thd->lex->view_prepare_mode && // (1) + (subselect= parent_unit->item)) // (2) { Item_in_subselect *in_subs= NULL; if (subselect->substype() == Item_subselect::IN_SUBS) @@ -134,7 +131,13 @@ int check_and_do_in_subquery_rewrites(JOIN *join) DBUG_RETURN(-1); /* purecov: deadcode */ } if (select_lex == parent_unit->fake_select_lex) + { + /* + The join and its select_lex object represent the 'fake' select used + to compute the result of a UNION. + */ DBUG_RETURN(0); + } DBUG_PRINT("info", ("Checking if subq can be converted to semi-join")); /* @@ -528,6 +531,15 @@ skip_conversion: FALSE)) DBUG_RETURN(TRUE); } + /* + Revert to the IN->EXISTS strategy in the rare case when the subquery could + be flattened. + TODO: This is a limitation done for simplicity. Such subqueries could also + be executed via materialization. In order to determine this, we should + re-run the test for materialization that was done in + check_and_do_in_subquery_rewrites. + */ + (*in_subq)->exec_method= Item_in_subselect::IN_TO_EXISTS; } if (arena) @@ -3524,6 +3536,34 @@ static void remove_subq_pushed_predicates(JOIN *join, Item **where) } +/** + Setup for execution all subqueries of a query, for which the optimizer + chose hash semi-join. + + @details Iterate over all immediate child subqueries of the query, and if + they are under an IN predicate, and the optimizer chose to compute it via + materialization: + - optimize each subquery, + - choose an optimial execution strategy for the IN predicate - either + materialization, or an IN=>EXISTS transformation with an approriate + engine. + + This phase must be called after substitute_for_best_equal_field() because + that function may replace items with other items from a multiple equality, + and we need to reference the correct items in the index access method of the + IN predicate. + + @return Operation status + @retval FALSE success. + @retval TRUE error occurred. +*/ + +bool JOIN::optimize_unflattened_subqueries() +{ + return select_lex->optimize_unflattened_subqueries(); +} + + bool JOIN::choose_subquery_plan() { double mat_strategy_cost; /* The cost to compute IN via materialization. */ @@ -3564,7 +3604,13 @@ bool JOIN::choose_subquery_plan() } } else - in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; + { + /* + Previous optimizer phases should have chosen either a materialization + or IN->EXISTS strategy. + */ + DBUG_ASSERT(in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS); + } if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) { diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 574353897de..26ec89c7a47 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -2618,34 +2618,6 @@ err: } -/** - Setup for execution all subqueries of a query, for which the optimizer - chose hash semi-join. - - @details Iterate over all immediate child subqueries of the query, and if - they are under an IN predicate, and the optimizer chose to compute it via - materialization: - - optimize each subquery, - - choose an optimial execution strategy for the IN predicate - either - materialization, or an IN=>EXISTS transformation with an approriate - engine. - - This phase must be called after substitute_for_best_equal_field() because - that function may replace items with other items from a multiple equality, - and we need to reference the correct items in the index access method of the - IN predicate. - - @return Operation status - @retval FALSE success. - @retval TRUE error occurred. -*/ - -bool JOIN::optimize_unflattened_subqueries() -{ - return select_lex->optimize_unflattened_subqueries(); -} - - /***************************************************************************** Create JOIN_TABS, make a guess about the table types, Approximate how many records will be used in each table From 8ec5e13f1f0d56afe42e5ded02baeab7a6a60261 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Sep 2010 18:32:44 +0300 Subject: [PATCH 10/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Phase 3: Implementation of re-optimization of subqueries with injected predicates and cost comparison between Materialization and IN->EXISTS strategies. The commit contains the following known problems: - The implementation of EXPLAIN has not been re-engineered to reflect the changes in subquery optimization. EXPLAIN for subqueries is called during the execute phase, which results in different code paths during JOIN::optimize and thus in differing EXPLAIN messages for constant/system tables. - There are some valgrind warnings that need investigation - Several EXPLAINs with minor differences need to be reconsidered after fixing the EXPLAIN problem above. This patch also adds one extra optimizer_switch: 'in_to_exists' for complete manual control of the subquery execution strategies. --- mysql-test/r/subselect3.result | 7 +- mysql-test/r/subselect3_jcl6.result | 11 +- mysql-test/t/subselect3.test | 3 +- sql/item_subselect.cc | 300 +++++++------------------ sql/item_subselect.h | 45 ++-- sql/mysql_priv.h | 18 +- sql/mysqld.cc | 8 +- sql/opt_subselect.cc | 328 +++++++++++++++++++--------- sql/share/errmsg.txt | 2 + sql/sql_lex.cc | 18 +- sql/sql_select.cc | 299 ++++++++++++++++++------- sql/sql_select.h | 15 +- 12 files changed, 600 insertions(+), 454 deletions(-) diff --git a/mysql-test/r/subselect3.result b/mysql-test/r/subselect3.result index cfc18413ee0..39ae0bbb4de 100644 --- a/mysql-test/r/subselect3.result +++ b/mysql-test/r/subselect3.result @@ -840,7 +840,12 @@ x ROW(11, 12) = (SELECT MAX(x), 22) ROW(11, 12) IN (SELECT MAX(x), 22) 1 0 0 2 0 0 11 0 0 -# 2nd and 3rd columns should be same for x == 11 only +# 2nd and 3rd columns should be same +EXPLAIN SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +3 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; x ROW(11, 12) = (SELECT MAX(x), 12) ROW(11, 12) IN (SELECT MAX(x), 12) 1 0 0 diff --git a/mysql-test/r/subselect3_jcl6.result b/mysql-test/r/subselect3_jcl6.result index d25ca436311..0a969839ed5 100644 --- a/mysql-test/r/subselect3_jcl6.result +++ b/mysql-test/r/subselect3_jcl6.result @@ -844,11 +844,16 @@ x ROW(11, 12) = (SELECT MAX(x), 22) ROW(11, 12) IN (SELECT MAX(x), 22) 1 0 0 2 0 0 11 0 0 -# 2nd and 3rd columns should be same for x == 11 only +# 2nd and 3rd columns should be same +EXPLAIN SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 +3 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; x ROW(11, 12) = (SELECT MAX(x), 12) ROW(11, 12) IN (SELECT MAX(x), 12) -1 0 1 -2 0 1 +1 0 0 +2 0 0 11 1 1 DROP TABLE t1; # both columns should be same diff --git a/mysql-test/t/subselect3.test b/mysql-test/t/subselect3.test index a621c4c776c..3f63149ce96 100644 --- a/mysql-test/t/subselect3.test +++ b/mysql-test/t/subselect3.test @@ -681,7 +681,8 @@ SELECT a, ROW(11, 12) = (SELECT a, 12), ROW(11, 12) IN (SELECT a, 12) FROM t1; # The x alias is used below to workaround bug #40674. # Regression tests for sum function on outer column in subselect from dual: SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 22), ROW(11, 12) IN (SELECT MAX(x), 22) FROM t1; ---echo # 2nd and 3rd columns should be same for x == 11 only +--echo # 2nd and 3rd columns should be same +EXPLAIN SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; SELECT a AS x, ROW(11, 12) = (SELECT MAX(x), 12), ROW(11, 12) IN (SELECT MAX(x), 12) FROM t1; DROP TABLE t1; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 38d1526f917..0a91e4a2a77 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -62,7 +62,6 @@ void Item_subselect::init(st_select_lex *select_lex, DBUG_PRINT("enter", ("select_lex: 0x%lx", (long) select_lex)); unit= select_lex->master_unit(); thd= unit->thd; - is_min_max_optimized= FALSE; if (unit->item) { /* @@ -160,8 +159,8 @@ void Item_in_subselect::cleanup() } first_execution= TRUE; is_constant= FALSE; - if (exec_method == MATERIALIZATION) - exec_method= NOT_TRANSFORMED; + if (in_strategy & SUBS_MATERIALIZATION) + in_strategy= 0; pushed_cond_guards= NULL; Item_subselect::cleanup(); DBUG_VOID_RETURN; @@ -618,7 +617,7 @@ bool Item_in_subselect::exec() - on a cost-based basis, that takes into account the cost of a cache lookup, the cache hit rate, and the savings per cache hit. */ - if (!left_expr_cache && exec_method == MATERIALIZATION) + if (!left_expr_cache && (in_strategy & SUBS_MATERIALIZATION)) init_left_expr_cache(); /* @@ -1072,7 +1071,7 @@ Item_in_subselect::Item_in_subselect(Item * left_exp, st_select_lex *select_lex): Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), is_constant(FALSE), optimizer(0), pushed_cond_guards(NULL), - exec_method(NOT_TRANSFORMED), upper_item(0) + in_strategy(0), upper_item(0) { DBUG_ENTER("Item_in_subselect::Item_in_subselect"); left_expr= left_exp; @@ -1488,7 +1487,6 @@ Item_in_subselect::single_value_transformer(JOIN *join) } /* fix fields is already called for left expression */ substitution= func->create(left_expr, subs); - is_min_max_optimized= TRUE; DBUG_RETURN(RES_OK); } @@ -1555,8 +1553,21 @@ Item_in_subselect::single_value_transformer(JOIN *join) } +bool Item_in_subselect::fix_having(Item *having, SELECT_LEX *select_lex) +{ + bool fix_res= 0; + if (!having->fixed) + { + select_lex->having_fix_field= 1; + fix_res= having->fix_fields(thd, 0); + select_lex->having_fix_field= 0; + } + return fix_res; +} + + /** - Transofrm an IN predicate into EXISTS via predicate injection. + Transform an IN predicate into EXISTS via predicate injection. @details The transformation injects additional predicates into the subquery (and makes the subquery correlated) as follows. @@ -1607,6 +1618,9 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, DBUG_ENTER("Item_in_subselect::create_single_in_to_exists_cond"); + *where_item= NULL; + *having_item= NULL; + if (join_having || select_lex->with_sum_func || select_lex->group_list.elements) { @@ -1626,11 +1640,11 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, item= new Item_func_trig_cond(item, get_cond_guard(0)); } - if (item->fix_fields(thd, 0)) + if (!join_having) + item->name= (char*) in_having_cond; + if (fix_having(item, select_lex)) DBUG_RETURN(RES_ERROR); - *having_item= item; - *where_item= NULL; } else { @@ -1651,10 +1665,9 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, get_cond_guard(0)))) DBUG_RETURN(RES_ERROR); } - - if (having->fix_fields(thd, 0)) - DBUG_RETURN(RES_ERROR); - + having->name= (char*) in_having_cond; + if (fix_having(having, select_lex)) + DBUG_RETURN(RES_ERROR); *having_item= having; item= new Item_cond_or(item, @@ -1670,20 +1683,20 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, DBUG_RETURN(RES_ERROR); } - if (item->fix_fields(thd, 0)) - DBUG_RETURN(RES_ERROR); - + /* + TODO: figure out why the following is done here in + single_value_transformer but there is no corresponding action in + row_value_transformer? + */ + item->name= (char *) in_additional_cond; + if (!item->fixed && item->fix_fields(thd, 0)) + DBUG_RETURN(RES_ERROR); *where_item= item; } else { if (select_lex->master_unit()->is_union()) { - /* - comparison functions can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ Item *new_having= func->create(expr, new Item_ref_null_helper(&select_lex->context, this, @@ -1697,132 +1710,10 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, DBUG_RETURN(RES_ERROR); } - if (new_having->fix_fields(thd, 0)) - DBUG_RETURN(RES_ERROR); - + new_having->name= (char*) in_having_cond; + if (fix_having(new_having, select_lex)) + DBUG_RETURN(RES_ERROR); *having_item= new_having; - *where_item= NULL; - } - else - { - DBUG_ASSERT(FALSE); - /* TIMOUR TODO */ - *having_item= NULL; - *where_item= (Item*) select_lex->item_list.head(); - } - } - } - - DBUG_RETURN(RES_OK); -} - - - -Item_subselect::trans_res -Item_in_subselect::inject_single_in_to_exists_cond(JOIN * join, - Item *where_item, - Item *having_item) -{ - SELECT_LEX *select_lex= join->select_lex; - /* - The non-transformed HAVING clause of 'join' may be stored differently in - JOIN::optimize: - this->tmp_having= this->having - this->having= 0; - */ - Item* join_having= join->having ? join->having : join->tmp_having; - bool fix_res= 0; - DBUG_ENTER("Item_in_subselect::inject_single_in_to_exists_cond"); - - if (join_having || select_lex->with_sum_func || - select_lex->group_list.elements) - { - /* - AND and comparison functions can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - thd->change_item_tree(&select_lex->having, and_items(join_having, having_item)); - join->having= select_lex->having; - if (join->having == having_item) - having_item->name= (char*)in_having_cond; - select_lex->having_fix_field= 1; - /* - we do not check join->having->fixed, because Item_and (from and_items) - or comparison function (from func->create) can't be fixed after creation - */ - if (!join->having->fixed) - fix_res= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (fix_res) - DBUG_RETURN(RES_ERROR); - } - else - { - if (select_lex->table_list.elements) - { - if (!abort_on_null && select_lex->item_list.head()->maybe_null) - { - /* - Item_is_not_null_test can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - having_item->name= (char*)in_having_cond; - thd->change_item_tree(&select_lex->having, having_item); - join->having= select_lex->having; - select_lex->having_fix_field= 1; - /* - we do not check join->having->fixed, because Item_and (from - and_items) or comparison function (from func->create) can't be - fixed after creation - */ - if (!join->having->fixed) - fix_res= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (fix_res) - DBUG_RETURN(RES_ERROR); - } - /* - TODO: figure out why the following is done here in - single_value_transformer but there is no corresponding action in - row_value_transformer? - */ - where_item->name= (char *)in_additional_cond; - - /* - AND can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - thd->change_item_tree(&select_lex->where, and_items(join->conds, where_item)); - join->conds= select_lex->where; - select_lex->where->top_level_item(); - /* - we do not check join->conds->fixed, because Item_and can't be fixed - after creation - */ - if (!join->conds->fixed && join->conds->fix_fields(thd, 0)) - DBUG_RETURN(RES_ERROR); - } - else - { - if (select_lex->master_unit()->is_union()) - { - having_item->name= (char*)in_having_cond; - thd->change_item_tree(&select_lex->having, having_item); - join->having= select_lex->having; - select_lex->having_fix_field= 1; - - /* - we do not check join->having->fixed, because comparison function - (from func->create) can't be fixed after creation - */ - if (!join->having->fixed) - fix_res= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (fix_res) - DBUG_RETURN(RES_ERROR); } else DBUG_ASSERT(FALSE); @@ -1992,7 +1883,6 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, item_having_part2->top_level_item(); } *having_item= and_items(*having_item, item_having_part2); - (*having_item)->top_level_item(); } else { @@ -2074,66 +1964,22 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, } *where_item= and_items(*where_item, item); } - (*where_item)->fix_fields(thd, 0); } - DBUG_RETURN(RES_OK); -} - - -Item_subselect::trans_res -Item_in_subselect::inject_row_in_to_exists_cond(JOIN * join, - Item *where_item, - Item *having_item) -{ - SELECT_LEX *select_lex= join->select_lex; - /* - The non-transformed HAVING clause of 'join' may be stored differently in - JOIN::optimize: - this->tmp_having= this->having - this->having= 0; - */ - Item* join_having= join->having ? join->having : join->tmp_having; - bool is_having_used= (join_having || select_lex->with_sum_func || - select_lex->group_list.first || - !select_lex->table_list.elements); - - DBUG_ENTER("Item_in_subselect::inject_row_in_to_exists_cond"); - - if (!is_having_used) + if (*where_item) { - /* - AND can't be changed during fix_fields() - we can assign select_lex->where here, and pass 0 as last - argument (reference) to fix_fields() - */ - thd->change_item_tree(&select_lex->where, and_items(join->conds, where_item)); - join->conds= select_lex->where; - select_lex->where->top_level_item(); - if (!join->conds->fixed && join->conds->fix_fields(thd, 0)) + if (!(*where_item)->fixed && (*where_item)->fix_fields(thd, 0)) DBUG_RETURN(RES_ERROR); + (*where_item)->top_level_item(); } - if (having_item) + if (*having_item) { - bool res; - thd->change_item_tree(&select_lex->having, - and_items(join_having, having_item)); - join->having= select_lex->having; - if (having_item == select_lex->having) - having_item->name= (char*)in_having_cond; - select_lex->having->top_level_item(); - /* - AND can't be changed during fix_fields() - we can assign select_lex->having here, and pass 0 as last - argument (reference) to fix_fields() - */ - select_lex->having_fix_field= 1; - if (!join->having->fixed) - res= join->having->fix_fields(thd, 0); - select_lex->having_fix_field= 0; - if (res) + if (!join_having) + (*having_item)->name= (char*) in_having_cond; + if (fix_having(*having_item, select_lex)) DBUG_RETURN(RES_ERROR); + (*having_item)->top_level_item(); } DBUG_RETURN(RES_OK); @@ -2175,20 +2021,36 @@ bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg) } -bool Item_in_subselect::inject_in_to_exists_cond(JOIN * join_arg) +bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) { - Item_subselect::trans_res res; + SELECT_LEX *select_lex= join_arg->select_lex; + Item *where_item= join_arg->in_to_exists_where; + Item *having_item= join_arg->in_to_exists_having; - if (left_expr->cols() == 1) - res= inject_single_in_to_exists_cond(join_arg, - join_arg->in_to_exists_where, - join_arg->in_to_exists_having); - else - res= inject_row_in_to_exists_cond(join_arg, - join_arg->in_to_exists_where, - join_arg->in_to_exists_having); + DBUG_ENTER("Item_in_subselect::inject_in_to_exists_cond"); - return (res != RES_OK && res != RES_REDUCE); + if (where_item) + { + where_item= and_items(join_arg->conds, where_item); + if (!where_item->fixed && where_item->fix_fields(thd, 0)) + DBUG_RETURN(true); + thd->change_item_tree(&select_lex->where, where_item); + select_lex->where->top_level_item(); + join_arg->conds= select_lex->where; + } + + if (having_item) + { + Item* join_having= join_arg->having ? join_arg->having:join_arg->tmp_having; + having_item= and_items(join_having, having_item); + if (fix_having(having_item, select_lex)) + DBUG_RETURN(true); + thd->change_item_tree(&select_lex->having, having_item); + select_lex->having->top_level_item(); + join_arg->having= select_lex->having; + } + + DBUG_RETURN(false); } @@ -2301,7 +2163,7 @@ err: void Item_in_subselect::print(String *str, enum_query_type query_type) { - if (exec_method == IN_TO_EXISTS) + if (in_strategy & SUBS_IN_TO_EXISTS) str->append(STRING_WITH_LEN("")); else { @@ -2317,7 +2179,7 @@ bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref) uint outer_cols_num; List *inner_cols; - if (exec_method == SEMI_JOIN) + if (in_strategy & SUBS_SEMI_JOIN) return !( (*ref)= new Item_int(1)); /* @@ -2498,10 +2360,7 @@ bool Item_in_subselect::init_cond_guards() Callback to test if an IN predicate is expensive. @details - IN predicates are considered expensive only if they will be executed via - materialization. The return value affects the behavior of - make_cond_for_table() in such a way that it is unchanged when we use - the IN=>EXISTS transformation to compute IN. + The return value affects the behavior of make_cond_for_table(). @retval TRUE if the predicate is expensive @retval FALSE otherwise @@ -2509,7 +2368,8 @@ bool Item_in_subselect::init_cond_guards() bool Item_in_subselect::is_expensive_processor(uchar *arg) { - return TRUE; // exec_method == MATERIALIZATION; + /* TIMOUR: TODO: decide on a cost basis whether it is expensive or not. */ + return TRUE; } @@ -2517,7 +2377,7 @@ Item_subselect::trans_res Item_allany_subselect::select_transformer(JOIN *join) { DBUG_ENTER("Item_allany_subselect::select_transformer"); - exec_method= IN_TO_EXISTS; + in_strategy= SUBS_IN_TO_EXISTS; if (upper_item) upper_item->show= 1; DBUG_RETURN(select_in_like_transformer(join)); @@ -2526,7 +2386,7 @@ Item_allany_subselect::select_transformer(JOIN *join) void Item_allany_subselect::print(String *str, enum_query_type query_type) { - if (exec_method == IN_TO_EXISTS) + if (in_strategy & SUBS_IN_TO_EXISTS) str->append(STRING_WITH_LEN("")); else { diff --git a/sql/item_subselect.h b/sql/item_subselect.h index d35d036e9ea..308fbc6b20a 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -108,9 +108,6 @@ public: /* subquery is transformed */ bool changed; - /* TIMOUR: this is temporary, remove it. */ - bool is_min_max_optimized; - /* TRUE <=> The underlying SELECT is correlated w.r.t some ancestor select */ bool is_correlated; @@ -121,6 +118,12 @@ public: Item_subselect(); virtual subs_type substype() { return UNKNOWN_SUBS; } + bool is_in_predicate() + { + return (substype() == Item_subselect::IN_SUBS || + substype() == Item_subselect::ALL_SUBS || + substype() == Item_subselect::ANY_SUBS); + } /* We need this method, because some compilers do not allow 'this' @@ -314,6 +317,18 @@ public: }; +/* + Possible methods to execute an IN predicate. These are set by the optimizer + based on user-set optimizer switches, syntactic analysis and cost comparison. +*/ +#define SUBS_NOT_TRANSFORMED 0 /* No execution method was chosen for this IN. */ +#define SUBS_SEMI_JOIN 1 /* IN was converted to semi-join. */ +#define SUBS_IN_TO_EXISTS 2 /* IN was converted to correlated EXISTS. */ +#define SUBS_MATERIALIZATION 4 /* Execute IN via subquery materialization. */ +/* Partial matching substrategies of MATERIALIZATION. */ +#define SUBS_PARTIAL_MATCH_ROWID_MERGE 8 +#define SUBS_PARTIAL_MATCH_TABLE_SCAN 16 + /** Representation of IN subquery predicates of the form "left_expr IN (SELECT ...)". @@ -362,19 +377,13 @@ protected: trans_res select_in_like_transformer(JOIN *join); trans_res single_value_transformer(JOIN *join); trans_res row_value_transformer(JOIN * join); + bool fix_having(Item *having, st_select_lex *select_lex); trans_res create_single_in_to_exists_cond(JOIN * join, Item **where_item, Item **having_item); - trans_res inject_single_in_to_exists_cond(JOIN * join, - Item *where_item, - Item *having_item); - trans_res create_row_in_to_exists_cond(JOIN * join, Item **where_item, Item **having_item); - trans_res inject_row_in_to_exists_cond(JOIN * join, - Item *where_item, - Item *having_item); public: Item *left_expr; /* Priority of this predicate in the convert-to-semi-join-nest process. */ @@ -407,14 +416,8 @@ public: */ bool sjm_scan_allowed; - /* The method chosen to execute the IN predicate. */ - enum enum_exec_method { - NOT_TRANSFORMED, /* No execution method was chosen for this IN. */ - SEMI_JOIN, /* IN was converted to semi-join nest and should be removed. */ - IN_TO_EXISTS, /* IN was converted to correlated EXISTS. */ - MATERIALIZATION /* IN will be executed via subquery materialization. */ - }; - enum_exec_method exec_method; + /* A bitmap of possible execution strategies for an IN predicate. */ + uchar in_strategy; bool *get_cond_guard(int i) { @@ -433,7 +436,7 @@ public: Item_in_subselect() :Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), is_constant(FALSE), optimizer(0), abort_on_null(0), - pushed_cond_guards(NULL), func(NULL), exec_method(NOT_TRANSFORMED), + pushed_cond_guards(NULL), func(NULL), in_strategy(0), upper_item(0) {} void cleanup(); @@ -446,8 +449,8 @@ public: was_null= 0; } trans_res select_transformer(JOIN *join); - bool create_in_to_exists_cond(JOIN * join_arg); - bool inject_in_to_exists_cond(JOIN * join_arg); + bool create_in_to_exists_cond(JOIN *join_arg); + bool inject_in_to_exists_cond(JOIN *join_arg); virtual bool exec(); longlong val_int(); diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index ba60bab9b50..cc2740c6e8b 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -563,20 +563,20 @@ protected: #define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION 4 #define OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT 8 #define OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN 16 - #define OPTIMIZER_SWITCH_FIRSTMATCH 32 #define OPTIMIZER_SWITCH_LOOSE_SCAN 64 #define OPTIMIZER_SWITCH_MATERIALIZATION 128 -#define OPTIMIZER_SWITCH_SEMIJOIN 256 -#define OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE 512 -#define OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN 1024 -#define OPTIMIZER_SWITCH_SUBQUERY_CACHE (1<<11) +#define OPTIMIZER_SWITCH_IN_TO_EXISTS 256 +#define OPTIMIZER_SWITCH_SEMIJOIN 512 +#define OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE 1024 +#define OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN (1<<11) +#define OPTIMIZER_SWITCH_SUBQUERY_CACHE (1<<12) #ifdef DBUG_OFF -# define OPTIMIZER_SWITCH_LAST (1<<12) -#else -# define OPTIMIZER_SWITCH_TABLE_ELIMINATION (1<<12) # define OPTIMIZER_SWITCH_LAST (1<<13) +#else +# define OPTIMIZER_SWITCH_TABLE_ELIMINATION (1<<13) +# define OPTIMIZER_SWITCH_LAST (1<<14) #endif #ifdef DBUG_OFF @@ -589,6 +589,7 @@ protected: OPTIMIZER_SWITCH_FIRSTMATCH | \ OPTIMIZER_SWITCH_LOOSE_SCAN | \ OPTIMIZER_SWITCH_MATERIALIZATION | \ + OPTIMIZER_SWITCH_IN_TO_EXISTS | \ OPTIMIZER_SWITCH_SEMIJOIN | \ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN|\ @@ -603,6 +604,7 @@ protected: OPTIMIZER_SWITCH_FIRSTMATCH | \ OPTIMIZER_SWITCH_LOOSE_SCAN | \ OPTIMIZER_SWITCH_MATERIALIZATION | \ + OPTIMIZER_SWITCH_IN_TO_EXISTS | \ OPTIMIZER_SWITCH_SEMIJOIN | \ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN|\ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 1593a584454..44fcd76ce47 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -304,7 +304,7 @@ static const char *optimizer_switch_names[]= "index_merge","index_merge_union","index_merge_sort_union", "index_merge_intersection", "index_condition_pushdown", - "firstmatch","loosescan","materialization", "semijoin", + "firstmatch","loosescan","materialization","in_to_exists","semijoin", "partial_match_rowid_merge", "partial_match_table_scan", "subquery_cache", @@ -325,6 +325,7 @@ static const unsigned int optimizer_switch_names_len[]= sizeof("firstmatch") - 1, sizeof("loosescan") - 1, sizeof("materialization") - 1, + sizeof("in_to_exists") - 1, sizeof("semijoin") - 1, sizeof("partial_match_rowid_merge") - 1, sizeof("partial_match_table_scan") - 1, @@ -412,9 +413,10 @@ static const char *optimizer_switch_str="index_merge=on,index_merge_union=on," "firstmatch=on," "loosescan=on," "materialization=on," + "in_to_exists=on," "semijoin=on," "partial_match_rowid_merge=on," - "partial_match_table_scan=on," + "partial_match_table_scan=on," "subquery_cache=on" #ifndef DBUG_OFF ",table_elimination=on"; @@ -7233,7 +7235,7 @@ The minimum value for this variable is 4096.", {"optimizer_switch", OPT_OPTIMIZER_SWITCH, "optimizer_switch=option=val[,option=val...], where option={index_merge, " "index_merge_union, index_merge_sort_union, index_merge_intersection, " - "index_condition_pushdown, firstmatch, loosescan, materialization, " + "index_condition_pushdown, firstmatch, loosescan, materialization, in_to_exists, " "semijoin, partial_match_rowid_merge, partial_match_table_scan, " "subquery_cache" #ifndef DBUG_OFF diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 0c7746ed72a..e93478c5195 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -165,7 +165,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join) thd->thd_marker.emb_on_expr_nest && // 5 select_lex->outer_select()->join && // 6 parent_unit->first_select()->leaf_tables && // 7 - in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED && // 8 + !in_subs->in_strategy && // 8 select_lex->outer_select()->leaf_tables && // 9 !((join->select_options | // 10 select_lex->outer_select()->join->select_options) // 10 @@ -185,65 +185,71 @@ int check_and_do_in_subquery_rewrites(JOIN *join) else { DBUG_PRINT("info", ("Subquery can't be converted to semi-join")); - /* - Check if the subquery predicate can be executed via materialization. - The required conditions are: - 1. Subquery predicate is an IN/=ANY subq predicate - 2. Subquery is a single SELECT (not a UNION) - 3. Subquery is not a table-less query. In this case there is no - point in materializing. - 3A The upper query is not a table-less SELECT ... FROM DUAL. We + if (!optimizer_flag(thd, OPTIMIZER_SWITCH_IN_TO_EXISTS) && + !optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION)) + my_error(ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES, MYF(0)); + + if (in_subs) + { + /* Subquery predicate is an IN/=ANY predicate. */ + if (optimizer_flag(thd, OPTIMIZER_SWITCH_IN_TO_EXISTS)) + in_subs->in_strategy|= SUBS_IN_TO_EXISTS; + if (optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION)) + in_subs->in_strategy|= SUBS_MATERIALIZATION; + + /* + Check if the subquery predicate can be executed via materialization. + The required conditions are: + 1. Subquery is a single SELECT (not a UNION) + 2. Subquery is not a table-less query. In this case there is no + point in materializing. + 2A The upper query is not a table-less SELECT ... FROM DUAL. We can't do materialization for SELECT .. FROM DUAL because it does not call setup_subquery_materialization(). We could make SELECT ... FROM DUAL call that function but that doesn't seem to be the case that is worth handling. - 4. Either the subquery predicate is a top-level predicate, or at - least one partial match strategy is enabled. If no partial match - strategy is enabled, then materialization cannot be used for - non-top-level queries because it cannot handle NULLs correctly. - 5. Subquery is non-correlated - TODO: - This is an overly restrictive condition. It can be extended to: - (Subquery is non-correlated || - Subquery is correlated to any query outer to IN predicate || - (Subquery is correlated to the immediate outer query && - Subquery !contains {GROUP BY, ORDER BY [LIMIT], - aggregate functions}) && subquery predicate is not under "NOT IN")) - 6. No execution method was already chosen (by a prepared statement). + 3. Either the subquery predicate is a top-level predicate, or at + least one partial match strategy is enabled. If no partial match + strategy is enabled, then materialization cannot be used for + non-top-level queries because it cannot handle NULLs correctly. + 4. Subquery is non-correlated + TODO: + This is an overly restrictive condition. It can be extended to: + (Subquery is non-correlated || + Subquery is correlated to any query outer to IN predicate || + (Subquery is correlated to the immediate outer query && + Subquery !contains {GROUP BY, ORDER BY [LIMIT], + aggregate functions}) && subquery predicate is not under "NOT IN")) - (*) The subquery must be part of a SELECT statement. The current - condition also excludes multi-table update statements. - - Determine whether we will perform subquery materialization before - calling the IN=>EXISTS transformation, so that we know whether to - perform the whole transformation or only that part of it which wraps - Item_in_subselect in an Item_in_optimizer. - */ - if (optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION) && - in_subs && // 1 - !select_lex->is_part_of_union() && // 2 - parent_unit->first_select()->leaf_tables && // 3 - thd->lex->sql_command == SQLCOM_SELECT && // * - select_lex->outer_select()->leaf_tables && // 3A - subquery_types_allow_materialization(in_subs) && - // psergey-todo: duplicated_subselect_card_check: where it's done? - (in_subs->is_top_level_item() || - optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) || - optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)) &&//4 - !in_subs->is_correlated && // 5 - in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6 - { - /* - Materialization is possible, later the optimize phase of each - subquery will choose either materialization or in-to-exists based - on cost. + (*) The subquery must be part of a SELECT statement. The current + condition also excludes multi-table update statements. */ - in_subs->exec_method= Item_in_subselect::MATERIALIZATION; - } - else if (in_subs) - { - /* Materialization is not possible at all. */ - in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; + if (!(in_subs->in_strategy & SUBS_MATERIALIZATION && + !select_lex->is_part_of_union() && // 1 + parent_unit->first_select()->leaf_tables && // 2 + thd->lex->sql_command == SQLCOM_SELECT && // * + select_lex->outer_select()->leaf_tables && // 2A + subquery_types_allow_materialization(in_subs) && + // psergey-todo: duplicated_subselect_card_check: where it's done? + (in_subs->is_top_level_item() || //3 + optimizer_flag(thd, + OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) || //3 + optimizer_flag(thd, + OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)) && //3 + !in_subs->is_correlated)) //4 + { + /* Materialization is not possible based on syntactic properties. */ + in_subs->in_strategy&= ~SUBS_MATERIALIZATION; + } + + if (!in_subs->in_strategy) + { + /* + If neither materialization is possible, nor the user chose + IN-TO-EXISTS, choose IN-TO-EXISTS as the only universal strategy. + */ + in_subs->in_strategy|= SUBS_IN_TO_EXISTS; + } } /* @@ -533,13 +539,13 @@ skip_conversion: } /* Revert to the IN->EXISTS strategy in the rare case when the subquery could - be flattened. + not be flattened. TODO: This is a limitation done for simplicity. Such subqueries could also be executed via materialization. In order to determine this, we should re-run the test for materialization that was done in check_and_do_in_subquery_rewrites. */ - (*in_subq)->exec_method= Item_in_subselect::IN_TO_EXISTS; + (*in_subq)->in_strategy= SUBS_IN_TO_EXISTS; } if (arena) @@ -800,8 +806,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred) /* 3. Remove the original subquery predicate from the WHERE/ON */ // The subqueries were replaced for Item_int(1) earlier - subq_pred->exec_method= - Item_in_subselect::SEMI_JOIN; // for subsequent executions + subq_pred->in_strategy= SUBS_SEMI_JOIN; // for subsequent executions /*TODO: also reset the 'with_subselect' there. */ /* n. Adjust the parent_join->tables counter */ @@ -3393,6 +3398,7 @@ int rewrite_to_index_subquery_engine(JOIN *join) JOIN_TAB* join_tab=join->join_tab; SELECT_LEX_UNIT *unit= join->unit; DBUG_ENTER("rewrite_to_index_subquery_engine"); + /* is this simple IN subquery? */ @@ -3564,56 +3570,157 @@ bool JOIN::optimize_unflattened_subqueries() } -bool JOIN::choose_subquery_plan() +bool JOIN::choose_subquery_plan(table_map join_tables) { - double mat_strategy_cost; /* The cost to compute IN via materialization. */ - double in_exists_strategy_cost; /* The cost of the IN->EXISTS strategy. */ - bool res; + /* The original QEP of the subquery. */ + DYNAMIC_ARRAY save_keyuse; /* Copy of the JOIN::keyuse array. */ + POSITION save_best_positions[MAX_TABLES+1]; /* Copy of JOIN::best_positions */ + /* Copies of the JOIN_TAB::keyuse pointers for each JOIN_TAB. */ + KEYUSE *save_join_tab_keyuse[MAX_TABLES]; + /* Copies of JOIN_TAB::checked_keys for each JOIN_TAB. */ + key_map save_join_tab_checked_keys[MAX_TABLES]; - DBUG_ASSERT(in_to_exists_where || in_to_exists_having); - DBUG_ASSERT(select_lex->master_unit()->item && - (select_lex->master_unit()->item->substype() == - Item_subselect::IN_SUBS || - select_lex->master_unit()->item->substype() == - Item_subselect::ALL_SUBS || - select_lex->master_unit()->item->substype() == - Item_subselect::ANY_SUBS)); + bool in_exists_reoptimized= false; + Item_in_subselect *in_subs; - Item_in_subselect *in_subs= (Item_in_subselect*) - select_lex->master_unit()->item; - - /* Always revert to IN->EXISTS. */ - mat_strategy_cost= 0; - in_exists_strategy_cost= 1; - - /* - If (1) materialization is a possible strategy based on static analysis, and - (2) it is cheaper strategy than the IN->EXISTS transformation, then compute - in_subs via the materialization trategy. - */ - if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION && // 1 - mat_strategy_cost < in_exists_strategy_cost) // 2 + if (select_lex->master_unit()->item && + select_lex->master_unit()->item->is_in_predicate()) { - if (in_subs->setup_mat_engine()) - { - /* - In some cases it is not possible to create usable indexes for the - materialization strategy, so fall back to IN->EXISTS. - */ - in_subs->exec_method= Item_in_subselect::IN_TO_EXISTS; - } + in_subs= (Item_in_subselect*) select_lex->master_unit()->item; + if (in_subs->create_in_to_exists_cond(this)) + return true; } else + return false; + + DBUG_ASSERT(in_subs->in_strategy); /* A strategy must be chosen earlier. */ + DBUG_ASSERT(in_to_exists_where || in_to_exists_having); + DBUG_ASSERT(!in_to_exists_where || in_to_exists_where->fixed); + DBUG_ASSERT(!in_to_exists_having || in_to_exists_having->fixed); + + save_keyuse.elements= 0; + save_keyuse.buffer= NULL; + + /* + Compute and compare the costs of materialization and in-exists if both + strategies are possible and allowed by the user (checked during the prepare + phase. + */ + if (in_subs->in_strategy & SUBS_MATERIALIZATION && + in_subs->in_strategy & SUBS_IN_TO_EXISTS) { + JOIN *outer_join= unit->outer_select() ? unit->outer_select()->join : NULL; + JOIN *inner_join= this; + /* Cost of the outer JOIN. */ + double outer_read_time= 0, outer_record_count= 0; + /* Cost of the unmodified subquery. */ + double inner_read_time_1= 0, inner_record_count_1= 0; + /* Cost of the subquery with injected IN-EXISTS predicates. */ + double inner_read_time_2= 0, inner_record_count_2= 0; + /* The cost to compute IN via materialization. */ + double materialize_strategy_cost= 0; + /* The cost of the IN->EXISTS strategy. */ + double in_exists_strategy_cost= 1; + + if (outer_join) + get_partial_join_cost(outer_join, outer_join->tables, + &outer_read_time, &outer_record_count); + else + { + /* TODO: outer_join can be NULL for DELETE statements. */ + outer_read_time= 1; /* TODO */ + outer_record_count= 1; /* TODO */ + } + + get_partial_join_cost(inner_join, inner_join->tables, + &inner_read_time_1, &inner_record_count_1); + + if (in_to_exists_where && const_tables != tables) + { + /* + Re-optimize and cost the subquery taking into account the IN-EXISTS + conditions. + */ + if (save_query_plan(&save_keyuse, save_best_positions, + save_join_tab_keyuse, save_join_tab_checked_keys) || + reoptimize(in_to_exists_where, join_tables, save_best_positions)) + return TRUE; + in_exists_reoptimized= true; + + get_partial_join_cost(inner_join, inner_join->tables, + &inner_read_time_2, &inner_record_count_2); + } + else + { + /* Reoptimization would not produce any better plan. */ + inner_read_time_2= inner_read_time_1; + } + + /* Compute execution costs. */ /* - Previous optimizer phases should have chosen either a materialization - or IN->EXISTS strategy. + 1. Compute the cost of the materialization strategy. */ - DBUG_ASSERT(in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS); + double materialization_cost; /* The cost of executing the subquery and */ + /* storing its result in an indexed temp table.*/ + /* The cost of a lookup into the unique index of the materialized table. */ + double lookup_cost; + double write_row_cost= 1; /* TODO: what is the real cost to write a row? */ + materialization_cost= inner_read_time_1 + + inner_record_count_1 * write_row_cost; + /* + The cost of a hash/btree lookup into a unique index of a materialized + subquery. + TIMOUR: TODO: the block of code below is exact copy/paste from + opt_subselect.cc:optimize_semi_join_nests() - refactor it. + */ + uint rowlen= get_tmp_table_rec_length(unit->first_select()->item_list); + if (rowlen * inner_record_count_1 < thd->variables.max_heap_table_size) + lookup_cost= HEAP_TEMPTABLE_LOOKUP_COST; + else + lookup_cost= DISK_TEMPTABLE_LOOKUP_COST; + materialize_strategy_cost= materialization_cost + + outer_record_count * lookup_cost; + + /* + 2. Compute the cost of the IN=>EXISTS strategy. + */ + in_exists_strategy_cost= outer_record_count * inner_read_time_2; + + /* Compare the costs and choose the cheaper strategy. */ + if (materialize_strategy_cost >= in_exists_strategy_cost) + in_subs->in_strategy&= ~SUBS_MATERIALIZATION; + else + in_subs->in_strategy&= ~SUBS_IN_TO_EXISTS; } - if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) + /* + If (1) materialization is a possible strategy based on static analysis + during the prepare phase, then if + (2) it is more expensive than the IN->EXISTS transformation, and + (3) it is not possible to create usable indexes for the materialization + strategy, + fall back to IN->EXISTS. + otherwise use materialization. + */ + if (in_subs->in_strategy & SUBS_MATERIALIZATION && + in_subs->setup_mat_engine()) { + /* + If materialization was the cheaper or the only user-selected strategy, + but it is not possible to execute it due to limitations in the + implementation, fall back to IN-TO-EXISTS. + */ + in_subs->in_strategy&= ~SUBS_MATERIALIZATION; + in_subs->in_strategy|= SUBS_IN_TO_EXISTS; + } + + if (in_subs->in_strategy & SUBS_MATERIALIZATION) + { + /* Restore orginal query plan used for materialization. */ + if (in_exists_reoptimized) + restore_query_plan(&save_keyuse, save_best_positions, + save_join_tab_keyuse, save_join_tab_checked_keys); + /* TODO: should we set/unset this flag for both select_lex and its unit? */ in_subs->unit->uncacheable&= ~UNCACHEABLE_DEPENDENT; select_lex->uncacheable&= ~UNCACHEABLE_DEPENDENT; @@ -3633,13 +3740,32 @@ bool JOIN::choose_subquery_plan() set in the beginning of JOIN::optimize, which was already done. */ select_limit= in_subs->unit->select_limit_cnt; - - // TODO: inner_join->restore_plan (keyuse, best_positions, best_read) } - else if (in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS) - res= in_subs->inject_in_to_exists_cond(this); + else if (in_subs->in_strategy & SUBS_IN_TO_EXISTS) + { + /* Keep the new query plan with injected conditions, delete the old one. */ + if (save_keyuse.elements) + { + DBUG_ASSERT(in_exists_reoptimized); + delete_dynamic(&save_keyuse); + } + + if (!in_exists_reoptimized && in_to_exists_where && const_tables != tables) + { + for (uint i= 0; i < tables; i++) + { + join_tab[i].keyuse= NULL; + join_tab[i].checked_keys.clear_all(); + } + if (reoptimize(in_to_exists_where, join_tables, NULL)) + return TRUE; + } + + if (in_subs->inject_in_to_exists_cond(this)) + return TRUE; + } else DBUG_ASSERT(FALSE); - return res; + return FALSE; } diff --git a/sql/share/errmsg.txt b/sql/share/errmsg.txt index 117a4b8e1e6..2152fdbe012 100644 --- a/sql/share/errmsg.txt +++ b/sql/share/errmsg.txt @@ -6245,3 +6245,5 @@ ER_UNKNOWN_OPTION eng "Unknown option '%-.64s'" ER_BAD_OPTION_VALUE eng "Incorrect value '%-.64s' for option '%-.64s'" +ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES + eng "At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'." diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index d9ceb431bb3..bfd24a2491c 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -2157,8 +2157,8 @@ void st_select_lex::print_limit(THD *thd, select_limit == 1, and there should be no offset_limit. */ (((subs_type == Item_subselect::IN_SUBS) && - ((Item_in_subselect*)item)->exec_method == - Item_in_subselect::MATERIALIZATION) ? + ((Item_in_subselect*)item)->in_strategy & + SUBS_MATERIALIZATION) ? TRUE : (select_limit->val_int() == 1LL) && offset_limit == 0)); @@ -3096,25 +3096,11 @@ bool st_select_lex::optimize_unflattened_subqueries() Item_subselect *subquery_predicate= un->item; if (subquery_predicate) { - Item_in_subselect *item_in= NULL; - if (subquery_predicate->substype() == Item_subselect::IN_SUBS || - subquery_predicate->substype() == Item_subselect::ALL_SUBS || - subquery_predicate->substype() == Item_subselect::ANY_SUBS) - item_in= (Item_in_subselect*) subquery_predicate; for (SELECT_LEX *sl= un->first_select(); sl; sl= sl->next_select()) { JOIN *inner_join= sl->join; SELECT_LEX *save_select= un->thd->lex->current_select; int res; - - /* - Make sure that we do not create IN->EXISTS conditions for - subquery predicates that were substituted by Item_maxmin_subselect - or by Item_singlerow_subselect. - */ - DBUG_ASSERT(!item_in || (item_in && !item_in->is_min_max_optimized)); - if (item_in && item_in->create_in_to_exists_cond(inner_join)) - return TRUE; /* We need only 1 row to determine existence */ un->set_limit(un->global_parameters); un->thd->lex->current_select= sl; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 26ec89c7a47..800458e988b 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -53,9 +53,9 @@ static bool make_join_statistics(JOIN *join, TABLE_LIST *leaves, COND *conds, static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse, JOIN_TAB *join_tab, uint tables, COND *conds, - COND_EQUAL *cond_equal, table_map table_map, SELECT_LEX *select_lex, st_sargable_param **sargables); +static bool sort_and_filter_keyuse(DYNAMIC_ARRAY *keyuse); static int sort_keyuse(KEYUSE *a,KEYUSE *b); static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse, table_map used_tables); @@ -928,24 +928,29 @@ JOIN::optimize() error= 0; if (optimize_unflattened_subqueries()) DBUG_RETURN(1); - if (in_to_exists_where || in_to_exists_having) - { - /* - TIMOUR: TODO: refactor this block and JOIN::choose_subquery_plan - */ - Item_in_subselect *in_subs= (Item_in_subselect*) - select_lex->master_unit()->item; + /* + TIMOUR: TODO: consider do we need to optimize here at all and refactor + this block and JOIN::choose_subquery_plan. - if (in_subs->exec_method == Item_in_subselect::MATERIALIZATION) - ; // setup materialized execution structures - else if (in_subs->exec_method == Item_in_subselect::IN_TO_EXISTS) + if (choose_subquery_plan()) + DBUG_RETURN(1); + */ + Item_in_subselect *in_subs; + if (select_lex->master_unit()->item && + select_lex->master_unit()->item->is_in_predicate()) + { + in_subs= (Item_in_subselect*) select_lex->master_unit()->item; + if (in_subs->in_strategy & SUBS_MATERIALIZATION && + in_subs->setup_mat_engine()) + in_subs->in_strategy= SUBS_IN_TO_EXISTS; + if (in_subs->in_strategy & SUBS_IN_TO_EXISTS) { + if (in_subs->create_in_to_exists_cond(this)) + DBUG_RETURN(1); if (in_subs->inject_in_to_exists_cond(this)) DBUG_RETURN(1); tmp_having= having; } - else - DBUG_ASSERT(FALSE); } DBUG_RETURN(0); } @@ -2828,10 +2833,14 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, } if (conds || outer_join) + { if (update_ref_and_keys(join->thd, keyuse_array, stat, join->tables, - conds, join->cond_equal, - ~outer_join, join->select_lex, &sargables)) + conds, ~outer_join, join->select_lex, &sargables)) goto error; + if (keyuse_array->elements && sort_and_filter_keyuse(keyuse_array)) + goto error; + DBUG_EXECUTE("opt", print_keyuse_array(keyuse_array);); + } join->const_table_map= no_rows_const_tables; join->const_tables= const_count; @@ -3135,8 +3144,7 @@ make_join_statistics(JOIN *join, TABLE_LIST *tables_arg, COND *conds, sizeof(POSITION)*join->const_tables); join->best_read=1.0; } - if ((join->in_to_exists_where || join->in_to_exists_having) - && join->choose_subquery_plan()) + if (join->choose_subquery_plan(all_table_map & ~join->const_table_map)) goto error; /* Generate an execution plan from the found optimal join order. */ @@ -4089,11 +4097,10 @@ static void add_key_fields_for_nj(JOIN *join, TABLE_LIST *nested_join_table, static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, - uint tables, COND *cond, COND_EQUAL *cond_equal, - table_map normal_tables, SELECT_LEX *select_lex, - SARGABLE_PARAM **sargables) + uint tables, COND *cond, table_map normal_tables, + SELECT_LEX *select_lex, SARGABLE_PARAM **sargables) { - uint and_level,i,found_eq_constant; + uint and_level,i; KEY_FIELD *key_fields, *end, *field; uint sz; uint m= max(select_lex->max_equal_elems,1); @@ -4189,67 +4196,76 @@ update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,JOIN_TAB *join_tab, return TRUE; } - /* - 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. - */ - if (keyuse->elements) - { - KEYUSE key_end,*prev,*save_pos,*use; - - 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; - - use=save_pos=dynamic_element(keyuse,0,KEYUSE*); - prev= &key_end; - found_eq_constant=0; - for (i=0 ; i < keyuse->elements-1 ; i++,use++) - { - if (!use->used_tables && 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+1 < use->keypart || - (prev->keypart == use->keypart && found_eq_constant)) - continue; /* remove */ - } - else if (use->keypart != 0) // First found must be 0 - continue; - } - -#ifdef HAVE_valgrind - /* Valgrind complains about overlapped memcpy when save_pos==use. */ - if (save_pos != use) -#endif - *save_pos= *use; - prev=use; - found_eq_constant= !use->used_tables; - /* Save ptr to first use */ - if (!use->table->reginfo.join_tab->keyuse) - use->table->reginfo.join_tab->keyuse=save_pos; - use->table->reginfo.join_tab->checked_keys.set_bit(use->key); - save_pos++; - } - i=(uint) (save_pos-(KEYUSE*) keyuse->buffer); - VOID(set_dynamic(keyuse,(uchar*) &key_end,i)); - keyuse->elements=i; - } - DBUG_EXECUTE("opt", print_keyuse_array(keyuse);); return FALSE; } + +/** + 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. +*/ + +static bool sort_and_filter_keyuse(DYNAMIC_ARRAY *keyuse) +{ + KEYUSE key_end, *prev, *save_pos, *use; + uint found_eq_constant, i; + + 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; + + use= save_pos= dynamic_element(keyuse,0,KEYUSE*); + prev= &key_end; + found_eq_constant= 0; + + for (i=0 ; i < keyuse->elements-1 ; i++,use++) + { + if (!use->used_tables && 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+1 < use->keypart || + (prev->keypart == use->keypart && found_eq_constant)) + continue; /* remove */ + } + else if (use->keypart != 0) // First found must be 0 + continue; + } + +#ifdef HAVE_valgrind + /* Valgrind complains about overlapped memcpy when save_pos==use. */ + if (save_pos != use) +#endif + *save_pos= *use; + prev= use; + found_eq_constant= !use->used_tables; + /* Save ptr to first use */ + if (!use->table->reginfo.join_tab->keyuse) + use->table->reginfo.join_tab->keyuse=save_pos; + use->table->reginfo.join_tab->checked_keys.set_bit(use->key); + save_pos++; + } + 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. */ @@ -19265,6 +19281,131 @@ bool JOIN::change_result(select_result *res) DBUG_RETURN(FALSE); } + +/** + Save the original query execution plan so that the caller can revert to it + if needed. +*/ +int JOIN::save_query_plan(DYNAMIC_ARRAY *save_keyuse, + POSITION *save_best_positions, + KEYUSE **save_join_tab_keyuse, + key_map *save_join_tab_checked_keys) +{ + if (keyuse.elements) + { + DYNAMIC_ARRAY tmp_keyuse; + if (my_init_dynamic_array(save_keyuse, sizeof(KEYUSE), 20, 64)) + return 1; + /* Swap the current and the backup keyuse arrays. */ + tmp_keyuse= keyuse; + keyuse= (*save_keyuse); + (*save_keyuse)= tmp_keyuse; + + for (uint i= 0; i < tables; i++) + { + save_join_tab_keyuse[i]= join_tab[i].keyuse; + join_tab[i].keyuse= NULL; + save_join_tab_checked_keys[i]= join_tab[i].checked_keys; + join_tab[i].checked_keys.clear_all(); + } + } + memcpy((uchar*) save_best_positions, (uchar*) best_positions, + sizeof(POSITION) * (tables + 1)); + memset(best_positions, 0, sizeof(POSITION) * (tables + 1)); + return 0; +} + + +/** + Restore the query plan saved before reoptimization with additional + conditions. +*/ + +void JOIN::restore_query_plan(DYNAMIC_ARRAY *save_keyuse, + POSITION *save_best_positions, + KEYUSE **save_join_tab_keyuse, + key_map *save_join_tab_checked_keys) +{ + if (save_keyuse->elements) + { + DYNAMIC_ARRAY tmp_keyuse; + tmp_keyuse= keyuse; + keyuse= (*save_keyuse); + (*save_keyuse)= tmp_keyuse; + delete_dynamic(save_keyuse); + + for (uint i= 0; i < tables; i++) + { + join_tab[i].keyuse= save_join_tab_keyuse[i]; + join_tab[i].checked_keys= save_join_tab_checked_keys[i]; + } + + } + memcpy((uchar*) best_positions, (uchar*) save_best_positions, + sizeof(POSITION) * (tables + 1)); +} + + +/** + Reoptimize a query plan taking into account an additional conjunct to the + WHERE clause. +*/ + +int JOIN::reoptimize(Item *added_where, table_map join_tables, + POSITION *save_best_positions) +{ + DYNAMIC_ARRAY added_keyuse; + SARGABLE_PARAM *sargables= 0; /* Used only as a dummy parameter. */ + + if (my_init_dynamic_array(&added_keyuse, sizeof(KEYUSE), 20, 64)) + { + delete_dynamic(&added_keyuse); + return 1; + } + + /* Re-run the REF optimizer to take into account the new conditions. */ + if (update_ref_and_keys(thd, &added_keyuse, join_tab, tables, added_where, + ~outer_join, select_lex, &sargables)) + { + delete_dynamic(&added_keyuse); + return 1; + } + + if (!added_keyuse.elements) + { + /* No need to optimize if no new access methods were discovered. */ + if (save_best_positions) + memcpy((uchar*) best_positions, (uchar*) save_best_positions, + sizeof(POSITION) * (tables + 1)); + delete_dynamic(&added_keyuse); + return 0; + } + + /* Add the new access methods to the keyuse array. */ + if (!keyuse.buffer && + my_init_dynamic_array(&keyuse, sizeof(KEYUSE), 20, 64)) + { + delete_dynamic(&added_keyuse); + return 1; + } + allocate_dynamic(&keyuse, keyuse.elements + added_keyuse.elements); + memcpy(keyuse.buffer + keyuse.elements * keyuse.size_of_element, + added_keyuse.buffer, + (size_t) added_keyuse.elements * added_keyuse.size_of_element); + keyuse.elements+= added_keyuse.elements; + delete_dynamic(&added_keyuse); + + if (sort_and_filter_keyuse(&keyuse)) + return 1; + optimize_keyuse(this, &keyuse); + + /* Re-run the join optimizer to compute a new query plan. */ + if (choose_plan(this, join_tables)) + return 1; + + return 0; +} + /** @} (end of group Query_Optimizer) */ diff --git a/sql/sql_select.h b/sql/sql_select.h index c87147f6106..5f1abcce87f 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1369,8 +1369,21 @@ inline bool sj_is_materialize_strategy(uint strategy) class JOIN :public Sql_alloc { +private: JOIN(const JOIN &rhs); /**< not implemented */ JOIN& operator=(const JOIN &rhs); /**< not implemented */ + +protected: + /* Support for plan reoptimization with rewritten conditions. */ + int reoptimize(Item *added_where, table_map join_tables, + POSITION *save_best_positions); + int save_query_plan(DYNAMIC_ARRAY *save_keyuse, POSITION *save_positions, + KEYUSE **save_join_tab_keyuse, + key_map *save_join_tab_checked_keys); + void restore_query_plan(DYNAMIC_ARRAY *save_keyuse, POSITION *save_positions, + KEYUSE **save_join_tab_keyuse, + key_map *save_join_tab_checked_keys); + public: JOIN_TAB *join_tab,**best_ref; JOIN_TAB **map2table; ///< mapping between table indexes and JOIN_TABs @@ -1746,7 +1759,7 @@ public: NULL : join_tab+const_tables; } bool setup_subquery_caches(); - bool choose_subquery_plan(); + bool choose_subquery_plan(table_map join_tables); private: /** TRUE if the query contains an aggregate function but has no GROUP From 77c03bcf45aef44971a454a1f1a622fc8779089f Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Oct 2010 17:30:46 +0300 Subject: [PATCH 11/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Improved handling of EXPLAIN statements for subqueries. This patch specifically solves the problem when EXPLAIN reports: "const row not found" instead of "no matching row in const table". --- sql/sql_lex.cc | 39 ++++++++++++++++++++++++++++++++++++++- sql/sql_lex.h | 3 +++ sql/sql_select.cc | 25 +++---------------------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index bfd24a2491c..445fdf5a7fd 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -3100,11 +3100,20 @@ bool st_select_lex::optimize_unflattened_subqueries() { JOIN *inner_join= sl->join; SELECT_LEX *save_select= un->thd->lex->current_select; + ulonglong save_options; int res; /* We need only 1 row to determine existence */ un->set_limit(un->global_parameters); un->thd->lex->current_select= sl; + save_options= inner_join->select_options; + if (un->outer_select()->options & SELECT_DESCRIBE) + { + /* Optimize the subquery in the context of EXPLAIN. */ + set_explain_type(); + inner_join->select_options= options; + } res= inner_join->optimize(); + inner_join->select_options= save_options; un->thd->lex->current_select= save_select; if (res) return TRUE; @@ -3112,7 +3121,35 @@ bool st_select_lex::optimize_unflattened_subqueries() } } return FALSE; -} +} + + +/** + Set the EXPLAIN type for this subquery. +*/ + +void st_select_lex::set_explain_type() +{ + SELECT_LEX *first= master_unit()->first_select(); + /* drop UNCACHEABLE_EXPLAIN, because it is for internal usage only */ + uint8 is_uncacheable= (uncacheable & ~UNCACHEABLE_EXPLAIN); + + type= ((&master_unit()->thd->lex->select_lex == this) ? + (first_inner_unit() || next_select() ? + "PRIMARY" : "SIMPLE") : + ((this == first) ? + ((linkage == DERIVED_TABLE_TYPE) ? + "DERIVED" : + ((is_uncacheable & UNCACHEABLE_DEPENDENT) ? + "DEPENDENT SUBQUERY" : + (is_uncacheable ? "UNCACHEABLE SUBQUERY" : + "SUBQUERY"))) : + ((is_uncacheable & UNCACHEABLE_DEPENDENT) ? + "DEPENDENT UNION": + is_uncacheable ? "UNCACHEABLE UNION": + "UNION"))); + options|= SELECT_DESCRIBE; +} /** diff --git a/sql/sql_lex.h b/sql/sql_lex.h index d106bfd55fd..7c49d0a407e 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -847,6 +847,9 @@ public: some SQL statements as DELETE do not have a corresponding JOIN object. */ bool optimize_unflattened_subqueries(); + /* Set the EXPLAIN type for this subquery. */ + void set_explain_type(); + private: /* current index hint kind. used in filling up index_hints */ enum index_hint_type current_index_hint_type; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 800458e988b..148a60efadd 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -18832,28 +18832,9 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result) bool res= 0; SELECT_LEX *first= unit->first_select(); - for (SELECT_LEX *sl= first; - sl; - sl= sl->next_select()) - { - // drop UNCACHEABLE_EXPLAIN, because it is for internal usage only - uint8 uncacheable= (sl->uncacheable & ~UNCACHEABLE_EXPLAIN); - sl->type= (((&thd->lex->select_lex)==sl)? - (sl->first_inner_unit() || sl->next_select() ? - "PRIMARY" : "SIMPLE"): - ((sl == first)? - ((sl->linkage == DERIVED_TABLE_TYPE) ? - "DERIVED": - ((uncacheable & UNCACHEABLE_DEPENDENT) ? - "DEPENDENT SUBQUERY": - (uncacheable?"UNCACHEABLE SUBQUERY": - "SUBQUERY"))): - ((uncacheable & UNCACHEABLE_DEPENDENT) ? - "DEPENDENT UNION": - uncacheable?"UNCACHEABLE UNION": - "UNION"))); - sl->options|= SELECT_DESCRIBE; - } + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) + sl->set_explain_type(); + if (unit->is_union()) { unit->fake_select_lex->select_number= UINT_MAX; // jost for initialization From 4a3f135a2ed9fe533d1a772d234ff5534524dcbf Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 5 Oct 2010 16:00:31 +0300 Subject: [PATCH 12/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Added/corrected/improved comments. --- mysql-test/r/subselect_mat.result | 4 +- sql/item_subselect.cc | 274 ++++++++++++++++-------------- sql/item_subselect.h | 2 +- sql/opt_subselect.cc | 64 +++++-- sql/sql_select.cc | 49 +++++- 5 files changed, 243 insertions(+), 150 deletions(-) diff --git a/mysql-test/r/subselect_mat.result b/mysql-test/r/subselect_mat.result index c9be1014f02..a4a69bb2f83 100644 --- a/mysql-test/r/subselect_mat.result +++ b/mysql-test/r/subselect_mat.result @@ -1139,7 +1139,7 @@ insert into t1 values (5); explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away -2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL no matching row in const table +2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found select min(a1) from t1 where 7 in (select b1 from t2 group by b1); min(a1) set @@optimizer_switch='default,materialization=off'; @@ -1153,7 +1153,7 @@ set @@optimizer_switch='default,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away -2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL no matching row in const table +2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found select min(a1) from t1 where 7 in (select b1 from t2); min(a1) set @@optimizer_switch='default,materialization=off'; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 0a91e4a2a77..5f9285e5df6 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -187,7 +187,7 @@ bool Item_subselect::fix_fields(THD *thd_param, Item **ref) bool res; DBUG_ASSERT(fixed == 0); - DBUG_ASSERT(thd == thd_param); + DBUG_ASSERT(thd == thd_param); /* thd can't change during execution. */ engine->set_thd(thd); if (!done_first_fix_fields) { @@ -288,6 +288,16 @@ bool Item_subselect::mark_as_eliminated_processor(uchar *arg) } +/** + Remove a subselect item from its unit so that the unit no longer + represents a subquery. + + @param arg unused parameter + + @return + FALSE to force the evaluation of the processor for the subsequent items. +*/ + bool Item_subselect::eliminate_subselect_processor(uchar *arg) { unit->item= NULL; @@ -297,34 +307,37 @@ bool Item_subselect::eliminate_subselect_processor(uchar *arg) } -/* +/** Adjust the master select of the subquery to be the fake_select which represents the whole UNION right above the subquery, instead of the last query of the UNION. + + @param arg pointer to the fake select + + @return + FALSE to force the evaluation of the processor for the subsequent items. */ bool Item_subselect::set_fake_select_as_master_processor(uchar *arg) { SELECT_LEX *fake_select= (SELECT_LEX*) arg; /* - Apply the substitution only for immediate child subqueries of a + Move the st_select_lex_unit of a subquery from a global ORDER BY clause to + become a direct child of the fake_select of a UNION. In this way the + ORDER BY is applied to the temporary table that contains the result of the + whole UNION, and all columns in the subquery are resolved against this table. + + Apply the transformation only for immediate child subqueries of a UNION query. */ if (unit->outer_select()->master_unit()->fake_select_lex == fake_select) { /* - Include the st_select_lex_unit of a subquery from a global ORDER BY - clause as a direct child of the fake_select of a UNION. In this way - the ORDER BY is applied to the temporary table that contains the - result of the whole UNION, and all columns in the subquery are - resolved against this table. - */ - /* - Set the master of the subquery to be the fake select (i.e. the whole - UNION, instead of the last query in the UNION. - TODO: this is a hack, instead we should call: - unit->include_down(fake_select); - however, this call results in an infinite loop where + Set the master of the subquery to be the fake select (i.e. the whole UNION), + instead of the last query in the UNION. + TODO: + This is a hack, instead we should call: unit->include_down(fake_select); + However, this call results in an infinite loop where some_select_lex->master == some_select_lex. */ unit->set_master(fake_select); @@ -332,14 +345,13 @@ bool Item_subselect::set_fake_select_as_master_processor(uchar *arg) for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) sl->context.outer_context= &(fake_select->context); /* - Undo Item_subselect::eliminate_subselect_processor because at that - phase we don't know yet (or don't know how to figure it out) that - the ORDER clause will be moved to the fake select. + Undo Item_subselect::eliminate_subselect_processor because at that phase + we don't know yet that the ORDER clause will be moved to the fake select. */ unit->item= this; eliminated= FALSE; } - return FALSE; // return TRUE ? because we need to stop processing down + return FALSE; } @@ -1341,54 +1353,33 @@ my_decimal *Item_in_subselect::val_decimal(my_decimal *decimal_value) } -/* - Rewrite a single-column IN/ALL/ANY subselect +/** + Rewrite a single-column IN/ALL/ANY subselect. - SYNOPSIS - Item_in_subselect::single_value_transformer() - join Join object of the subquery (i.e. 'child' join). - func Subquery comparison creator + @param join Join object of the subquery (i.e. 'child' join). - DESCRIPTION - Rewrite a single-column subquery using rule-based approach. The subquery + @details + Rewrite a single-column subquery using rule-based approach. The subquery + + oe $cmp$ (SELECT ie FROM ... WHERE subq_where ... HAVING subq_having) + + First, try to convert the subquery to scalar-result subquery in one of + the forms: - oe $cmp$ (SELECT ie FROM ... WHERE subq_where ... HAVING subq_having) - - First, try to convert the subquery to scalar-result subquery in one of - the forms: - - - oe $cmp$ (SELECT MAX(...) ) // handled by Item_singlerow_subselect - - oe $cmp$ (SELECT ...) // handled by Item_maxmin_subselect + - oe $cmp$ (SELECT MAX(...) ) // handled by Item_singlerow_subselect + - oe $cmp$ (SELECT ...) // handled by Item_maxmin_subselect + + If that fails, check if the subquery is a single select without tables, + and substitute the subquery predicate with "oe $cmp$ ie". - If that fails, the subquery will be handled with class Item_in_optimizer, - Inject the predicates into subquery, i.e. convert it to: + If that fails, the subquery predicate is wrapped into an Item_in_optimizer. + Later the query optimization phase chooses whether the subquery under the + Item_in_optimizer will be further transformed into an equivalent correlated + EXISTS by injecting additional predicates, or will be executed via subquery + materialization in its unmodified form. - - If the subquery has aggregates, GROUP BY, or HAVING, convert to - - SELECT ie FROM ... HAVING subq_having AND - trigcond(oe $cmp$ ref_or_null_helper) - - the addition is wrapped into trigger only when we want to distinguish - between NULL and FALSE results. - - - Otherwise (no aggregates/GROUP BY/HAVING) convert it to one of the - following: - - = If we don't need to distinguish between NULL and FALSE subquery: - - SELECT 1 FROM ... WHERE (oe $cmp$ ie) AND subq_where - - = If we need to distinguish between those: - - SELECT 1 FROM ... - WHERE subq_where AND trigcond((oe $cmp$ ie) OR (ie IS NULL)) - HAVING trigcond((ie)) - - RETURN - RES_OK Either subquery was transformed, or appopriate - predicates where injected into it. - RES_REDUCE The subquery was reduced to non-subquery - RES_ERROR Error + @retval RES_OK The subquery was transformed + @retval RES_ERROR Error */ Item_subselect::trans_res @@ -1424,7 +1415,7 @@ Item_in_subselect::single_value_transformer(JOIN *join) { if (substitution) { - // It is second (third, ...) SELECT of UNION => All is done + /* It is second (third, ...) SELECT of UNION => All is done */ DBUG_RETURN(RES_OK); } @@ -1516,6 +1507,10 @@ Item_in_subselect::single_value_transformer(JOIN *join) DBUG_RETURN(RES_OK); } + /* + Wrap the current IN predicate in an Item_in_optimizer. The actual + substitution in the Item tree takes place in Item_subselect::fix_fields. + */ if (!substitution) { /* We're invoked for the 1st (or the only) SELECT in the subquery UNION */ @@ -1546,7 +1541,8 @@ Item_in_subselect::single_value_transformer(JOIN *join) (char *)in_left_expr_name); master_unit->uncacheable|= UNCACHEABLE_DEPENDENT; - //select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; + // TODO: do we need to set both? + // select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; } DBUG_RETURN(RES_OK); @@ -1567,10 +1563,15 @@ bool Item_in_subselect::fix_having(Item *having, SELECT_LEX *select_lex) /** - Transform an IN predicate into EXISTS via predicate injection. + Create the predicates needed to transform a single-column IN/ALL/ANY + subselect into a correlated EXISTS via predicate injection. - @details The transformation injects additional predicates into the subquery - (and makes the subquery correlated) as follows. + @param join[in] Join object of the subquery (i.e. 'child' join). + @param where_item[out] the in-to-exists addition to the where clause + @param having_item[out] the in-to-exists addition to the having clause + + @details + The correlated predicates are created as follows: - If the subquery has aggregates, GROUP BY, or HAVING, convert to @@ -1585,21 +1586,16 @@ bool Item_in_subselect::fix_having(Item *having, SELECT_LEX *select_lex) = If we don't need to distinguish between NULL and FALSE subquery: - SELECT 1 FROM ... WHERE (oe $cmp$ ie) AND subq_where + SELECT ie FROM ... WHERE subq_where AND (oe $cmp$ ie) = If we need to distinguish between those: - SELECT 1 FROM ... + SELECT ie FROM ... WHERE subq_where AND trigcond((oe $cmp$ ie) OR (ie IS NULL)) HAVING trigcond((ie)) - @param join Join object of the subquery (i.e. 'child' join). - @param func Subquery comparison creator - - @retval RES_OK Either subquery was transformed, or appopriate - predicates where injected into it. - @retval RES_REDUCE The subquery was reduced to non-subquery - @retval RES_ERROR Error + @retval RES_OK If the new conditions were created successfully + @retval RES_ERROR Error */ Item_subselect::trans_res @@ -1609,10 +1605,8 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, { SELECT_LEX *select_lex= join->select_lex; /* - The non-transformed HAVING clause of 'join' may be stored differently in - JOIN::optimize: - this->tmp_having= this->having - this->having= 0; + The non-transformed HAVING clause of 'join' may be stored in two ways + during JOIN::optimize: this->tmp_having= this->having; this->having= 0; */ Item* join_having= join->having ? join->having : join->tmp_having; @@ -1724,6 +1718,22 @@ Item_in_subselect::create_single_in_to_exists_cond(JOIN * join, } +/** + Wrap a multi-column IN/ALL/ANY subselect into an Item_in_optimizer. + + @param join Join object of the subquery (i.e. 'child' join). + + @details + The subquery predicate is wrapped into an Item_in_optimizer. Later the query + optimization phase chooses whether the subquery under the Item_in_optimizer + will be further transformed into an equivalent correlated EXISTS by injecting + additional predicates, or will be executed via subquery materialization in its + unmodified form. + + @retval RES_OK The subquery was transformed + @retval RES_ERROR Error +*/ + Item_subselect::trans_res Item_in_subselect::row_value_transformer(JOIN *join) { @@ -1763,6 +1773,7 @@ Item_in_subselect::row_value_transformer(JOIN *join) thd->lex->current_select= current; master_unit->uncacheable|= UNCACHEABLE_DEPENDENT; + // TODO: do we need to set both? //select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; } @@ -1771,21 +1782,19 @@ Item_in_subselect::row_value_transformer(JOIN *join) /** - Tranform a (possibly non-correlated) IN subquery into a correlated EXISTS. + Create the predicates needed to transform a multi-column IN/ALL/ANY + subselect into a correlated EXISTS via predicate injection. - @todo - The IF-ELSE below can be refactored so that there is no duplication of the - statements that create the new conditions. For this we have to invert the IF - and the FOR statements as this: - for (each left operand) - create the equi-join condition - if (is_having_used || !abort_on_null) - create the "is null" and is_not_null_test items - if (is_having_used) - add the equi-join and the null tests to HAVING - else - add the equi-join and the "is null" to WHERE - add the is_not_null_test to HAVING + @details + There are two cases - either the subquery has aggregates, GROUP BY, + or HAVING, or not. Both cases are described inline in the code. + + @param join[in] Join object of the subquery (i.e. 'child' join). + @param where_item[out] the in-to-exists addition to the where clause + @param having_item[out] the in-to-exists addition to the having clause + + @retval RES_OK If the new conditions were created successfully + @retval RES_ERROR Error */ Item_subselect::trans_res @@ -1796,10 +1805,8 @@ Item_in_subselect::create_row_in_to_exists_cond(JOIN * join, SELECT_LEX *select_lex= join->select_lex; uint cols_num= left_expr->cols(); /* - The non-transformed HAVING clause of 'join' may be stored differently in - JOIN::optimize: - this->tmp_having= this->having - this->having= 0; + The non-transformed HAVING clause of 'join' may be stored in two ways + during JOIN::optimize: this->tmp_having= this->having; this->having= 0; */ Item* join_having= join->having ? join->having : join->tmp_having; bool is_having_used= (join_having || select_lex->with_sum_func || @@ -1993,6 +2000,16 @@ Item_in_subselect::select_transformer(JOIN *join) } +/** + Create the predicates needed to transform an IN/ALL/ANY subselect into a + correlated EXISTS via predicate injection. + + @param join_arg Join object of the subquery. + + @retval FALSE ok + @retval TRUE error +*/ + bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg) { Item_subselect::trans_res res; @@ -2000,12 +2017,11 @@ bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg) DBUG_ASSERT(engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE || engine->engine_type() == subselect_engine::UNION_ENGINE); /* - TIMOUR TODO: the call to init_cond_guards allocates and initializes an + TODO: the call to init_cond_guards allocates and initializes an array of booleans that may not be used later because we may choose materialization. The two calls below to create_XYZ_cond depend on this boolean array. - This dependency can be easily removed, and the call moved to a later - phase. + If the dependency is removed, the call can be moved to a later phase. */ init_cond_guards(); join_arg->select_lex->uncacheable|= UNCACHEABLE_DEPENDENT; @@ -2021,6 +2037,16 @@ bool Item_in_subselect::create_in_to_exists_cond(JOIN *join_arg) } +/** + Transform an IN/ALL/ANY subselect into a correlated EXISTS via injecting + correlated in-to-exists predicates. + + @param join_arg Join object of the subquery. + + @retval FALSE ok + @retval TRUE error +*/ + bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) { SELECT_LEX *select_lex= join_arg->select_lex; @@ -2034,6 +2060,7 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) where_item= and_items(join_arg->conds, where_item); if (!where_item->fixed && where_item->fix_fields(thd, 0)) DBUG_RETURN(true); + // TIMOUR TODO: call optimize_cond() for the new where clause thd->change_item_tree(&select_lex->where, where_item); select_lex->where->top_level_item(); join_arg->conds= select_lex->where; @@ -2045,6 +2072,7 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) having_item= and_items(join_having, having_item); if (fix_having(having_item, select_lex)) DBUG_RETURN(true); + // TIMOUR TODO: call optimize_cond() for the new having clause thd->change_item_tree(&select_lex->having, having_item); select_lex->having->top_level_item(); join_arg->having= select_lex->having; @@ -2058,21 +2086,16 @@ bool Item_in_subselect::inject_in_to_exists_cond(JOIN *join_arg) Prepare IN/ALL/ANY/SOME subquery transformation and call appropriate transformation function. - To decide which transformation procedure (scalar or row) applicable here - we have to call fix_fields() for left expression to be able to call - cols() method on it. Also this method make arena management for - underlying transformation methods. - @param join JOIN object of transforming subquery - @param func creator of condition function of subquery - @retval - RES_OK OK - @retval - RES_REDUCE OK, and current subquery was reduced during - transformation - @retval - RES_ERROR Error + @notes + To decide which transformation procedure (scalar or row) applicable here + we have to call fix_fields() for left expression to be able to call + cols() method on it. Also this method make arena management for + underlying transformation methods. + + @retval RES_OK OK + @retval RES_ERROR Error */ Item_subselect::trans_res @@ -2252,24 +2275,17 @@ void Item_in_subselect::update_used_tables() used_tables_cache |= left_expr->used_tables(); } + /** - Try to create an engine to compute the subselect via materialization, - and if this fails, revert to execution via the IN=>EXISTS transformation. + Try to create and initialize an engine to compute a subselect via + materialization. @details - The purpose of this method is to hide the implementation details - of this Item's execution. The method creates a new engine for - materialized execution, and initializes the engine. - - If this initialization fails - - either because it wasn't possible to create the needed temporary table - and its index, - - or because of a memory allocation error, - then we revert back to execution via the IN=>EXISTS tranformation. - - The initialization of the new engine is divided in two parts - a permanent - one that lives across prepared statements, and one that is repeated for each - execution. + The method creates a new engine for materialized execution, and initializes + the engine. The initialization may fail + - either because it wasn't possible to create the needed temporary table + and its index, + - or because of a memory allocation error, @returns @retval TRUE memory allocation error occurred diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 308fbc6b20a..2a6d310cbb4 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -319,7 +319,7 @@ public: /* Possible methods to execute an IN predicate. These are set by the optimizer - based on user-set optimizer switches, syntactic analysis and cost comparison. + based on user-set optimizer switches, semantic analysis and cost comparison. */ #define SUBS_NOT_TRANSFORMED 0 /* No execution method was chosen for this IN. */ #define SUBS_SEMI_JOIN 1 /* IN was converted to semi-join. */ diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index e93478c5195..e0ffe31dfea 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -185,6 +185,7 @@ int check_and_do_in_subquery_rewrites(JOIN *join) else { DBUG_PRINT("info", ("Subquery can't be converted to semi-join")); + /* Test if the user has set a legal combination of optimizer switches. */ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_IN_TO_EXISTS) && !optimizer_flag(thd, OPTIMIZER_SWITCH_MATERIALIZATION)) my_error(ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES, MYF(0)); @@ -3543,16 +3544,10 @@ static void remove_subq_pushed_predicates(JOIN *join, Item **where) /** - Setup for execution all subqueries of a query, for which the optimizer - chose hash semi-join. + Optimize all subqueries of a query that have were flattened into a semijoin. - @details Iterate over all immediate child subqueries of the query, and if - they are under an IN predicate, and the optimizer chose to compute it via - materialization: - - optimize each subquery, - - choose an optimial execution strategy for the IN predicate - either - materialization, or an IN=>EXISTS transformation with an approriate - engine. + @details + Optimize all immediate children subqueries of a query. This phase must be called after substitute_for_best_equal_field() because that function may replace items with other items from a multiple equality, @@ -3570,6 +3565,42 @@ bool JOIN::optimize_unflattened_subqueries() } +/** + Choose an optimal strategy to execute an IN/ALL/ANY subquery predicate + based on cost. + + @param join_tables the set of tables joined in the subquery + + @notes + The method chooses between the materialization and IN=>EXISTS rewrite + strategies for the execution of a non-flattened subquery IN predicate. + The cost-based decision is made as follows: + + 1. compute materialize_strategy_cost based on the unmodified subquery + 2. reoptimize the subquery taking into account the IN-EXISTS predicates + 3. compute in_exists_strategy_cost based on the reoptimized plan + 4. compare and set the cheaper strategy + if (materialize_strategy_cost >= in_exists_strategy_cost) + in_strategy = MATERIALIZATION + else + in_strategy = IN_TO_EXISTS + 5. if in_strategy = MATERIALIZATION and it is not possible to initialize it + revert to IN_TO_EXISTS + 6. if (in_strategy == MATERIALIZATION) + revert the subquery plan to the original one before reoptimizing + else + inject the IN=>EXISTS predicates into the new EXISTS subquery plan + + The implementation itself is a bit more complicated because it takes into + account two more factors: + - whether the user allowed both strategies through an optimizer_switch, and + - if materialization was the cheaper strategy, whether it can be executed + or not. + + @retval FALSE success. + @retval TRUE error occurred. +*/ + bool JOIN::choose_subquery_plan(table_map join_tables) { /* The original QEP of the subquery. */ @@ -3627,7 +3658,10 @@ bool JOIN::choose_subquery_plan(table_map join_tables) &outer_read_time, &outer_record_count); else { - /* TODO: outer_join can be NULL for DELETE statements. */ + /* + TODO: outer_join can be NULL for DELETE statements. + How to compute its cost? + */ outer_read_time= 1; /* TODO */ outer_record_count= 1; /* TODO */ } @@ -3694,13 +3728,14 @@ bool JOIN::choose_subquery_plan(table_map join_tables) } /* - If (1) materialization is a possible strategy based on static analysis + If (1) materialization is a possible strategy based on semantic analysis during the prepare phase, then if (2) it is more expensive than the IN->EXISTS transformation, and (3) it is not possible to create usable indexes for the materialization strategy, fall back to IN->EXISTS. - otherwise use materialization. + otherwise + use materialization. */ if (in_subs->in_strategy & SUBS_MATERIALIZATION && in_subs->setup_mat_engine()) @@ -3752,6 +3787,11 @@ bool JOIN::choose_subquery_plan(table_map join_tables) if (!in_exists_reoptimized && in_to_exists_where && const_tables != tables) { + /* + The subquery was not reoptimized either because the user allowed only the + IN-EXISTS strategy, or because materialization was not possible based on + semantic analysis. Clenup the original plan and reoptimize. + */ for (uint i= 0; i < tables; i++) { join_tab[i].keyuse= NULL; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 148a60efadd..b05c7a34d3f 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -19264,8 +19264,18 @@ bool JOIN::change_result(select_result *res) /** - Save the original query execution plan so that the caller can revert to it - if needed. + Save a query execution plan so that the caller can revert to it if needed, + and reset the current query plan so that it can be reoptimized. + + @param save_keyuse[out] a KEYUSE array to save JOIN::keyuse + @param save_best_positions[out] array to save JOIN::best_positions + @param save_join_tab_keyuse[out] array of KEYUSE pointers to save each + JOIN_TAB::keyuse pointer + @param save_join_tab_checked_keys[out] an array of bitmaps to save + each JOIN_TAB::checked_keys + + @retval 0 OK + @retval 1 memory allocation error */ int JOIN::save_query_plan(DYNAMIC_ARRAY *save_keyuse, POSITION *save_best_positions, @@ -19298,8 +19308,14 @@ int JOIN::save_query_plan(DYNAMIC_ARRAY *save_keyuse, /** - Restore the query plan saved before reoptimization with additional - conditions. + Restore a query plan previously saved by the caller. + + @param save_keyuse a KEYUSE array to restore into JOIN::keyuse + @param save_best_positions array to restore into JOIN::best_positions + @param save_join_tab_keyuse array of KEYUSE pointers to restore each + JOIN_TAB::keyuse pointer + @param save_join_tab_checked_keys an array of bitmaps to restore + each JOIN_TAB::checked_keys */ void JOIN::restore_query_plan(DYNAMIC_ARRAY *save_keyuse, @@ -19328,8 +19344,29 @@ void JOIN::restore_query_plan(DYNAMIC_ARRAY *save_keyuse, /** - Reoptimize a query plan taking into account an additional conjunct to the - WHERE clause. + Reoptimize a query plan taking into account an additional conjunct to the + WHERE clause. + + @param added_where An extra conjunct to the WHERE clause to reoptimize with + @param join_tables The set of tables to reoptimize + @param save_best_positions The join order of the original plan to restore to + if needed. + + @notes + Given a query plan that already optimized taking into account some WHERE clause + 'C', reoptimize this plan with a new WHERE clause 'C AND added_where'. The + reoptimization works as follows: + + 1. Call update_ref_and_keys *only* for the new conditions 'added_where' + that are about to be injected into the query. + 2. Expand if necessary the original KEYUSE array JOIN::keyuse to + accommodate the new REF accesses computed for the 'added_where' condition. + 3. Add the new KEYUSEs into JOIN::keyuse. + 4. Re-sort and re-filter the JOIN::keyuse array with the newly added + KEYUSE elements. + + @retval 0 OK + @retval 1 memory allocation error */ int JOIN::reoptimize(Item *added_where, table_map join_tables, From addd57828d5702349cf052e964c6bbc6e0371f9a Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 9 Oct 2010 17:48:05 +0300 Subject: [PATCH 13/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation - Changed the default optimizer switches to provide 5.1/5.2 compatible behavior - Added a regression test file to test consistently all cases covered by MWL#89 - Added/corrected/improved comments. --- mysql-test/r/subselect_mat_cost.result | 191 +++++++++++++++++++++++++ mysql-test/t/subselect_mat_cost.test | 139 ++++++++++++++++++ sql/mysql_priv.h | 7 +- sql/mysqld.cc | 4 +- sql/opt_subselect.cc | 11 +- sql/sql_select.cc | 3 - 6 files changed, 343 insertions(+), 12 deletions(-) create mode 100644 mysql-test/r/subselect_mat_cost.result create mode 100644 mysql-test/t/subselect_mat_cost.test diff --git a/mysql-test/r/subselect_mat_cost.result b/mysql-test/r/subselect_mat_cost.result new file mode 100644 index 00000000000..bfa4589bdd2 --- /dev/null +++ b/mysql-test/r/subselect_mat_cost.result @@ -0,0 +1,191 @@ +drop table if exists t1, t2, t3, t1i, t2i, t3i; +create table t1 (a1 char(8), a2 char(8)); +create table t2 (b1 char(8), b2 char(8)); +create table t3 (c1 char(8), c2 char(8)); +Unindexed tables +insert into t1 values ('1 - 00', '2 - 00'); +insert into t1 values ('1 - 01', '2 - 01'); +insert into t1 values ('1 - 02', '2 - 02'); +insert into t2 values ('1 - 01', '2 - 01'); +insert into t2 values ('1 - 01', '2 - 01'); +insert into t2 values ('1 - 02', '2 - 02'); +insert into t2 values ('1 - 02', '2 - 02'); +insert into t2 values ('1 - 03', '2 - 03'); +insert into t3 values ('1 - 01', '2 - 01'); +insert into t3 values ('1 - 02', '2 - 02'); +insert into t3 values ('1 - 03', '2 - 03'); +insert into t3 values ('1 - 04', '2 - 04'); +Indexed tables +create table t1i (a1 char(8), a2 char(8)); +create table t2i (b1 char(8), b2 char(8)); +create table t3i (c1 char(8), c2 char(8)); +create index it1i1 on t1i (a1); +create index it1i2 on t1i (a2); +create index it1i3 on t1i (a1, a2); +create index it2i1 on t2i (b1); +create index it2i2 on t2i (b2); +create index it2i3 on t2i (b1, b2); +create index it3i1 on t3i (c1); +create index it3i2 on t3i (c2); +create index it3i3 on t3i (c1, c2); +insert into t1i select * from t1; +insert into t2i select * from t2; +insert into t3i select * from t3; + +1. Both materialization and in-to-exists are possible to execute + +set @@optimizer_switch='materialization=on,in_to_exists=on'; + +1.1 In-to-exists is cheaper +set @@optimizer_switch='semijoin=off'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,(select `test`.`t2`.`b1` from `test`.`t2` where ((`test`.`t2`.`b1` > '0') and ((`test`.`t1`.`a1`) = `test`.`t2`.`b1`)))) +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where ((`test`.`t2`.`b1` > '0') and ((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) and ((`test`.`t1`.`a2`) = `test`.`t2`.`b2`)))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +set @@optimizer_switch='semijoin=on'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,(select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1` having ((`test`.`t1`.`a1`) = (`test`.`t2`.`b1`)))) +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having ((((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) or isnull(`test`.`t2`.`b1`)) and (((`test`.`t1`.`a2`) = `test`.`t2`.`b2`) or isnull(`test`.`t2`.`b2`)) and (`test`.`t2`.`b1`) and (`test`.`t2`.`b2`)))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having ((`test`.`t2`.`b2` < '2 - 04') and (((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) or isnull(`test`.`t2`.`b1`)) and (((`test`.`t1`.`a2`) = `test`.`t2`.`b2`) or isnull(`test`.`t2`.`b2`)) and (`test`.`t2`.`b1`) and (`test`.`t2`.`b2`)))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +1.2 Materialization is cheaper +1.2.1 Materialization is executable +insert into t1 values ('1 - 03', '2 - 03'); +insert into t1 values ('1 - 04', '2 - 04'); +insert into t1 values ('1 - 05', '2 - 05'); +set @@optimizer_switch='semijoin=off'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,`test`.`t1`.`a1` in ( (select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`))))) +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +set @@optimizer_switch='semijoin=on'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,`test`.`t1`.`a1` in ( (select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1` ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`))))) +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort +Warnings: +Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having (`test`.`t2`.`b2` < '2 - 04') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +delete from t1 where a1 >= '1 - 03'; +1.2.2 Materialization is NOT executable, revert to in-to-exists + +2. Materialization is OFF, in-to-exists is ON + +set @@optimizer_switch='materialization=off,in_to_exists=on'; + +3. Materialization is ON, in-to-exists is OFF + +set @@optimizer_switch='materialization=on,in_to_exists=off'; +3.1 Materialization is executable +3.2 Materialization is NOT executable, revert to in-to-exists + +4. Edge cases + +4.0 Both materialization and in_to_exists cannot be off +set @@optimizer_switch='materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +4.1 Outer query with no tables +4.2 Subquery with no tables +4.3 optimize_cond detects FALSE where/having clause +4.4 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const +4.4 make_join_select detects impossible WHERE +4.5 constant optimization detects "no matching row in const table" +5. UPDATE/DELETE with subqueries +drop table t1, t2, t3, t1i, t2i, t3i; diff --git a/mysql-test/t/subselect_mat_cost.test b/mysql-test/t/subselect_mat_cost.test new file mode 100644 index 00000000000..702eac508d6 --- /dev/null +++ b/mysql-test/t/subselect_mat_cost.test @@ -0,0 +1,139 @@ +# +# Tets of cost-based choice between the materialization and in-to-exists +# subquery execution strategies (MWL#89) +# + +--disable_warnings +drop table if exists t1, t2, t3, t1i, t2i, t3i; +--enable_warnings + +create table t1 (a1 char(8), a2 char(8)); +create table t2 (b1 char(8), b2 char(8)); +create table t3 (c1 char(8), c2 char(8)); + +--echo Unindexed tables +insert into t1 values ('1 - 00', '2 - 00'); +insert into t1 values ('1 - 01', '2 - 01'); +insert into t1 values ('1 - 02', '2 - 02'); + +insert into t2 values ('1 - 01', '2 - 01'); +insert into t2 values ('1 - 01', '2 - 01'); +insert into t2 values ('1 - 02', '2 - 02'); +insert into t2 values ('1 - 02', '2 - 02'); +insert into t2 values ('1 - 03', '2 - 03'); + +insert into t3 values ('1 - 01', '2 - 01'); +insert into t3 values ('1 - 02', '2 - 02'); +insert into t3 values ('1 - 03', '2 - 03'); +insert into t3 values ('1 - 04', '2 - 04'); + +--echo Indexed tables +create table t1i (a1 char(8), a2 char(8)); +create table t2i (b1 char(8), b2 char(8)); +create table t3i (c1 char(8), c2 char(8)); +create index it1i1 on t1i (a1); +create index it1i2 on t1i (a2); +create index it1i3 on t1i (a1, a2); + +create index it2i1 on t2i (b1); +create index it2i2 on t2i (b2); +create index it2i3 on t2i (b1, b2); + +create index it3i1 on t3i (c1); +create index it3i2 on t3i (c2); +create index it3i3 on t3i (c1, c2); + +insert into t1i select * from t1; +insert into t2i select * from t2; +insert into t3i select * from t3; + +--echo +--echo 1. Both materialization and in-to-exists are possible to execute +--echo +set @@optimizer_switch='materialization=on,in_to_exists=on'; + +--echo +--echo 1.1 In-to-exists is cheaper + +set @@optimizer_switch='semijoin=off'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); + +set @@optimizer_switch='semijoin=on'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); + +--echo +--echo 1.2 Materialization is cheaper + +--echo 1.2.1 Materialization is executable + +insert into t1 values ('1 - 03', '2 - 03'); +insert into t1 values ('1 - 04', '2 - 04'); +insert into t1 values ('1 - 05', '2 - 05'); + +set @@optimizer_switch='semijoin=off'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); + +set @@optimizer_switch='semijoin=on'; +explain extended +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +explain extended +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); + +delete from t1 where a1 >= '1 - 03'; + +--echo 1.2.2 Materialization is NOT executable, revert to in-to-exists + +--echo +--echo 2. Materialization is OFF, in-to-exists is ON +--echo +set @@optimizer_switch='materialization=off,in_to_exists=on'; + +--echo +--echo 3. Materialization is ON, in-to-exists is OFF +--echo +set @@optimizer_switch='materialization=on,in_to_exists=off'; + +--echo 3.1 Materialization is executable +--echo 3.2 Materialization is NOT executable, revert to in-to-exists + +--echo +--echo 4. Edge cases +--echo +--echo 4.0 Both materialization and in_to_exists cannot be off +set @@optimizer_switch='materialization=off,in_to_exists=off'; +--error ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); + +--echo 4.1 Outer query with no tables +--echo 4.2 Subquery with no tables +--echo 4.3 optimize_cond detects FALSE where/having clause +--echo 4.4 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const +--echo 4.4 make_join_select detects impossible WHERE +--echo 4.5 constant optimization detects "no matching row in const table" + +--echo 5. UPDATE/DELETE with subqueries + +drop table t1, t2, t3, t1i, t2i, t3i; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index cc2740c6e8b..a79bb225d72 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -581,6 +581,11 @@ protected: #ifdef DBUG_OFF /* The following must be kept in sync with optimizer_switch_str in mysqld.cc */ +/* +TODO: Materialization is off by default to mimic 5.1/5.2 behavior. +Once cost based choice between materialization and in-to-exists should be +enabled by default, add OPTIMIZER_SWITCH_MATERIALIZATION +*/ # define OPTIMIZER_SWITCH_DEFAULT (OPTIMIZER_SWITCH_INDEX_MERGE | \ OPTIMIZER_SWITCH_INDEX_MERGE_UNION | \ OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION | \ @@ -588,7 +593,6 @@ protected: OPTIMIZER_SWITCH_INDEX_COND_PUSHDOWN | \ OPTIMIZER_SWITCH_FIRSTMATCH | \ OPTIMIZER_SWITCH_LOOSE_SCAN | \ - OPTIMIZER_SWITCH_MATERIALIZATION | \ OPTIMIZER_SWITCH_IN_TO_EXISTS | \ OPTIMIZER_SWITCH_SEMIJOIN | \ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\ @@ -603,7 +607,6 @@ protected: OPTIMIZER_SWITCH_TABLE_ELIMINATION | \ OPTIMIZER_SWITCH_FIRSTMATCH | \ OPTIMIZER_SWITCH_LOOSE_SCAN | \ - OPTIMIZER_SWITCH_MATERIALIZATION | \ OPTIMIZER_SWITCH_IN_TO_EXISTS | \ OPTIMIZER_SWITCH_SEMIJOIN | \ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\ diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 44fcd76ce47..7f65932c056 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -412,11 +412,11 @@ static const char *optimizer_switch_str="index_merge=on,index_merge_union=on," "index_condition_pushdown=on," "firstmatch=on," "loosescan=on," - "materialization=on," + "materialization=off," "in_to_exists=on," "semijoin=on," "partial_match_rowid_merge=on," - "partial_match_table_scan=on," + "partial_match_table_scan=on," "subquery_cache=on" #ifndef DBUG_OFF ",table_elimination=on"; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index e0ffe31dfea..86d7f606212 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -3643,15 +3643,15 @@ bool JOIN::choose_subquery_plan(table_map join_tables) JOIN *outer_join= unit->outer_select() ? unit->outer_select()->join : NULL; JOIN *inner_join= this; /* Cost of the outer JOIN. */ - double outer_read_time= 0, outer_record_count= 0; + double outer_read_time, outer_record_count; /* Cost of the unmodified subquery. */ - double inner_read_time_1= 0, inner_record_count_1= 0; + double inner_read_time_1, inner_record_count_1; /* Cost of the subquery with injected IN-EXISTS predicates. */ - double inner_read_time_2= 0, inner_record_count_2= 0; + double inner_read_time_2, inner_record_count_2; /* The cost to compute IN via materialization. */ - double materialize_strategy_cost= 0; + double materialize_strategy_cost; /* The cost of the IN->EXISTS strategy. */ - double in_exists_strategy_cost= 1; + double in_exists_strategy_cost; if (outer_join) get_partial_join_cost(outer_join, outer_join->tables, @@ -3688,6 +3688,7 @@ bool JOIN::choose_subquery_plan(table_map join_tables) { /* Reoptimization would not produce any better plan. */ inner_read_time_2= inner_read_time_1; + inner_record_count_2= inner_record_count_1; } /* Compute execution costs. */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b05c7a34d3f..ee99f98c1ea 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1308,7 +1308,6 @@ JOIN::optimize() if (!(select_options & SELECT_DESCRIBE)) init_ftfuncs(thd, select_lex, test(order)); - /* Create all structures needed for materialized subquery execution. */ if (optimize_unflattened_subqueries()) DBUG_RETURN(1); @@ -1411,8 +1410,6 @@ setup_subq_exit: /* Even with zero matching rows, subqueries in the HAVING clause may need to be evaluated if there are aggregate functions in the query. - If we planned to materialize the subquery, we need to set it up - properly before prematurely leaving optimize(). */ if (optimize_unflattened_subqueries()) DBUG_RETURN(1); From e85a4cb6b58b92f324f661d7bb78e6a5f90da883 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 20 Oct 2010 15:43:55 +0300 Subject: [PATCH 14/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation - Added more tests to the MWL#89 specific test, and made the test more modular. - Updated test files. - Fixed a memory leak. - More comments. mysql-test/r/subselect_mat.result: - Updated the test file to reflect the new optimizer switches related to materialized subquery execution. - Added one extra test to test all cases that expose BUG#40037 (this is an old bug from 5.x). - Updated the test result with correct results that expose BUG#40037. mysql-test/t/subselect_mat.test: - Updated the test file to reflect the new optimizer switches related to materialized subquery execution. - Added one extra test to test all cases that expose BUG#40037 (this is an old bug from 5.x). - Updated the test result with correct results that expose BUG#40037. sql/sql_select.cc: Fixed a memory leak reported by Valgrind. --- mysql-test/include/subselect_mat_cost.inc | 152 + mysql-test/r/subselect_mat.result | 49 +- mysql-test/r/subselect_mat_cost.result | 3746 ++++++++++++++++++++- mysql-test/t/subselect_mat.test | 45 +- mysql-test/t/subselect_mat_cost.test | 286 +- sql/opt_subselect.cc | 13 + sql/sql_select.cc | 6 - 7 files changed, 3972 insertions(+), 325 deletions(-) create mode 100644 mysql-test/include/subselect_mat_cost.inc diff --git a/mysql-test/include/subselect_mat_cost.inc b/mysql-test/include/subselect_mat_cost.inc new file mode 100644 index 00000000000..4ccbd08693a --- /dev/null +++ b/mysql-test/include/subselect_mat_cost.inc @@ -0,0 +1,152 @@ +-- echo +-- echo /* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +-- echo +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +-- echo +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; + +-- echo +-- echo /* +-- echo B. "Natural" examples of subqueries without grouping that +-- echo cannot be flattened into semijoin. +-- echo */ + +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +-- echo +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +-- echo UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +-- echo +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +-- echo +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); + +-- echo +-- echo /* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +-- echo +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +-- echo +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +-- echo +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +-- echo +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +-- echo +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); + +-- echo +-- echo /* +-- echo D. Subqueries for which materialization is not possible, and the +-- echo optimizer reverts to in-to-exists. +-- echo */ +# The first two cases are rejected during the prepare phase by the procedure +# subquery_types_allow_materialization(). +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +-- echo +# The following two subqueries return the result of a string function with a +# blob argument, where the return type may be != blob. These are rejected during +# cost-based optimization when attempting to create a temporary table. +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +-- echo + + +-- echo +-- echo /* E. Edge cases. */ +-- echo + +-- echo /* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +--error ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +set @@optimizer_switch = @save_optimizer_switch; + +-- echo /* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +select '1 - 03' in (select b1 from t2 where b1 > '0'); + +-- echo /* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +-- echo UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); + +-- echo /* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; + +-- echo /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +-- echo TODO this test produces wrong result due to missing logic to handle the case +-- echo when JOIN::optimize detects an empty subquery result. +#explain +#select a1 from t1 where a1 in (select max(b1) from t2); +#select a1 from t1 where a1 in (select max(b1) from t2); +-- echo +#explain +#select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +#select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); + +-- echo /* E.6 make_join_select detects impossible WHERE. * + +-- echo TODO + +-- echo /* E.7 constant optimization detects "no matching row in const table". */ + +-- echo TODO + +-- echo /* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); + +-- echo +-- echo /* F. UPDATE/DELETE with subqueries. */ +-- echo + +-- echo TODO +-- echo diff --git a/mysql-test/r/subselect_mat.result b/mysql-test/r/subselect_mat.result index a4a69bb2f83..ca511caaabe 100644 --- a/mysql-test/r/subselect_mat.result +++ b/mysql-test/r/subselect_mat.result @@ -30,7 +30,7 @@ create index it3i3 on t3i (c1, c2); insert into t1i select * from t1; insert into t2i select * from t2; insert into t3i select * from t3; -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; /****************************************************************************** * Simple tests. ******************************************************************************/ @@ -176,33 +176,33 @@ a1 a2 1 - 02 2 - 02 select * from t1 where (a1, a2) in (select b1, min(b2) from t2i limit 1,1); ERROR 42000: This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; prepare st1 from "select * from t1 where (a1, a2) in (select b1, min(b2) from t2 where b1 > '0' group by b1)"; -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; execute st1; a1 a2 1 - 01 2 - 01 1 - 02 2 - 02 -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; execute st1; a1 a2 1 - 01 2 - 01 1 - 02 2 - 02 -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; prepare st1 from "select * from t1 where (a1, a2) in (select b1, min(b2) from t2 where b1 > '0' group by b1)"; -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; execute st1; a1 a2 1 - 01 2 - 01 1 - 02 2 - 02 -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; execute st1; a1 a2 1 - 01 2 - 01 1 - 02 2 - 02 -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain extended select * from t1 where (a1, a2) in (select b1, b2 from t2 order by b1, b2); id select_type table type possible_keys key key_len ref rows filtered Extra @@ -549,7 +549,7 @@ a1 a2 Test that BLOBs are not materialized (except when arguments of some functions). */ # force materialization to be always considered -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; set @prefix_len = 6; set @blob_len = 16; set @suffix_len = @blob_len - @prefix_len; @@ -951,7 +951,7 @@ insert into t1bit values (b'010', b'110'); insert into t2bit values (b'001', b'101'); insert into t2bit values (b'010', b'110'); insert into t2bit values (b'110', b'111'); -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain extended select bin(a1), bin(a2) from t1bit where (a1, a2) in (select b1, b2 from t2bit); @@ -994,7 +994,7 @@ drop table t1, t2, t3, t1i, t2i, t3i, columns; /****************************************************************************** * Test the cache of the left operand of IN. ******************************************************************************/ -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; create table t1 (s1 int); create table t2 (s2 int); insert into t1 values (5),(1),(0); @@ -1136,27 +1136,40 @@ drop table t2; create table t1 (a1 int key); create table t2 (b1 int); insert into t1 values (5); +Only the last query returns correct result. Filed as BUG#40037. +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away 2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found select min(a1) from t1 where 7 in (select b1 from t2 group by b1); min(a1) -set @@optimizer_switch='default,materialization=off'; +NULL +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away 2 DEPENDENT SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found select min(a1) from t1 where 7 in (select b1 from t2 group by b1); min(a1) -set @@optimizer_switch='default,semijoin=off'; +NULL +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away 2 SUBQUERY t2 system NULL NULL NULL NULL 0 const row not found select min(a1) from t1 where 7 in (select b1 from t2); min(a1) -set @@optimizer_switch='default,materialization=off'; +NULL +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=off'; +explain select min(a1) from t1 where 7 in (select b1 from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select min(a1) from t1 where 7 in (select b1 from t2); +min(a1) +NULL +set @@optimizer_switch='materialization=off,in_to_exists=off,semijoin=on'; explain select min(a1) from t1 where 7 in (select b1 from t2); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables @@ -1167,7 +1180,7 @@ drop table t1,t2; create table t1 (a char(2), b varchar(10)); insert into t1 values ('a', 'aaa'); insert into t1 values ('aa', 'aaaa'); -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain select a,b from t1 where b in (select a from t1); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where @@ -1187,7 +1200,7 @@ INSERT INTO t1 (f1, f2) VALUES (10, 1.668); CREATE TABLE t2 LIKE t1; INSERT INTO t2 VALUES (1, 1.789); INSERT INTO t2 VALUES (13, 1.454); -SET @@optimizer_switch='default,semijoin=on,materialization=on'; +SET @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; EXPLAIN SELECT COUNT(*) FROM t1 WHERE (f1,f2) IN (SELECT f1,f2 FROM t2); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY subselect2 ALL unique_key NULL NULL NULL 2 @@ -1208,7 +1221,7 @@ PRIMARY KEY (pk) INSERT INTO t1 VALUES (1,'o','ffff','ffff','ffoo'),(2,'f','ffff','ffff','ffff'); CREATE TABLE t2 LIKE t1; INSERT INTO t2 VALUES (1,'i','iiii','iiii','iiii'),(2,'f','ffff','ffff','ffff'); -SET @@optimizer_switch='default,semijoin=on,materialization=on'; +SET @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; EXPLAIN SELECT pk FROM t1 WHERE (a) IN (SELECT a FROM t2 WHERE pk > 0); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 2 @@ -1237,7 +1250,7 @@ i 3 4 set @save_optimizer_switch=@@optimizer_switch; -set session optimizer_switch='materialization=off'; +set session optimizer_switch='materialization=off,in_to_exists=on'; select * from t1 where t1.i in (select t2.i from t2 join t3 where t2.i + t3.i = 5); i 1 diff --git a/mysql-test/r/subselect_mat_cost.result b/mysql-test/r/subselect_mat_cost.result index bfa4589bdd2..c6cb7685f4a 100644 --- a/mysql-test/r/subselect_mat_cost.result +++ b/mysql-test/r/subselect_mat_cost.result @@ -1,191 +1,3597 @@ -drop table if exists t1, t2, t3, t1i, t2i, t3i; -create table t1 (a1 char(8), a2 char(8)); -create table t2 (b1 char(8), b2 char(8)); -create table t3 (c1 char(8), c2 char(8)); -Unindexed tables -insert into t1 values ('1 - 00', '2 - 00'); -insert into t1 values ('1 - 01', '2 - 01'); -insert into t1 values ('1 - 02', '2 - 02'); -insert into t2 values ('1 - 01', '2 - 01'); -insert into t2 values ('1 - 01', '2 - 01'); -insert into t2 values ('1 - 02', '2 - 02'); -insert into t2 values ('1 - 02', '2 - 02'); -insert into t2 values ('1 - 03', '2 - 03'); -insert into t3 values ('1 - 01', '2 - 01'); -insert into t3 values ('1 - 02', '2 - 02'); -insert into t3 values ('1 - 03', '2 - 03'); -insert into t3 values ('1 - 04', '2 - 04'); -Indexed tables -create table t1i (a1 char(8), a2 char(8)); -create table t2i (b1 char(8), b2 char(8)); -create table t3i (c1 char(8), c2 char(8)); -create index it1i1 on t1i (a1); -create index it1i2 on t1i (a2); -create index it1i3 on t1i (a1, a2); -create index it2i1 on t2i (b1); -create index it2i2 on t2i (b2); -create index it2i3 on t2i (b1, b2); -create index it3i1 on t3i (c1); -create index it3i2 on t3i (c2); -create index it3i3 on t3i (c1, c2); -insert into t1i select * from t1; -insert into t2i select * from t2; -insert into t3i select * from t3; - -1. Both materialization and in-to-exists are possible to execute +drop table if exists t1, t2, t1_1024, t2_1024; +drop procedure if exists make_t1_indexes; +drop procedure if exists make_t2_indexes; +drop procedure if exists remove_t1_indexes; +drop procedure if exists remove_t2_indexes; +drop procedure if exists add_materialization_data; +drop procedure if exists delete_materialization_data; +drop procedure if exists set_all_columns_not_null; +drop procedure if exists set_all_columns_nullable; +create table t1 (a1 char(8), a2 char(8), a3 char(8), a4 int); +insert into t1 values ('1 - 00', '2 - 00', '3 - 00', 0); +insert into t1 values ('1 - 01', '2 - 01', '3 - 01', 1); +insert into t1 values ('1 - 02', '2 - 02', '3 - 02', 2); +create table t2 (b1 char(8), b2 char(8), b3 char(8), b4 int); +insert into t2 values ('1 - 01', '2 - 01', '3 - 01', 1); +insert into t2 values ('1 - 01', '2 - 01', '3 - 02', 2); +insert into t2 values ('1 - 02', '2 - 02', '3 - 03', 3); +insert into t2 values ('1 - 02', '2 - 02', '3 - 04', 4); +insert into t2 values ('1 - 03', '2 - 03', '3 - 05', 5); +create table t1_1024 (a1 blob(1024), a2 blob(1024)); +insert into t1_1024 values (concat('1 - 00', repeat('x', 1018)), concat('2 - 00', repeat('x', 1018))); +insert into t1_1024 values (concat('1 - 01', repeat('x', 1018)), concat('2 - 01', repeat('x', 1018))); +create table t2_1024 (b1 blob(1024), b2 blob(1024)); +insert into t2_1024 values (concat('1 - 01', repeat('x', 1018)), concat('2 - 01', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 02', repeat('x', 1018)), concat('2 - 02', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 03', repeat('x', 1018)), concat('2 - 03', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 04', repeat('x', 1018)), concat('2 - 04', repeat('x', 1018))); +create procedure make_t1_indexes() +begin +create index it1i1 on t1 (a1); +create index it1i2 on t1 (a2); +create index it1i3 on t1 (a1, a2); +create index it1_1024i1 on t1_1024 (a1(6)); +create index it1_1024i2 on t1_1024 (a2(6)); +create index it1_1024i3 on t1_1024 (a1(6), a2(6)); +end| +create procedure make_t2_indexes() +begin +create index it2i1 on t2 (b1); +create index it2i2 on t2 (b2); +create index it2i3 on t2 (b1, b2); +create unique index it2i4 on t2 (b1, b2, b3); +create index it2_1024i1 on t2_1024 (b1(6)); +create index it2_1024i2 on t2_1024 (b2(6)); +create index it2_1024i3 on t2_1024 (b1(6), b2(6)); +end| +create procedure remove_t1_indexes() +begin +drop index it1i1 on t1; +drop index it1i2 on t1; +drop index it1i3 on t1; +drop index it1_1024i1 on t1_1024; +drop index it1_1024i2 on t1_1024; +drop index it1_1024i3 on t1_1024; +end| +create procedure remove_t2_indexes() +begin +drop index it2i1 on t2; +drop index it2i2 on t2; +drop index it2i3 on t2; +drop index it2i4 on t2; +drop index it2_1024i1 on t2_1024; +drop index it2_1024i2 on t2_1024; +drop index it2_1024i3 on t2_1024; +end| +create procedure add_materialization_data() +begin +insert into t1 values ('1 - 03', '2 - 03', '3 - 03', 3); +insert into t1 values ('1 - 04', '2 - 04', '3 - 04', 4); +insert into t1 values ('1 - 05', '2 - 05', '3 - 05', 5); +insert into t1 values ('1 - 06', '2 - 06', '3 - 06', 6); +insert into t1 values ('1 - 07', '2 - 07', '3 - 07', 7); +insert into t1_1024 values (concat('1 - 03', repeat('x', 1018)), concat('2 - 03', repeat('x', 1018))); +end| +create procedure delete_materialization_data() +begin +delete from t1 where a1 >= '1 - 03'; +delete from t1_1024 where a1 >= '1 - 03'; +end| +create procedure set_all_columns_not_null() +begin +alter table t1 modify a1 char(8) not null, modify a2 char(8) not null, modify a3 char(8) not null; +alter table t2 modify b1 char(8) not null, modify b2 char(8) not null, modify b3 char(8) not null; +end| +create procedure set_all_columns_nullable() +begin +alter table t1 modify a1 char(8) null, modify a2 char(8) null, modify a3 char(8) null; +alter table t2 modify b1 char(8) null, modify b2 char(8) null, modify b3 char(8) null; +end| +/****************************************************************************** +1. Both materialization and in-to-exists are ON, make a cost-based choice. +******************************************************************************/ set @@optimizer_switch='materialization=on,in_to_exists=on'; -1.1 In-to-exists is cheaper -set @@optimizer_switch='semijoin=off'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where -2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,(select `test`.`t2`.`b1` from `test`.`t2` where ((`test`.`t2`.`b1` > '0') and ((`test`.`t1`.`a1`) = `test`.`t2`.`b1`)))) -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); +/* 1.1 In-to-exists is cheaper */ +call make_t1_indexes(); +/* 1.1.1 non-indexed table access */ + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +3 DEPENDENT UNION t3 ALL NULL NULL NULL NULL 5 Using where +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 9 const 1 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); a1 a2 1 - 01 2 - 01 1 - 02 2 - 02 -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where -2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where ((`test`.`t2`.`b1` > '0') and ((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) and ((`test`.`t1`.`a2`) = `test`.`t2`.`b2`)))) -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -set @@optimizer_switch='semijoin=on'; -explain extended + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where -2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,(select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1` having ((`test`.`t1`.`a1`) = (`test`.`t2`.`b1`)))) +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -explain extended +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where -2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having ((((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) or isnull(`test`.`t2`.`b1`)) and (((`test`.`t1`.`a2`) = `test`.`t2`.`b2`) or isnull(`test`.`t2`.`b2`)) and (`test`.`t2`.`b1`) and (`test`.`t2`.`b2`)))) +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -explain extended +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 3 100.00 Using where -2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having ((`test`.`t2`.`b2` < '2 - 04') and (((`test`.`t1`.`a1`) = `test`.`t2`.`b1`) or isnull(`test`.`t2`.`b1`)) and (((`test`.`t1`.`a2`) = `test`.`t2`.`b2`) or isnull(`test`.`t2`.`b2`)) and (`test`.`t2`.`b1`) and (`test`.`t2`.`b2`)))) +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 -1.2 Materialization is cheaper -1.2.1 Materialization is executable -insert into t1 values ('1 - 03', '2 - 03'); -insert into t1 values ('1 - 04', '2 - 04'); -insert into t1 values ('1 - 05', '2 - 05'); -set @@optimizer_switch='semijoin=off'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where -2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,`test`.`t1`.`a1` in ( (select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`))))) -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -1 - 03 2 - 03 -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where -2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -1 - 03 2 - 03 -set @@optimizer_switch='semijoin=on'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where -2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where (`test`.`t1`.`a1`,`test`.`t1`.`a1` in ( (select `test`.`t2`.`b1` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1` ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`))))) -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -1 - 03 2 - 03 -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where -2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -1 - 03 2 - 03 -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -id select_type table type possible_keys key key_len ref rows filtered Extra -1 PRIMARY t1 ALL NULL NULL NULL NULL 6 100.00 Using where -2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 100.00 Using where; Using temporary; Using filesort -Warnings: -Note 1003 select `test`.`t1`.`a1` AS `a1`,`test`.`t1`.`a2` AS `a2` from `test`.`t1` where ((`test`.`t1`.`a1`,`test`.`t1`.`a2`),(`test`.`t1`.`a1`,`test`.`t1`.`a2`) in ( (select `test`.`t2`.`b1`,`test`.`t2`.`b2` from `test`.`t2` where (`test`.`t2`.`b1` > '0') group by `test`.`t2`.`b1`,`test`.`t2`.`b2` having (`test`.`t2`.`b2` < '2 - 04') ), (`test`.`t1`.`a1` in on distinct_key where ((`test`.`t1`.`a1` = `materialized subselect`.`b1`) and (`test`.`t1`.`a2` = `materialized subselect`.`b2`))))) -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -a1 a2 -1 - 01 2 - 01 -1 - 02 2 - 02 -1 - 03 2 - 03 -delete from t1 where a1 >= '1 - 03'; -1.2.2 Materialization is NOT executable, revert to in-to-exists +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 -2. Materialization is OFF, in-to-exists is ON +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 -set @@optimizer_switch='materialization=off,in_to_exists=on'; +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 -3. Materialization is ON, in-to-exists is OFF +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x -set @@optimizer_switch='materialization=on,in_to_exists=off'; -3.1 Materialization is executable -3.2 Materialization is NOT executable, revert to in-to-exists +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x -4. Edge cases -4.0 Both materialization and in_to_exists cannot be off -set @@optimizer_switch='materialization=off,in_to_exists=off'; +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. -4.1 Outer query with no tables -4.2 Subquery with no tables -4.3 optimize_cond detects FALSE where/having clause -4.4 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const -4.4 make_join_select detects impossible WHERE -4.5 constant optimization detects "no matching row in const table" -5. UPDATE/DELETE with subqueries -drop table t1, t2, t3, t1i, t2i, t3i; +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 18 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 1.1.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i3 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i3 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i2 it2i2 9 func 2 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 18 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 27 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 9 const 1 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 18 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 1 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 27 NULL 1 Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 27 NULL 1 Using where; Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 27 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 const 2 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 18 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 1.1.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i4 8 func 1 Using index; Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 ref it2i4,it2i1,it2i2,it2i3 it2i4 8 func 2 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using index +2 DEPENDENT SUBQUERY t2 ref it2i4,it2i1,it2i2,it2i3 it2i4 8 func 2 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i2 it2i2 8 func 2 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 16 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 24 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 8 const 1 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 16 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 1 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 24 NULL 1 Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 24 NULL 1 Using where; Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 24 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 8 const 5 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 16 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +call set_all_columns_nullable(); + +/* 1.2 Materialization is cheaper */ +call add_materialization_data(); +call remove_t1_indexes(); +/* 1.2.1 non-indexed table access */ +call remove_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +3 DEPENDENT UNION t3 ALL NULL NULL NULL NULL 5 Using where +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 1.2.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 5 Using where; Using index +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i3 18 NULL 5 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 range it2i4,it2i1,it2i2,it2i3 it2i3 9 NULL 4 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i2 it2i2 9 NULL 5 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i3 18 NULL 5 Using where; Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 27 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ref it2i2 it2i2 9 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i4 27 NULL 5 Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 5 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range NULL it2i4 27 NULL 6 Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range NULL it2i4 27 NULL 6 Using where; Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 27 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 const 2 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 1.2.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 5 Using where; Using index +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i3 16 NULL 5 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 SUBQUERY t2 range it2i4,it2i1,it2i2,it2i3 it2i3 8 NULL 4 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i2 it2i2 8 NULL 5 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i3 16 NULL 5 Using where; Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 24 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ref it2i2 it2i2 8 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i2,it2i3 it2i4 24 NULL 5 Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 5 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range NULL it2i4 24 NULL 6 Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range NULL it2i4 24 NULL 6 Using where; Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 24 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 8 const 5 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +call set_all_columns_nullable(); +/****************************************************************************** +2. Materialization is OFF, in-to-exists is ON, materialization is cheaper. +******************************************************************************/ +set @@optimizer_switch='materialization=off,in_to_exists=on'; +/* 2.1 non-indexed table access */ +call remove_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +3 DEPENDENT UNION t3 ALL NULL NULL NULL NULL 5 Using where +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 2.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i3 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i3 9 func 2 Using index; Using where; Full scan on NULL key +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i2 it2i2 9 func 2 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 18 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 27 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 18 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 1 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 27 NULL 1 Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 27 NULL 1 Using where; Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 27 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 const 2 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 2.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i4 8 func 1 Using index; Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 +1 - 03 1 +1 - 04 0 +1 - 05 0 +1 - 06 0 +1 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 ref it2i4,it2i1,it2i2,it2i3 it2i4 8 func 2 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 +2 DEPENDENT SUBQUERY t2 ref it2i4,it2i1,it2i2,it2i3 it2i4 8 func 2 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 +1 - 03 2 - 03 1 +1 - 04 2 - 04 0 +1 - 05 2 - 05 0 +1 - 06 2 - 06 0 +1 - 07 2 - 07 0 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i2 it2i2 8 func 2 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 16 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 +1 - 04 2 - 04 +1 - 05 2 - 05 +1 - 06 2 - 06 +1 - 07 2 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 24 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 +2 - 03 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i2,it2i3 it2i4 16 func,func 1 Using index; Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 +1 - 03 2 - 03 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 1 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 1 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 +1 - 03 2 - 03 3 - 03 3 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 24 NULL 1 Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 index NULL it2i4 24 NULL 1 Using where; Using index +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 24 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL NULL NULL NULL NULL 3 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +1 - 03x 2 - 03x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 8 const 5 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +1 - 03 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +1 - 03 +1 - 04 +1 - 05 +1 - 06 +1 - 07 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +call set_all_columns_nullable(); +/****************************************************************************** +3. Materialization is ON, in-to-exists is OFF, in-to-exists is cheaper. +******************************************************************************/ +set @@optimizer_switch='materialization=on,in_to_exists=off'; +call delete_materialization_data(); +call make_t1_indexes(); +/* 3.1 non-indexed table access */ +call remove_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +3 DEPENDENT UNION t3 ALL NULL NULL NULL NULL 5 Using where +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 9 const 1 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where; Using temporary; Using filesort +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 ALL NULL NULL NULL NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 18 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 3.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 5 Using where; Using index +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using index +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i3 9 NULL 4 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 index NULL it2i2 9 NULL 5 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 9 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 27 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 9 const 1 Using where; Using index +2 SUBQUERY t2 ref it2i2 it2i2 9 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY t2 index NULL it2i4 27 NULL 5 Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 9 NULL 5 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 18 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range NULL it2i4 27 NULL 6 Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range NULL it2i4 27 NULL 6 Using where; Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 27 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 9 const 2 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 18 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 18 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +/* 3.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); + +/* A. Subqueries in the SELECT clause. */ +explain +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 5 Using where; Using index +select a1, a1 in (select b1 from t2 where b1 > '0') from t1; +a1 a1 in (select b1 from t2 where b1 > '0') +1 - 00 0 +1 - 01 1 +1 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +explain +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using index +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i3 8 NULL 4 Using where; Using index +select a1, a2, (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') from t1; +a1 a2 (a1, a2) in (select b1, b2 from t2 where b1 > '0' and b1 < '9') +1 - 00 2 - 00 0 +1 - 01 2 - 01 1 +1 - 02 2 - 02 1 + +/* +B. "Natural" examples of subqueries without grouping that +cannot be flattened into semijoin. +*/ +explain +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 SUBQUERY t2 index NULL it2i2 8 NULL 5 Using index +select a1 from t1 where a1 in (select b2 from t2) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0') or a2 < '9'; +a1 a2 +1 - 00 2 - 00 +1 - 01 2 - 01 +1 - 02 2 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i2 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ref it2i2 it2i2 8 func 2 Using index +3 DEPENDENT UNION t3 index NULL it2i4 24 NULL 5 Using where; Using index +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a2 from t1 where a2 in (select b2 from t2 UNION select b3 from t2 as t3); +a2 +2 - 01 +2 - 02 + +explain +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ref it1i1,it1i3 it1i1 8 const 1 Using where; Using index +2 SUBQUERY t2 ref it2i2 it2i2 8 const 1 Using index condition +select a1 from t1 where a1 = '1 - 02' and a1 in (select max(b1) from t2 where b2 = '2 - 02'); +a1 +1 - 02 + +explain +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i3 16 NULL 3 Using where; Using index +2 SUBQUERY t2 index NULL it2i4 24 NULL 5 Using index +select a1, a2 from t1 where (a1, a2) in (select b1, b2 from t2 order by b3); +a1 a2 +1 - 01 2 - 01 +1 - 02 2 - 02 + +/* C. Subqueries in the WHERE clause with GROUP BY. */ +explain +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i1 8 NULL 5 Using where; Using index +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 index it2i4,it2i1,it2i3 it2i3 16 NULL 5 Using where; Using index +select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 +1 - 02 2 - 02 3 - 02 2 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range NULL it2i4 24 NULL 6 Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 group by b1, b2, b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +explain +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range NULL it2i4 24 NULL 6 Using where; Using index for group-by +select * from t1 where (a1, a2, a3) in (select b1, b2, b3 from t2 where b3 = '3 - 02' group by b1, b2); +a1 a2 a3 a4 + +explain +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 3 Using where +2 SUBQUERY t2 range it2i4,it2i1,it2i3 it2i4 24 NULL 2 Using where; Using index for group-by +select * from t1 where (a1,a2,a3) in (select b1,b2,b3 from t2 where b1 = '1 - 01' group by b1,b2,b3); +a1 a2 a3 a4 +1 - 01 2 - 01 3 - 01 1 + +/* +D. Subqueries for which materialization is not possible, and the +optimizer reverts to in-to-exists. +*/ +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i3 it2_1024i3 9 func 1 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select b1 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 index_subquery it2_1024i1,it2_1024i2,it2_1024i3 it2_1024i1 9 func 2 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select b1, b2 from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + +explain +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where a1 in (select substring(b1,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x +explain +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1_1024 ALL it1_1024i2 NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t2_1024 range it2_1024i1,it2_1024i3 it2_1024i1 9 NULL 4 Using where +select left(a1,7), left(a2,7) from t1_1024 where (a1,a2) in (select substring(b1,1,1024), substring(b2,1,1024) from t2_1024 where b1 > '0') or a2 < '9'; +left(a1,7) left(a2,7) +1 - 00x 2 - 00x +1 - 01x 2 - 01x + + +/* E. Edge cases. */ + +/* E.1 Both materialization and in_to_exists cannot be off. */ +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch = 'materialization=off,in_to_exists=off'; +select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +ERROR HY000: At least one of the 'in_to_exists' or 'materialization' optimizer_switch flags must be 'on'. +set @@optimizer_switch = @save_optimizer_switch; +/* E.2 Outer query without tables, always uses IN-TO-EXISTS. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY t2 index_subquery it2i4,it2i1,it2i3 it2i1 8 const 5 Using index; Using where +select '1 - 03' in (select b1 from t2 where b1 > '0'); +'1 - 03' in (select b1 from t2 where b1 > '0') +1 +/* E.3 Subqueries without tables. */ +explain +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i1,it1i2,it1i3 it1i3 16 NULL 3 Using where; Using index +Warnings: +Note 1249 Select 2 was reduced during optimization +select a1 from t1 where a1 in (select '1 - 03') or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +UNION subqueries are currently limited to only use IN-TO-EXISTS. +explain +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No tables used +3 DEPENDENT UNION NULL NULL NULL NULL NULL NULL NULL No tables used +NULL UNION RESULT ALL NULL NULL NULL NULL NULL +select a1 from t1 where a1 in (select '1 - 03' UNION select '1 - 02'); +a1 +1 - 02 +/* E.4 optimize_cond detects FALSE where/having clause. */ +explain +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index it1i2 it1i3 16 NULL 3 Using where; Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE +select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' and b1 = '1 - 02' ) or a2 < '9'; +a1 +1 - 00 +1 - 01 +1 - 02 +/* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ +TODO this test produces wrong result due to missing logic to handle the case +when JOIN::optimize detects an empty subquery result. + +/* E.6 make_join_select detects impossible WHERE. * +TODO +/* E.7 constant optimization detects "no matching row in const table". */ +TODO +/* E.8 Impossible WHERE noticed after reading const tables. */ +explain +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL No tables used +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables +select '1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0'); +'1 - 03' in (select b1 from t2 where b1 > '0' and b1 < '0') +0 + +/* F. UPDATE/DELETE with subqueries. */ + +TODO + +call set_all_columns_nullable(); +drop procedure make_t1_indexes; +drop procedure make_t2_indexes; +drop procedure remove_t1_indexes; +drop procedure remove_t2_indexes; +drop procedure add_materialization_data; +drop procedure delete_materialization_data; +drop procedure set_all_columns_not_null; +drop procedure set_all_columns_nullable; +drop table t1, t2, t1_1024, t2_1024; diff --git a/mysql-test/t/subselect_mat.test b/mysql-test/t/subselect_mat.test index 1c2869c628a..0b3a89336cc 100644 --- a/mysql-test/t/subselect_mat.test +++ b/mysql-test/t/subselect_mat.test @@ -48,7 +48,7 @@ insert into t2i select * from t2; insert into t3i select * from t3; # force the use of materialization -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; /****************************************************************************** * Simple tests. @@ -111,22 +111,22 @@ select * from t1 where (a1, a2) in (select b1, min(b2) from t2i limit 1,1); # test re-optimization/re-execution with different execution methods # prepare once, exec with different modes -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; prepare st1 from "select * from t1 where (a1, a2) in (select b1, min(b2) from t2 where b1 > '0' group by b1)"; -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; execute st1; -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; execute st1; -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; prepare st1 from "select * from t1 where (a1, a2) in (select b1, min(b2) from t2 where b1 > '0' group by b1)"; -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; execute st1; -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=on'; execute st1; -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; # materialize the result of ORDER BY # non-indexed fields @@ -327,7 +327,7 @@ select * from t1 order by (select col from columns limit 1); Test that BLOBs are not materialized (except when arguments of some functions). */ # force materialization to be always considered -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; set @prefix_len = 6; # BLOB == 16 (small blobs that could be stored in HEAP tables) @@ -680,7 +680,7 @@ insert into t2bit values (b'001', b'101'); insert into t2bit values (b'010', b'110'); insert into t2bit values (b'110', b'111'); -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain extended select bin(a1), bin(a2) from t1bit @@ -718,7 +718,7 @@ drop table t1, t2, t3, t1i, t2i, t3i, columns; /****************************************************************************** * Test the cache of the left operand of IN. ******************************************************************************/ -set @@optimizer_switch='semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; # Test that default values of Cached_item are not used for comparison create table t1 (s1 int); @@ -812,23 +812,28 @@ drop table t2; create table t1 (a1 int key); create table t2 (b1 int); insert into t1 values (5); - +-- echo Only the last query returns correct result. Filed as BUG#40037. # Query with group by, executed via materialization +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); select min(a1) from t1 where 7 in (select b1 from t2 group by b1); # Query with group by, executed via IN=>EXISTS -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2 group by b1); select min(a1) from t1 where 7 in (select b1 from t2 group by b1); # Executed with materialization -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; +explain select min(a1) from t1 where 7 in (select b1 from t2); +select min(a1) from t1 where 7 in (select b1 from t2); +# Executed via IN=>EXISTS +set @@optimizer_switch='materialization=off,in_to_exists=on,semijoin=off'; explain select min(a1) from t1 where 7 in (select b1 from t2); select min(a1) from t1 where 7 in (select b1 from t2); # Executed with semi-join. Notice, this time we get a different result (NULL). -# This is the only correct result of all four queries. This difference is +# This is the only correct result of all five queries. This difference is # filed as BUG#40037. -set @@optimizer_switch='default,materialization=off'; +set @@optimizer_switch='materialization=off,in_to_exists=off,semijoin=on'; explain select min(a1) from t1 where 7 in (select b1 from t2); select min(a1) from t1 where 7 in (select b1 from t2); drop table t1,t2; @@ -840,7 +845,7 @@ create table t1 (a char(2), b varchar(10)); insert into t1 values ('a', 'aaa'); insert into t1 values ('aa', 'aaaa'); -set @@optimizer_switch='default,semijoin=off'; +set @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=off'; explain select a,b from t1 where b in (select a from t1); select a,b from t1 where b in (select a from t1); prepare st1 from "select a,b from t1 where b in (select a from t1)"; @@ -861,7 +866,7 @@ CREATE TABLE t2 LIKE t1; INSERT INTO t2 VALUES (1, 1.789); INSERT INTO t2 VALUES (13, 1.454); -SET @@optimizer_switch='default,semijoin=on,materialization=on'; +SET @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; EXPLAIN SELECT COUNT(*) FROM t1 WHERE (f1,f2) IN (SELECT f1,f2 FROM t2); SELECT COUNT(*) FROM t1 WHERE (f1,f2) IN (SELECT f1,f2 FROM t2); @@ -883,7 +888,7 @@ INSERT INTO t1 VALUES (1,'o','ffff','ffff','ffoo'),(2,'f','ffff','ffff','ffff'); CREATE TABLE t2 LIKE t1; INSERT INTO t2 VALUES (1,'i','iiii','iiii','iiii'),(2,'f','ffff','ffff','ffff'); -SET @@optimizer_switch='default,semijoin=on,materialization=on'; +SET @@optimizer_switch='materialization=on,in_to_exists=off,semijoin=on'; EXPLAIN SELECT pk FROM t1 WHERE (a) IN (SELECT a FROM t2 WHERE pk > 0); SELECT pk FROM t1 WHERE (a) IN (SELECT a FROM t2 WHERE pk > 0); SELECT pk FROM t1 WHERE (b,c,d) IN (SELECT b,c,d FROM t2 WHERE pk > 0); @@ -900,7 +905,7 @@ create table t3(i int); insert into t3 values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10); select * from t1 where t1.i in (select t2.i from t2 join t3 where t2.i + t3.i = 5); set @save_optimizer_switch=@@optimizer_switch; -set session optimizer_switch='materialization=off'; +set session optimizer_switch='materialization=off,in_to_exists=on'; select * from t1 where t1.i in (select t2.i from t2 join t3 where t2.i + t3.i = 5); set session optimizer_switch=@save_optimizer_switch; drop table t1, t2, t3; diff --git a/mysql-test/t/subselect_mat_cost.test b/mysql-test/t/subselect_mat_cost.test index 702eac508d6..a268322dc5f 100644 --- a/mysql-test/t/subselect_mat_cost.test +++ b/mysql-test/t/subselect_mat_cost.test @@ -4,136 +4,200 @@ # --disable_warnings -drop table if exists t1, t2, t3, t1i, t2i, t3i; +drop table if exists t1, t2, t1_1024, t2_1024; +drop procedure if exists make_t1_indexes; +drop procedure if exists make_t2_indexes; +drop procedure if exists remove_t1_indexes; +drop procedure if exists remove_t2_indexes; +drop procedure if exists add_materialization_data; +drop procedure if exists delete_materialization_data; +drop procedure if exists set_all_columns_not_null; +drop procedure if exists set_all_columns_nullable; --enable_warnings -create table t1 (a1 char(8), a2 char(8)); -create table t2 (b1 char(8), b2 char(8)); -create table t3 (c1 char(8), c2 char(8)); +create table t1 (a1 char(8), a2 char(8), a3 char(8), a4 int); +insert into t1 values ('1 - 00', '2 - 00', '3 - 00', 0); +insert into t1 values ('1 - 01', '2 - 01', '3 - 01', 1); +insert into t1 values ('1 - 02', '2 - 02', '3 - 02', 2); ---echo Unindexed tables -insert into t1 values ('1 - 00', '2 - 00'); -insert into t1 values ('1 - 01', '2 - 01'); -insert into t1 values ('1 - 02', '2 - 02'); +create table t2 (b1 char(8), b2 char(8), b3 char(8), b4 int); +insert into t2 values ('1 - 01', '2 - 01', '3 - 01', 1); +insert into t2 values ('1 - 01', '2 - 01', '3 - 02', 2); +insert into t2 values ('1 - 02', '2 - 02', '3 - 03', 3); +insert into t2 values ('1 - 02', '2 - 02', '3 - 04', 4); +insert into t2 values ('1 - 03', '2 - 03', '3 - 05', 5); -insert into t2 values ('1 - 01', '2 - 01'); -insert into t2 values ('1 - 01', '2 - 01'); -insert into t2 values ('1 - 02', '2 - 02'); -insert into t2 values ('1 - 02', '2 - 02'); -insert into t2 values ('1 - 03', '2 - 03'); +create table t1_1024 (a1 blob(1024), a2 blob(1024)); +insert into t1_1024 values (concat('1 - 00', repeat('x', 1018)), concat('2 - 00', repeat('x', 1018))); +insert into t1_1024 values (concat('1 - 01', repeat('x', 1018)), concat('2 - 01', repeat('x', 1018))); -insert into t3 values ('1 - 01', '2 - 01'); -insert into t3 values ('1 - 02', '2 - 02'); -insert into t3 values ('1 - 03', '2 - 03'); -insert into t3 values ('1 - 04', '2 - 04'); +create table t2_1024 (b1 blob(1024), b2 blob(1024)); +insert into t2_1024 values (concat('1 - 01', repeat('x', 1018)), concat('2 - 01', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 02', repeat('x', 1018)), concat('2 - 02', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 03', repeat('x', 1018)), concat('2 - 03', repeat('x', 1018))); +insert into t2_1024 values (concat('1 - 04', repeat('x', 1018)), concat('2 - 04', repeat('x', 1018))); ---echo Indexed tables -create table t1i (a1 char(8), a2 char(8)); -create table t2i (b1 char(8), b2 char(8)); -create table t3i (c1 char(8), c2 char(8)); -create index it1i1 on t1i (a1); -create index it1i2 on t1i (a2); -create index it1i3 on t1i (a1, a2); +delimiter |; +create procedure make_t1_indexes() +begin + create index it1i1 on t1 (a1); + create index it1i2 on t1 (a2); + create index it1i3 on t1 (a1, a2); + create index it1_1024i1 on t1_1024 (a1(6)); + create index it1_1024i2 on t1_1024 (a2(6)); + create index it1_1024i3 on t1_1024 (a1(6), a2(6)); +end| -create index it2i1 on t2i (b1); -create index it2i2 on t2i (b2); -create index it2i3 on t2i (b1, b2); +create procedure make_t2_indexes() +begin + create index it2i1 on t2 (b1); + create index it2i2 on t2 (b2); + create index it2i3 on t2 (b1, b2); + create unique index it2i4 on t2 (b1, b2, b3); + create index it2_1024i1 on t2_1024 (b1(6)); + create index it2_1024i2 on t2_1024 (b2(6)); + create index it2_1024i3 on t2_1024 (b1(6), b2(6)); +end| -create index it3i1 on t3i (c1); -create index it3i2 on t3i (c2); -create index it3i3 on t3i (c1, c2); +create procedure remove_t1_indexes() +begin + drop index it1i1 on t1; + drop index it1i2 on t1; + drop index it1i3 on t1; + drop index it1_1024i1 on t1_1024; + drop index it1_1024i2 on t1_1024; + drop index it1_1024i3 on t1_1024; +end| -insert into t1i select * from t1; -insert into t2i select * from t2; -insert into t3i select * from t3; +create procedure remove_t2_indexes() +begin + drop index it2i1 on t2; + drop index it2i2 on t2; + drop index it2i3 on t2; + drop index it2i4 on t2; + drop index it2_1024i1 on t2_1024; + drop index it2_1024i2 on t2_1024; + drop index it2_1024i3 on t2_1024; +end| ---echo ---echo 1. Both materialization and in-to-exists are possible to execute ---echo -set @@optimizer_switch='materialization=on,in_to_exists=on'; - ---echo ---echo 1.1 In-to-exists is cheaper - -set @@optimizer_switch='semijoin=off'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); - -set @@optimizer_switch='semijoin=on'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); - ---echo ---echo 1.2 Materialization is cheaper - ---echo 1.2.1 Materialization is executable - -insert into t1 values ('1 - 03', '2 - 03'); -insert into t1 values ('1 - 04', '2 - 04'); -insert into t1 values ('1 - 05', '2 - 05'); - -set @@optimizer_switch='semijoin=off'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -select * from t1 where a1 in (select b1 from t2 where b1 > '0'); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0'); - -set @@optimizer_switch='semijoin=on'; -explain extended -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2); -explain extended -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); -select * from t1 where (a1, a2) in (select b1, b2 from t2 where b1 > '0' group by b1, b2 having b2 < '2 - 04'); +create procedure add_materialization_data() +begin +insert into t1 values ('1 - 03', '2 - 03', '3 - 03', 3); +insert into t1 values ('1 - 04', '2 - 04', '3 - 04', 4); +insert into t1 values ('1 - 05', '2 - 05', '3 - 05', 5); +insert into t1 values ('1 - 06', '2 - 06', '3 - 06', 6); +insert into t1 values ('1 - 07', '2 - 07', '3 - 07', 7); +insert into t1_1024 values (concat('1 - 03', repeat('x', 1018)), concat('2 - 03', repeat('x', 1018))); +end| +create procedure delete_materialization_data() +begin delete from t1 where a1 >= '1 - 03'; +delete from t1_1024 where a1 >= '1 - 03'; +end| ---echo 1.2.2 Materialization is NOT executable, revert to in-to-exists +create procedure set_all_columns_not_null() +begin +alter table t1 modify a1 char(8) not null, modify a2 char(8) not null, modify a3 char(8) not null; +alter table t2 modify b1 char(8) not null, modify b2 char(8) not null, modify b3 char(8) not null; +end| ---echo ---echo 2. Materialization is OFF, in-to-exists is ON ---echo +create procedure set_all_columns_nullable() +begin +alter table t1 modify a1 char(8) null, modify a2 char(8) null, modify a3 char(8) null; +alter table t2 modify b1 char(8) null, modify b2 char(8) null, modify b3 char(8) null; +end| + +delimiter ;| +-- echo + +-- echo /****************************************************************************** +-- echo 1. Both materialization and in-to-exists are ON, make a cost-based choice. +-- echo ******************************************************************************/ +set @@optimizer_switch='materialization=on,in_to_exists=on'; +-- echo +-- echo /* 1.1 In-to-exists is cheaper */ +call make_t1_indexes(); + +-- echo /* 1.1.1 non-indexed table access */ +-- source include/subselect_mat_cost.inc + +-- echo /* 1.1.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); +-- source include/subselect_mat_cost.inc + +-- echo /* 1.1.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); +-- source include/subselect_mat_cost.inc +call set_all_columns_nullable(); + +-- echo +-- echo /* 1.2 Materialization is cheaper */ +# make materialization cheaper +call add_materialization_data(); +call remove_t1_indexes(); + +-- echo /* 1.2.1 non-indexed table access */ +call remove_t2_indexes(); +-- source include/subselect_mat_cost.inc + +-- echo /* 1.2.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); +-- source include/subselect_mat_cost.inc + +-- echo /* 1.2.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); +-- source include/subselect_mat_cost.inc +call set_all_columns_nullable(); + + +-- echo /****************************************************************************** +-- echo 2. Materialization is OFF, in-to-exists is ON, materialization is cheaper. +-- echo ******************************************************************************/ set @@optimizer_switch='materialization=off,in_to_exists=on'; ---echo ---echo 3. Materialization is ON, in-to-exists is OFF ---echo +-- echo /* 2.1 non-indexed table access */ +call remove_t2_indexes(); +-- source include/subselect_mat_cost.inc + +-- echo /* 2.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); +-- source include/subselect_mat_cost.inc + +-- echo /* 2.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); +-- source include/subselect_mat_cost.inc +call set_all_columns_nullable(); + + +-- echo /****************************************************************************** +-- echo 3. Materialization is ON, in-to-exists is OFF, in-to-exists is cheaper. +-- echo ******************************************************************************/ set @@optimizer_switch='materialization=on,in_to_exists=off'; +# make IN-TO-EXISTS cheaper +call delete_materialization_data(); +call make_t1_indexes(); ---echo 3.1 Materialization is executable ---echo 3.2 Materialization is NOT executable, revert to in-to-exists +-- echo /* 3.1 non-indexed table access */ +call remove_t2_indexes(); +-- source include/subselect_mat_cost.inc ---echo ---echo 4. Edge cases ---echo ---echo 4.0 Both materialization and in_to_exists cannot be off -set @@optimizer_switch='materialization=off,in_to_exists=off'; ---error ER_ILLEGAL_SUBQUERY_OPTIMIZER_SWITCHES -select * from t1 where a1 in (select b1 from t2 where b1 > '0' group by b1); +-- echo /* 3.2 indexed table access, nullabale columns. */ +call make_t2_indexes(); +-- source include/subselect_mat_cost.inc ---echo 4.1 Outer query with no tables ---echo 4.2 Subquery with no tables ---echo 4.3 optimize_cond detects FALSE where/having clause ---echo 4.4 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const ---echo 4.4 make_join_select detects impossible WHERE ---echo 4.5 constant optimization detects "no matching row in const table" +-- echo /* 3.3 indexed table access, non-nullabale columns. */ +call set_all_columns_not_null(); +-- source include/subselect_mat_cost.inc +call set_all_columns_nullable(); ---echo 5. UPDATE/DELETE with subqueries -drop table t1, t2, t3, t1i, t2i, t3i; +drop procedure make_t1_indexes; +drop procedure make_t2_indexes; +drop procedure remove_t1_indexes; +drop procedure remove_t2_indexes; +drop procedure add_materialization_data; +drop procedure delete_materialization_data; +drop procedure set_all_columns_not_null; +drop procedure set_all_columns_nullable; +drop table t1, t2, t1_1024, t2_1024; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 86d7f606212..25d3aa29904 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -3403,6 +3403,19 @@ int rewrite_to_index_subquery_engine(JOIN *join) /* is this simple IN subquery? */ + /* TODO: In order to use these more efficient subquery engines in more cases, + the following problems need to be solved: + - the code that removes GROUP BY (group_list), also adds an ORDER BY + (order), thus GROUP BY queries (almost?) never pass through this branch. + Solution: remove the test below '!join->order', because we remove the + ORDER clase for subqueries anyway. + - in order to set a more efficient engine, the optimizer needs to both + decide to remove GROUP BY, *and* select one of the JT_[EQ_]REF[_OR_NULL] + access methods, *and* loose scan should be more expensive or + inapliccable. When is that possible? + - Consider expanding the applicability of this rewrite for loose scan + for group by queries. + */ if (!join->group_list && !join->order && join->unit->item && join->unit->item->substype() == Item_subselect::IN_SUBS && diff --git a/sql/sql_select.cc b/sql/sql_select.cc index ee99f98c1ea..4e5dc9074f2 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -19372,12 +19372,6 @@ int JOIN::reoptimize(Item *added_where, table_map join_tables, DYNAMIC_ARRAY added_keyuse; SARGABLE_PARAM *sargables= 0; /* Used only as a dummy parameter. */ - if (my_init_dynamic_array(&added_keyuse, sizeof(KEYUSE), 20, 64)) - { - delete_dynamic(&added_keyuse); - return 1; - } - /* Re-run the REF optimizer to take into account the new conditions. */ if (update_ref_and_keys(thd, &added_keyuse, join_tab, tables, added_where, ~outer_join, select_lex, &sargables)) From f670b6d22f55401d4329f9ac9827a5145d1dd667 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 23 Oct 2010 21:28:58 +0300 Subject: [PATCH 15/16] MWL#89: Cost-based choice between Materialization and IN->EXISTS transformation Added missing logic to handle the case when subquery tables are optimized away early during optimization. --- mysql-test/include/subselect_mat_cost.inc | 12 +- mysql-test/r/subselect_mat_cost.result | 174 ++++++++++++++++++++++ sql/item_subselect.cc | 32 ++-- sql/item_subselect.h | 26 +++- sql/opt_subselect.cc | 62 ++++++++ sql/sql_select.cc | 43 +----- sql/sql_select.h | 2 + 7 files changed, 284 insertions(+), 67 deletions(-) diff --git a/mysql-test/include/subselect_mat_cost.inc b/mysql-test/include/subselect_mat_cost.inc index 4ccbd08693a..04b116e9527 100644 --- a/mysql-test/include/subselect_mat_cost.inc +++ b/mysql-test/include/subselect_mat_cost.inc @@ -123,13 +123,13 @@ select a1 from t1 where a1 in (select b1 from t2 where b1 = b2 and b2 = '1 - 03' -- echo /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ -- echo TODO this test produces wrong result due to missing logic to handle the case -- echo when JOIN::optimize detects an empty subquery result. -#explain -#select a1 from t1 where a1 in (select max(b1) from t2); -#select a1 from t1 where a1 in (select max(b1) from t2); +explain +select a1 from t1 where a1 in (select max(b1) from t2); +select a1 from t1 where a1 in (select max(b1) from t2); -- echo -#explain -#select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); -#select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); -- echo /* E.6 make_join_select detects impossible WHERE. * diff --git a/mysql-test/r/subselect_mat_cost.result b/mysql-test/r/subselect_mat_cost.result index c6cb7685f4a..9d6986cf092 100644 --- a/mysql-test/r/subselect_mat_cost.result +++ b/mysql-test/r/subselect_mat_cost.result @@ -342,7 +342,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -609,7 +623,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -876,7 +904,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -1193,7 +1235,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -1505,7 +1562,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -1817,7 +1889,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -2134,7 +2221,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -2446,7 +2548,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -2758,7 +2875,22 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +1 - 03 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 8 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -3032,7 +3164,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 SUBQUERY t2 ALL NULL NULL NULL NULL 5 Using where +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -3299,7 +3445,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 9 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ @@ -3566,7 +3726,21 @@ a1 /* E.5 opt_sum_query detects no matching min/max row or substitutes MIN/MAX with a const. */ TODO this test produces wrong result due to missing logic to handle the case when JOIN::optimize detects an empty subquery result. +explain +select a1 from t1 where a1 in (select max(b1) from t2); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +select a1 from t1 where a1 in (select max(b1) from t2); +a1 +explain +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 index NULL it1i1 8 NULL 3 Using where; Using index +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL No matching min/max row +select a1 from t1 where a1 in (select max(b1) from t2 where b1 = '7 - 02'); +a1 /* E.6 make_join_select detects impossible WHERE. * TODO /* E.7 constant optimization detects "no matching row in const table". */ diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 5f9285e5df6..475a58df88e 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -36,7 +36,7 @@ Item_subselect::Item_subselect(): Item_result_field(), value_assigned(0), thd(0), substitution(0), engine(0), old_engine(0), used_tables_cache(0), have_to_be_excluded(0), const_item_cache(1), - inside_first_fix_fields(0), done_first_fix_fields(FALSE), + inside_first_fix_fields(0), done_first_fix_fields(FALSE), forced_const(FALSE), eliminated(FALSE), engine_changed(0), changed(0), is_correlated(FALSE) { @@ -121,6 +121,7 @@ void Item_subselect::cleanup() depends_on.empty(); reset(); value_assigned= 0; + forced_const= FALSE; DBUG_VOID_RETURN; } @@ -158,7 +159,6 @@ void Item_in_subselect::cleanup() left_expr_cache= NULL; } first_execution= TRUE; - is_constant= FALSE; if (in_strategy & SUBS_MATERIALIZATION) in_strategy= 0; pushed_cond_guards= NULL; @@ -682,12 +682,15 @@ Item *Item_subselect::get_tmp_table_item(THD *thd_arg) void Item_subselect::update_used_tables() { - recalc_used_tables(parent_select, FALSE); - if (!engine->uncacheable()) + if (!forced_const) { - // did all used tables become static? - if (!(used_tables_cache & ~engine->upper_select_const_tables())) - const_item_cache= 1; + recalc_used_tables(parent_select, FALSE); + if (!engine->uncacheable()) + { + // did all used tables become static? + if (!(used_tables_cache & ~engine->upper_select_const_tables())) + const_item_cache= 1; + } } } @@ -753,7 +756,7 @@ Item_maxmin_subselect::Item_maxmin_subselect(THD *thd_param, of Items belonged to subquery, which will be not repeated */ used_tables_cache= parent->get_used_tables_cache(); - const_item_cache= parent->get_const_item_cache(); + const_item_cache= parent->const_item(); /* this subquery always creates during preparation, so we can assign @@ -791,8 +794,7 @@ void Item_maxmin_subselect::print(String *str, enum_query_type query_type) void Item_singlerow_subselect::reset() { - eliminated= FALSE; - null_value= 1; + Item_subselect::reset(); if (value) value->null_value= 1; } @@ -1082,8 +1084,7 @@ bool Item_in_subselect::test_limit(st_select_lex_unit *unit_arg) Item_in_subselect::Item_in_subselect(Item * left_exp, st_select_lex *select_lex): Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), - is_constant(FALSE), optimizer(0), pushed_cond_guards(NULL), - in_strategy(0), upper_item(0) + optimizer(0), pushed_cond_guards(NULL), in_strategy(0), upper_item(0) { DBUG_ENTER("Item_in_subselect::Item_in_subselect"); left_expr= left_exp; @@ -1313,7 +1314,7 @@ bool Item_in_subselect::val_bool() { DBUG_ASSERT(fixed == 1); null_value= 0; - if (is_constant) + if (forced_const) return value; if (exec()) { @@ -4147,11 +4148,10 @@ int subselect_hash_sj_engine::exec() tmp_table->file->info(HA_STATUS_VARIABLE); if (!tmp_table->file->stats.records) { - item_in->value= FALSE; /* The value of IN will not change during this execution. */ - item_in->is_constant= TRUE; + item_in->reset(); + item_in->make_const(); item_in->set_first_execution(); - /* TIMOUR: check if we need this: item_in->null_value= FALSE; */ DBUG_RETURN(FALSE); } diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 2a6d310cbb4..dc29323f756 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -70,6 +70,13 @@ protected: bool inside_first_fix_fields; bool done_first_fix_fields; + /* + Set to TRUE if at optimization or execution time we determine that this + item's value is a constant. We need this member because it is not possible + to substitute 'this' with a constant item. + */ + bool forced_const; + public: /* A reference from inside subquery predicate to somewhere outside of it */ class Ref_to_outside : public Sql_alloc @@ -154,12 +161,21 @@ public: void fix_after_pullout(st_select_lex *new_parent, Item **ref); void recalc_used_tables(st_select_lex *new_parent, bool after_pullout); virtual bool exec(); + /* + If subquery optimization or execution determines that the subquery has + an empty result, mark the subquery predicate as a constant value. + */ + void make_const() + { + used_tables_cache= 0; + const_item_cache= 0; + forced_const= TRUE; + } virtual void fix_length_and_dec(); table_map used_tables() const; table_map not_null_tables() const { return 0; } bool const_item() const; inline table_map get_used_tables_cache() { return used_tables_cache; } - inline bool get_const_item_cache() { return const_item_cache; } Item *get_tmp_table_item(THD *thd); void update_used_tables(); virtual void print(String *str, enum_query_type query_type); @@ -353,12 +369,6 @@ protected: */ List *left_expr_cache; bool first_execution; - /* - Set to TRUE if at query execution time we determine that this item's - value is a constant during this execution. We need this member because - it is not possible to substitute 'this' with a constant item. - */ - bool is_constant; /* expr & optimizer used in subselect rewriting to store Item for @@ -435,7 +445,7 @@ public: Item_in_subselect(Item * left_expr, st_select_lex *select_lex); Item_in_subselect() :Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE), - is_constant(FALSE), optimizer(0), abort_on_null(0), + optimizer(0), abort_on_null(0), pushed_cond_guards(NULL), func(NULL), in_strategy(0), upper_item(0) {} diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index 25d3aa29904..a15418ad23d 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -3823,3 +3823,65 @@ bool JOIN::choose_subquery_plan(table_map join_tables) return FALSE; } + + +/** + Choose a query plan for a table-less subquery. + + @notes + + @retval FALSE success. + @retval TRUE error occurred. +*/ + +bool JOIN::choose_tableless_subquery_plan() +{ + DBUG_ASSERT(!tables_list || !tables); + if (select_lex->master_unit()->item) + { + DBUG_ASSERT(select_lex->master_unit()->item->type() == + Item::SUBSELECT_ITEM); + Item_subselect *subs_predicate= select_lex->master_unit()->item; + + /* + If the optimizer determined that his query has an empty result, + in most cases the subquery predicate is a known constant value - + either FALSE or NULL. The implementation of Item_subselect::reset() + determines which one. + */ + if (zero_result_cause) + { + if (!implicit_grouping) + { + /* + Both group by queries and non-group by queries without aggregate + functions produce empty subquery result. + */ + subs_predicate->reset(); + subs_predicate->make_const(); + return FALSE; + } + + /* TODO: + A further optimization is possible when a non-group query with + MIN/MAX/COUNT is optimized by opt_sum_query. Then, if there are + only MIN/MAX functions over an empty result set, the subquery + result is a NULL value/row, thus the value of subs_predicate is + NULL. + */ + } + + if (subs_predicate->is_in_predicate()) + { + Item_in_subselect *in_subs; + in_subs= (Item_in_subselect*) subs_predicate; + in_subs->in_strategy= SUBS_IN_TO_EXISTS; + if (in_subs->create_in_to_exists_cond(this) || + in_subs->inject_in_to_exists_cond(this)) + return TRUE; + tmp_having= having; + } + } + return FALSE; +} + diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4e5dc9074f2..6f9c02b77ec 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -829,6 +829,7 @@ JOIN::optimize() "Impossible HAVING" : "Impossible WHERE"; tables= 0; error= 0; + choose_tableless_subquery_plan(); goto setup_subq_exit; } } @@ -873,12 +874,13 @@ JOIN::optimize() */ if ((res=opt_sum_query(select_lex->leaf_tables, all_fields, conds))) { - if (res == HA_ERR_KEY_NOT_FOUND) + if (res == HA_ERR_KEY_NOT_FOUND || res < 0) { DBUG_PRINT("info",("No matching min/max row")); zero_result_cause= "No matching min/max row"; tables= 0; error=0; + choose_tableless_subquery_plan(); goto setup_subq_exit; } if (res > 1) @@ -887,14 +889,7 @@ JOIN::optimize() DBUG_PRINT("error",("Error from opt_sum_query")); DBUG_RETURN(1); } - if (res < 0) - { - DBUG_PRINT("info",("No matching min/max row")); - zero_result_cause= "No matching min/max row"; - tables= 0; - error=0; - goto setup_subq_exit; - } + DBUG_PRINT("info",("Select tables optimized away")); zero_result_cause= "Select tables optimized away"; tables_list= 0; // All tables resolved @@ -919,40 +914,14 @@ JOIN::optimize() QT_ORDINARY);); conds= table_independent_conds; } - goto setup_subq_exit; } } if (!tables_list) { DBUG_PRINT("info",("No tables")); error= 0; - if (optimize_unflattened_subqueries()) - DBUG_RETURN(1); - /* - TIMOUR: TODO: consider do we need to optimize here at all and refactor - this block and JOIN::choose_subquery_plan. - - if (choose_subquery_plan()) - DBUG_RETURN(1); - */ - Item_in_subselect *in_subs; - if (select_lex->master_unit()->item && - select_lex->master_unit()->item->is_in_predicate()) - { - in_subs= (Item_in_subselect*) select_lex->master_unit()->item; - if (in_subs->in_strategy & SUBS_MATERIALIZATION && - in_subs->setup_mat_engine()) - in_subs->in_strategy= SUBS_IN_TO_EXISTS; - if (in_subs->in_strategy & SUBS_IN_TO_EXISTS) - { - if (in_subs->create_in_to_exists_cond(this)) - DBUG_RETURN(1); - if (in_subs->inject_in_to_exists_cond(this)) - DBUG_RETURN(1); - tmp_having= having; - } - } - DBUG_RETURN(0); + choose_tableless_subquery_plan(); + goto setup_subq_exit; } error= -1; // Error is sent to client sort_by_table= get_sort_by_table(order, group_list, select_lex->leaf_tables); diff --git a/sql/sql_select.h b/sql/sql_select.h index 5f1abcce87f..0bb8c9e9285 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1383,6 +1383,8 @@ protected: void restore_query_plan(DYNAMIC_ARRAY *save_keyuse, POSITION *save_positions, KEYUSE **save_join_tab_keyuse, key_map *save_join_tab_checked_keys); + /* Choose a subquery plan for a table-less subquery. */ + bool choose_tableless_subquery_plan(); public: JOIN_TAB *join_tab,**best_ref; From 9f2bddbd80fae92840c2db07b75968e816adc213 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 2 Nov 2010 15:27:01 +0200 Subject: [PATCH 16/16] Fixed LP BUG#652727 and LP BUG#643424. The fixes for #643424 was part of the fix for #652727, that's why both fixes are pushed together. - The cause for #643424 was the improper use of get_partial_join_cost(), which assumed that the 'n_tables' parameter was the upper bound for query plan node indexes. Fixed by generalizing get_partial_join_cost() as a method that computes the cost of any partial join. - The cause of #652727 was that JOIN::choose_subquery_plan() incorrectly deleted the contents of the old keyuse array in the cases when an injected plan would not provide more key accesses, and reoptimization was not actually performed. --- mysql-test/r/subselect_mat_cost.result | 51 ++++++++++++++++++ mysql-test/t/subselect_mat_cost.test | 68 ++++++++++++++++++++++++ sql/opt_subselect.cc | 48 ++++++++--------- sql/sql_select.cc | 72 ++++++++++++-------------- sql/sql_select.h | 16 ++++-- 5 files changed, 188 insertions(+), 67 deletions(-) diff --git a/mysql-test/r/subselect_mat_cost.result b/mysql-test/r/subselect_mat_cost.result index 9d6986cf092..b5db804f7ff 100644 --- a/mysql-test/r/subselect_mat_cost.result +++ b/mysql-test/r/subselect_mat_cost.result @@ -3769,3 +3769,54 @@ drop procedure delete_materialization_data; drop procedure set_all_columns_not_null; drop procedure set_all_columns_nullable; drop table t1, t2, t1_1024, t2_1024; +# +# LP BUG#643424 valgrind warning in choose_subquery_plan() +# +CREATE TABLE t1 ( +pk int(11) NOT NULL AUTO_INCREMENT, +c1 int(11) DEFAULT NULL, +c2 int(11) DEFAULT NULL, +PRIMARY KEY (pk), +KEY c2 (c2)); +INSERT INTO t1 VALUES (1,NULL,2); +INSERT INTO t1 VALUES (2,7,9); +INSERT INTO t1 VALUES (9,NULL,8); +CREATE TABLE t2 ( +pk int(11) NOT NULL AUTO_INCREMENT, +c1 int(11) DEFAULT NULL, +c2 int(11) DEFAULT NULL, +PRIMARY KEY (pk), +KEY c2 (c2)); +INSERT INTO t2 VALUES (1,1,7); +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch='materialization=on,in_to_exists=on,semijoin=off'; +SELECT pk FROM t1 WHERE (c2, c1) IN (SELECT c2, c2 FROM t2); +pk +set session optimizer_switch=@save_optimizer_switch; +drop table t1, t2; +# +# LP BUG#652727 Crash in create_ref_for_key() +# +CREATE TABLE t2 ( +pk int(11) NOT NULL AUTO_INCREMENT, +c1 int(11) DEFAULT NULL, +PRIMARY KEY (pk)); +INSERT INTO t2 VALUES (10,7); +INSERT INTO t2 VALUES (11,1); +INSERT INTO t2 VALUES (17,NULL); +CREATE TABLE t1 ( +pk int(11) NOT NULL AUTO_INCREMENT, +c1 int(11) DEFAULT NULL, +PRIMARY KEY (pk)); +INSERT INTO t1 VALUES (15,1); +INSERT INTO t1 VALUES (19,NULL); +CREATE TABLE t3 (c2 int(11) DEFAULT NULL, KEY c2 (c2)); +INSERT INTO t3 VALUES (1); +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch='materialization=on,in_to_exists=on,semijoin=off'; +SELECT c2 +FROM t3 +WHERE (2, 6) IN (SELECT t1.c1, t1.c1 FROM t1 STRAIGHT_JOIN t2 ON t2.pk = t1.pk); +c2 +set session optimizer_switch=@save_optimizer_switch; +drop table t1, t2, t3; diff --git a/mysql-test/t/subselect_mat_cost.test b/mysql-test/t/subselect_mat_cost.test index a268322dc5f..b817c05f7d4 100644 --- a/mysql-test/t/subselect_mat_cost.test +++ b/mysql-test/t/subselect_mat_cost.test @@ -201,3 +201,71 @@ drop procedure delete_materialization_data; drop procedure set_all_columns_not_null; drop procedure set_all_columns_nullable; drop table t1, t2, t1_1024, t2_1024; + +--echo # +--echo # LP BUG#643424 valgrind warning in choose_subquery_plan() +--echo # + +CREATE TABLE t1 ( + pk int(11) NOT NULL AUTO_INCREMENT, + c1 int(11) DEFAULT NULL, + c2 int(11) DEFAULT NULL, + PRIMARY KEY (pk), + KEY c2 (c2)); + +INSERT INTO t1 VALUES (1,NULL,2); +INSERT INTO t1 VALUES (2,7,9); +INSERT INTO t1 VALUES (9,NULL,8); + +CREATE TABLE t2 ( + pk int(11) NOT NULL AUTO_INCREMENT, + c1 int(11) DEFAULT NULL, + c2 int(11) DEFAULT NULL, + PRIMARY KEY (pk), + KEY c2 (c2)); + +INSERT INTO t2 VALUES (1,1,7); + +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch='materialization=on,in_to_exists=on,semijoin=off'; + +SELECT pk FROM t1 WHERE (c2, c1) IN (SELECT c2, c2 FROM t2); + +set session optimizer_switch=@save_optimizer_switch; + +drop table t1, t2; + + +--echo # +--echo # LP BUG#652727 Crash in create_ref_for_key() +--echo # + +CREATE TABLE t2 ( + pk int(11) NOT NULL AUTO_INCREMENT, + c1 int(11) DEFAULT NULL, + PRIMARY KEY (pk)); + +INSERT INTO t2 VALUES (10,7); +INSERT INTO t2 VALUES (11,1); +INSERT INTO t2 VALUES (17,NULL); + +CREATE TABLE t1 ( + pk int(11) NOT NULL AUTO_INCREMENT, + c1 int(11) DEFAULT NULL, + PRIMARY KEY (pk)); + +INSERT INTO t1 VALUES (15,1); +INSERT INTO t1 VALUES (19,NULL); + +CREATE TABLE t3 (c2 int(11) DEFAULT NULL, KEY c2 (c2)); +INSERT INTO t3 VALUES (1); + +set @save_optimizer_switch=@@optimizer_switch; +set @@optimizer_switch='materialization=on,in_to_exists=on,semijoin=off'; + +SELECT c2 +FROM t3 +WHERE (2, 6) IN (SELECT t1.c1, t1.c1 FROM t1 STRAIGHT_JOIN t2 ON t2.pk = t1.pk); + +set session optimizer_switch=@save_optimizer_switch; +drop table t1, t2, t3; diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc index a15418ad23d..a9241e7f18c 100644 --- a/sql/opt_subselect.cc +++ b/sql/opt_subselect.cc @@ -1204,8 +1204,8 @@ bool optimize_semijoin_nests(JOIN *join, table_map all_table_map) sjm->tables= n_tables; sjm->is_used= FALSE; double subjoin_out_rows, subjoin_read_time; - get_partial_join_cost(join, n_tables, - &subjoin_read_time, &subjoin_out_rows); + join->get_partial_join_cost(n_tables + join->const_tables, + &subjoin_read_time, &subjoin_out_rows); sjm->materialization_cost.convert_from_cost(subjoin_read_time); sjm->rows= subjoin_out_rows; @@ -3615,16 +3615,14 @@ bool JOIN::optimize_unflattened_subqueries() */ bool JOIN::choose_subquery_plan(table_map join_tables) -{ - /* The original QEP of the subquery. */ +{ /* The original QEP of the subquery. */ DYNAMIC_ARRAY save_keyuse; /* Copy of the JOIN::keyuse array. */ POSITION save_best_positions[MAX_TABLES+1]; /* Copy of JOIN::best_positions */ /* Copies of the JOIN_TAB::keyuse pointers for each JOIN_TAB. */ KEYUSE *save_join_tab_keyuse[MAX_TABLES]; /* Copies of JOIN_TAB::checked_keys for each JOIN_TAB. */ key_map save_join_tab_checked_keys[MAX_TABLES]; - - bool in_exists_reoptimized= false; + enum_reopt_result reopt_result= REOPT_NONE; Item_in_subselect *in_subs; if (select_lex->master_unit()->item && @@ -3667,8 +3665,8 @@ bool JOIN::choose_subquery_plan(table_map join_tables) double in_exists_strategy_cost; if (outer_join) - get_partial_join_cost(outer_join, outer_join->tables, - &outer_read_time, &outer_record_count); + outer_join->get_partial_join_cost(outer_join->tables, + &outer_read_time, &outer_record_count); else { /* @@ -3679,8 +3677,8 @@ bool JOIN::choose_subquery_plan(table_map join_tables) outer_record_count= 1; /* TODO */ } - get_partial_join_cost(inner_join, inner_join->tables, - &inner_read_time_1, &inner_record_count_1); + inner_join->get_partial_join_cost(inner_join->tables, + &inner_read_time_1, &inner_record_count_1); if (in_to_exists_where && const_tables != tables) { @@ -3689,13 +3687,17 @@ bool JOIN::choose_subquery_plan(table_map join_tables) conditions. */ if (save_query_plan(&save_keyuse, save_best_positions, - save_join_tab_keyuse, save_join_tab_checked_keys) || - reoptimize(in_to_exists_where, join_tables, save_best_positions)) + save_join_tab_keyuse, save_join_tab_checked_keys)) + return TRUE; + reopt_result= reoptimize(in_to_exists_where, join_tables); + if (reopt_result == REOPT_OLD_PLAN) + restore_query_plan(&save_keyuse, save_best_positions, + save_join_tab_keyuse, save_join_tab_checked_keys); + else if (reopt_result == REOPT_ERROR) return TRUE; - in_exists_reoptimized= true; - get_partial_join_cost(inner_join, inner_join->tables, - &inner_read_time_2, &inner_record_count_2); + inner_join->get_partial_join_cost(inner_join->tables, + &inner_read_time_2, &inner_record_count_2); } else { @@ -3765,8 +3767,8 @@ bool JOIN::choose_subquery_plan(table_map join_tables) if (in_subs->in_strategy & SUBS_MATERIALIZATION) { - /* Restore orginal query plan used for materialization. */ - if (in_exists_reoptimized) + /* Restore the original query plan used for materialization. */ + if (reopt_result == REOPT_NEW_PLAN) restore_query_plan(&save_keyuse, save_best_positions, save_join_tab_keyuse, save_join_tab_checked_keys); @@ -3792,14 +3794,11 @@ bool JOIN::choose_subquery_plan(table_map join_tables) } else if (in_subs->in_strategy & SUBS_IN_TO_EXISTS) { - /* Keep the new query plan with injected conditions, delete the old one. */ - if (save_keyuse.elements) - { - DBUG_ASSERT(in_exists_reoptimized); + /* Keep the new query plan with injected conditions, delete the old plan. */ + if (reopt_result == REOPT_NEW_PLAN) delete_dynamic(&save_keyuse); - } - if (!in_exists_reoptimized && in_to_exists_where && const_tables != tables) + if (reopt_result == REOPT_NONE && in_to_exists_where && const_tables != tables) { /* The subquery was not reoptimized either because the user allowed only the @@ -3811,7 +3810,8 @@ bool JOIN::choose_subquery_plan(table_map join_tables) join_tab[i].keyuse= NULL; join_tab[i].checked_keys.clear_all(); } - if (reoptimize(in_to_exists_where, join_tables, NULL)) + if ((reopt_result= reoptimize(in_to_exists_where, join_tables)) == + REOPT_ERROR) return TRUE; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6f9c02b77ec..56d6309897c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -229,8 +229,6 @@ 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); -void get_partial_join_cost(JOIN *join, uint idx, double *read_time_arg, - double *record_count_arg); static uint make_join_orderinfo(JOIN *join); static int join_read_record_no_init(JOIN_TAB *tab); @@ -5422,40 +5420,43 @@ greedy_search(JOIN *join, } -/* - Calculate a cost of given partial join order +/** + Calculate a cost of given partial join order in join->positions. - SYNOPSIS - get_partial_join_cost() - join IN Join to use. join->positions holds the - partial join order - idx IN # tables in the partial join order - read_time_arg OUT Store read time here - record_count_arg OUT Store record count here + @param n_tables[in] # tables in the partial join order after the last + constant table + @param read_time_arg[out] store read time here + @param record_count_arg[out] store record count here - DESCRIPTION - - This is needed for semi-join materialization code. The idea is that - we detect sj-materialization after we've put all sj-inner tables into - the join prefix + @note + When used by semi-join materialization code the idea is that we + detect sj-materialization after we've put all sj-inner tables into + the join prefix. prefix-tables semi-join-inner-tables tN ^--we're here and we'll need to get the cost of prefix-tables prefix again. + + When used with non-flattened subqueries, the method computes the + total cost of query plan. + + @returns + read_time_arg and record_count_arg contain the computed cost. */ -void get_partial_join_cost(JOIN *join, uint n_tables, double *read_time_arg, - double *record_count_arg) +void JOIN::get_partial_join_cost(uint n_tables, + double *read_time_arg, double *record_count_arg) { double record_count= 1; double read_time= 0.0; - for (uint i= join->const_tables; i < n_tables + join->const_tables ; i++) + + for (uint i= const_tables; i < n_tables; i++) { - if (join->best_positions[i].records_read) + if (best_positions[i].records_read) { - record_count *= join->best_positions[i].records_read; - read_time += join->best_positions[i].read_time; + record_count *= best_positions[i].records_read; + read_time += best_positions[i].read_time; } } *read_time_arg= read_time;// + record_count / TIME_FOR_COMPARE; @@ -19331,12 +19332,12 @@ void JOIN::restore_query_plan(DYNAMIC_ARRAY *save_keyuse, 4. Re-sort and re-filter the JOIN::keyuse array with the newly added KEYUSE elements. - @retval 0 OK - @retval 1 memory allocation error + @retval REOPT_NEW_PLAN there is a new plan. + @retval REOPT_OLD_PLAN no new improved plan was produced, use the old one. + @retval REOPT_ERROR an irrecovarable error occured during reoptimization. */ -int JOIN::reoptimize(Item *added_where, table_map join_tables, - POSITION *save_best_positions) +JOIN::enum_reopt_result JOIN::reoptimize(Item *added_where, table_map join_tables) { DYNAMIC_ARRAY added_keyuse; SARGABLE_PARAM *sargables= 0; /* Used only as a dummy parameter. */ @@ -19346,25 +19347,18 @@ int JOIN::reoptimize(Item *added_where, table_map join_tables, ~outer_join, select_lex, &sargables)) { delete_dynamic(&added_keyuse); - return 1; + return REOPT_ERROR; } if (!added_keyuse.elements) - { - /* No need to optimize if no new access methods were discovered. */ - if (save_best_positions) - memcpy((uchar*) best_positions, (uchar*) save_best_positions, - sizeof(POSITION) * (tables + 1)); - delete_dynamic(&added_keyuse); - return 0; - } + return REOPT_OLD_PLAN; /* Add the new access methods to the keyuse array. */ if (!keyuse.buffer && my_init_dynamic_array(&keyuse, sizeof(KEYUSE), 20, 64)) { delete_dynamic(&added_keyuse); - return 1; + return REOPT_ERROR; } allocate_dynamic(&keyuse, keyuse.elements + added_keyuse.elements); memcpy(keyuse.buffer + keyuse.elements * keyuse.size_of_element, @@ -19374,14 +19368,14 @@ int JOIN::reoptimize(Item *added_where, table_map join_tables, delete_dynamic(&added_keyuse); if (sort_and_filter_keyuse(&keyuse)) - return 1; + return REOPT_ERROR; optimize_keyuse(this, &keyuse); /* Re-run the join optimizer to compute a new query plan. */ if (choose_plan(this, join_tables)) - return 1; + return REOPT_ERROR; - return 0; + return REOPT_NEW_PLAN; } /** diff --git a/sql/sql_select.h b/sql/sql_select.h index 0bb8c9e9285..14af63f5dd1 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1374,9 +1374,16 @@ private: JOIN& operator=(const JOIN &rhs); /**< not implemented */ protected: + /* Results of reoptimizing a JOIN via JOIN::reoptimize(). */ + enum enum_reopt_result { + REOPT_NEW_PLAN, /* there is a new reoptimized plan */ + REOPT_OLD_PLAN, /* no new improved plan can be found, use the old one */ + REOPT_ERROR, /* an irrecovarable error occured during reoptimization */ + REOPT_NONE /* not yet reoptimized */ + }; + /* Support for plan reoptimization with rewritten conditions. */ - int reoptimize(Item *added_where, table_map join_tables, - POSITION *save_best_positions); + enum_reopt_result reoptimize(Item *added_where, table_map join_tables); int save_query_plan(DYNAMIC_ARRAY *save_keyuse, POSITION *save_positions, KEYUSE **save_join_tab_keyuse, key_map *save_join_tab_checked_keys); @@ -1762,6 +1769,9 @@ public: } bool setup_subquery_caches(); bool choose_subquery_plan(table_map join_tables); + void get_partial_join_cost(uint n_tables, + double *read_time_arg, double *record_count_arg); + private: /** TRUE if the query contains an aggregate function but has no GROUP @@ -1993,8 +2003,6 @@ inline Item * and_items(Item* cond, Item *item) return (cond? (new Item_cond_and(cond, item)) : item); } bool choose_plan(JOIN *join,table_map join_tables); -void get_partial_join_cost(JOIN *join, uint n_tables, double *read_time_arg, - double *record_count_arg); void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab, table_map last_remaining_tables, bool first_alt, uint no_jbuf_before,