/* Copyright (C) 2019-20 MariaDB Corporation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "ha_mcs_opt_rewrites.h" // Search simplify_joins() function in the server's code for detail COND * simplify_joins_mcs(JOIN *join, List *join_list, COND *conds, bool top, bool in_sj) { TABLE_LIST *table; NESTED_JOIN *nested_join; TABLE_LIST *prev_table= 0; List_iterator li(*join_list); bool straight_join= MY_TEST(join->select_options & SELECT_STRAIGHT_JOIN); DBUG_ENTER("simplify_joins_mcs"); /* Try to simplify join operations from join_list. The most outer join operation is checked for conversion first. */ while ((table= li++)) { table_map used_tables; table_map not_null_tables= (table_map) 0; if ((nested_join= table->nested_join)) { /* If the element of join_list is a nested join apply the procedure to its nested join list first. */ if (table->on_expr) { Item *expr= table->on_expr; /* If an on expression E is attached to the table, check all null rejected predicates in this expression. If such a predicate over an attribute belonging to an inner table of an embedded outer join is found, the outer join is converted to an inner join and the corresponding on expression is added to E. */ expr= simplify_joins_mcs(join, &nested_join->join_list, expr, FALSE, in_sj || table->sj_on_expr); if (!table->prep_on_expr || expr != table->on_expr) { DBUG_ASSERT(expr); table->on_expr= expr; table->prep_on_expr= expr->copy_andor_structure(join->thd); } } nested_join->used_tables= (table_map) 0; nested_join->not_null_tables=(table_map) 0; conds= simplify_joins_mcs(join, &nested_join->join_list, conds, top, in_sj || table->sj_on_expr); used_tables= nested_join->used_tables; not_null_tables= nested_join->not_null_tables; /* The following two might become unequal after table elimination: */ nested_join->n_tables= nested_join->join_list.elements; } else { if (!table->prep_on_expr) table->prep_on_expr= table->on_expr; used_tables= table->get_map(); if (conds) not_null_tables= conds->not_null_tables(); } if (table->embedding) { table->embedding->nested_join->used_tables|= used_tables; table->embedding->nested_join->not_null_tables|= not_null_tables; } if (!(table->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) || (used_tables & not_null_tables)) { /* For some of the inner tables there are conjunctive predicates that reject nulls => the outer join can be replaced by an inner join. */ if (table->outer_join && !table->embedding && table->table) table->table->maybe_null= FALSE; table->outer_join= 0; if (!(straight_join || table->straight)) { table->dep_tables= 0; TABLE_LIST *embedding= table->embedding; while (embedding) { if (embedding->nested_join->join_list.head()->outer_join) { if (!embedding->sj_subq_pred) table->dep_tables= embedding->dep_tables; break; } embedding= embedding->embedding; } } if (table->on_expr) { /* Add ON expression to the WHERE or upper-level ON condition. */ if (conds) { conds= and_conds(join->thd, conds, table->on_expr); conds->top_level_item(); /* conds is always a new item as both cond and on_expr existed */ DBUG_ASSERT(!conds->is_fixed()); conds->fix_fields(join->thd, &conds); } else conds= table->on_expr; table->prep_on_expr= table->on_expr= 0; } } /* Only inner tables of non-convertible outer joins remain with on_expr. */ if (table->on_expr) { table_map table_on_expr_used_tables= table->on_expr->used_tables(); table->dep_tables|= table_on_expr_used_tables; if (table->embedding) { table->dep_tables&= ~table->embedding->nested_join->used_tables; /* Embedding table depends on tables used in embedded on expressions. */ table->embedding->on_expr_dep_tables|= table_on_expr_used_tables; } else table->dep_tables&= ~table->get_map(); } if (prev_table) { /* The order of tables is reverse: prev_table follows table */ if (prev_table->straight || straight_join) prev_table->dep_tables|= used_tables; if (prev_table->on_expr) { prev_table->dep_tables|= table->on_expr_dep_tables; table_map prev_used_tables= prev_table->nested_join ? prev_table->nested_join->used_tables : prev_table->get_map(); /* If on expression contains only references to inner tables we still make the inner tables dependent on the outer tables. It would be enough to set dependency only on one outer table for them. Yet this is really a rare case. Note: RAND_TABLE_BIT mask should not be counted as it prevents update of inner table dependences. For example it might happen if RAND() function is used in JOIN ON clause. */ if (!((prev_table->on_expr->used_tables() & ~(OUTER_REF_TABLE_BIT | RAND_TABLE_BIT)) & ~prev_used_tables)) prev_table->dep_tables|= used_tables; } } prev_table= table; } /* Flatten nested joins that can be flattened. no ON expression and not a semi-join => can be flattened. */ li.rewind(); while ((table= li++)) { nested_join= table->nested_join; if (table->sj_on_expr && !in_sj) { /* If this is a semi-join that is not contained within another semi-join leave it intact (otherwise it is flattened) */ /* Make sure that any semi-join appear in the join->select_lex->sj_nests list only once */ List_iterator_fast sj_it(join->select_lex->sj_nests); TABLE_LIST *sj_nest; while ((sj_nest= sj_it++)) { if (table == sj_nest) break; } if (sj_nest) continue; join->select_lex->sj_nests.push_back(table, join->thd->mem_root); /* Also, walk through semi-join children and mark those that are now top-level */ TABLE_LIST *tbl; List_iterator it(nested_join->join_list); while ((tbl= it++)) { if (!tbl->on_expr && tbl->table) tbl->table->maybe_null= FALSE; } } else if (nested_join && !table->on_expr) { TABLE_LIST *tbl; List_iterator it(nested_join->join_list); List repl_list; while ((tbl= it++)) { tbl->embedding= table->embedding; if (!tbl->embedding && !tbl->on_expr && tbl->table) tbl->table->maybe_null= FALSE; tbl->join_list= table->join_list; repl_list.push_back(tbl, join->thd->mem_root); tbl->dep_tables|= table->dep_tables; } li.replace(repl_list); } } DBUG_RETURN(conds); } /*@brief in_subselect_rewrite_walk - Rewrites Item_in_subselect*/ /************************************************************ * DESCRIPTION: * It traverses filter predicates searching for * Item_in_subselect and rewrites it adding equi-join predicate * to finalise IN_2_EXISTS rewrite. * PARAMETERS: * item_arg - Item to check. * arg - bool to early return if predicate injection fails. * RETURN: ***********************************************************/ void in_subselect_rewrite_walk(const Item* item_arg, void* arg) { bool* result= reinterpret_cast(arg); if (*result) return; Item* item= const_cast(item_arg); JOIN* join= nullptr; if (typeid(*item) == typeid(Item_in_subselect)) { Item_in_subselect* sub= reinterpret_cast(item); // MCS 1.4.3 doesn't support IN + subquery with UNION so // we safe to take this JOIN. join= sub->unit->first_select()->join; // Inject equi-JOIN predicates if needed. *result= sub->create_in_to_exists_cond(join); *result= (*result) ? *result : sub->inject_in_to_exists_cond(join); sub->unit->first_select()->prep_where= join->conds ? join->conds->copy_andor_structure(current_thd) : 0; } else if (typeid(*item) == typeid(Item_singlerow_subselect)) { Item_singlerow_subselect* sub= reinterpret_cast(item); // MCS 1.4.3 doesn't support IN + subquery with UNION so // we safe to take this JOIN. join= sub->unit->first_select()->join; } else { // Exit for any but dedicated Items. return; } // Walk recursively to process nested IN ops. if (join->conds) { join->conds->traverse_cond(in_subselect_rewrite_walk, arg, Item::POSTFIX); } } /* @brief opt_flag_unset_PS() - Unsets first_cond_optimization */ /************************************************************ * DESCRIPTION: * This function traverses derived tables to unset * SELECT_LEX::first_cond_optimization: a marker to control * optimizations executing PS. If set it allows to apply * optimizations. If unset, it disables optimizations. * PARAMETERS: * select_lex - SELECT_LEX* that describes the query. ***********************************************************/ void opt_flag_unset_PS(SELECT_LEX *select_lex) { TABLE_LIST *tbl; List_iterator_fast li(select_lex->leaf_tables); while ((tbl= li++)) { if (tbl->is_view_or_derived()) { SELECT_LEX_UNIT *unit= tbl->get_unit(); for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) opt_flag_unset_PS(sl); } } if (select_lex->first_cond_optimization) { select_lex->first_cond_optimization= false; } } /* @brief in_subselect_rewrite - Rewrites Item_in_subselect */ /************************************************************ * DESCRIPTION: * This function traverses TABLE_LISTs running in_subselect_rewrite_walk * PARAMETERS: * select_lex - SELECT_LEX* that describes the query. * RETURN: * bool to indicate predicate injection failures. ***********************************************************/ bool in_subselect_rewrite(SELECT_LEX *select_lex) { bool result = false; TABLE_LIST *tbl; List_iterator_fast li(select_lex->leaf_tables); while (!result && (tbl = li++)) { if (tbl->is_view_or_derived()) { SELECT_LEX_UNIT *unit= tbl->get_unit(); for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select()) result = in_subselect_rewrite(sl); } } if (select_lex->join && select_lex->join->conds) { select_lex->join->conds->traverse_cond(in_subselect_rewrite_walk, &result, Item::POSTFIX); } return result; } uint build_bitmap_for_nested_joins_mcs(List *join_list, uint first_unused) { List_iterator li(*join_list); TABLE_LIST *table; DBUG_ENTER("build_bitmap_for_nested_joins_mcs"); while ((table= li++)) { NESTED_JOIN *nested_join; if ((nested_join= table->nested_join)) { if (nested_join->n_tables != 1) { /* Don't assign bits to sj-nests */ if (table->on_expr) nested_join->nj_map= (nested_join_map) 1 << first_unused++; first_unused= build_bitmap_for_nested_joins_mcs(&nested_join->join_list, first_unused); } } } DBUG_RETURN(first_unused); }