summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore4
-rw-r--r--libmysqld/Makefile.am1
-rw-r--r--sql/Makefile.am4
-rw-r--r--sql/item_subselect.cc6
-rw-r--r--sql/opt_subselect.cc3455
-rw-r--r--sql/opt_subselect.h368
-rw-r--r--sql/sql_join_cache.cc1
-rw-r--r--sql/sql_lex.h2
-rw-r--r--sql/sql_select.cc3700
-rw-r--r--sql/sql_select.h131
10 files changed, 3911 insertions, 3761 deletions
diff --git a/.bzrignore b/.bzrignore
index d2a1ce94f70..9625cb43c69 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -1922,3 +1922,7 @@ libmysqld/examples/mysqltest.cc
extra/libevent/event-config.h
libmysqld/opt_table_elimination.cc
libmysqld/ha_federatedx.cc
+libmysqld/multi_range_read.cc
+libmysqld/opt_index_cond_pushdown.cc
+libmysqld/opt_subselect.cc
+libmysqld/sql_join_cache.cc
diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am
index d2f1113041c..e9a1438182c 100644
--- a/libmysqld/Makefile.am
+++ b/libmysqld/Makefile.am
@@ -58,6 +58,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
log_event.cc rpl_record.cc \
log_event_old.cc rpl_record_old.cc \
protocol.cc net_serv.cc opt_range.cc \
+ opt_subselect.cc \
opt_sum.cc procedure.cc records.cc sql_acl.cc \
sql_load.cc discover.cc sql_locale.cc \
sql_profile.cc \
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 06b5bcd9b50..2833fb487c9 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -60,6 +60,7 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
ha_ndbcluster_binlog.h ha_ndbcluster_tables.h \
ha_partition.h rpl_constants.h \
opt_range.h protocol.h rpl_tblmap.h rpl_utility.h \
+ opt_subselect.h \
rpl_reporting.h \
log.h log_slow.h sql_show.h rpl_rli.h rpl_mi.h \
sql_select.h structs.h table.h sql_udf.h hash_filo.h \
@@ -102,7 +103,8 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
unireg.cc des_key_file.cc \
log_event.cc rpl_record.cc \
log_event_old.cc rpl_record_old.cc \
- discover.cc time.cc opt_range.cc opt_sum.cc \
+ discover.cc time.cc opt_range.cc opt_subselect.cc \
+ opt_sum.cc \
records.cc filesort.cc handler.cc \
ha_partition.cc \
sql_db.cc sql_table.cc sql_rename.cc sql_crypt.cc \
diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc
index fd73590d3f5..1b2a4d66c06 100644
--- a/sql/item_subselect.cc
+++ b/sql/item_subselect.cc
@@ -31,10 +31,6 @@
#include "mysql_priv.h"
#include "sql_select.h"
-inline Item * and_items(Item* cond, Item *item)
-{
- return (cond? (new Item_cond_and(cond, item)) : item);
-}
Item_subselect::Item_subselect():
Item_result_field(), value_assigned(0), thd(0), substitution(0),
@@ -1899,7 +1895,7 @@ Item_in_subselect::select_in_like_transformer(JOIN *join, Comp_creator *func)
object, but we can't know it here, but here we need address correct
reference on left expresion.
- //psergey: he means confluent cases like "... IN (SELECT 1)"
+ //psergey: he means degenerate cases like "... IN (SELECT 1)"
*/
if (!optimizer)
{
diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc
new file mode 100644
index 00000000000..49b6d9caa31
--- /dev/null
+++ b/sql/opt_subselect.cc
@@ -0,0 +1,3455 @@
+/**
+ @file
+
+ @brief
+ Subquery optimization code here.
+
+*/
+
+#ifdef USE_PRAGMA_IMPLEMENTATION
+#pragma implementation // gcc: Class implementation
+#endif
+
+#include "mysql_priv.h"
+#include "sql_select.h"
+#include "opt_subselect.h"
+
+#include <my_bit.h>
+
+// Our own:
+static
+bool subquery_types_allow_materialization(Item_in_subselect *in_subs);
+static bool replace_where_subcondition(JOIN *join, Item **expr,
+ Item *old_cond, Item *new_cond,
+ bool do_fix_fields);
+static int subq_sj_candidate_cmp(Item_in_subselect* const *el1,
+ Item_in_subselect* const *el2);
+static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred);
+static TABLE_LIST *alloc_join_nest(THD *thd);
+static
+void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist);
+static uint get_tmp_table_rec_length(List<Item> &items);
+bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables);
+static SJ_MATERIALIZATION_INFO *
+at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
+ uint idx, bool *loose_scan);
+void best_access_path(JOIN *join, JOIN_TAB *s,
+ table_map remaining_tables, uint idx,
+ bool disable_jbuf, double record_count,
+ POSITION *pos, POSITION *loose_scan_pos);
+
+static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm,
+ Item_in_subselect *subq_pred);
+static void remove_sj_conds(Item **tree);
+static bool is_cond_sj_in_equality(Item *item);
+static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab);
+static Item *remove_additional_cond(Item* conds);
+static void remove_subq_pushed_predicates(JOIN *join, Item **where);
+
+
+/*
+ Check if we need JOIN::prepare()-phase subquery rewrites and if yes, do them
+
+ DESCRIPTION
+ Check if we need to do
+ - subquery->semi-join rewrite
+ - if the subquery can be handled with materialization
+ - 'substitution' rewrite for table-less subqueries like "(select 1)"
+
+ and mark appropriately
+
+ RETURN
+ 0 - OK
+ -1 - Some sort of query error
+*/
+
+int check_and_do_in_subquery_rewrites(JOIN *join)
+{
+ THD *thd=join->thd;
+ st_select_lex *select_lex= join->select_lex;
+ 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
+
+ Then perform early unconditional subquery transformations:
+ - Convert subquery predicate into semi-join, or
+ - Mark the subquery for execution using materialization, or
+ - Perform IN->EXISTS transformation, or
+ - Perform more/less ALL/ANY -> MIN/MAX rewrite
+ - Substitute trivial scalar-context subquery with its value
+
+ 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)
+ {
+ Item_in_subselect *in_subs= NULL;
+ if (subselect->substype() == Item_subselect::IN_SUBS)
+ in_subs= (Item_in_subselect*)subselect;
+
+ /* Resolve expressions and perform semantic analysis for IN query */
+ if (in_subs != NULL)
+ /*
+ TODO: Add the condition below to this if statement when we have proper
+ support for is_correlated handling for materialized semijoins.
+ If we were to add this condition now, the fix_fields() call in
+ convert_subq_to_sj() would force the flag is_correlated to be set
+ erroneously for prepared queries.
+
+ thd->stmt_arena->state != Query_arena::PREPARED)
+ */
+ {
+ /*
+ Check if the left and right expressions have the same # of
+ columns, i.e. we don't have a case like
+ (oe1, oe2) IN (SELECT ie1, ie2, ie3 ...)
+
+ TODO why do we have this duplicated in IN->EXISTS transformers?
+ psergey-todo: fix these: grep for duplicated_subselect_card_check
+ */
+ if (select_lex->item_list.elements != in_subs->left_expr->cols())
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), in_subs->left_expr->cols());
+ DBUG_RETURN(-1);
+ }
+
+ SELECT_LEX *current= thd->lex->current_select;
+ thd->lex->current_select= current->return_after_parsing();
+ char const *save_where= thd->where;
+ thd->where= " IN/ALL/ANY subquery";
+
+ bool failure= !in_subs->left_expr->fixed &&
+ in_subs->left_expr->fix_fields(thd, &in_subs->left_expr);
+ thd->lex->current_select= current;
+ thd->where= save_where;
+ if (failure)
+ DBUG_RETURN(-1); /* purecov: deadcode */
+ }
+ 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
+ semi-join (which is done in flatten_subqueries()). The
+ requirements are:
+ 1. Subquery predicate is an IN/=ANY subq predicate
+ 2. Subquery is a single SELECT (not a UNION)
+ 3. Subquery does not have GROUP BY or ORDER BY
+ 4. Subquery does not use aggregate functions or HAVING
+ 5. Subquery predicate is at the AND-top-level of ON/WHERE clause
+ 6. We are not in a subquery of a single table UPDATE/DELETE that
+ doesn't have a JOIN (TODO: We should handle this at some
+ point by switching to multi-table UPDATE/DELETE)
+ 7. We're not in a table-less subquery like "SELECT 1"
+ 8. No execution method was already chosen (by a prepared statement)
+ 9. Parent select is not a table-less select
+ 10. Neither parent nor child select have STRAIGHT_JOIN option.
+ */
+ if (optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
+ in_subs && // 1
+ !select_lex->is_part_of_union() && // 2
+ !select_lex->group_list.elements && !join->order && // 3
+ !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
+ in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED && // 8
+ select_lex->outer_select()->leaf_tables && // 9
+ !((join->select_options | // 10
+ select_lex->outer_select()->join->select_options) // 10
+ & SELECT_STRAIGHT_JOIN)) // 10
+ {
+ DBUG_PRINT("info", ("Subquery is semi-join conversion candidate"));
+
+ (void)subquery_types_allow_materialization(in_subs);
+
+ in_subs->emb_on_expr_nest= thd->thd_marker.emb_on_expr_nest;
+
+ /* Register the subquery for further processing in flatten_subqueries() */
+ select_lex->
+ outer_select()->join->sj_subselects.append(thd->mem_root, in_subs);
+ in_subs->expr_join_nest= thd->thd_marker.emb_on_expr_nest;
+ }
+ 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
+ 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. Subquery predicate is a top-level predicate
+ (this implies it is not negated)
+ TODO: this is a limitation that should be lifted once we
+ implement correct NULL semantics (WL#3830)
+ 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).
+
+ (*) The subquery must be part of a SELECT statement. The current
+ condition also excludes multi-table update statements.
+
+ We have to 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
+ select_lex->master_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?
+ if (in_subs->is_top_level_item() && // 4
+ !in_subs->is_correlated && // 5
+ in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6
+ in_subs->exec_method= Item_in_subselect::MATERIALIZATION;
+ }
+
+ 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);
+}
+
+
+/**
+ @brief Check if subquery's compared types allow materialization.
+
+ @param in_subs Subquery predicate, updated as follows:
+ types_allow_materialization TRUE if subquery materialization is allowed.
+ sjm_scan_allowed If types_allow_materialization is TRUE,
+ indicates whether it is possible to use subquery
+ materialization and scan the materialized table.
+
+ @retval TRUE If subquery types allow materialization.
+ @retval FALSE Otherwise.
+
+ @details
+ This is a temporary fix for BUG#36752.
+
+ There are two subquery materialization strategies:
+
+ 1. Materialize and do index lookups in the materialized table. See
+ BUG#36752 for description of restrictions we need to put on the
+ compared expressions.
+
+ 2. Materialize and then do a full scan of the materialized table. At the
+ moment, this strategy's applicability criteria are even stricter than
+ in #1.
+
+ This is so because of the following: consider an uncorrelated subquery
+
+ ...WHERE (ot1.col1, ot2.col2 ...) IN (SELECT ie1,ie2,... FROM it1 ...)
+
+ and a join order that could be used to do sjm-materialization:
+
+ SJM-Scan(it1, it1), ot1, ot2
+
+ IN-equalities will be parts of conditions attached to the outer tables:
+
+ ot1: ot1.col1 = ie1 AND ... (C1)
+ ot2: ot1.col2 = ie2 AND ... (C2)
+
+ besides those there may be additional references to ie1 and ie2
+ generated by equality propagation. The problem with evaluating C1 and
+ C2 is that ie{1,2} refer to subquery tables' columns, while we only have
+ current value of materialization temptable. Our solution is to
+ * require that all ie{N} are table column references. This allows
+ to copy the values of materialization temptable columns to the
+ original table's columns (see setup_sj_materialization for more
+ details)
+ * require that compared columns have exactly the same type. This is
+ a temporary measure to avoid BUG#36752-type problems.
+*/
+
+static
+bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
+{
+ DBUG_ENTER("subquery_types_allow_materialization");
+
+ DBUG_ASSERT(in_subs->left_expr->fixed);
+
+ List_iterator<Item> it(in_subs->unit->first_select()->item_list);
+ uint elements= in_subs->unit->first_select()->item_list.elements;
+
+ in_subs->types_allow_materialization= FALSE; // Assign default values
+ in_subs->sjm_scan_allowed= FALSE;
+
+ bool all_are_fields= TRUE;
+ for (uint i= 0; i < elements; i++)
+ {
+ Item *outer= in_subs->left_expr->element_index(i);
+ Item *inner= it++;
+ all_are_fields &= (outer->real_item()->type() == Item::FIELD_ITEM &&
+ inner->real_item()->type() == Item::FIELD_ITEM);
+ if (outer->result_type() != inner->result_type())
+ DBUG_RETURN(FALSE);
+ switch (outer->result_type()) {
+ case STRING_RESULT:
+ if (outer->is_datetime() != inner->is_datetime())
+ DBUG_RETURN(FALSE);
+
+ if (!(outer->collation.collation == inner->collation.collation
+ /*&& outer->max_length <= inner->max_length */))
+ DBUG_RETURN(FALSE);
+ /*case INT_RESULT:
+ if (!(outer->unsigned_flag ^ inner->unsigned_flag))
+ DBUG_RETURN(FALSE); */
+ default:
+ ;/* suitable for materialization */
+ }
+ }
+ in_subs->types_allow_materialization= TRUE;
+ in_subs->sjm_scan_allowed= all_are_fields;
+ DBUG_PRINT("info",("subquery_types_allow_materialization: ok, allowed"));
+ DBUG_RETURN(TRUE);
+}
+
+
+/*
+ Convert semi-join subquery predicates into semi-join join nests
+
+ SYNOPSIS
+ convert_join_subqueries_to_semijoins()
+
+ DESCRIPTION
+
+ Convert candidate subquery predicates into semi-join join nests. This
+ transformation is performed once in query lifetime and is irreversible.
+
+ Conversion of one subquery predicate
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ We start with a join that has a semi-join subquery:
+
+ SELECT ...
+ FROM ot, ...
+ WHERE oe IN (SELECT ie FROM it1 ... itN WHERE subq_where) AND outer_where
+
+ and convert it into a semi-join nest:
+
+ SELECT ...
+ FROM ot SEMI JOIN (it1 ... itN), ...
+ WHERE outer_where AND subq_where AND oe=ie
+
+ that is, in order to do the conversion, we need to
+
+ * Create the "SEMI JOIN (it1 .. itN)" part and add it into the parent
+ query's FROM structure.
+ * Add "AND subq_where AND oe=ie" into parent query's WHERE (or ON if
+ the subquery predicate was in an ON expression)
+ * Remove the subquery predicate from the parent query's WHERE
+
+ Considerations when converting many predicates
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ A join may have at most MAX_TABLES tables. This may prevent us from
+ flattening all subqueries when the total number of tables in parent and
+ child selects exceeds MAX_TABLES.
+ We deal with this problem by flattening children's subqueries first and
+ then using a heuristic rule to determine each subquery predicate's
+ "priority".
+
+ RETURN
+ FALSE OK
+ TRUE Error
+*/
+
+bool convert_join_subqueries_to_semijoins(JOIN *join)
+{
+ Query_arena *arena, backup;
+ Item_in_subselect **in_subq;
+ Item_in_subselect **in_subq_end;
+ THD *thd= join->thd;
+ DBUG_ENTER("convert_join_subqueries_to_semijoins");
+
+ if (join->sj_subselects.elements() == 0)
+ DBUG_RETURN(FALSE);
+
+ /* First, convert child join's subqueries. We proceed bottom-up here */
+ for (in_subq= join->sj_subselects.front(),
+ in_subq_end= join->sj_subselects.back();
+ in_subq != in_subq_end;
+ in_subq++)
+ {
+ st_select_lex *child_select= (*in_subq)->get_select_lex();
+ JOIN *child_join= child_select->join;
+ child_join->outer_tables = child_join->tables;
+
+ /*
+ child_select->where contains only the WHERE predicate of the
+ subquery itself here. We may be selecting from a VIEW, which has its
+ own predicate. The combined predicates are available in child_join->conds,
+ which was built by setup_conds() doing prepare_where() for all views.
+ */
+ child_select->where= child_join->conds;
+
+ if (convert_join_subqueries_to_semijoins(child_join))
+ DBUG_RETURN(TRUE);
+ (*in_subq)->sj_convert_priority=
+ (*in_subq)->is_correlated * MAX_TABLES + child_join->outer_tables;
+ }
+
+ // Temporary measure: disable semi-joins when they are together with outer
+ // joins.
+ for (TABLE_LIST *tbl= join->select_lex->leaf_tables; tbl; tbl=tbl->next_leaf)
+ {
+ TABLE_LIST *embedding= tbl->embedding;
+ if (tbl->on_expr || (tbl->embedding && !(embedding->sj_on_expr &&
+ !embedding->embedding)))
+ {
+ in_subq= join->sj_subselects.front();
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ goto skip_conversion;
+ }
+ }
+
+ //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
+ /*
+ 2. Pick which subqueries to convert:
+ sort the subquery array
+ - prefer correlated subqueries over uncorrelated;
+ - prefer subqueries that have greater number of outer tables;
+ */
+ join->sj_subselects.sort(subq_sj_candidate_cmp);
+ // #tables-in-parent-query + #tables-in-subquery < MAX_TABLES
+ /* Replace all subqueries to be flattened with Item_int(1) */
+ arena= thd->activate_stmt_arena_if_needed(&backup);
+ for (in_subq= join->sj_subselects.front();
+ in_subq != in_subq_end &&
+ join->tables + (*in_subq)->unit->first_select()->join->tables < MAX_TABLES;
+ in_subq++)
+ {
+ Item **tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
+ &join->conds : &((*in_subq)->emb_on_expr_nest->on_expr);
+ if (replace_where_subcondition(join, tree, *in_subq, new Item_int(1),
+ FALSE))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+
+ for (in_subq= join->sj_subselects.front();
+ in_subq != in_subq_end &&
+ join->tables + (*in_subq)->unit->first_select()->join->tables < MAX_TABLES;
+ in_subq++)
+ {
+ if (convert_subq_to_sj(join, *in_subq))
+ DBUG_RETURN(TRUE);
+ }
+skip_conversion:
+ /*
+ 3. Finalize (perform IN->EXISTS rewrite) the subqueries that we didn't
+ convert:
+ */
+ for (; in_subq!= in_subq_end; in_subq++)
+ {
+ JOIN *child_join= (*in_subq)->unit->first_select()->join;
+ Item_subselect::trans_res res;
+ (*in_subq)->changed= 0;
+ (*in_subq)->fixed= 0;
+
+ SELECT_LEX *save_select_lex= thd->lex->current_select;
+ thd->lex->current_select= (*in_subq)->unit->first_select();
+
+ res= (*in_subq)->select_transformer(child_join);
+
+ thd->lex->current_select= save_select_lex;
+
+ if (res == Item_subselect::RES_ERROR)
+ DBUG_RETURN(TRUE);
+
+ (*in_subq)->changed= 1;
+ (*in_subq)->fixed= 1;
+
+ Item *substitute= (*in_subq)->substitution;
+ bool do_fix_fields= !(*in_subq)->substitution->fixed;
+ Item **tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
+ &join->conds : &((*in_subq)->emb_on_expr_nest->on_expr);
+ if (replace_where_subcondition(join, tree, *in_subq, substitute,
+ do_fix_fields))
+ DBUG_RETURN(TRUE);
+ (*in_subq)->substitution= NULL;
+
+ if (!thd->stmt_arena->is_conventional())
+ {
+ tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
+ &join->select_lex->prep_where :
+ &((*in_subq)->emb_on_expr_nest->prep_on_expr);
+
+ if (replace_where_subcondition(join, tree, *in_subq, substitute,
+ FALSE))
+ DBUG_RETURN(TRUE);
+ }
+ }
+
+ if (arena)
+ thd->restore_active_arena(arena, &backup);
+ join->sj_subselects.clear();
+ DBUG_RETURN(FALSE);
+}
+
+/**
+ @brief Replaces an expression destructively inside the expression tree of
+ the WHERE clase.
+
+ @note Because of current requirements for semijoin flattening, we do not
+ need to recurse here, hence this function will only examine the top-level
+ AND conditions. (see JOIN::prepare, comment starting with "Check if the
+ subquery predicate can be executed via materialization".
+
+ @param join The top-level query.
+ @param old_cond The expression to be replaced.
+ @param new_cond The expression to be substituted.
+ @param do_fix_fields If true, Item::fix_fields(THD*, Item**) is called for
+ the new expression.
+ @return <code>true</code> if there was an error, <code>false</code> if
+ successful.
+*/
+static bool replace_where_subcondition(JOIN *join, Item **expr,
+ Item *old_cond, Item *new_cond,
+ bool do_fix_fields)
+{
+ //Item **expr= (emb_nest == (TABLE_LIST*)1)? &join->conds : &emb_nest->on_expr;
+ if (*expr == old_cond)
+ {
+ *expr= new_cond;
+ if (do_fix_fields)
+ new_cond->fix_fields(join->thd, expr);
+ join->select_lex->where= *expr;
+ return FALSE;
+ }
+
+ if ((*expr)->type() == Item::COND_ITEM)
+ {
+ List_iterator<Item> li(*((Item_cond*)(*expr))->argument_list());
+ Item *item;
+ while ((item= li++))
+ {
+ if (item == old_cond)
+ {
+ li.replace(new_cond);
+ if (do_fix_fields)
+ new_cond->fix_fields(join->thd, li.ref());
+ return FALSE;
+ }
+ }
+ }
+ // If we came here it means there were an error during prerequisites check.
+ DBUG_ASSERT(0);
+ return TRUE;
+}
+
+static int subq_sj_candidate_cmp(Item_in_subselect* const *el1,
+ Item_in_subselect* const *el2)
+{
+ return ((*el1)->sj_convert_priority < (*el2)->sj_convert_priority) ? 1 :
+ ( ((*el1)->sj_convert_priority == (*el2)->sj_convert_priority)? 0 : -1);
+}
+
+
+/*
+ Convert a subquery predicate into a TABLE_LIST semi-join nest
+
+ SYNOPSIS
+ convert_subq_to_sj()
+ parent_join Parent join, the one that has subq_pred in its WHERE/ON
+ clause
+ subq_pred Subquery predicate to be converted
+
+ DESCRIPTION
+ Convert a subquery predicate into a TABLE_LIST semi-join nest. All the
+ prerequisites are already checked, so the conversion is always successfull.
+
+ Prepared Statements: the transformation is permanent:
+ - Changes in TABLE_LIST structures are naturally permanent
+ - Item tree changes are performed on statement MEM_ROOT:
+ = we activate statement MEM_ROOT
+ = this function is called before the first fix_prepare_information
+ call.
+
+ This is intended because the criteria for subquery-to-sj conversion remain
+ constant for the lifetime of the Prepared Statement.
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory error
+*/
+
+static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
+{
+ SELECT_LEX *parent_lex= parent_join->select_lex;
+ TABLE_LIST *emb_tbl_nest= NULL;
+ List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list;
+ THD *thd= parent_join->thd;
+ DBUG_ENTER("convert_subq_to_sj");
+
+ /*
+ 1. Find out where to put the predicate into.
+ Note: for "t1 LEFT JOIN t2" this will be t2, a leaf.
+ */
+ if ((void*)subq_pred->expr_join_nest != (void*)1)
+ {
+ if (subq_pred->expr_join_nest->nested_join)
+ {
+ /*
+ We're dealing with
+
+ ... [LEFT] JOIN ( ... ) ON (subquery AND whatever) ...
+
+ The sj-nest will be inserted into the brackets nest.
+ */
+ emb_tbl_nest= subq_pred->expr_join_nest;
+ emb_join_list= &emb_tbl_nest->nested_join->join_list;
+ }
+ else if (!subq_pred->expr_join_nest->outer_join)
+ {
+ /*
+ We're dealing with
+
+ ... INNER JOIN tblX ON (subquery AND whatever) ...
+
+ The sj-nest will be tblX's "sibling", i.e. another child of its
+ parent. This is ok because tblX is joined as an inner join.
+ */
+ emb_tbl_nest= subq_pred->expr_join_nest->embedding;
+ if (emb_tbl_nest)
+ emb_join_list= &emb_tbl_nest->nested_join->join_list;
+ }
+ else if (!subq_pred->expr_join_nest->nested_join)
+ {
+ TABLE_LIST *outer_tbl= subq_pred->expr_join_nest;
+ TABLE_LIST *wrap_nest;
+ /*
+ We're dealing with
+
+ ... LEFT JOIN tbl ON (on_expr AND subq_pred) ...
+
+ we'll need to convert it into:
+
+ ... LEFT JOIN ( tbl SJ (subq_tables) ) ON (on_expr AND subq_pred) ...
+ | |
+ |<----- wrap_nest ---->|
+
+ Q: other subqueries may be pointing to this element. What to do?
+ A1: simple solution: copy *subq_pred->expr_join_nest= *parent_nest.
+ But we'll need to fix other pointers.
+ A2: Another way: have TABLE_LIST::next_ptr so the following
+ subqueries know the table has been nested.
+ A3: changes in the TABLE_LIST::outer_join will make everything work
+ automatically.
+ */
+ if (!(wrap_nest= alloc_join_nest(parent_join->thd)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ wrap_nest->embedding= outer_tbl->embedding;
+ wrap_nest->join_list= outer_tbl->join_list;
+ wrap_nest->alias= (char*) "(sj-wrap)";
+
+ wrap_nest->nested_join->join_list.empty();
+ wrap_nest->nested_join->join_list.push_back(outer_tbl);
+
+ outer_tbl->embedding= wrap_nest;
+ outer_tbl->join_list= &wrap_nest->nested_join->join_list;
+
+ /*
+ wrap_nest will take place of outer_tbl, so move the outer join flag
+ and on_expr
+ */
+ wrap_nest->outer_join= outer_tbl->outer_join;
+ outer_tbl->outer_join= 0;
+
+ wrap_nest->on_expr= outer_tbl->on_expr;
+ outer_tbl->on_expr= NULL;
+
+ List_iterator<TABLE_LIST> li(*wrap_nest->join_list);
+ TABLE_LIST *tbl;
+ while ((tbl= li++))
+ {
+ if (tbl == outer_tbl)
+ {
+ li.replace(wrap_nest);
+ break;
+ }
+ }
+ /*
+ Ok now wrap_nest 'contains' outer_tbl and we're ready to add the
+ semi-join nest into it
+ */
+ emb_join_list= &wrap_nest->nested_join->join_list;
+ emb_tbl_nest= wrap_nest;
+ }
+ }
+
+ TABLE_LIST *sj_nest;
+ NESTED_JOIN *nested_join;
+ if (!(sj_nest= alloc_join_nest(parent_join->thd)))
+ {
+ DBUG_RETURN(TRUE);
+ }
+ nested_join= sj_nest->nested_join;
+
+ sj_nest->join_list= emb_join_list;
+ sj_nest->embedding= emb_tbl_nest;
+ sj_nest->alias= (char*) "(sj-nest)";
+ sj_nest->sj_subq_pred= subq_pred;
+ /* Nests do not participate in those 'chains', so: */
+ /* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/
+ emb_join_list->push_back(sj_nest);
+
+ /*
+ nested_join->used_tables and nested_join->not_null_tables are
+ initialized in simplify_joins().
+ */
+
+ /*
+ 2. Walk through subquery's top list and set 'embedding' to point to the
+ sj-nest.
+ */
+ st_select_lex *subq_lex= subq_pred->unit->first_select();
+ nested_join->join_list.empty();
+ List_iterator_fast<TABLE_LIST> li(subq_lex->top_join_list);
+ TABLE_LIST *tl, *last_leaf;
+ while ((tl= li++))
+ {
+ tl->embedding= sj_nest;
+ tl->join_list= &nested_join->join_list;
+ nested_join->join_list.push_back(tl);
+ }
+
+ /*
+ Reconnect the next_leaf chain.
+ TODO: Do we have to put subquery's tables at the end of the chain?
+ Inserting them at the beginning would be a bit faster.
+ NOTE: We actually insert them at the front! That's because the order is
+ reversed in this list.
+ */
+ for (tl= parent_lex->leaf_tables; tl->next_leaf; tl= tl->next_leaf) ;
+ tl->next_leaf= subq_lex->leaf_tables;
+ last_leaf= tl;
+
+ /*
+ Same as above for next_local chain
+ (a theory: a next_local chain always starts with ::leaf_tables
+ because view's tables are inserted after the view)
+ */
+ for (tl= parent_lex->leaf_tables; tl->next_local; tl= tl->next_local) ;
+ tl->next_local= subq_lex->leaf_tables;
+
+ /* A theory: no need to re-connect the next_global chain */
+
+ /* 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
+ /*TODO: also reset the 'with_subselect' there. */
+
+ /* n. Adjust the parent_join->tables counter */
+ uint table_no= parent_join->tables;
+ /* n. Walk through child's tables and adjust table->map */
+ for (tl= subq_lex->leaf_tables; tl; tl= tl->next_leaf, table_no++)
+ {
+ tl->table->tablenr= table_no;
+ tl->table->map= ((table_map)1) << table_no;
+ SELECT_LEX *old_sl= tl->select_lex;
+ tl->select_lex= parent_join->select_lex;
+ for (TABLE_LIST *emb= tl->embedding;
+ emb && emb->select_lex == old_sl;
+ emb= emb->embedding)
+ emb->select_lex= parent_join->select_lex;
+ }
+ parent_join->tables += subq_lex->join->tables;
+
+ /*
+ Put the subquery's WHERE into semi-join's sj_on_expr
+ Add the subquery-induced equalities too.
+ */
+ SELECT_LEX *save_lex= thd->lex->current_select;
+ thd->lex->current_select=subq_lex;
+ if (!subq_pred->left_expr->fixed &&
+ subq_pred->left_expr->fix_fields(thd, &subq_pred->left_expr))
+ DBUG_RETURN(TRUE);
+ thd->lex->current_select=save_lex;
+
+ sj_nest->nested_join->sj_corr_tables= subq_pred->used_tables();
+ sj_nest->nested_join->sj_depends_on= subq_pred->used_tables() |
+ subq_pred->left_expr->used_tables();
+ sj_nest->sj_on_expr= subq_lex->where;
+
+ /*
+ Create the IN-equalities and inject them into semi-join's ON expression.
+ Additionally, for LooseScan strategy
+ - Record the number of IN-equalities.
+ - Create list of pointers to (oe1, ..., ieN). We'll need the list to
+ see which of the expressions are bound and which are not (for those
+ we'll produce a distinct stream of (ie_i1,...ie_ik).
+
+ (TODO: can we just create a list of pointers and hope the expressions
+ will not substitute themselves on fix_fields()? or we need to wrap
+ them into Item_direct_view_refs and store pointers to those. The
+ pointers to Item_direct_view_refs are guaranteed to be stable as
+ Item_direct_view_refs doesn't substitute itself with anything in
+ Item_direct_view_ref::fix_fields.
+ */
+ sj_nest->sj_in_exprs= subq_pred->left_expr->cols();
+ sj_nest->nested_join->sj_outer_expr_list.empty();
+
+ if (subq_pred->left_expr->cols() == 1)
+ {
+ nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr);
+ Item_func_eq *item_eq=
+ new Item_func_eq(subq_pred->left_expr, subq_lex->ref_pointer_array[0]);
+ item_eq->in_equality_no= 0;
+ sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+ }
+ else
+ {
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr->
+ element_index(i));
+ Item_func_eq *item_eq=
+ new Item_func_eq(subq_pred->left_expr->element_index(i),
+ subq_lex->ref_pointer_array[i]);
+ item_eq->in_equality_no= i;
+ sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
+ }
+ }
+ /* Fix the created equality and AND */
+ sj_nest->sj_on_expr->fix_fields(parent_join->thd, &sj_nest->sj_on_expr);
+
+ /*
+ Walk through sj nest's WHERE and ON expressions and call
+ item->fix_table_changes() for all items.
+ */
+ sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr);
+ fix_list_after_tbl_changes(parent_lex, &sj_nest->nested_join->join_list);
+
+
+ /* Unlink the child select_lex so it doesn't show up in EXPLAIN: */
+ subq_lex->master_unit()->exclude_level();
+
+ DBUG_EXECUTE("where",
+ print_where(sj_nest->sj_on_expr,"SJ-EXPR", QT_ORDINARY););
+
+ /* Inject sj_on_expr into the parent's WHERE or ON */
+ if (emb_tbl_nest)
+ {
+ emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr,
+ sj_nest->sj_on_expr);
+ emb_tbl_nest->on_expr->fix_fields(parent_join->thd, &emb_tbl_nest->on_expr);
+ }
+ else
+ {
+ /* Inject into the WHERE */
+ parent_join->conds= and_items(parent_join->conds, sj_nest->sj_on_expr);
+ parent_join->conds->fix_fields(parent_join->thd, &parent_join->conds);
+ parent_join->select_lex->where= parent_join->conds;
+ }
+
+ if (subq_lex->ftfunc_list->elements)
+ {
+ Item_func_match *ifm;
+ List_iterator_fast<Item_func_match> li(*(subq_lex->ftfunc_list));
+ while ((ifm= li++))
+ parent_lex->ftfunc_list->push_front(ifm);
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+static TABLE_LIST *alloc_join_nest(THD *thd)
+{
+ TABLE_LIST *tbl;
+ if (!(tbl= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
+ sizeof(NESTED_JOIN))))
+ return NULL;
+ tbl->nested_join= (NESTED_JOIN*) ((uchar*)tbl +
+ ALIGN_SIZE(sizeof(TABLE_LIST)));
+ return tbl;
+}
+
+
+static
+void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist)
+{
+ List_iterator<TABLE_LIST> it(*tlist);
+ TABLE_LIST *table;
+ while ((table= it++))
+ {
+ if (table->on_expr)
+ table->on_expr->fix_after_pullout(new_parent, &table->on_expr);
+ if (table->nested_join)
+ fix_list_after_tbl_changes(new_parent, &table->nested_join->join_list);
+ }
+}
+
+
+/*
+ Pull tables out of semi-join nests, if possible
+
+ SYNOPSIS
+ pull_out_semijoin_tables()
+ join The join where to do the semi-join flattening
+
+ DESCRIPTION
+ Try to pull tables out of semi-join nests.
+
+ PRECONDITIONS
+ When this function is called, the join may have several semi-join nests
+ but it is guaranteed that one semi-join nest does not contain another.
+
+ ACTION
+ A table can be pulled out of the semi-join nest if
+ - It is a constant table, or
+ - It is accessed via eq_ref(outer_tables)
+
+ POSTCONDITIONS
+ * Tables that were pulled out have JOIN_TAB::emb_sj_nest == NULL
+ * Tables that were not pulled out have JOIN_TAB::emb_sj_nest pointing
+ to semi-join nest they are in.
+ * Semi-join nests' TABLE_LIST::sj_inner_tables is updated accordingly
+
+ This operation is (and should be) performed at each PS execution since
+ tables may become/cease to be constant across PS reexecutions.
+
+ NOTE
+ Table pullout may make uncorrelated subquery correlated. Consider this
+ example:
+
+ ... WHERE oe IN (SELECT it1.primary_key WHERE p(it1, it2) ... )
+
+ here table it1 can be pulled out (we have it1.primary_key=oe which gives
+ us functional dependency). Once it1 is pulled out, all references to it1
+ from p(it1, it2) become references to outside of the subquery and thus
+ make the subquery (i.e. its semi-join nest) correlated.
+ Making the subquery (i.e. its semi-join nest) correlated prevents us from
+ using Materialization or LooseScan to execute it.
+
+ RETURN
+ 0 - OK
+ 1 - Out of memory error
+*/
+
+int pull_out_semijoin_tables(JOIN *join)
+{
+ TABLE_LIST *sj_nest;
+ DBUG_ENTER("pull_out_semijoin_tables");
+ List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
+
+ /* Try pulling out of the each of the semi-joins */
+ while ((sj_nest= sj_list_it++))
+ {
+ /* Action #1: Mark the constant tables to be pulled out */
+ table_map pulled_tables= 0;
+
+ List_iterator<TABLE_LIST> child_li(sj_nest->nested_join->join_list);
+ TABLE_LIST *tbl;
+ while ((tbl= child_li++))
+ {
+ if (tbl->table)
+ {
+ tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
+ if (tbl->table->map & join->const_table_map)
+ {
+ pulled_tables |= tbl->table->map;
+ DBUG_PRINT("info", ("Table %s pulled out (reason: constant)",
+ tbl->table->alias));
+ }
+ }
+ }
+
+ /*
+ Action #2: Find which tables we can pull out based on
+ update_ref_and_keys() data. Note that pulling one table out can allow
+ us to pull out some other tables too.
+ */
+ bool pulled_a_table;
+ do
+ {
+ pulled_a_table= FALSE;
+ child_li.rewind();
+ while ((tbl= child_li++))
+ {
+ if (tbl->table && !(pulled_tables & tbl->table->map))
+ {
+ if (find_eq_ref_candidate(tbl->table,
+ sj_nest->nested_join->used_tables &
+ ~pulled_tables))
+ {
+ pulled_a_table= TRUE;
+ pulled_tables |= tbl->table->map;
+ DBUG_PRINT("info", ("Table %s pulled out (reason: func dep)",
+ tbl->table->alias));
+ /*
+ Pulling a table out of uncorrelated subquery in general makes
+ makes it correlated. See the NOTE to this funtion.
+ */
+ sj_nest->sj_subq_pred->is_correlated= TRUE;
+ sj_nest->nested_join->sj_corr_tables|= tbl->table->map;
+ sj_nest->nested_join->sj_depends_on|= tbl->table->map;
+ }
+ }
+ }
+ } while (pulled_a_table);
+
+ child_li.rewind();
+ /*
+ Action #3: Move the pulled out TABLE_LIST elements to the parents.
+ */
+ table_map inner_tables= sj_nest->nested_join->used_tables &
+ ~pulled_tables;
+ /* Record the bitmap of inner tables */
+ sj_nest->sj_inner_tables= inner_tables;
+ if (pulled_tables)
+ {
+ List<TABLE_LIST> *upper_join_list= (sj_nest->embedding != NULL)?
+ (&sj_nest->embedding->nested_join->join_list):
+ (&join->select_lex->top_join_list);
+ Query_arena *arena, backup;
+ arena= join->thd->activate_stmt_arena_if_needed(&backup);
+ while ((tbl= child_li++))
+ {
+ if (tbl->table)
+ {
+ if (inner_tables & tbl->table->map)
+ {
+ /* This table is not pulled out */
+ tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
+ }
+ else
+ {
+ /* This table has been pulled out of the semi-join nest */
+ tbl->table->reginfo.join_tab->emb_sj_nest= NULL;
+ /*
+ Pull the table up in the same way as simplify_joins() does:
+ update join_list and embedding pointers but keep next[_local]
+ pointers.
+ */
+ child_li.remove();
+ upper_join_list->push_back(tbl);
+ tbl->join_list= upper_join_list;
+ tbl->embedding= sj_nest->embedding;
+ }
+ }
+ }
+
+ /* Remove the sj-nest itself if we've removed everything from it */
+ if (!inner_tables)
+ {
+ List_iterator<TABLE_LIST> li(*upper_join_list);
+ /* Find the sj_nest in the list. */
+ while (sj_nest != li++) ;
+ li.remove();
+ /* Also remove it from the list of SJ-nests: */
+ sj_list_it.remove();
+ }
+
+ if (arena)
+ join->thd->restore_active_arena(arena, &backup);
+ }
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Optimize semi-join nests that could be run with sj-materialization
+
+ SYNOPSIS
+ optimize_semijoin_nests()
+ join The join to optimize semi-join nests for
+ all_table_map Bitmap of all tables in the join
+
+ DESCRIPTION
+ Optimize each of the semi-join nests that can be run with
+ materialization. For each of the nests, we
+ - Generate the best join order for this "sub-join" and remember it;
+ - Remember the sub-join execution cost (it's part of materialization
+ cost);
+ - Calculate other costs that will be incurred if we decide
+ to use materialization strategy for this semi-join nest.
+
+ All obtained information is saved and will be used by the main join
+ optimization pass.
+
+ RETURN
+ FALSE Ok
+ TRUE Out of memory error
+*/
+
+bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
+{
+ DBUG_ENTER("optimize_semijoin_nests");
+ List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
+ TABLE_LIST *sj_nest;
+ /*
+ The statement may have been executed with 'semijoin=on' earlier.
+ We need to verify that 'semijoin=on' still holds.
+ */
+ if (optimizer_flag(join->thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_MATERIALIZATION))
+ {
+ while ((sj_nest= sj_list_it++))
+ {
+ /* semi-join nests with only constant tables are not valid */
+ DBUG_ASSERT(sj_nest->sj_inner_tables & ~join->const_table_map);
+
+ sj_nest->sj_mat_info= NULL;
+ if (sj_nest->sj_inner_tables && /* not everything was pulled out */
+ !sj_nest->sj_subq_pred->is_correlated &&
+ sj_nest->sj_subq_pred->types_allow_materialization)
+ {
+ join->emb_sjm_nest= sj_nest;
+ if (choose_plan(join, all_table_map))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ /*
+ The best plan to run the subquery is now in join->best_positions,
+ save it.
+ */
+ uint n_tables= my_count_bits(sj_nest->sj_inner_tables);
+ SJ_MATERIALIZATION_INFO* sjm;
+ if (!(sjm= new SJ_MATERIALIZATION_INFO) ||
+ !(sjm->positions= (POSITION*)join->thd->alloc(sizeof(POSITION)*
+ n_tables)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ 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);
+
+ sjm->materialization_cost.convert_from_cost(subjoin_read_time);
+ sjm->rows= subjoin_out_rows;
+
+ List<Item> &right_expr_list=
+ sj_nest->sj_subq_pred->unit->first_select()->item_list;
+ /*
+ Adjust output cardinality estimates. If the subquery has form
+
+ ... oe IN (SELECT t1.colX, t2.colY, func(X,Y,Z) )
+
+ then the number of distinct output record combinations has an
+ upper bound of product of number of records matching the tables
+ that are used by the SELECT clause.
+ TODO:
+ We can get a more precise estimate if we
+ - use rec_per_key cardinality estimates. For simple cases like
+ "oe IN (SELECT t.key ...)" it is trivial.
+ - Functional dependencies between the tables in the semi-join
+ nest (the payoff is probably less here?)
+ */
+ {
+ for (uint i=0 ; i < join->const_tables + sjm->tables ; i++)
+ {
+ JOIN_TAB *tab= join->best_positions[i].table;
+ join->map2table[tab->table->tablenr]= tab;
+ }
+ List_iterator<Item> it(right_expr_list);
+ Item *item;
+ table_map map= 0;
+ while ((item= it++))
+ map |= item->used_tables();
+ map= map & ~PSEUDO_TABLE_BITS;
+ Table_map_iterator tm_it(map);
+ int tableno;
+ double rows= 1.0;
+ while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
+ rows *= join->map2table[tableno]->table->quick_condition_rows;
+ sjm->rows= min(sjm->rows, rows);
+ }
+ memcpy(sjm->positions, join->best_positions + join->const_tables,
+ sizeof(POSITION) * n_tables);
+
+ /*
+ Calculate temporary table parameters and usage costs
+ */
+ uint rowlen= get_tmp_table_rec_length(right_expr_list);
+ double lookup_cost;
+ if (rowlen * subjoin_out_rows< join->thd->variables.max_heap_table_size)
+ lookup_cost= HEAP_TEMPTABLE_LOOKUP_COST;
+ else
+ lookup_cost= DISK_TEMPTABLE_LOOKUP_COST;
+
+ /*
+ Let materialization cost include the cost to write the data into the
+ temporary table:
+ */
+ sjm->materialization_cost.add_io(subjoin_out_rows, lookup_cost);
+
+ /*
+ Set the cost to do a full scan of the temptable (will need this to
+ consider doing sjm-scan):
+ */
+ sjm->scan_cost.zero();
+ sjm->scan_cost.add_io(sjm->rows, lookup_cost);
+
+ sjm->lookup_cost.convert_from_cost(lookup_cost);
+ sj_nest->sj_mat_info= sjm;
+ DBUG_EXECUTE("opt", print_sjm(sjm););
+ }
+ }
+ }
+ join->emb_sjm_nest= NULL;
+ DBUG_RETURN(FALSE);
+}
+
+/*
+ Get estimated record length for semi-join materialization temptable
+
+ SYNOPSIS
+ get_tmp_table_rec_length()
+ items IN subquery's select list.
+
+ DESCRIPTION
+ Calculate estimated record length for semi-join materialization
+ temptable. It's an estimate because we don't follow every bit of
+ create_tmp_table()'s logic. This isn't necessary as the return value of
+ this function is used only for cost calculations.
+
+ RETURN
+ Length of the temptable record, in bytes
+*/
+
+static uint get_tmp_table_rec_length(List<Item> &items)
+{
+ uint len= 0;
+ Item *item;
+ List_iterator<Item> it(items);
+ while ((item= it++))
+ {
+ switch (item->result_type()) {
+ case REAL_RESULT:
+ len += sizeof(double);
+ break;
+ case INT_RESULT:
+ if (item->max_length >= (MY_INT32_NUM_DECIMAL_DIGITS - 1))
+ len += 8;
+ else
+ len += 4;
+ break;
+ case STRING_RESULT:
+ enum enum_field_types type;
+ /* DATE/TIME and GEOMETRY fields have STRING_RESULT result type. */
+ if ((type= item->field_type()) == MYSQL_TYPE_DATETIME ||
+ type == MYSQL_TYPE_TIME || type == MYSQL_TYPE_DATE ||
+ type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_GEOMETRY)
+ len += 8;
+ else
+ len += item->max_length;
+ break;
+ case DECIMAL_RESULT:
+ len += 10;
+ break;
+ case ROW_RESULT:
+ default:
+ DBUG_ASSERT(0); /* purecov: deadcode */
+ break;
+ }
+ }
+ return len;
+}
+
+//psergey-todo: is the below a kind of table elimination??
+/*
+ Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+ SYNOPSIS
+ find_eq_ref_candidate()
+ table Table to be checked
+ sj_inner_tables Bitmap of inner tables. eq_ref(inner_table) doesn't
+ count.
+
+ DESCRIPTION
+ Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
+
+ TODO
+ Check again if it is feasible to factor common parts with constant table
+ search
+
+ RETURN
+ TRUE - There exists an eq_ref(outer-tables) candidate
+ FALSE - Otherwise
+*/
+
+bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables)
+{
+ KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
+ uint key;
+
+ if (keyuse)
+ {
+ while (1) /* For each key */
+ {
+ key= keyuse->key;
+ KEY *keyinfo= table->key_info + key;
+ key_part_map bound_parts= 0;
+ if ((keyinfo->flags & (HA_NOSAME | HA_END_SPACE_KEY)) == HA_NOSAME)
+ {
+ do /* For all equalities on all key parts */
+ {
+ /* Check if this is "t.keypart = expr(outer_tables) */
+ if (!(keyuse->used_tables & sj_inner_tables) &&
+ !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
+ {
+ bound_parts |= 1 << keyuse->keypart;
+ }
+ keyuse++;
+ } while (keyuse->key == key && keyuse->table == table);
+
+ if (bound_parts == PREV_BITS(uint, keyinfo->key_parts))
+ return TRUE;
+ if (keyuse->table != table)
+ return FALSE;
+ }
+ else
+ {
+ do
+ {
+ keyuse++;
+ if (keyuse->table != table)
+ return FALSE;
+ }
+ while (keyuse->key == key);
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ Do semi-join optimization step after we've added a new tab to join prefix
+
+ SYNOPSIS
+ advance_sj_state()
+ join The join we're optimizing
+ remaining_tables Tables not in the join prefix
+ new_join_tab Join tab we've just added to the join prefix
+ idx Index of this join tab (i.e. number of tables
+ in the prefix minus one)
+ current_record_count INOUT Estimate of #records in join prefix's output
+ current_read_time INOUT Cost to execute the join prefix
+ loose_scan_pos IN A POSITION with LooseScan plan to access
+ table new_join_tab
+ (produced by the last best_access_path call)
+
+ DESCRIPTION
+ Update semi-join optimization state after we've added another tab (table
+ and access method) to the join prefix.
+
+ The state is maintained in join->positions[#prefix_size]. Each of the
+ available strategies has its own state variables.
+
+ for each semi-join strategy
+ {
+ update strategy's state variables;
+
+ if (join prefix has all the tables that are needed to consider
+ using this strategy for the semi-join(s))
+ {
+ calculate cost of using the strategy
+ if ((this is the first strategy to handle the semi-join nest(s) ||
+ the cost is less than other strategies))
+ {
+ // Pick this strategy
+ pos->sj_strategy= ..
+ ..
+ }
+ }
+
+ Most of the new state is saved join->positions[idx] (and hence no undo
+ is necessary). Several members of class JOIN are updated also, these
+ changes can be rolled back with restore_prev_sj_state().
+
+ See setup_semijoin_dups_elimination() for a description of what kinds of
+ join prefixes each strategy can handle.
+*/
+
+void advance_sj_state(JOIN *join, table_map remaining_tables,
+ const JOIN_TAB *new_join_tab, uint idx,
+ double *current_record_count, double *current_read_time,
+ POSITION *loose_scan_pos)
+{
+ TABLE_LIST *emb_sj_nest;
+ POSITION *pos= join->positions + idx;
+ remaining_tables &= ~new_join_tab->table->map;
+
+ pos->prefix_cost.convert_from_cost(*current_read_time);
+ pos->prefix_record_count= *current_record_count;
+ pos->sj_strategy= SJ_OPT_NONE;
+
+ /* Initialize the state or copy it from prev. tables */
+ if (idx == join->const_tables)
+ {
+ pos->first_firstmatch_table= MAX_TABLES;
+ pos->first_loosescan_table= MAX_TABLES;
+ pos->dupsweedout_tables= 0;
+ pos->sjm_scan_need_tables= 0;
+ LINT_INIT(pos->sjm_scan_last_inner);
+ }
+ else
+ {
+ // FirstMatch
+ pos->first_firstmatch_table=
+ (pos[-1].sj_strategy == SJ_OPT_FIRST_MATCH) ?
+ MAX_TABLES : pos[-1].first_firstmatch_table;
+ pos->first_firstmatch_rtbl= pos[-1].first_firstmatch_rtbl;
+ pos->firstmatch_need_tables= pos[-1].firstmatch_need_tables;
+
+ // LooseScan
+ pos->first_loosescan_table=
+ (pos[-1].sj_strategy == SJ_OPT_LOOSE_SCAN) ?
+ MAX_TABLES : pos[-1].first_loosescan_table;
+ pos->loosescan_need_tables= pos[-1].loosescan_need_tables;
+
+ // SJ-Materialization Scan
+ pos->sjm_scan_need_tables=
+ (pos[-1].sj_strategy == SJ_OPT_MATERIALIZE_SCAN) ?
+ 0 : pos[-1].sjm_scan_need_tables;
+ pos->sjm_scan_last_inner= pos[-1].sjm_scan_last_inner;
+
+ // Duplicate Weedout
+ pos->dupsweedout_tables= pos[-1].dupsweedout_tables;
+ pos->first_dupsweedout_table= pos[-1].first_dupsweedout_table;
+ }
+
+ table_map handled_by_fm_or_ls= 0;
+ /* FirstMatch Strategy */
+ if (new_join_tab->emb_sj_nest &&
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_FIRSTMATCH))
+ {
+ const table_map outer_corr_tables=
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on;
+ const table_map sj_inner_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables;
+
+ /*
+ Enter condition:
+ 1. The next join tab belongs to semi-join nest
+ (verified for the encompassing code block above).
+ 2. We're not in a duplicate producer range yet
+ 3. All outer tables that
+ - the subquery is correlated with, or
+ - referred to from the outer_expr
+ are in the join prefix
+ 4. All inner tables are still part of remaining_tables.
+ */
+ if (!join->cur_sj_inner_tables && // (2)
+ !(remaining_tables & outer_corr_tables) && // (3)
+ (sj_inner_tables == // (4)
+ ((remaining_tables | new_join_tab->table->map) & sj_inner_tables)))
+ {
+ /* Start tracking potential FirstMatch range */
+ pos->first_firstmatch_table= idx;
+ pos->firstmatch_need_tables= sj_inner_tables;
+ pos->first_firstmatch_rtbl= remaining_tables;
+ }
+
+ if (pos->first_firstmatch_table != MAX_TABLES)
+ {
+ if (outer_corr_tables & pos->first_firstmatch_rtbl)
+ {
+ /*
+ Trying to add an sj-inner table whose sj-nest has an outer correlated
+ table that was not in the prefix. This means FirstMatch can't be used.
+ */
+ pos->first_firstmatch_table= MAX_TABLES;
+ }
+ else
+ {
+ /* Record that we need all of this semi-join's inner tables, too */
+ pos->firstmatch_need_tables|= sj_inner_tables;
+ }
+
+ if (!(pos->firstmatch_need_tables & remaining_tables))
+ {
+ /*
+ Got a complete FirstMatch range.
+ Calculate correct costs and fanout
+ */
+ double reopt_cost, reopt_rec_count, sj_inner_fanout;
+ optimize_wo_join_buffering(join, pos->first_firstmatch_table, idx,
+ remaining_tables, FALSE, idx,
+ &reopt_rec_count, &reopt_cost,
+ &sj_inner_fanout);
+ /*
+ We don't yet know what are the other strategies, so pick the
+ FirstMatch.
+
+ We ought to save the alternate POSITIONs produced by
+ optimize_wo_join_buffering but the problem is that providing save
+ space uses too much space. Instead, we will re-calculate the
+ alternate POSITIONs after we've picked the best QEP.
+ */
+ pos->sj_strategy= SJ_OPT_FIRST_MATCH;
+ *current_read_time= reopt_cost;
+ *current_record_count= reopt_rec_count / sj_inner_fanout;
+ handled_by_fm_or_ls= pos->firstmatch_need_tables;
+ }
+ }
+ }
+
+ /* LooseScan Strategy */
+ {
+ POSITION *first=join->positions+pos->first_loosescan_table;
+ /*
+ LooseScan strategy can't handle interleaving between tables from the
+ semi-join that LooseScan is handling and any other tables.
+
+ If we were considering LooseScan for the join prefix (1)
+ and the table we're adding creates an interleaving (2)
+ then
+ stop considering loose scan
+ */
+ if ((pos->first_loosescan_table != MAX_TABLES) && // (1)
+ (first->table->emb_sj_nest->sj_inner_tables & remaining_tables) && //(2)
+ new_join_tab->emb_sj_nest != first->table->emb_sj_nest) //(2)
+ {
+ pos->first_loosescan_table= MAX_TABLES;
+ }
+
+ /*
+ If we got an option to use LooseScan for the current table, start
+ considering using LooseScan strategy
+ */
+ if (loose_scan_pos->read_time != DBL_MAX)
+ {
+ pos->first_loosescan_table= idx;
+ pos->loosescan_need_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
+ }
+
+ if ((pos->first_loosescan_table != MAX_TABLES) &&
+ !(remaining_tables & pos->loosescan_need_tables))
+ {
+ /*
+ Ok we have LooseScan plan and also have all LooseScan sj-nest's
+ inner tables and outer correlated tables into the prefix.
+ */
+
+ first=join->positions + pos->first_loosescan_table;
+ uint n_tables= my_count_bits(first->table->emb_sj_nest->sj_inner_tables);
+ /* Got a complete LooseScan range. Calculate its cost */
+ double reopt_cost, reopt_rec_count, sj_inner_fanout;
+ /*
+ The same problem as with FirstMatch - we need to save POSITIONs
+ somewhere but reserving space for all cases would require too
+ much space. We will re-calculate POSITION structures later on.
+ */
+ optimize_wo_join_buffering(join, pos->first_loosescan_table, idx,
+ remaining_tables,
+ TRUE, //first_alt
+ pos->first_loosescan_table + n_tables,
+ &reopt_rec_count,
+ &reopt_cost, &sj_inner_fanout);
+ /*
+ We don't yet have any other strategies that could handle this
+ semi-join nest (the other options are Duplicate Elimination or
+ Materialization, which need at least the same set of tables in
+ the join prefix to be considered) so unconditionally pick the
+ LooseScan.
+ */
+ pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
+ *current_read_time= reopt_cost;
+ *current_record_count= reopt_rec_count / sj_inner_fanout;
+ handled_by_fm_or_ls= first->table->emb_sj_nest->sj_inner_tables;
+ }
+ }
+
+ /*
+ Update join->cur_sj_inner_tables (Used by FirstMatch in this function and
+ LooseScan detector in best_access_path)
+ */
+ if ((emb_sj_nest= new_join_tab->emb_sj_nest))
+ {
+ join->cur_sj_inner_tables |= emb_sj_nest->sj_inner_tables;
+ join->cur_dups_producing_tables |= emb_sj_nest->sj_inner_tables;
+
+ /* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
+ if (!(remaining_tables &
+ emb_sj_nest->sj_inner_tables & ~new_join_tab->table->map))
+ join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
+ }
+ join->cur_dups_producing_tables &= ~handled_by_fm_or_ls;
+
+ /* 4. SJ-Materialization and SJ-Materialization-scan strategy handler */
+ bool sjm_scan;
+ SJ_MATERIALIZATION_INFO *mat_info;
+ if ((mat_info= at_sjmat_pos(join, remaining_tables,
+ new_join_tab, idx, &sjm_scan)))
+ {
+ if (sjm_scan)
+ {
+ /*
+ We can't yet evaluate this option yet. This is because we can't
+ accout for fanout of sj-inner tables yet:
+
+ ntX SJM-SCAN(it1 ... itN) | ot1 ... otN |
+ ^(1) ^(2)
+
+ we're now at position (1). SJM temptable in general has multiple
+ records, so at point (1) we'll get the fanout from sj-inner tables (ie
+ there will be multiple record combinations).
+
+ The final join result will not contain any semi-join produced
+ fanout, i.e. tables within SJM-SCAN(...) will not contribute to
+ the cardinality of the join output. Extra fanout produced by
+ SJM-SCAN(...) will be 'absorbed' into fanout produced by ot1 ... otN.
+
+ The simple way to model this is to remove SJM-SCAN(...) fanout once
+ we reach the point #2.
+ */
+ pos->sjm_scan_need_tables=
+ new_join_tab->emb_sj_nest->sj_inner_tables |
+ new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
+ new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
+ pos->sjm_scan_last_inner= idx;
+ }
+ else
+ {
+ /* This is SJ-Materialization with lookups */
+ COST_VECT prefix_cost;
+ signed int first_tab= (int)idx - mat_info->tables;
+ double prefix_rec_count;
+ if (first_tab < (int)join->const_tables)
+ {
+ prefix_cost.zero();
+ prefix_rec_count= 1.0;
+ }
+ else
+ {
+ prefix_cost= join->positions[first_tab].prefix_cost;
+ prefix_rec_count= join->positions[first_tab].prefix_record_count;
+ }
+
+ double mat_read_time= prefix_cost.total_cost();
+ mat_read_time += mat_info->materialization_cost.total_cost() +
+ prefix_rec_count * mat_info->lookup_cost.total_cost();
+
+ if (mat_read_time < *current_read_time || join->cur_dups_producing_tables)
+ {
+ /*
+ NOTE: When we pick to use SJM[-Scan] we don't memcpy its POSITION
+ elements to join->positions as that makes it hard to return things
+ back when making one step back in join optimization. That's done
+ after the QEP has been chosen.
+ */
+ pos->sj_strategy= SJ_OPT_MATERIALIZE;
+ *current_read_time= mat_read_time;
+ *current_record_count= prefix_rec_count;
+ join->cur_dups_producing_tables&=
+ ~new_join_tab->emb_sj_nest->sj_inner_tables;
+ }
+ }
+ }
+
+ /* 4.A SJM-Scan second phase check */
+ if (pos->sjm_scan_need_tables && /* Have SJM-Scan prefix */
+ !(pos->sjm_scan_need_tables & remaining_tables))
+ {
+ TABLE_LIST *mat_nest=
+ join->positions[pos->sjm_scan_last_inner].table->emb_sj_nest;
+ SJ_MATERIALIZATION_INFO *mat_info= mat_nest->sj_mat_info;
+
+ double prefix_cost;
+ double prefix_rec_count;
+ int first_tab= pos->sjm_scan_last_inner + 1 - mat_info->tables;
+ /* Get the prefix cost */
+ if (first_tab == (int)join->const_tables)
+ {
+ prefix_rec_count= 1.0;
+ prefix_cost= 0.0;
+ }
+ else
+ {
+ prefix_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+ prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
+ }
+
+ /* Add materialization cost */
+ prefix_cost += mat_info->materialization_cost.total_cost() +
+ prefix_rec_count * mat_info->scan_cost.total_cost();
+ prefix_rec_count *= mat_info->rows;
+
+ uint i;
+ table_map rem_tables= remaining_tables;
+ for (i= idx; i != (first_tab + mat_info->tables - 1); i--)
+ rem_tables |= join->positions[i].table->table->map;
+
+ POSITION curpos, dummy;
+ /* Need to re-run best-access-path as we prefix_rec_count has changed */
+ for (i= first_tab + mat_info->tables; i <= idx; i++)
+ {
+ best_access_path(join, join->positions[i].table, rem_tables, i, FALSE,
+ prefix_rec_count, &curpos, &dummy);
+ prefix_rec_count *= curpos.records_read;
+ prefix_cost += curpos.read_time;
+ }
+
+ /*
+ Use the strategy if
+ * it is cheaper then what we've had, or
+ * we haven't picked any other semi-join strategy yet
+ In the second case, we pick this strategy unconditionally because
+ comparing cost without semi-join duplicate removal with cost with
+ duplicate removal is not an apples-to-apples comparison.
+ */
+ if (prefix_cost < *current_read_time || join->cur_dups_producing_tables)
+ {
+ pos->sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
+ *current_read_time= prefix_cost;
+ *current_record_count= prefix_rec_count;
+ join->cur_dups_producing_tables&= ~mat_nest->sj_inner_tables;
+
+ }
+ }
+
+ /* 5. Duplicate Weedout strategy handler */
+ {
+ /*
+ Duplicate weedout can be applied after all ON-correlated and
+ correlated
+ */
+ TABLE_LIST *nest;
+ if ((nest= new_join_tab->emb_sj_nest))
+ {
+ if (!pos->dupsweedout_tables)
+ pos->first_dupsweedout_table= idx;
+
+ pos->dupsweedout_tables |= nest->sj_inner_tables |
+ nest->nested_join->sj_depends_on |
+ nest->nested_join->sj_corr_tables;
+ }
+
+ if (pos->dupsweedout_tables &&
+ !(remaining_tables &
+ ~new_join_tab->table->map & pos->dupsweedout_tables))
+ {
+ /*
+ Ok, reached a state where we could put a dups weedout point.
+ Walk back and calculate
+ - the join cost (this is needed as the accumulated cost may assume
+ some other duplicate elimination method)
+ - extra fanout that will be removed by duplicate elimination
+ - duplicate elimination cost
+ There are two cases:
+ 1. We have other strategy/ies to remove all of the duplicates.
+ 2. We don't.
+
+ We need to calculate the cost in case #2 also because we need to make
+ choice between this join order and others.
+ */
+ uint first_tab= pos->first_dupsweedout_table;
+ double dups_cost;
+ double prefix_rec_count;
+ double sj_inner_fanout= 1.0;
+ double sj_outer_fanout= 1.0;
+ uint temptable_rec_size;
+ if (first_tab == join->const_tables)
+ {
+ prefix_rec_count= 1.0;
+ temptable_rec_size= 0;
+ dups_cost= 0.0;
+ }
+ else
+ {
+ dups_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+ prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
+ temptable_rec_size= 8; /* This is not true but we'll make it so */
+ }
+
+ table_map dups_removed_fanout= 0;
+ for (uint j= pos->first_dupsweedout_table; j <= idx; j++)
+ {
+ POSITION *p= join->positions + j;
+ dups_cost += p->read_time;
+ if (p->table->emb_sj_nest)
+ {
+ sj_inner_fanout *= p->records_read;
+ dups_removed_fanout |= p->table->table->map;
+ }
+ else
+ {
+ sj_outer_fanout *= p->records_read;
+ temptable_rec_size += p->table->table->file->ref_length;
+ }
+ }
+
+ /*
+ Add the cost of temptable use. The table will have sj_outer_fanout
+ records, and we will make
+ - sj_outer_fanout table writes
+ - sj_inner_fanout*sj_outer_fanout lookups.
+
+ */
+ double one_lookup_cost;
+ if (sj_outer_fanout*temptable_rec_size >
+ join->thd->variables.max_heap_table_size)
+ one_lookup_cost= DISK_TEMPTABLE_LOOKUP_COST;
+ else
+ one_lookup_cost= HEAP_TEMPTABLE_LOOKUP_COST;
+
+ double write_cost= join->positions[first_tab].prefix_record_count*
+ sj_outer_fanout * one_lookup_cost;
+ double full_lookup_cost= join->positions[first_tab].prefix_record_count*
+ sj_outer_fanout* sj_inner_fanout *
+ one_lookup_cost;
+ dups_cost += write_cost + full_lookup_cost;
+
+ /*
+ Use the strategy if
+ * it is cheaper then what we've had, or
+ * we haven't picked any other semi-join strategy yet
+ The second part is necessary because this strategy is the last one
+ to consider (it needs "the most" tables in the prefix) and we can't
+ leave duplicate-producing tables not handled by any strategy.
+ */
+ if (dups_cost < *current_read_time || join->cur_dups_producing_tables)
+ {
+ pos->sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+ *current_read_time= dups_cost;
+ *current_record_count= *current_record_count / sj_inner_fanout;
+ join->cur_dups_producing_tables &= ~dups_removed_fanout;
+ }
+ }
+ }
+}
+
+
+/*
+ Remove the last join tab from from join->cur_sj_inner_tables bitmap
+ we assume remaining_tables doesnt contain @tab.
+*/
+
+void restore_prev_sj_state(const table_map remaining_tables,
+ const JOIN_TAB *tab, uint idx)
+{
+ TABLE_LIST *emb_sj_nest;
+ if ((emb_sj_nest= tab->emb_sj_nest))
+ {
+ /* If we're removing the last SJ-inner table, remove the sj-nest */
+ if ((remaining_tables & emb_sj_nest->sj_inner_tables) ==
+ (emb_sj_nest->sj_inner_tables & ~tab->table->map))
+ {
+ tab->join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
+ }
+ }
+}
+
+
+/*
+ Given a semi-join nest, find out which of the IN-equalities are bound
+
+ SYNOPSIS
+ get_bound_sj_equalities()
+ sj_nest Semi-join nest
+ remaining_tables Tables that are not yet bound
+
+ DESCRIPTION
+ Given a semi-join nest, find out which of the IN-equalities have their
+ left part expression bound (i.e. the said expression doesn't refer to
+ any of remaining_tables and can be evaluated).
+
+ RETURN
+ Bitmap of bound IN-equalities.
+*/
+
+ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest,
+ table_map remaining_tables)
+{
+ List_iterator<Item> li(sj_nest->nested_join->sj_outer_expr_list);
+ Item *item;
+ uint i= 0;
+ ulonglong res= 0;
+ while ((item= li++))
+ {
+ /*
+ Q: should this take into account equality propagation and how?
+ A: If e->outer_side is an Item_field, walk over the equality
+ class and see if there is an element that is bound?
+ (this is an optional feature)
+ */
+ if (!(item->used_tables() & remaining_tables))
+ {
+ res |= 1ULL << i;
+ }
+ }
+ return res;
+}
+
+
+/*
+ Check if the last tables of the partial join order allow to use
+ sj-materialization strategy for them
+
+ SYNOPSIS
+ at_sjmat_pos()
+ join
+ remaining_tables
+ tab the last table's join tab
+ idx last table's index
+ loose_scan OUT TRUE <=> use LooseScan
+
+ RETURN
+ TRUE Yes, can apply sj-materialization
+ FALSE No, some of the requirements are not met
+*/
+
+static SJ_MATERIALIZATION_INFO *
+at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
+ uint idx, bool *loose_scan)
+{
+ /*
+ Check if
+ 1. We're in a semi-join nest that can be run with SJ-materialization
+ 2. All the tables correlated through the IN subquery are in the prefix
+ */
+ TABLE_LIST *emb_sj_nest= tab->emb_sj_nest;
+ table_map suffix= remaining_tables & ~tab->table->map;
+ if (emb_sj_nest && emb_sj_nest->sj_mat_info &&
+ !(suffix & emb_sj_nest->sj_inner_tables))
+ {
+ /*
+ Walk back and check if all immediately preceding tables are from
+ this semi-join.
+ */
+ uint n_tables= my_count_bits(tab->emb_sj_nest->sj_inner_tables);
+ for (uint i= 1; i < n_tables ; i++)
+ {
+ if (join->positions[idx - i].table->emb_sj_nest != tab->emb_sj_nest)
+ return NULL;
+ }
+ *loose_scan= test(remaining_tables & ~tab->table->map &
+ (emb_sj_nest->sj_inner_tables |
+ emb_sj_nest->nested_join->sj_depends_on));
+ if (*loose_scan && !emb_sj_nest->sj_subq_pred->sjm_scan_allowed)
+ return NULL;
+ else
+ return emb_sj_nest->sj_mat_info;
+ }
+ return NULL;
+}
+
+
+
+/*
+ Fix semi-join strategies for the picked join order
+
+ SYNOPSIS
+ fix_semijoin_strategies_for_picked_join_order()
+ join The join with the picked join order
+
+ DESCRIPTION
+ Fix semi-join strategies for the picked join order. This is a step that
+ needs to be done right after we have fixed the join order. What we do
+ here is switch join's semi-join strategy description from backward-based
+ to forwards based.
+
+ When join optimization is in progress, we re-consider semi-join
+ strategies after we've added another table. Here's an illustration.
+ Suppose the join optimization is underway:
+
+ 1) ot1 it1 it2
+ sjX -- looking at (ot1, it1, it2) join prefix, we decide
+ to use semi-join strategy sjX.
+
+ 2) ot1 it1 it2 ot2
+ sjX sjY -- Having added table ot2, we now may consider
+ another semi-join strategy and decide to use a
+ different strategy sjY. Note that the record
+ of sjX has remained under it2. That is
+ necessary because we need to be able to get
+ back to (ot1, it1, it2) join prefix.
+ what makes things even worse is that there are cases where the choice
+ of sjY changes the way we should access it2.
+
+ 3) [ot1 it1 it2 ot2 ot3]
+ sjX sjY -- This means that after join optimization is
+ finished, semi-join info should be read
+ right-to-left (while nearly all plan refinement
+ functions, EXPLAIN, etc proceed from left to
+ right)
+
+ This function does the needed reversal, making it possible to read the
+ join and semi-join order from left to right.
+*/
+
+void fix_semijoin_strategies_for_picked_join_order(JOIN *join)
+{
+ uint table_count=join->tables;
+ uint tablenr;
+ table_map remaining_tables= 0;
+ table_map handled_tabs= 0;
+ for (tablenr= table_count - 1 ; tablenr != join->const_tables - 1; tablenr--)
+ {
+ POSITION *pos= join->best_positions + tablenr;
+ JOIN_TAB *s= pos->table;
+ uint first;
+ LINT_INIT(first); // Set by every branch except SJ_OPT_NONE which doesn't use it
+
+ if ((handled_tabs & s->table->map) || pos->sj_strategy == SJ_OPT_NONE)
+ {
+ remaining_tables |= s->table->map;
+ continue;
+ }
+
+ if (pos->sj_strategy == SJ_OPT_MATERIALIZE)
+ {
+ SJ_MATERIALIZATION_INFO *sjm= s->emb_sj_nest->sj_mat_info;
+ sjm->is_used= TRUE;
+ sjm->is_sj_scan= FALSE;
+ memcpy(pos - sjm->tables + 1, sjm->positions,
+ sizeof(POSITION) * sjm->tables);
+ first= tablenr - sjm->tables + 1;
+ join->best_positions[first].n_sj_tables= sjm->tables;
+ join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE;
+ }
+ else if (pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
+ {
+ POSITION *first_inner= join->best_positions + pos->sjm_scan_last_inner;
+ SJ_MATERIALIZATION_INFO *sjm= first_inner->table->emb_sj_nest->sj_mat_info;
+ sjm->is_used= TRUE;
+ sjm->is_sj_scan= TRUE;
+ first= pos->sjm_scan_last_inner - sjm->tables + 1;
+ memcpy(join->best_positions + first,
+ sjm->positions, sizeof(POSITION) * sjm->tables);
+ join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
+ join->best_positions[first].n_sj_tables= sjm->tables;
+ /*
+ Do what advance_sj_state did: re-run best_access_path for every table
+ in the [last_inner_table + 1; pos..) range
+ */
+ double prefix_rec_count;
+ /* Get the prefix record count */
+ if (first == join->const_tables)
+ prefix_rec_count= 1.0;
+ else
+ prefix_rec_count= join->best_positions[first-1].prefix_record_count;
+
+ /* Add materialization record count*/
+ prefix_rec_count *= sjm->rows;
+
+ uint i;
+ table_map rem_tables= remaining_tables;
+ for (i= tablenr; i != (first + sjm->tables - 1); i--)
+ rem_tables |= join->best_positions[i].table->table->map;
+
+ POSITION dummy;
+ join->cur_sj_inner_tables= 0;
+ for (i= first + sjm->tables; i <= tablenr; i++)
+ {
+ best_access_path(join, join->best_positions[i].table, rem_tables, i, FALSE,
+ prefix_rec_count, join->best_positions + i, &dummy);
+ prefix_rec_count *= join->best_positions[i].records_read;
+ rem_tables &= ~join->best_positions[i].table->table->map;
+ }
+ }
+
+ if (pos->sj_strategy == SJ_OPT_FIRST_MATCH)
+ {
+ first= pos->first_firstmatch_table;
+ join->best_positions[first].sj_strategy= SJ_OPT_FIRST_MATCH;
+ join->best_positions[first].n_sj_tables= tablenr - first + 1;
+ POSITION dummy; // For loose scan paths
+ double record_count= (first== join->const_tables)? 1.0:
+ join->best_positions[tablenr - 1].prefix_record_count;
+
+ table_map rem_tables= remaining_tables;
+ uint idx;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ rem_tables |= join->best_positions[idx].table->table->map;
+ }
+ /*
+ Re-run best_access_path to produce best access methods that do not use
+ join buffering
+ */
+ join->cur_sj_inner_tables= 0;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ if (join->best_positions[idx].use_join_buffer)
+ {
+ best_access_path(join, join->best_positions[idx].table,
+ rem_tables, idx, TRUE /* no jbuf */,
+ record_count, join->best_positions + idx, &dummy);
+ }
+ record_count *= join->best_positions[idx].records_read;
+ rem_tables &= ~join->best_positions[idx].table->table->map;
+ }
+ }
+
+ if (pos->sj_strategy == SJ_OPT_LOOSE_SCAN)
+ {
+ first= pos->first_loosescan_table;
+ POSITION *first_pos= join->best_positions + first;
+ POSITION loose_scan_pos; // For loose scan paths
+ double record_count= (first== join->const_tables)? 1.0:
+ join->best_positions[tablenr - 1].prefix_record_count;
+
+ table_map rem_tables= remaining_tables;
+ uint idx;
+ for (idx= first; idx <= tablenr; idx++)
+ rem_tables |= join->best_positions[idx].table->table->map;
+ /*
+ Re-run best_access_path to produce best access methods that do not use
+ join buffering
+ */
+ join->cur_sj_inner_tables= 0;
+ for (idx= first; idx <= tablenr; idx++)
+ {
+ if (join->best_positions[idx].use_join_buffer || (idx == first))
+ {
+ best_access_path(join, join->best_positions[idx].table,
+ rem_tables, idx, TRUE /* no jbuf */,
+ record_count, join->best_positions + idx,
+ &loose_scan_pos);
+ if (idx==first)
+ join->best_positions[idx]= loose_scan_pos;
+ }
+ rem_tables &= ~join->best_positions[idx].table->table->map;
+ record_count *= join->best_positions[idx].records_read;
+ }
+ first_pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
+ first_pos->n_sj_tables= my_count_bits(first_pos->table->emb_sj_nest->sj_inner_tables);
+ }
+
+ if (pos->sj_strategy == SJ_OPT_DUPS_WEEDOUT)
+ {
+ /*
+ Duplicate Weedout starting at pos->first_dupsweedout_table, ending at
+ this table.
+ */
+ first= pos->first_dupsweedout_table;
+ join->best_positions[first].sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+ join->best_positions[first].n_sj_tables= tablenr - first + 1;
+ }
+
+ uint i_end= first + join->best_positions[first].n_sj_tables;
+ for (uint i= first; i < i_end; i++)
+ {
+ if (i != first)
+ join->best_positions[i].sj_strategy= SJ_OPT_NONE;
+ handled_tabs |= join->best_positions[i].table->table->map;
+ }
+
+ if (tablenr != first)
+ pos->sj_strategy= SJ_OPT_NONE;
+ remaining_tables |= s->table->map;
+ }
+}
+
+/*
+ Setup semi-join materialization strategy for one semi-join nest
+
+ SYNOPSIS
+
+ setup_sj_materialization()
+ tab The first tab in the semi-join
+
+ DESCRIPTION
+ Setup execution structures for one semi-join materialization nest:
+ - Create the materialization temporary table
+ - If we're going to do index lookups
+ create TABLE_REF structure to make the lookus
+ - else (if we're going to do a full scan of the temptable)
+ create Copy_field structures to do copying.
+
+ RETURN
+ FALSE Ok
+ TRUE Error
+*/
+
+bool setup_sj_materialization(JOIN_TAB *tab)
+{
+ uint i;
+ DBUG_ENTER("setup_sj_materialization");
+ TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding;
+ SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info;
+ THD *thd= tab->join->thd;
+ /* First the calls come to the materialization function */
+ List<Item> &item_list= emb_sj_nest->sj_subq_pred->unit->first_select()->item_list;
+
+ /*
+ Set up the table to write to, do as select_union::create_result_table does
+ */
+ sjm->sjm_table_param.init();
+ sjm->sjm_table_param.field_count= item_list.elements;
+ // psergey-merge: the following is not in 5.x: sjm->sjm_table_param.bit_fields_as_long= TRUE;
+ List_iterator<Item> it(item_list);
+ Item *right_expr;
+ while((right_expr= it++))
+ sjm->sjm_table_cols.push_back(right_expr);
+
+ if (!(sjm->table= create_tmp_table(thd, &sjm->sjm_table_param,
+ sjm->sjm_table_cols, (ORDER*) 0,
+ TRUE /* distinct */,
+ 1, /*save_sum_fields*/
+ thd->options | TMP_TABLE_ALL_COLUMNS,
+ HA_POS_ERROR /*rows_limit */,
+ (char*)"sj-materialize")))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjm->table->file->extra(HA_EXTRA_WRITE_CACHE);
+ sjm->table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ tab->join->sj_tmp_tables.push_back(sjm->table);
+ tab->join->sjm_info_list.push_back(sjm);
+
+ sjm->materialized= FALSE;
+ if (!sjm->is_sj_scan)
+ {
+ KEY *tmp_key; /* The only index on the temporary table. */
+ uint tmp_key_parts; /* Number of keyparts in tmp_key. */
+ tmp_key= sjm->table->key_info;
+ tmp_key_parts= tmp_key->key_parts;
+
+ /*
+ Create/initialize everything we will need to index lookups into the
+ temptable.
+ */
+ TABLE_REF *tab_ref;
+ if (!(tab_ref= (TABLE_REF*) thd->alloc(sizeof(TABLE_REF))))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ tab_ref->key= 0; /* The only temp table index. */
+ tab_ref->key_length= tmp_key->key_length;
+ if (!(tab_ref->key_buff=
+ (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
+ !(tab_ref->key_copy=
+ (store_key**) thd->alloc((sizeof(store_key*) *
+ (tmp_key_parts + 1)))) ||
+ !(tab_ref->items=
+ (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+
+ tab_ref->key_buff2=tab_ref->key_buff+ALIGN_SIZE(tmp_key->key_length);
+ tab_ref->key_err=1;
+ tab_ref->null_rejecting= 1;
+ tab_ref->disable_cache= FALSE;
+
+ KEY_PART_INFO *cur_key_part= tmp_key->key_part;
+ store_key **ref_key= tab_ref->key_copy;
+ uchar *cur_ref_buff= tab_ref->key_buff;
+
+ for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
+ {
+ tab_ref->items[i]= emb_sj_nest->sj_subq_pred->left_expr->element_index(i);
+ int null_count= test(cur_key_part->field->real_maybe_null());
+ *ref_key= new store_key_item(thd, cur_key_part->field,
+ /* TODO:
+ the NULL byte is taken into account in
+ cur_key_part->store_length, so instead of
+ cur_ref_buff + test(maybe_null), we could
+ use that information instead.
+ */
+ cur_ref_buff + null_count,
+ null_count ? tab_ref->key_buff : 0,
+ cur_key_part->length, tab_ref->items[i]);
+ cur_ref_buff+= cur_key_part->store_length;
+ }
+ *ref_key= NULL; /* End marker. */
+ tab_ref->key_err= 1;
+ tab_ref->key_parts= tmp_key_parts;
+ sjm->tab_ref= tab_ref;
+
+ /*
+ Remove the injected semi-join IN-equalities from join_tab conds. This
+ needs to be done because the IN-equalities refer to columns of
+ sj-inner tables which are not available after the materialization
+ has been finished.
+ */
+ for (i= 0; i < sjm->tables; i++)
+ {
+ remove_sj_conds(&tab[i].select_cond);
+ if (tab[i].select)
+ remove_sj_conds(&tab[i].select->cond);
+ }
+ if (!(sjm->in_equality= create_subq_in_equalities(thd, sjm,
+ emb_sj_nest->sj_subq_pred)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ }
+ else
+ {
+ /*
+ We'll be doing full scan of the temptable.
+ Setup copying of temptable columns back to the record buffers
+ for their source tables. We need this because IN-equalities
+ refer to the original tables.
+
+ EXAMPLE
+
+ Consider the query:
+ SELECT * FROM ot WHERE ot.col1 IN (SELECT it.col2 FROM it)
+
+ Suppose it's executed with SJ-Materialization-scan. We choose to do scan
+ if we can't do the lookup, i.e. the join order is (it, ot). The plan
+ would look as follows:
+
+ table access method condition
+ it materialize+scan -
+ ot (whatever) ot1.col1=it.col2 (C2)
+
+ The condition C2 refers to current row of table it. The problem is
+ that by the time we evaluate C2, we would have finished with scanning
+ it itself and will be scanning the temptable.
+
+ At the moment, our solution is to copy back: when we get the next
+ temptable record, we copy its columns to their corresponding columns
+ in the record buffers for the source tables.
+ */
+ sjm->copy_field= new Copy_field[sjm->sjm_table_cols.elements];
+ it.rewind();
+ for (uint i=0; i < sjm->sjm_table_cols.elements; i++)
+ {
+ bool dummy;
+ Item_equal *item_eq;
+ Item *item= (it++)->real_item();
+ DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
+ Field *copy_to= ((Item_field*)item)->field;
+ /*
+ Tricks with Item_equal are due to the following: suppose we have a
+ query:
+
+ ... WHERE cond(ot.col) AND ot.col IN (SELECT it2.col FROM it1,it2
+ WHERE it1.col= it2.col)
+ then equality propagation will create an
+
+ Item_equal(it1.col, it2.col, ot.col)
+
+ then substitute_for_best_equal_field() will change the conditions
+ according to the join order:
+
+ it1
+ it2 it1.col=it2.col
+ ot cond(it1.col)
+
+ although we've originally had "SELECT it2.col", conditions attached
+ to subsequent outer tables will refer to it1.col, so SJM-Scan will
+ need to unpack data to there.
+ That is, if an element from subquery's select list participates in
+ equality propagation, then we need to unpack it to the first
+ element equality propagation member that refers to table that is
+ within the subquery.
+ */
+ item_eq= find_item_equal(tab->join->cond_equal, copy_to, &dummy);
+
+ if (item_eq)
+ {
+ List_iterator<Item_field> it(item_eq->fields);
+ Item_field *item;
+ while ((item= it++))
+ {
+ if (!(item->used_tables() & ~emb_sj_nest->sj_inner_tables))
+ {
+ copy_to= item->field;
+ break;
+ }
+ }
+ }
+ sjm->copy_field[i].set(copy_to, sjm->table->field[i], FALSE);
+ /* The write_set for source tables must be set up to allow the copying */
+ bitmap_set_bit(copy_to->table->write_set, copy_to->field_index);
+ }
+ }
+
+ DBUG_RETURN(FALSE);
+}
+
+
+
+/*
+ Create subquery IN-equalities assuming use of materialization strategy
+
+ SYNOPSIS
+ create_subq_in_equalities()
+ thd Thread handle
+ sjm Semi-join materialization structure
+ subq_pred The subquery predicate
+
+ DESCRIPTION
+ Create subquery IN-equality predicates. That is, for a subquery
+
+ (oe1, oe2, ...) IN (SELECT ie1, ie2, ... FROM ...)
+
+ create "oe1=ie1 AND ie1=ie2 AND ..." expression, such that ie1, ie2, ..
+ refer to the columns of the table that's used to materialize the
+ subquery.
+
+ RETURN
+ Created condition
+*/
+
+static Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm,
+ Item_in_subselect *subq_pred)
+{
+ Item *res= NULL;
+ if (subq_pred->left_expr->cols() == 1)
+ {
+ if (!(res= new Item_func_eq(subq_pred->left_expr,
+ new Item_field(sjm->table->field[0]))))
+ return NULL; /* purecov: inspected */
+ }
+ else
+ {
+ Item *conj;
+ for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
+ {
+ if (!(conj= new Item_func_eq(subq_pred->left_expr->element_index(i),
+ new Item_field(sjm->table->field[i]))) ||
+ !(res= and_items(res, conj)))
+ return NULL; /* purecov: inspected */
+ }
+ }
+ if (res->fix_fields(thd, &res))
+ return NULL; /* purecov: inspected */
+ return res;
+}
+
+
+
+
+static void remove_sj_conds(Item **tree)
+{
+ if (*tree)
+ {
+ if (is_cond_sj_in_equality(*tree))
+ {
+ *tree= NULL;
+ return;
+ }
+ else if ((*tree)->type() == Item::COND_ITEM)
+ {
+ Item *item;
+ List_iterator<Item> li(*(((Item_cond*)*tree)->argument_list()));
+ while ((item= li++))
+ {
+ if (is_cond_sj_in_equality(item))
+ li.replace(new Item_int(1));
+ }
+ }
+ }
+}
+
+/* Check if given Item was injected by semi-join equality */
+static bool is_cond_sj_in_equality(Item *item)
+{
+ if (item->type() == Item::FUNC_ITEM &&
+ ((Item_func*)item)->functype()== Item_func::EQ_FUNC)
+ {
+ Item_func_eq *item_eq= (Item_func_eq*)item;
+ return test(item_eq->in_equality_no != UINT_MAX);
+ }
+ return FALSE;
+}
+
+
+/*
+ Create a temporary table to weed out duplicate rowid combinations
+
+ SYNOPSIS
+
+ create_duplicate_weedout_tmp_table()
+ thd Thread handle
+ uniq_tuple_length_arg Length of the table's column
+ sjtbl Update sjtbl->[start_]recinfo values which
+ will be needed if we'll need to convert the
+ created temptable from HEAP to MyISAM/Maria.
+
+ DESCRIPTION
+ Create a temporary table to weed out duplicate rowid combinations. The
+ table has a single column that is a concatenation of all rowids in the
+ combination.
+
+ Depending on the needed length, there are two cases:
+
+ 1. When the length of the column < max_key_length:
+
+ CREATE TABLE tmp (col VARBINARY(n) NOT NULL, UNIQUE KEY(col));
+
+ 2. Otherwise (not a valid SQL syntax but internally supported):
+
+ CREATE TABLE tmp (col VARBINARY NOT NULL, UNIQUE CONSTRAINT(col));
+
+ The code in this function was produced by extraction of relevant parts
+ from create_tmp_table().
+
+ RETURN
+ created table
+ NULL on error
+*/
+
+TABLE *create_duplicate_weedout_tmp_table(THD *thd,
+ uint uniq_tuple_length_arg,
+ SJ_TMP_TABLE *sjtbl)
+{
+ MEM_ROOT *mem_root_save, own_root;
+ TABLE *table;
+ TABLE_SHARE *share;
+ uint temp_pool_slot=MY_BIT_NONE;
+ char *tmpname,path[FN_REFLEN];
+ Field **reg_field;
+ KEY_PART_INFO *key_part_info;
+ KEY *keyinfo;
+ uchar *group_buff;
+ uchar *bitmaps;
+ uint *blob_field;
+ ENGINE_COLUMNDEF *recinfo, *start_recinfo;
+ bool using_unique_constraint=FALSE;
+ bool use_packed_rows= FALSE;
+ Field *field, *key_field;
+ uint blob_count, null_pack_length, null_count;
+ uchar *null_flags;
+ uchar *pos;
+ DBUG_ENTER("create_duplicate_weedout_tmp_table");
+ DBUG_ASSERT(!sjtbl->is_degenerate);
+ /*
+ STEP 1: Get temporary table name
+ */
+ statistic_increment(thd->status_var.created_tmp_tables, &LOCK_status);
+ if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
+ temp_pool_slot = bitmap_lock_set_next(&temp_pool);
+
+ if (temp_pool_slot != MY_BIT_NONE) // we got a slot
+ sprintf(path, "%s_%lx_%i", tmp_file_prefix,
+ current_pid, temp_pool_slot);
+ else
+ {
+ /* if we run out of slots or we are not using tempool */
+ sprintf(path,"%s%lx_%lx_%x", tmp_file_prefix,current_pid,
+ thd->thread_id, thd->tmp_table++);
+ }
+ fn_format(path, path, mysql_tmpdir, "", MY_REPLACE_EXT|MY_UNPACK_FILENAME);
+
+ /* STEP 2: Figure if we'll be using a key or blob+constraint */
+ if (uniq_tuple_length_arg >= CONVERT_IF_BIGGER_TO_BLOB)
+ using_unique_constraint= TRUE;
+
+ /* STEP 3: Allocate memory for temptable description */
+ init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0);
+ if (!multi_alloc_root(&own_root,
+ &table, sizeof(*table),
+ &share, sizeof(*share),
+ &reg_field, sizeof(Field*) * (1+1),
+ &blob_field, sizeof(uint)*2,
+ &keyinfo, sizeof(*keyinfo),
+ &key_part_info, sizeof(*key_part_info) * 2,
+ &start_recinfo,
+ sizeof(*recinfo)*(1*2+4),
+ &tmpname, (uint) strlen(path)+1,
+ &group_buff, (!using_unique_constraint ?
+ uniq_tuple_length_arg : 0),
+ &bitmaps, bitmap_buffer_size(1)*3,
+ NullS))
+ {
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(NULL);
+ }
+ strmov(tmpname,path);
+
+
+ /* STEP 4: Create TABLE description */
+ bzero((char*) table,sizeof(*table));
+ bzero((char*) reg_field,sizeof(Field*)*2);
+
+ table->mem_root= own_root;
+ mem_root_save= thd->mem_root;
+ thd->mem_root= &table->mem_root;
+
+ table->field=reg_field;
+ table->alias= "weedout-tmp";
+ table->reginfo.lock_type=TL_WRITE; /* Will be updated */
+ table->db_stat=HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
+ table->map=1;
+ table->temp_pool_slot = temp_pool_slot;
+ table->copy_blobs= 1;
+ table->in_use= thd;
+ table->quick_keys.init();
+ table->covering_keys.init();
+ table->keys_in_use_for_query.init();
+
+ table->s= share;
+ init_tmp_table_share(thd, share, "", 0, tmpname, tmpname);
+ share->blob_field= blob_field;
+ share->blob_ptr_size= portable_sizeof_char_ptr;
+ share->db_low_byte_first=1; // True for HEAP and MyISAM
+ share->table_charset= NULL;
+ share->primary_key= MAX_KEY; // Indicate no primary key
+ share->keys_for_keyread.init();
+ share->keys_in_use.init();
+
+ blob_count= 0;
+
+ /* Create the field */
+ {
+ /*
+ For the sake of uniformity, always use Field_varstring (altough we could
+ use Field_string for shorter keys)
+ */
+ field= new Field_varstring(uniq_tuple_length_arg, FALSE, "rowids", share,
+ &my_charset_bin);
+ if (!field)
+ DBUG_RETURN(0);
+ field->table= table;
+ field->key_start.init(0);
+ field->part_of_key.init(0);
+ field->part_of_sortkey.init(0);
+ field->unireg_check= Field::NONE;
+ field->flags= (NOT_NULL_FLAG | BINARY_FLAG | NO_DEFAULT_VALUE_FLAG);
+ field->reset_fields();
+ field->init(table);
+ field->orig_table= NULL;
+
+ field->field_index= 0;
+
+ *(reg_field++)= field;
+ *blob_field= 0;
+ *reg_field= 0;
+
+ share->fields= 1;
+ share->blob_fields= 0;
+ }
+
+ uint reclength= field->pack_length();
+ if (using_unique_constraint)
+ {
+ share->db_plugin= ha_lock_engine(0, TMP_ENGINE_HTON);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length());
+ }
+ else
+ {
+ share->db_plugin= ha_lock_engine(0, heap_hton);
+ table->file= get_new_handler(share, &table->mem_root,
+ share->db_type());
+ }
+ if (!table->file)
+ goto err;
+
+ null_count=1;
+
+ null_pack_length= 1;
+ reclength += null_pack_length;
+
+ share->reclength= reclength;
+ {
+ uint alloc_length=ALIGN_SIZE(share->reclength + MI_UNIQUE_HASH_LENGTH+1);
+ share->rec_buff_length= alloc_length;
+ if (!(table->record[0]= (uchar*)
+ alloc_root(&table->mem_root, alloc_length*3)))
+ goto err;
+ table->record[1]= table->record[0]+alloc_length;
+ share->default_values= table->record[1]+alloc_length;
+ }
+ setup_tmp_table_column_bitmaps(table, bitmaps);
+
+ recinfo= start_recinfo;
+ null_flags=(uchar*) table->record[0];
+ pos=table->record[0]+ null_pack_length;
+ if (null_pack_length)
+ {
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+ recinfo->type=FIELD_NORMAL;
+ recinfo->length=null_pack_length;
+ recinfo++;
+ bfill(null_flags,null_pack_length,255); // Set null fields
+
+ table->null_flags= (uchar*) table->record[0];
+ share->null_fields= null_count;
+ share->null_bytes= null_pack_length;
+ }
+ null_count=1;
+
+ {
+ //Field *field= *reg_field;
+ uint length;
+ bzero((uchar*) recinfo,sizeof(*recinfo));
+ field->move_field(pos,(uchar*) 0,0);
+
+ field->reset();
+ /*
+ Test if there is a default field value. The test for ->ptr is to skip
+ 'offset' fields generated by initalize_tables
+ */
+ // Initialize the table field:
+ bzero(field->ptr, field->pack_length());
+
+ length=field->pack_length();
+ pos+= length;
+
+ /* Make entry for create table */
+ recinfo->length=length;
+ if (field->flags & BLOB_FLAG)
+ recinfo->type= FIELD_BLOB;
+ else if (use_packed_rows &&
+ field->real_type() == MYSQL_TYPE_STRING &&
+ length >= MIN_STRING_LENGTH_TO_PACK_ROWS)
+ recinfo->type=FIELD_SKIP_ENDSPACE;
+ else
+ recinfo->type=FIELD_NORMAL;
+
+ field->table_name= &table->alias;
+ }
+
+ //param->recinfo=recinfo;
+ //store_record(table,s->default_values); // Make empty default record
+
+ if (thd->variables.tmp_table_size == ~ (ulonglong) 0) // No limit
+ share->max_rows= ~(ha_rows) 0;
+ else
+ share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ?
+ min(thd->variables.tmp_table_size,
+ thd->variables.max_heap_table_size) :
+ thd->variables.tmp_table_size) /
+ share->reclength);
+ set_if_bigger(share->max_rows,1); // For dummy start options
+
+
+ //// keyinfo= param->keyinfo;
+ if (TRUE)
+ {
+ DBUG_PRINT("info",("Creating group key in temporary table"));
+ share->keys=1;
+ share->uniques= test(using_unique_constraint);
+ table->key_info=keyinfo;
+ keyinfo->key_part=key_part_info;
+ keyinfo->flags=HA_NOSAME;
+ keyinfo->usable_key_parts= keyinfo->key_parts= 1;
+ keyinfo->key_length=0;
+ keyinfo->rec_per_key=0;
+ keyinfo->algorithm= HA_KEY_ALG_UNDEF;
+ keyinfo->name= (char*) "weedout_key";
+ {
+ key_part_info->null_bit=0;
+ key_part_info->field= field;
+ key_part_info->offset= field->offset(table->record[0]);
+ key_part_info->length= (uint16) field->key_length();
+ key_part_info->type= (uint8) field->key_type();
+ key_part_info->key_type = FIELDFLAG_BINARY;
+ if (!using_unique_constraint)
+ {
+ if (!(key_field= field->new_key_field(thd->mem_root, table,
+ group_buff,
+ field->null_ptr,
+ field->null_bit)))
+ goto err;
+ key_part_info->key_part_flag|= HA_END_SPACE_ARE_EQUAL; //todo need this?
+ }
+ keyinfo->key_length+= key_part_info->length;
+ }
+ }
+
+ if (thd->is_fatal_error) // If end of memory
+ goto err;
+ share->db_record_offset= 1;
+ if (share->db_type() == TMP_ENGINE_HTON)
+ {
+ recinfo++;
+ if (create_internal_tmp_table(table, keyinfo, start_recinfo, &recinfo, 0))
+ goto err;
+ }
+ sjtbl->start_recinfo= start_recinfo;
+ sjtbl->recinfo= recinfo;
+ if (open_tmp_table(table))
+ goto err;
+
+ thd->mem_root= mem_root_save;
+ DBUG_RETURN(table);
+
+err:
+ thd->mem_root= mem_root_save;
+ free_tmp_table(thd,table); /* purecov: inspected */
+ if (temp_pool_slot != MY_BIT_NONE)
+ bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
+ DBUG_RETURN(NULL); /* purecov: inspected */
+}
+
+
+/*
+ SemiJoinDuplicateElimination: Reset the temporary table
+*/
+
+int do_sj_reset(SJ_TMP_TABLE *sj_tbl)
+{
+ DBUG_ENTER("do_sj_reset");
+ if (sj_tbl->tmp_table)
+ {
+ int rc= sj_tbl->tmp_table->file->ha_delete_all_rows();
+ DBUG_RETURN(rc);
+ }
+ sj_tbl->have_degenerate_row= FALSE;
+ DBUG_RETURN(0);
+}
+
+/*
+ SemiJoinDuplicateElimination: Weed out duplicate row combinations
+
+ SYNPOSIS
+ do_sj_dups_weedout()
+ thd Thread handle
+ sjtbl Duplicate weedout table
+
+ DESCRIPTION
+ Try storing current record combination of outer tables (i.e. their
+ rowids) in the temporary table. This records the fact that we've seen
+ this record combination and also tells us if we've seen it before.
+
+ RETURN
+ -1 Error
+ 1 The row combination is a duplicate (discard it)
+ 0 The row combination is not a duplicate (continue)
+*/
+
+int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl)
+{
+ int error;
+ SJ_TMP_TABLE::TAB *tab= sjtbl->tabs;
+ SJ_TMP_TABLE::TAB *tab_end= sjtbl->tabs_end;
+ uchar *ptr;
+ uchar *nulls_ptr;
+
+ DBUG_ENTER("do_sj_dups_weedout");
+
+ if (sjtbl->is_degenerate)
+ {
+ if (sjtbl->have_degenerate_row)
+ DBUG_RETURN(1);
+
+ sjtbl->have_degenerate_row= TRUE;
+ DBUG_RETURN(0);
+ }
+
+ ptr= sjtbl->tmp_table->record[0] + 1;
+ nulls_ptr= ptr;
+
+ /* Put the the rowids tuple into table->record[0]: */
+
+ // 1. Store the length
+ if (((Field_varstring*)(sjtbl->tmp_table->field[0]))->length_bytes == 1)
+ {
+ *ptr= (uchar)(sjtbl->rowid_len + sjtbl->null_bytes);
+ ptr++;
+ }
+ else
+ {
+ int2store(ptr, sjtbl->rowid_len + sjtbl->null_bytes);
+ ptr += 2;
+ }
+
+ // 2. Zero the null bytes
+ if (sjtbl->null_bytes)
+ {
+ bzero(ptr, sjtbl->null_bytes);
+ ptr += sjtbl->null_bytes;
+ }
+
+ // 3. Put the rowids
+ for (uint i=0; tab != tab_end; tab++, i++)
+ {
+ handler *h= tab->join_tab->table->file;
+ if (tab->join_tab->table->maybe_null && tab->join_tab->table->null_row)
+ {
+ /* It's a NULL-complemented row */
+ *(nulls_ptr + tab->null_byte) |= tab->null_bit;
+ bzero(ptr + tab->rowid_offset, h->ref_length);
+ }
+ else
+ {
+ /* Copy the rowid value */
+ memcpy(ptr + tab->rowid_offset, h->ref, h->ref_length);
+ }
+ }
+
+ error= sjtbl->tmp_table->file->ha_write_row(sjtbl->tmp_table->record[0]);
+ if (error)
+ {
+ /* create_internal_tmp_table_from_heap will generate error if needed */
+ if (!sjtbl->tmp_table->file->is_fatal_error(error, HA_CHECK_DUP))
+ DBUG_RETURN(1); /* Duplicate */
+ if (create_internal_tmp_table_from_heap(thd, sjtbl->tmp_table,
+ sjtbl->start_recinfo,
+ &sjtbl->recinfo, error, 1))
+ DBUG_RETURN(-1);
+ }
+ DBUG_RETURN(0);
+}
+
+
+/*
+ Setup the strategies to eliminate semi-join duplicates.
+
+ SYNOPSIS
+ setup_semijoin_dups_elimination()
+ join Join to process
+ options Join options (needed to see if join buffering will be
+ used or not)
+ no_jbuf_after Another bit of information re where join buffering will
+ be used.
+
+ DESCRIPTION
+ Setup the strategies to eliminate semi-join duplicates. ATM there are 4
+ strategies:
+
+ 1. DuplicateWeedout (use of temptable to remove duplicates based on rowids
+ of row combinations)
+ 2. FirstMatch (pick only the 1st matching row combination of inner tables)
+ 3. LooseScan (scanning the sj-inner table in a way that groups duplicates
+ together and picking the 1st one)
+ 4. SJ-Materialization.
+
+ The join order has "duplicate-generating ranges", and every range is
+ served by one strategy or a combination of FirstMatch with with some
+ other strategy.
+
+ "Duplicate-generating range" is defined as a range within the join order
+ that contains all of the inner tables of a semi-join. All ranges must be
+ disjoint, if tables of several semi-joins are interleaved, then the ranges
+ are joined together, which is equivalent to converting
+ SELECT ... WHERE oe1 IN (SELECT ie1 ...) AND oe2 IN (SELECT ie2 )
+ to
+ SELECT ... WHERE (oe1, oe2) IN (SELECT ie1, ie2 ... ...)
+ .
+
+ Applicability conditions are as follows:
+
+ DuplicateWeedout strategy
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ (ot|nt)* [ it ((it|ot|nt)* (it|ot))] (nt)*
+ +------+ +=========================+ +---+
+ (1) (2) (3)
+
+ (1) - Prefix of OuterTables (those that participate in
+ IN-equality and/or are correlated with subquery) and outer
+ Non-correlated tables.
+ (2) - The handled range. The range starts with the first sj-inner
+ table, and covers all sj-inner and outer tables
+ Within the range, Inner, Outer, outer non-correlated tables
+ may follow in any order.
+ (3) - The suffix of outer non-correlated tables.
+
+ FirstMatch strategy
+ ~~~~~~~~~~~~~~~~~~~
+
+ (ot|nt)* [ it ((it|nt)* it) ] (nt)*
+ +------+ +==================+ +---+
+ (1) (2) (3)
+
+ (1) - Prefix of outer and non-correlated tables
+ (2) - The handled range, which may contain only inner and
+ non-correlated tables.
+ (3) - The suffix of outer non-correlated tables.
+
+ LooseScan strategy
+ ~~~~~~~~~~~~~~~~~~
+
+ (ot|ct|nt) [ loosescan_tbl (ot|nt|it)* it ] (ot|nt)*
+ +--------+ +===========+ +=============+ +------+
+ (1) (2) (3) (4)
+
+ (1) - Prefix that may contain any outer tables. The prefix must contain
+ all the non-trivially correlated outer tables. (non-trivially means
+ that the correlation is not just through the IN-equality).
+
+ (2) - Inner table for which the LooseScan scan is performed.
+
+ (3) - The remainder of the duplicate-generating range. It is served by
+ application of FirstMatch strategy, with the exception that
+ outer IN-correlated tables are considered to be non-correlated.
+
+ (4) - THe suffix of outer and outer non-correlated tables.
+
+
+ The choice between the strategies is made by the join optimizer (see
+ advance_sj_state() and fix_semijoin_strategies_for_picked_join_order()).
+ This function sets up all fields/structures/etc needed for execution except
+ for setup/initialization of semi-join materialization which is done in
+ setup_sj_materialization() (todo: can't we move that to here also?)
+
+ RETURN
+ FALSE OK
+ TRUE Out of memory error
+*/
+
+int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
+ uint no_jbuf_after)
+{
+ uint i;
+ THD *thd= join->thd;
+ DBUG_ENTER("setup_semijoin_dups_elimination");
+
+ for (i= join->const_tables ; i < join->tables ; i++)
+ {
+ JOIN_TAB *tab=join->join_tab + i;
+ POSITION *pos= join->best_positions + i;
+ uint keylen, keyno;
+ switch (pos->sj_strategy) {
+ case SJ_OPT_MATERIALIZE:
+ case SJ_OPT_MATERIALIZE_SCAN:
+ /* Do nothing */
+ i += pos->n_sj_tables;
+ break;
+ case SJ_OPT_LOOSE_SCAN:
+ {
+ /* We jump from the last table to the first one */
+ tab->loosescan_match_tab= tab + pos->n_sj_tables - 1;
+
+ /* Calculate key length */
+ keylen= 0;
+ keyno= pos->loosescan_key;
+ for (uint kp=0; kp < pos->loosescan_parts; kp++)
+ keylen += tab->table->key_info[keyno].key_part[kp].store_length;
+
+ tab->loosescan_key_len= keylen;
+ if (pos->n_sj_tables > 1)
+ tab[pos->n_sj_tables - 1].do_firstmatch= tab;
+ i += pos->n_sj_tables;
+ break;
+ }
+ case SJ_OPT_DUPS_WEEDOUT:
+ {
+ /*
+ Check for join buffering. If there is one, move the first table
+ forwards, but do not destroy other duplicate elimination methods.
+ */
+ uint first_table= i;
+ for (uint j= i; j < i + pos->n_sj_tables; j++)
+ {
+ if (join->best_positions[j].use_join_buffer && j <= no_jbuf_after)
+ {
+ first_table= join->const_tables;
+ break;
+ }
+ }
+
+ SJ_TMP_TABLE::TAB sjtabs[MAX_TABLES];
+ SJ_TMP_TABLE::TAB *last_tab= sjtabs;
+ uint jt_rowid_offset= 0; // # tuple bytes are already occupied (w/o NULL bytes)
+ uint jt_null_bits= 0; // # null bits in tuple bytes
+ /*
+ Walk through the range and remember
+ - tables that need their rowids to be put into temptable
+ - the last outer table
+ */
+ for (JOIN_TAB *j=join->join_tab + first_table;
+ j < join->join_tab + i + pos->n_sj_tables; j++)
+ {
+ if (sj_table_is_included(join, j))
+ {
+ last_tab->join_tab= j;
+ last_tab->rowid_offset= jt_rowid_offset;
+ jt_rowid_offset += j->table->file->ref_length;
+ if (j->table->maybe_null)
+ {
+ last_tab->null_byte= jt_null_bits / 8;
+ last_tab->null_bit= jt_null_bits++;
+ }
+ last_tab++;
+ j->table->prepare_for_position();
+ j->keep_current_rowid= TRUE;
+ }
+ }
+
+ SJ_TMP_TABLE *sjtbl;
+ if (jt_rowid_offset) /* Temptable has at least one rowid */
+ {
+ uint tabs_size= (last_tab - sjtabs) * sizeof(SJ_TMP_TABLE::TAB);
+ if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))) ||
+ !(sjtbl->tabs= (SJ_TMP_TABLE::TAB*) thd->alloc(tabs_size)))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ memcpy(sjtbl->tabs, sjtabs, tabs_size);
+ sjtbl->is_degenerate= FALSE;
+ sjtbl->tabs_end= sjtbl->tabs + (last_tab - sjtabs);
+ sjtbl->rowid_len= jt_rowid_offset;
+ sjtbl->null_bits= jt_null_bits;
+ sjtbl->null_bytes= (jt_null_bits + 7)/8;
+ sjtbl->tmp_table=
+ create_duplicate_weedout_tmp_table(thd,
+ sjtbl->rowid_len +
+ sjtbl->null_bytes,
+ sjtbl);
+ join->sj_tmp_tables.push_back(sjtbl->tmp_table);
+ }
+ else
+ {
+ /*
+ This is a special case where the entire subquery predicate does
+ not depend on anything at all, ie this is
+ WHERE const IN (uncorrelated select)
+ */
+ if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))))
+ DBUG_RETURN(TRUE); /* purecov: inspected */
+ sjtbl->tmp_table= NULL;
+ sjtbl->is_degenerate= TRUE;
+ sjtbl->have_degenerate_row= FALSE;
+ }
+ join->join_tab[first_table].flush_weedout_table= sjtbl;
+ join->join_tab[i + pos->n_sj_tables - 1].check_weed_out_table= sjtbl;
+
+ i += pos->n_sj_tables;
+ break;
+ }
+ case SJ_OPT_FIRST_MATCH:
+ {
+ JOIN_TAB *j, *jump_to= tab-1;
+ for (j= tab; j != tab + pos->n_sj_tables; j++)
+ {
+ if (!tab->emb_sj_nest)
+ jump_to= tab;
+ else
+ {
+ j->first_sj_inner_tab= tab;
+ j->last_sj_inner_tab= tab + pos->n_sj_tables - 1;
+ }
+ }
+ j[-1].do_firstmatch= jump_to;
+ i += pos->n_sj_tables;
+ break;
+ }
+ case SJ_OPT_NONE:
+ break;
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
+/*
+ Destroy all temporary tables created by NL-semijoin runtime
+*/
+
+void destroy_sj_tmp_tables(JOIN *join)
+{
+ List_iterator<TABLE> it(join->sj_tmp_tables);
+ TABLE *table;
+ while ((table= it++))
+ {
+ /*
+ SJ-Materialization tables are initialized for either sequential reading
+ or index lookup, DuplicateWeedout tables are not initialized for read
+ (we only write to them), so need to call ha_index_or_rnd_end.
+ */
+ table->file->ha_index_or_rnd_end();
+ free_tmp_table(join->thd, table);
+ }
+ join->sj_tmp_tables.empty();
+ join->sjm_info_list.empty();
+}
+
+
+/*
+ Remove all records from all temp tables used by NL-semijoin runtime
+
+ SYNOPSIS
+ clear_sj_tmp_tables()
+ join The join to remove tables for
+
+ DESCRIPTION
+ Remove all records from all temp tables used by NL-semijoin runtime. This
+ must be done before every join re-execution.
+*/
+
+int clear_sj_tmp_tables(JOIN *join)
+{
+ int res;
+ List_iterator<TABLE> it(join->sj_tmp_tables);
+ TABLE *table;
+ while ((table= it++))
+ {
+ if ((res= table->file->ha_delete_all_rows()))
+ return res; /* purecov: inspected */
+ }
+
+ SJ_MATERIALIZATION_INFO *sjm;
+ List_iterator<SJ_MATERIALIZATION_INFO> it2(join->sjm_info_list);
+ while ((sjm= it2++))
+ {
+ sjm->materialized= FALSE;
+ }
+ return 0;
+}
+
+
+/*
+ Check if the table's rowid is included in the temptable
+
+ SYNOPSIS
+ sj_table_is_included()
+ join The join
+ join_tab The table to be checked
+
+ DESCRIPTION
+ SemiJoinDuplicateElimination: check the table's rowid should be included
+ in the temptable. This is so if
+
+ 1. The table is not embedded within some semi-join nest
+ 2. The has been pulled out of a semi-join nest, or
+
+ 3. The table is functionally dependent on some previous table
+
+ [4. This is also true for constant tables that can't be
+ NULL-complemented but this function is not called for such tables]
+
+ RETURN
+ TRUE - Include table's rowid
+ FALSE - Don't
+*/
+
+static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab)
+{
+ if (join_tab->emb_sj_nest)
+ return FALSE;
+
+ /* Check if this table is functionally dependent on the tables that
+ are within the same outer join nest
+ */
+ TABLE_LIST *embedding= join_tab->table->pos_in_table_list->embedding;
+ if (join_tab->type == JT_EQ_REF)
+ {
+ table_map depends_on= 0;
+ uint idx;
+
+ for (uint kp= 0; kp < join_tab->ref.key_parts; kp++)
+ depends_on |= join_tab->ref.items[kp]->used_tables();
+
+ Table_map_iterator it(depends_on & ~PSEUDO_TABLE_BITS);
+ while ((idx= it.next_bit())!=Table_map_iterator::BITMAP_END)
+ {
+ JOIN_TAB *ref_tab= join->map2table[idx];
+ if (embedding != ref_tab->table->pos_in_table_list->embedding)
+ return TRUE;
+ }
+ /* Ok, functionally dependent */
+ return FALSE;
+ }
+ /* Not functionally dependent => need to include*/
+ return TRUE;
+}
+
+
+/*
+ Index lookup-based subquery: save some flags for EXPLAIN output
+
+ SYNOPSIS
+ save_index_subquery_explain_info()
+ join_tab Subquery's join tab (there is only one as index lookup is
+ only used for subqueries that are single-table SELECTs)
+ where Subquery's WHERE clause
+
+ DESCRIPTION
+ For index lookup-based subquery (i.e. one executed with
+ subselect_uniquesubquery_engine or subselect_indexsubquery_engine),
+ check its EXPLAIN output row should contain
+ "Using index" (TAB_INFO_FULL_SCAN_ON_NULL)
+ "Using Where" (TAB_INFO_USING_WHERE)
+ "Full scan on NULL key" (TAB_INFO_FULL_SCAN_ON_NULL)
+ and set appropriate flags in join_tab->packed_info.
+*/
+
+static void save_index_subquery_explain_info(JOIN_TAB *join_tab, Item* where)
+{
+ join_tab->packed_info= TAB_INFO_HAVE_VALUE;
+ if (join_tab->table->covering_keys.is_set(join_tab->ref.key))
+ join_tab->packed_info |= TAB_INFO_USING_INDEX;
+ if (where)
+ join_tab->packed_info |= TAB_INFO_USING_WHERE;
+ for (uint i = 0; i < join_tab->ref.key_parts; i++)
+ {
+ if (join_tab->ref.cond_guards[i])
+ {
+ join_tab->packed_info |= TAB_INFO_FULL_SCAN_ON_NULL;
+ break;
+ }
+ }
+}
+
+
+/*
+ Check if the join can be rewritten to [unique_]indexsubquery_engine
+
+ DESCRIPTION
+ Check if the join can be changed into [unique_]indexsubquery_engine.
+
+ The check is done after join optimization, the idea is that if the join
+ has only one table and uses a [eq_]ref access generated from subselect's
+ IN-equality then we replace it with a subselect_indexsubquery_engine or a
+ subselect_uniquesubquery_engine.
+
+ RETURN
+ 0 - Ok, rewrite done (stop join optimization and return)
+ 1 - Fatal error (stop join optimization and return)
+ -1 - No rewrite performed, continue with join optimization
+*/
+
+int rewrite_to_index_subquery_engine(JOIN *join)
+{
+ THD *thd= join->thd;
+ 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?
+ */
+ if (!join->group_list && !join->order &&
+ join->unit->item &&
+ join->unit->item->substype() == Item_subselect::IN_SUBS &&
+ join->tables == 1 && join->conds &&
+ !join->unit->is_union())
+ {
+ if (!join->having)
+ {
+ Item *where= join->conds;
+ if (join_tab[0].type == JT_EQ_REF &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name)
+ {
+ remove_subq_pushed_predicates(join, &where);
+ save_index_subquery_explain_info(join_tab, where);
+ join_tab[0].type= JT_UNIQUE_SUBQUERY;
+ join->error= 0;
+ DBUG_RETURN(unit->item->
+ change_engine(new
+ subselect_uniquesubquery_engine(thd,
+ join_tab,
+ unit->item,
+ where)));
+ }
+ else if (join_tab[0].type == JT_REF &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name)
+ {
+ remove_subq_pushed_predicates(join, &where);
+ save_index_subquery_explain_info(join_tab, where);
+ join_tab[0].type= JT_INDEX_SUBQUERY;
+ join->error= 0;
+ DBUG_RETURN(unit->item->
+ change_engine(new
+ subselect_indexsubquery_engine(thd,
+ join_tab,
+ unit->item,
+ where,
+ NULL,
+ 0)));
+ }
+ } else if (join_tab[0].type == JT_REF_OR_NULL &&
+ join_tab[0].ref.items[0]->name == in_left_expr_name &&
+ join->having->name == in_having_cond)
+ {
+ join_tab[0].type= JT_INDEX_SUBQUERY;
+ join->error= 0;
+ join->conds= remove_additional_cond(join->conds);
+ save_index_subquery_explain_info(join_tab, join->conds);
+ DBUG_RETURN(unit->item->
+ change_engine(new subselect_indexsubquery_engine(thd,
+ join_tab,
+ unit->item,
+ join->conds,
+ join->having,
+ 1)));
+ }
+ }
+
+ DBUG_RETURN(-1); /* Haven't done the rewrite */
+}
+
+
+/**
+ Remove additional condition inserted by IN/ALL/ANY transformation.
+
+ @param conds condition for processing
+
+ @return
+ new conditions
+*/
+
+static Item *remove_additional_cond(Item* conds)
+{
+ if (conds->name == in_additional_cond)
+ return 0;
+ if (conds->type() == Item::COND_ITEM)
+ {
+ Item_cond *cnd= (Item_cond*) conds;
+ List_iterator<Item> li(*(cnd->argument_list()));
+ Item *item;
+ while ((item= li++))
+ {
+ if (item->name == in_additional_cond)
+ {
+ li.remove();
+ if (cnd->argument_list()->elements == 1)
+ return cnd->argument_list()->head();
+ return conds;
+ }
+ }
+ }
+ return conds;
+}
+
+
+/*
+ Remove the predicates pushed down into the subquery
+
+ SYNOPSIS
+ remove_subq_pushed_predicates()
+ where IN Must be NULL
+ OUT The remaining WHERE condition, or NULL
+
+ DESCRIPTION
+ Given that this join will be executed using (unique|index)_subquery,
+ without "checking NULL", remove the predicates that were pushed down
+ into the subquery.
+
+ If the subquery compares scalar values, we can remove the condition that
+ was wrapped into trig_cond (it will be checked when needed by the subquery
+ engine)
+
+ If the subquery compares row values, we need to keep the wrapped
+ equalities in the WHERE clause: when the left (outer) tuple has both NULL
+ and non-NULL values, we'll do a full table scan and will rely on the
+ equalities corresponding to non-NULL parts of left tuple to filter out
+ non-matching records.
+
+ TODO: We can remove the equalities that will be guaranteed to be true by the
+ fact that subquery engine will be using index lookup. This must be done only
+ for cases where there are no conversion errors of significance, e.g. 257
+ that is searched in a byte. But this requires homogenization of the return
+ codes of all Field*::store() methods.
+*/
+
+static void remove_subq_pushed_predicates(JOIN *join, Item **where)
+{
+ if (join->conds->type() == Item::FUNC_ITEM &&
+ ((Item_func *)join->conds)->functype() == Item_func::EQ_FUNC &&
+ ((Item_func *)join->conds)->arguments()[0]->type() == Item::REF_ITEM &&
+ ((Item_func *)join->conds)->arguments()[1]->type() == Item::FIELD_ITEM &&
+ test_if_ref (join->conds,
+ (Item_field *)((Item_func *)join->conds)->arguments()[1],
+ ((Item_func *)join->conds)->arguments()[0]))
+ {
+ *where= 0;
+ return;
+ }
+}
+
+
diff --git a/sql/opt_subselect.h b/sql/opt_subselect.h
new file mode 100644
index 00000000000..d8716dbb77f
--- /dev/null
+++ b/sql/opt_subselect.h
@@ -0,0 +1,368 @@
+/* */
+
+#ifdef USE_PRAGMA_INTERFACE
+#pragma interface /* gcc class implementation */
+#endif
+
+int check_and_do_in_subquery_rewrites(JOIN *join);
+bool convert_join_subqueries_to_semijoins(JOIN *join);
+int pull_out_semijoin_tables(JOIN *join);
+bool optimize_semijoin_nests(JOIN *join, table_map all_table_map);
+
+// used by Loose_scan_opt
+ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest,
+ table_map remaining_tables);
+
+/*
+ This is a class for considering possible loose index scan optimizations.
+ It's usage pattern is as follows:
+ best_access_path()
+ {
+ Loose_scan_opt opt;
+
+ opt.init()
+ for each index we can do ref access with
+ {
+ opt.next_ref_key();
+ for each keyuse
+ opt.add_keyuse();
+ opt.check_ref_access();
+ }
+
+ if (some criteria for range scans)
+ opt.check_range_access();
+
+ opt.get_best_option();
+ }
+*/
+
+class Loose_scan_opt
+{
+public:
+ /* All methods must check this before doing anything else */
+ bool try_loosescan;
+
+ /*
+ If we consider (oe1, .. oeN) IN (SELECT ie1, .. ieN) then ieK=oeK is
+ called sj-equality. If oeK depends only on preceding tables then such
+ equality is called 'bound'.
+ */
+ ulonglong bound_sj_equalities;
+
+ /* Accumulated properties of ref access we're now considering: */
+ ulonglong handled_sj_equalities;
+ key_part_map loose_scan_keyparts;
+ uint max_loose_keypart;
+ bool part1_conds_met;
+
+ /*
+ Use of quick select is a special case. Some of its properties:
+ */
+ uint quick_uses_applicable_index;
+ uint quick_max_loose_keypart;
+
+ /* Best loose scan method so far */
+ uint best_loose_scan_key;
+ double best_loose_scan_cost;
+ double best_loose_scan_records;
+ KEYUSE *best_loose_scan_start_key;
+
+ uint best_max_loose_keypart;
+
+ Loose_scan_opt():
+ try_loosescan(FALSE),
+ bound_sj_equalities(0),
+ quick_uses_applicable_index(FALSE)
+ {
+ UNINIT_VAR(quick_max_loose_keypart); /* Protected by quick_uses_applicable_index */
+ /* The following are protected by best_loose_scan_cost!= DBL_MAX */
+ UNINIT_VAR(best_loose_scan_key);
+ UNINIT_VAR(best_loose_scan_records);
+ UNINIT_VAR(best_max_loose_keypart);
+ UNINIT_VAR(best_loose_scan_start_key);
+ }
+
+ void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
+ {
+ /*
+ Discover the bound equalities. We need to do this if
+ 1. The next table is an SJ-inner table, and
+ 2. It is the first table from that semijoin, and
+ 3. We're not within a semi-join range (i.e. all semi-joins either have
+ all or none of their tables in join_table_map), except
+ s->emb_sj_nest (which we've just entered, see #2).
+ 4. All non-IN-equality correlation references from this sj-nest are
+ bound
+ 5. But some of the IN-equalities aren't (so this can't be handled by
+ FirstMatch strategy)
+ */
+ best_loose_scan_cost= DBL_MAX;
+ if (!join->emb_sjm_nest && s->emb_sj_nest && // (1)
+ s->emb_sj_nest->sj_in_exprs < 64 &&
+ ((remaining_tables & s->emb_sj_nest->sj_inner_tables) == // (2)
+ s->emb_sj_nest->sj_inner_tables) && // (2)
+ join->cur_sj_inner_tables == 0 && // (3)
+ !(remaining_tables &
+ s->emb_sj_nest->nested_join->sj_corr_tables) && // (4)
+ remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on &&// (5)
+ optimizer_flag(join->thd, OPTIMIZER_SWITCH_LOOSE_SCAN))
+ {
+ /* This table is an LooseScan scan candidate */
+ bound_sj_equalities= get_bound_sj_equalities(s->emb_sj_nest,
+ remaining_tables);
+ try_loosescan= TRUE;
+ DBUG_PRINT("info", ("Will try LooseScan scan, bound_map=%llx",
+ (longlong)bound_sj_equalities));
+ }
+ }
+
+ void next_ref_key()
+ {
+ handled_sj_equalities=0;
+ loose_scan_keyparts= 0;
+ max_loose_keypart= 0;
+ part1_conds_met= FALSE;
+ }
+
+ void add_keyuse(table_map remaining_tables, KEYUSE *keyuse)
+ {
+ if (try_loosescan && keyuse->sj_pred_no != UINT_MAX)
+ {
+ if (!(remaining_tables & keyuse->used_tables))
+ {
+ /*
+ This allows to use equality propagation to infer that some
+ sj-equalities are bound.
+ */
+ bound_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+ }
+ else
+ {
+ handled_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+ loose_scan_keyparts |= ((key_part_map)1) << keyuse->keypart;
+ set_if_bigger(max_loose_keypart, keyuse->keypart);
+ }
+ }
+ }
+
+ bool have_a_case() { return test(handled_sj_equalities); }
+
+ void check_ref_access_part1(JOIN_TAB *s, uint key, KEYUSE *start_key,
+ table_map found_part)
+ {
+ /*
+ Check if we can use LooseScan semi-join strategy. We can if
+ 1. This is the right table at right location
+ 2. All IN-equalities are either
+ - "bound", ie. the outer_expr part refers to the preceding tables
+ - "handled", ie. covered by the index we're considering
+ 3. Index order allows to enumerate subquery's duplicate groups in
+ order. This happens when the index definition matches this
+ pattern:
+
+ (handled_col|bound_col)* (other_col|bound_col)
+
+ */
+ if (try_loosescan && // (1)
+ (handled_sj_equalities | bound_sj_equalities) == // (2)
+ PREV_BITS(ulonglong, s->emb_sj_nest->sj_in_exprs) && // (2)
+ (PREV_BITS(key_part_map, max_loose_keypart+1) & // (3)
+ (found_part | loose_scan_keyparts)) == // (3)
+ (found_part | loose_scan_keyparts) && // (3)
+ !key_uses_partial_cols(s->table, key))
+ {
+ /* Ok, can use the strategy */
+ part1_conds_met= TRUE;
+ if (s->quick && s->quick->index == key &&
+ s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
+ {
+ quick_uses_applicable_index= TRUE;
+ quick_max_loose_keypart= max_loose_keypart;
+ }
+ DBUG_PRINT("info", ("Can use LooseScan scan"));
+
+ /*
+ Check if this is a special case where there are no usable bound
+ IN-equalities, i.e. we have
+
+ outer_expr IN (SELECT innertbl.key FROM ...)
+
+ and outer_expr cannot be evaluated yet, so it's actually full
+ index scan and not a ref access
+ */
+ if (!(found_part & 1 ) && /* no usable ref access for 1st key part */
+ s->table->covering_keys.is_set(key))
+ {
+ DBUG_PRINT("info", ("Can use full index scan for LooseScan"));
+
+ /* Calculate the cost of complete loose index scan. */
+ double records= rows2double(s->table->file->stats.records);
+
+ /* The cost is entire index scan cost (divided by 2) */
+ double read_time= s->table->file->index_only_read_time(key, records);
+
+ /*
+ Now find out how many different keys we will get (for now we
+ ignore the fact that we have "keypart_i=const" restriction for
+ some key components, that may make us think think that loose
+ scan will produce more distinct records than it actually will)
+ */
+ ulong rpc;
+ if ((rpc= s->table->key_info[key].rec_per_key[max_loose_keypart]))
+ records= records / rpc;
+
+ // TODO: previous version also did /2
+ if (read_time < best_loose_scan_cost)
+ {
+ best_loose_scan_key= key;
+ best_loose_scan_cost= read_time;
+ best_loose_scan_records= records;
+ best_max_loose_keypart= max_loose_keypart;
+ best_loose_scan_start_key= start_key;
+ }
+ }
+ }
+ }
+
+ void check_ref_access_part2(uint key, KEYUSE *start_key, double records,
+ double read_time)
+ {
+ if (part1_conds_met && read_time < best_loose_scan_cost)
+ {
+ /* TODO use rec-per-key-based fanout calculations */
+ best_loose_scan_key= key;
+ best_loose_scan_cost= read_time;
+ best_loose_scan_records= records;
+ best_max_loose_keypart= max_loose_keypart;
+ best_loose_scan_start_key= start_key;
+ }
+ }
+
+ void check_range_access(JOIN *join, uint idx, QUICK_SELECT_I *quick)
+ {
+ /* TODO: this the right part restriction: */
+ if (quick_uses_applicable_index && idx == join->const_tables &&
+ quick->read_time < best_loose_scan_cost)
+ {
+ best_loose_scan_key= quick->index;
+ best_loose_scan_cost= quick->read_time;
+ /* this is ok because idx == join->const_tables */
+ best_loose_scan_records= rows2double(quick->records);
+ best_max_loose_keypart= quick_max_loose_keypart;
+ best_loose_scan_start_key= NULL;
+ }
+ }
+
+ void save_to_position(JOIN_TAB *tab, POSITION *pos)
+ {
+ pos->read_time= best_loose_scan_cost;
+ if (best_loose_scan_cost != DBL_MAX)
+ {
+ pos->records_read= best_loose_scan_records;
+ pos->key= best_loose_scan_start_key;
+ pos->loosescan_key= best_loose_scan_key;
+ pos->loosescan_parts= best_max_loose_keypart + 1;
+ pos->use_join_buffer= FALSE;
+ pos->table= tab;
+ // todo need ref_depend_map ?
+ DBUG_PRINT("info", ("Produced a LooseScan plan, key %s, %s",
+ tab->table->key_info[best_loose_scan_key].name,
+ best_loose_scan_start_key? "(ref access)":
+ "(range/index access)"));
+ }
+ }
+};
+
+
+void advance_sj_state(JOIN *join, const table_map remaining_tables,
+ const JOIN_TAB *new_join_tab, uint idx,
+ double *current_record_count, double *current_read_time,
+ POSITION *loose_scan_pos);
+void restore_prev_sj_state(const table_map remaining_tables,
+ const JOIN_TAB *tab, uint idx);
+
+void fix_semijoin_strategies_for_picked_join_order(JOIN *join);
+bool setup_sj_materialization(JOIN_TAB *tab);
+
+TABLE *create_duplicate_weedout_tmp_table(THD *thd, uint uniq_tuple_length_arg,
+ SJ_TMP_TABLE *sjtbl);
+int do_sj_reset(SJ_TMP_TABLE *sj_tbl);
+int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl);
+
+/*
+ Temporary table used by semi-join DuplicateElimination strategy
+
+ This consists of the temptable itself and data needed to put records
+ into it. The table's DDL is as follows:
+
+ CREATE TABLE tmptable (col VARCHAR(n) BINARY, PRIMARY KEY(col));
+
+ where the primary key can be replaced with unique constraint if n exceeds
+ the limit (as it is always done for query execution-time temptables).
+
+ The record value is a concatenation of rowids of tables from the join we're
+ executing. If a join table is on the inner side of the outer join, we
+ assume that its rowid can be NULL and provide means to store this rowid in
+ the tuple.
+*/
+
+class SJ_TMP_TABLE : public Sql_alloc
+{
+public:
+ /*
+ Array of pointers to tables whose rowids compose the temporary table
+ record.
+ */
+ class TAB
+ {
+ public:
+ JOIN_TAB *join_tab;
+ uint rowid_offset;
+ ushort null_byte;
+ uchar null_bit;
+ };
+ TAB *tabs;
+ TAB *tabs_end;
+
+ /*
+ is_degenerate==TRUE means this is a special case where the temptable record
+ has zero length (and presence of a unique key means that the temptable can
+ have either 0 or 1 records).
+ In this case we don't create the physical temptable but instead record
+ its state in SJ_TMP_TABLE::have_degenerate_row.
+ */
+ bool is_degenerate;
+
+ /*
+ When is_degenerate==TRUE: the contents of the table (whether it has the
+ record or not).
+ */
+ bool have_degenerate_row;
+
+ /* table record parameters */
+ uint null_bits;
+ uint null_bytes;
+ uint rowid_len;
+
+ /* The temporary table itself (NULL means not created yet) */
+ TABLE *tmp_table;
+
+ /*
+ These are the members we got from temptable creation code. We'll need
+ them if we'll need to convert table from HEAP to MyISAM/Maria.
+ */
+ ENGINE_COLUMNDEF *start_recinfo;
+ ENGINE_COLUMNDEF *recinfo;
+
+ /* Pointer to next table (next->start_idx > this->end_idx) */
+ SJ_TMP_TABLE *next;
+};
+
+int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
+ uint no_jbuf_after);
+void destroy_sj_tmp_tables(JOIN *join);
+int clear_sj_tmp_tables(JOIN *join);
+int rewrite_to_index_subquery_engine(JOIN *join);
+
+
+
diff --git a/sql/sql_join_cache.cc b/sql/sql_join_cache.cc
index 79234e6f7bf..cf7da5b5566 100644
--- a/sql/sql_join_cache.cc
+++ b/sql/sql_join_cache.cc
@@ -29,6 +29,7 @@
#include "mysql_priv.h"
#include "sql_select.h"
+#include "opt_subselect.h"
/*****************************************************************************
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index 2bb9fd80bfd..a4b46c54ad3 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -605,7 +605,7 @@ public:
List<TABLE_LIST> top_join_list; /* join list of the top level */
List<TABLE_LIST> *join_list; /* list for the currently parsed join */
TABLE_LIST *embedding; /* table embedding to the above list */
- List<TABLE_LIST> sj_nests;
+ List<TABLE_LIST> sj_nests; /* Semi-join nests within this join */
/*
Beginning of the list of leaves in a FROM clause, where the leaves
inlcude all base tables including view tables. The tables are connected
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index bb086a5d43a..30e06a9e7f7 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -31,17 +31,12 @@
#include "mysql_priv.h"
#include "sql_select.h"
#include "sql_cursor.h"
+#include "opt_subselect.h"
#include <m_ctype.h>
#include <my_bit.h>
#include <hash.h>
#include <ft_global.h>
-#if defined(WITH_MARIA_STORAGE_ENGINE) && defined(USE_MARIA_FOR_TMP_TABLES)
-#include "../storage/maria/ha_maria.h"
-#define TMP_ENGINE_HTON maria_hton
-#else
-#define TMP_ENGINE_HTON myisam_hton
-#endif
const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
"MAYBE_REF","ALL","range","index","fulltext",
@@ -54,7 +49,6 @@ struct st_sargable_param;
static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array);
static bool make_join_statistics(JOIN *join, TABLE_LIST *leaves, COND *conds,
DYNAMIC_ARRAY *keyuse);
-static bool optimize_semijoin_nests(JOIN *join, table_map all_table_map);
static bool update_ref_and_keys(THD *thd, DYNAMIC_ARRAY *keyuse,
JOIN_TAB *join_tab,
uint tables, COND *conds,
@@ -64,9 +58,9 @@ static bool update_ref_and_keys(THD *thd, 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);
-static bool choose_plan(JOIN *join,table_map join_tables);
+bool choose_plan(JOIN *join,table_map join_tables);
-static void best_access_path(JOIN *join, JOIN_TAB *s,
+void best_access_path(JOIN *join, JOIN_TAB *s,
table_map remaining_tables, uint idx,
bool disable_jbuf, double record_count,
POSITION *pos, POSITION *loose_scan_pos);
@@ -123,23 +117,11 @@ static void restore_prev_nj_state(JOIN_TAB *last);
static uint reset_nj_counters(JOIN *join, List<TABLE_LIST> *join_list);
static uint build_bitmap_for_nested_joins(List<TABLE_LIST> *join_list,
uint first_unused);
-static
-void advance_sj_state(JOIN *join, const table_map remaining_tables,
- const JOIN_TAB *new_join_tab, uint idx,
- double *current_record_count, double *current_read_time,
- POSITION *loose_scan_pos);
-static void restore_prev_sj_state(const table_map remaining_tables,
- const JOIN_TAB *tab, uint idx);
static COND *optimize_cond(JOIN *join, COND *conds,
List<TABLE_LIST> *join_list,
Item::cond_result *cond_value);
static bool const_expression_in_where(COND *conds,Item *item, Item **comp_item);
-static bool open_tmp_table(TABLE *table);
-static bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
- ENGINE_COLUMNDEF *start_recinfo,
- ENGINE_COLUMNDEF **recinfo,
- ulonglong options);
static bool
create_internal_tmp_table_from_heap2(THD *thd, TABLE *table,
ENGINE_COLUMNDEF *start_recinfo,
@@ -250,23 +232,12 @@ static bool init_sum_functions(Item_sum **func, Item_sum **end);
static bool update_sum_func(Item_sum **func);
static void select_describe(JOIN *join, bool need_tmp_table,bool need_order,
bool distinct, const char *message=NullS);
-static Item *remove_additional_cond(Item* conds);
static void add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab);
-static bool test_if_ref(COND *root_cond,
- Item_field *left_item,Item *right_item);
-static bool replace_where_subcondition(JOIN *join, Item **expr,
- Item *old_cond, Item *new_cond,
- bool do_fix_fields);
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);
-static
-bool subquery_types_allow_materialization(Item_in_subselect *in_subs);
-int do_sj_reset(SJ_TMP_TABLE *sj_tbl);
-TABLE *create_duplicate_weedout_tmp_table(THD *thd, uint uniq_tuple_length_arg,
- SJ_TMP_TABLE *sjtbl);
Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
bool *inherited_fl);
@@ -472,6 +443,7 @@ inline int setup_without_group(THD *thd, Item **ref_pointer_array,
mysql_select assumes that all tables are already opened
*****************************************************************************/
+
/**
Prepare of whole select (including sub queries in future).
@@ -558,173 +530,13 @@ JOIN::prepare(Item ***rref_pointer_array,
DBUG_RETURN(-1); /* purecov: inspected */
thd->lex->allow_sum_func= save_allow_sum_func;
}
-
- /*
- If
- 1) this join is inside a subquery (of any type except FROM-clause
- subquery) and
- 2) we aren't just normalizing a VIEW
-
- Then perform early unconditional subquery transformations:
- - Convert subquery predicate into semi-join, or
- - Mark the subquery for execution using materialization, or
- - Perform IN->EXISTS transformation, or
- - Perform more/less ALL/ANY -> MIN/MAX rewrite
- - Substitute trivial scalar-context subquery with its value
-
- 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)
- {
- Item_in_subselect *in_subs= NULL;
- if (subselect->substype() == Item_subselect::IN_SUBS)
- in_subs= (Item_in_subselect*)subselect;
-
- /* Resolve expressions and perform semantic analysis for IN query */
- if (in_subs != NULL)
- /*
- TODO: Add the condition below to this if statement when we have proper
- support for is_correlated handling for materialized semijoins.
- If we were to add this condition now, the fix_fields() call in
- convert_subq_to_sj() would force the flag is_correlated to be set
- erroneously for prepared queries.
-
- thd->stmt_arena->state != Query_arena::PREPARED)
- */
- {
- /*
- Check if the left and right expressions have the same # of
- columns, i.e. we don't have a case like
- (oe1, oe2) IN (SELECT ie1, ie2, ie3 ...)
-
- TODO why do we have this duplicated in IN->EXISTS transformers?
- psergey-todo: fix these: grep for duplicated_subselect_card_check
- */
- if (select_lex->item_list.elements != in_subs->left_expr->cols())
- {
- my_error(ER_OPERAND_COLUMNS, MYF(0), in_subs->left_expr->cols());
- DBUG_RETURN(-1);
- }
-
- SELECT_LEX *current= thd->lex->current_select;
- thd->lex->current_select= current->return_after_parsing();
- char const *save_where= thd->where;
- thd->where= "IN/ALL/ANY subquery";
-
- bool failure= !in_subs->left_expr->fixed &&
- in_subs->left_expr->fix_fields(thd, &in_subs->left_expr);
- thd->lex->current_select= current;
- thd->where= save_where;
- if (failure)
- DBUG_RETURN(-1); /* purecov: deadcode */
- }
- 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
- semi-join (which is done in flatten_subqueries()). The
- requirements are:
- 1. Subquery predicate is an IN/=ANY subq predicate
- 2. Subquery is a single SELECT (not a UNION)
- 3. Subquery does not have GROUP BY or ORDER BY
- 4. Subquery does not use aggregate functions or HAVING
- 5. Subquery predicate is at the AND-top-level of ON/WHERE clause
- 6. We are not in a subquery of a single table UPDATE/DELETE that
- doesn't have a JOIN (TODO: We should handle this at some
- point by switching to multi-table UPDATE/DELETE)
- 7. We're not in a confluent table-less subquery, like "SELECT 1".
- 8. No execution method was already chosen (by a prepared statement)
- 9. Parent select is not a confluent table-less select
- 10. Neither parent nor child select have STRAIGHT_JOIN option.
- */
- if (optimizer_flag(thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
- in_subs && // 1
- !select_lex->is_part_of_union() && // 2
- !select_lex->group_list.elements && !order && // 3
- !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
- in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED && // 8
- select_lex->outer_select()->leaf_tables && // 9
- !((select_options | select_lex->outer_select()->join->select_options)
- & SELECT_STRAIGHT_JOIN)) // 10
- {
- DBUG_PRINT("info", ("Subquery is semi-join conversion candidate"));
-
- (void)subquery_types_allow_materialization(in_subs);
-
- in_subs->emb_on_expr_nest= thd->thd_marker.emb_on_expr_nest;
-
- /* Register the subquery for further processing in flatten_subqueries() */
- select_lex->
- outer_select()->join->sj_subselects.append(thd->mem_root, in_subs);
- in_subs->expr_join_nest= thd->thd_marker.emb_on_expr_nest;
- }
- 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 confluent 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. Subquery predicate is a top-level predicate
- (this implies it is not negated)
- TODO: this is a limitation that should be lifted once we
- implement correct NULL semantics (WL#3830)
- 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).
-
- (*) The subquery must be part of a SELECT statement. The current
- condition also excludes multi-table update statements.
-
- We have to 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
- select_lex->master_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?
- if (in_subs->is_top_level_item() && // 4
- !in_subs->is_correlated && // 5
- in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6
- in_subs->exec_method= Item_in_subselect::MATERIALIZATION;
- }
-
- Item_subselect::trans_res trans_res;
- if ((trans_res= subselect->select_transformer(this)) !=
- Item_subselect::RES_OK)
- {
- select_lex->fix_prepare_information(thd, &conds, &having);
- DBUG_RETURN((trans_res == Item_subselect::RES_ERROR));
- }
- }
- }
+
+ int res= check_and_do_in_subquery_rewrites(this);
select_lex->fix_prepare_information(thd, &conds, &having);
+
+ if (res)
+ DBUG_RETURN(res);
if (order)
{
@@ -856,535 +668,6 @@ err:
/**
- @brief Check if subquery's compared types allow materialization.
-
- @param in_subs Subquery predicate, updated as follows:
- types_allow_materialization TRUE if subquery materialization is allowed.
- sjm_scan_allowed If types_allow_materialization is TRUE,
- indicates whether it is possible to use subquery
- materialization and scan the materialized table.
-
- @retval TRUE If subquery types allow materialization.
- @retval FALSE Otherwise.
-
- @details
- This is a temporary fix for BUG#36752.
-
- There are two subquery materialization strategies:
-
- 1. Materialize and do index lookups in the materialized table. See
- BUG#36752 for description of restrictions we need to put on the
- compared expressions.
-
- 2. Materialize and then do a full scan of the materialized table. At the
- moment, this strategy's applicability criteria are even stricter than
- in #1.
-
- This is so because of the following: consider an uncorrelated subquery
-
- ...WHERE (ot1.col1, ot2.col2 ...) IN (SELECT ie1,ie2,... FROM it1 ...)
-
- and a join order that could be used to do sjm-materialization:
-
- SJM-Scan(it1, it1), ot1, ot2
-
- IN-equalities will be parts of conditions attached to the outer tables:
-
- ot1: ot1.col1 = ie1 AND ... (C1)
- ot2: ot1.col2 = ie2 AND ... (C2)
-
- besides those there may be additional references to ie1 and ie2
- generated by equality propagation. The problem with evaluating C1 and
- C2 is that ie{1,2} refer to subquery tables' columns, while we only have
- current value of materialization temptable. Our solution is to
- * require that all ie{N} are table column references. This allows
- to copy the values of materialization temptable columns to the
- original table's columns (see setup_sj_materialization for more
- details)
- * require that compared columns have exactly the same type. This is
- a temporary measure to avoid BUG#36752-type problems.
-*/
-
-static
-bool subquery_types_allow_materialization(Item_in_subselect *in_subs)
-{
- DBUG_ENTER("subquery_types_allow_materialization");
-
- DBUG_ASSERT(in_subs->left_expr->fixed);
-
- List_iterator<Item> it(in_subs->unit->first_select()->item_list);
- uint elements= in_subs->unit->first_select()->item_list.elements;
-
- in_subs->types_allow_materialization= FALSE; // Assign default values
- in_subs->sjm_scan_allowed= FALSE;
-
- bool all_are_fields= TRUE;
- for (uint i= 0; i < elements; i++)
- {
- Item *outer= in_subs->left_expr->element_index(i);
- Item *inner= it++;
- all_are_fields &= (outer->real_item()->type() == Item::FIELD_ITEM &&
- inner->real_item()->type() == Item::FIELD_ITEM);
- if (outer->result_type() != inner->result_type())
- DBUG_RETURN(FALSE);
- switch (outer->result_type()) {
- case STRING_RESULT:
- if (outer->is_datetime() != inner->is_datetime())
- DBUG_RETURN(FALSE);
-
- if (!(outer->collation.collation == inner->collation.collation
- /*&& outer->max_length <= inner->max_length */))
- DBUG_RETURN(FALSE);
- /*case INT_RESULT:
- if (!(outer->unsigned_flag ^ inner->unsigned_flag))
- DBUG_RETURN(FALSE); */
- default:
- ;/* suitable for materialization */
- }
- }
- in_subs->types_allow_materialization= TRUE;
- in_subs->sjm_scan_allowed= all_are_fields;
- DBUG_PRINT("info",("subquery_types_allow_materialization: ok, allowed"));
- DBUG_RETURN(TRUE);
-}
-
-
-/*
- Remove the predicates pushed down into the subquery
-
- SYNOPSIS
- JOIN::remove_subq_pushed_predicates()
- where IN Must be NULL
- OUT The remaining WHERE condition, or NULL
-
- DESCRIPTION
- Given that this join will be executed using (unique|index)_subquery,
- without "checking NULL", remove the predicates that were pushed down
- into the subquery.
-
- If the subquery compares scalar values, we can remove the condition that
- was wrapped into trig_cond (it will be checked when needed by the subquery
- engine)
-
- If the subquery compares row values, we need to keep the wrapped
- equalities in the WHERE clause: when the left (outer) tuple has both NULL
- and non-NULL values, we'll do a full table scan and will rely on the
- equalities corresponding to non-NULL parts of left tuple to filter out
- non-matching records.
-
- TODO: We can remove the equalities that will be guaranteed to be true by the
- fact that subquery engine will be using index lookup. This must be done only
- for cases where there are no conversion errors of significance, e.g. 257
- that is searched in a byte. But this requires homogenization of the return
- codes of all Field*::store() methods.
-*/
-
-void JOIN::remove_subq_pushed_predicates(Item **where)
-{
- if (conds->type() == Item::FUNC_ITEM &&
- ((Item_func *)this->conds)->functype() == Item_func::EQ_FUNC &&
- ((Item_func *)conds)->arguments()[0]->type() == Item::REF_ITEM &&
- ((Item_func *)conds)->arguments()[1]->type() == Item::FIELD_ITEM &&
- test_if_ref (this->conds,
- (Item_field *)((Item_func *)conds)->arguments()[1],
- ((Item_func *)conds)->arguments()[0]))
- {
- *where= 0;
- return;
- }
-}
-
-
-/*
- Index lookup-based subquery: save some flags for EXPLAIN output
-
- SYNOPSIS
- save_index_subquery_explain_info()
- join_tab Subquery's join tab (there is only one as index lookup is
- only used for subqueries that are single-table SELECTs)
- where Subquery's WHERE clause
-
- DESCRIPTION
- For index lookup-based subquery (i.e. one executed with
- subselect_uniquesubquery_engine or subselect_indexsubquery_engine),
- check its EXPLAIN output row should contain
- "Using index" (TAB_INFO_FULL_SCAN_ON_NULL)
- "Using Where" (TAB_INFO_USING_WHERE)
- "Full scan on NULL key" (TAB_INFO_FULL_SCAN_ON_NULL)
- and set appropriate flags in join_tab->packed_info.
-*/
-
-static void save_index_subquery_explain_info(JOIN_TAB *join_tab, Item* where)
-{
- join_tab->packed_info= TAB_INFO_HAVE_VALUE;
- if (join_tab->table->covering_keys.is_set(join_tab->ref.key))
- join_tab->packed_info |= TAB_INFO_USING_INDEX;
- if (where)
- join_tab->packed_info |= TAB_INFO_USING_WHERE;
- for (uint i = 0; i < join_tab->ref.key_parts; i++)
- {
- if (join_tab->ref.cond_guards[i])
- {
- join_tab->packed_info |= TAB_INFO_FULL_SCAN_ON_NULL;
- break;
- }
- }
-}
-
-
-/*
- Check if the table's rowid is included in the temptable
-
- SYNOPSIS
- sj_table_is_included()
- join The join
- join_tab The table to be checked
-
- DESCRIPTION
- SemiJoinDuplicateElimination: check the table's rowid should be included
- in the temptable. This is so if
-
- 1. The table is not embedded within some semi-join nest
- 2. The has been pulled out of a semi-join nest, or
-
- 3. The table is functionally dependent on some previous table
-
- [4. This is also true for constant tables that can't be
- NULL-complemented but this function is not called for such tables]
-
- RETURN
- TRUE - Include table's rowid
- FALSE - Don't
-*/
-
-static bool sj_table_is_included(JOIN *join, JOIN_TAB *join_tab)
-{
- if (join_tab->emb_sj_nest)
- return FALSE;
-
- /* Check if this table is functionally dependent on the tables that
- are within the same outer join nest
- */
- TABLE_LIST *embedding= join_tab->table->pos_in_table_list->embedding;
- if (join_tab->type == JT_EQ_REF)
- {
- table_map depends_on= 0;
- uint idx;
-
- for (uint kp= 0; kp < join_tab->ref.key_parts; kp++)
- depends_on |= join_tab->ref.items[kp]->used_tables();
-
- Table_map_iterator it(depends_on & ~PSEUDO_TABLE_BITS);
- while ((idx= it.next_bit())!=Table_map_iterator::BITMAP_END)
- {
- JOIN_TAB *ref_tab= join->map2table[idx];
- if (embedding != ref_tab->table->pos_in_table_list->embedding)
- return TRUE;
- }
- /* Ok, functionally dependent */
- return FALSE;
- }
- /* Not functionally dependent => need to include*/
- return TRUE;
-}
-
-
-/*
- Setup the strategies to eliminate semi-join duplicates.
-
- SYNOPSIS
- setup_semijoin_dups_elimination()
- join Join to process
- options Join options (needed to see if join buffering will be
- used or not)
- no_jbuf_after Another bit of information re where join buffering will
- be used.
-
- DESCRIPTION
- Setup the strategies to eliminate semi-join duplicates. ATM there are 4
- strategies:
-
- 1. DuplicateWeedout (use of temptable to remove duplicates based on rowids
- of row combinations)
- 2. FirstMatch (pick only the 1st matching row combination of inner tables)
- 3. LooseScan (scanning the sj-inner table in a way that groups duplicates
- together and picking the 1st one)
- 4. SJ-Materialization.
-
- The join order has "duplicate-generating ranges", and every range is
- served by one strategy or a combination of FirstMatch with with some
- other strategy.
-
- "Duplicate-generating range" is defined as a range within the join order
- that contains all of the inner tables of a semi-join. All ranges must be
- disjoint, if tables of several semi-joins are interleaved, then the ranges
- are joined together, which is equivalent to converting
- SELECT ... WHERE oe1 IN (SELECT ie1 ...) AND oe2 IN (SELECT ie2 )
- to
- SELECT ... WHERE (oe1, oe2) IN (SELECT ie1, ie2 ... ...)
- .
-
- Applicability conditions are as follows:
-
- DuplicateWeedout strategy
- ~~~~~~~~~~~~~~~~~~~~~~~~~
-
- (ot|nt)* [ it ((it|ot|nt)* (it|ot))] (nt)*
- +------+ +=========================+ +---+
- (1) (2) (3)
-
- (1) - Prefix of OuterTables (those that participate in
- IN-equality and/or are correlated with subquery) and outer
- Non-correlated tables.
- (2) - The handled range. The range starts with the first sj-inner
- table, and covers all sj-inner and outer tables
- Within the range, Inner, Outer, outer non-correlated tables
- may follow in any order.
- (3) - The suffix of outer non-correlated tables.
-
- FirstMatch strategy
- ~~~~~~~~~~~~~~~~~~~
-
- (ot|nt)* [ it ((it|nt)* it) ] (nt)*
- +------+ +==================+ +---+
- (1) (2) (3)
-
- (1) - Prefix of outer and non-correlated tables
- (2) - The handled range, which may contain only inner and
- non-correlated tables.
- (3) - The suffix of outer non-correlated tables.
-
- LooseScan strategy
- ~~~~~~~~~~~~~~~~~~
-
- (ot|ct|nt) [ loosescan_tbl (ot|nt|it)* it ] (ot|nt)*
- +--------+ +===========+ +=============+ +------+
- (1) (2) (3) (4)
-
- (1) - Prefix that may contain any outer tables. The prefix must contain
- all the non-trivially correlated outer tables. (non-trivially means
- that the correlation is not just through the IN-equality).
-
- (2) - Inner table for which the LooseScan scan is performed.
-
- (3) - The remainder of the duplicate-generating range. It is served by
- application of FirstMatch strategy, with the exception that
- outer IN-correlated tables are considered to be non-correlated.
-
- (4) - THe suffix of outer and outer non-correlated tables.
-
-
- The choice between the strategies is made by the join optimizer (see
- advance_sj_state() and fix_semijoin_strategies_for_picked_join_order()).
- This function sets up all fields/structures/etc needed for execution except
- for setup/initialization of semi-join materialization which is done in
- setup_sj_materialization() (todo: can't we move that to here also?)
-
- RETURN
- FALSE OK
- TRUE Out of memory error
-*/
-
-int setup_semijoin_dups_elimination(JOIN *join, ulonglong options,
- uint no_jbuf_after)
-{
- uint i;
- THD *thd= join->thd;
- DBUG_ENTER("setup_semijoin_dups_elimination");
-
- for (i= join->const_tables ; i < join->tables ; i++)
- {
- JOIN_TAB *tab=join->join_tab + i;
- POSITION *pos= join->best_positions + i;
- uint keylen, keyno;
- switch (pos->sj_strategy) {
- case SJ_OPT_MATERIALIZE:
- case SJ_OPT_MATERIALIZE_SCAN:
- /* Do nothing */
- i += pos->n_sj_tables;
- break;
- case SJ_OPT_LOOSE_SCAN:
- {
- /* We jump from the last table to the first one */
- tab->loosescan_match_tab= tab + pos->n_sj_tables - 1;
-
- /* Calculate key length */
- keylen= 0;
- keyno= pos->loosescan_key;
- for (uint kp=0; kp < pos->loosescan_parts; kp++)
- keylen += tab->table->key_info[keyno].key_part[kp].store_length;
-
- tab->loosescan_key_len= keylen;
- if (pos->n_sj_tables > 1)
- tab[pos->n_sj_tables - 1].do_firstmatch= tab;
- i += pos->n_sj_tables;
- break;
- }
- case SJ_OPT_DUPS_WEEDOUT:
- {
- /*
- Check for join buffering. If there is one, move the first table
- forwards, but do not destroy other duplicate elimination methods.
- */
- uint first_table= i;
- for (uint j= i; j < i + pos->n_sj_tables; j++)
- {
- if (join->best_positions[j].use_join_buffer && j <= no_jbuf_after)
- {
- first_table= join->const_tables;
- break;
- }
- }
-
- SJ_TMP_TABLE::TAB sjtabs[MAX_TABLES];
- SJ_TMP_TABLE::TAB *last_tab= sjtabs;
- uint jt_rowid_offset= 0; // # tuple bytes are already occupied (w/o NULL bytes)
- uint jt_null_bits= 0; // # null bits in tuple bytes
- /*
- Walk through the range and remember
- - tables that need their rowids to be put into temptable
- - the last outer table
- */
- for (JOIN_TAB *j=join->join_tab + first_table;
- j < join->join_tab + i + pos->n_sj_tables; j++)
- {
- if (sj_table_is_included(join, j))
- {
- last_tab->join_tab= j;
- last_tab->rowid_offset= jt_rowid_offset;
- jt_rowid_offset += j->table->file->ref_length;
- if (j->table->maybe_null)
- {
- last_tab->null_byte= jt_null_bits / 8;
- last_tab->null_bit= jt_null_bits++;
- }
- last_tab++;
- j->table->prepare_for_position();
- j->keep_current_rowid= TRUE;
- }
- }
-
- SJ_TMP_TABLE *sjtbl;
- if (jt_rowid_offset) /* Temptable has at least one rowid */
- {
- uint tabs_size= (last_tab - sjtabs) * sizeof(SJ_TMP_TABLE::TAB);
- if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))) ||
- !(sjtbl->tabs= (SJ_TMP_TABLE::TAB*) thd->alloc(tabs_size)))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- memcpy(sjtbl->tabs, sjtabs, tabs_size);
- sjtbl->is_confluent= FALSE;
- sjtbl->tabs_end= sjtbl->tabs + (last_tab - sjtabs);
- sjtbl->rowid_len= jt_rowid_offset;
- sjtbl->null_bits= jt_null_bits;
- sjtbl->null_bytes= (jt_null_bits + 7)/8;
- sjtbl->tmp_table=
- create_duplicate_weedout_tmp_table(thd,
- sjtbl->rowid_len +
- sjtbl->null_bytes,
- sjtbl);
- join->sj_tmp_tables.push_back(sjtbl->tmp_table);
- }
- else
- {
- /*
- This is confluent case where the entire subquery predicate does
- not depend on anything at all, ie this is
- WHERE const IN (uncorrelated select)
- */
- if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- sjtbl->tmp_table= NULL;
- sjtbl->is_confluent= TRUE;
- sjtbl->have_confluent_row= FALSE;
- }
- join->join_tab[first_table].flush_weedout_table= sjtbl;
- join->join_tab[i + pos->n_sj_tables - 1].check_weed_out_table= sjtbl;
-
- i += pos->n_sj_tables;
- break;
- }
- case SJ_OPT_FIRST_MATCH:
- {
- JOIN_TAB *j, *jump_to= tab-1;
- for (j= tab; j != tab + pos->n_sj_tables; j++)
- {
- if (!tab->emb_sj_nest)
- jump_to= tab;
- else
- {
- j->first_sj_inner_tab= tab;
- j->last_sj_inner_tab= tab + pos->n_sj_tables - 1;
- }
- }
- j[-1].do_firstmatch= jump_to;
- i += pos->n_sj_tables;
- break;
- }
- case SJ_OPT_NONE:
- break;
- }
- }
- DBUG_RETURN(FALSE);
-}
-
-
-/*
- Destroy all temporary tables created by NL-semijoin runtime
-*/
-
-static void destroy_sj_tmp_tables(JOIN *join)
-{
- List_iterator<TABLE> it(join->sj_tmp_tables);
- TABLE *table;
- while ((table= it++))
- {
- /*
- SJ-Materialization tables are initialized for either sequential reading
- or index lookup, DuplicateWeedout tables are not initialized for read
- (we only write to them), so need to call ha_index_or_rnd_end.
- */
- table->file->ha_index_or_rnd_end();
- free_tmp_table(join->thd, table);
- }
- join->sj_tmp_tables.empty();
- join->sjm_info_list.empty();
-}
-
-
-/*
- Remove all records from all temp tables used by NL-semijoin runtime
-
- SYNOPSIS
- clear_sj_tmp_tables()
- join The join to remove tables for
-
- DESCRIPTION
- Remove all records from all temp tables used by NL-semijoin runtime. This
- must be done before every join re-execution.
-*/
-
-static int clear_sj_tmp_tables(JOIN *join)
-{
- int res;
- List_iterator<TABLE> it(join->sj_tmp_tables);
- TABLE *table;
- while ((table= it++))
- {
- if ((res= table->file->ha_delete_all_rows()))
- return res; /* purecov: inspected */
- }
-
- SJ_MATERIALIZATION_INFO *sjm;
- List_iterator<SJ_MATERIALIZATION_INFO> it2(join->sjm_info_list);
- while ((sjm= it2++))
- {
- sjm->materialized= FALSE;
- }
- return 0;
-}
-
-
-/**
global select optimisation.
@note
@@ -1411,7 +694,7 @@ JOIN::optimize()
thd_proc_info(thd, "optimizing");
/* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
- if (flatten_subqueries())
+ if (convert_join_subqueries_to_semijoins(this))
DBUG_RETURN(1); /* purecov: inspected */
/* dump_TABLE_LIST_graph(select_lex, select_lex->leaf_tables); */
@@ -1926,66 +1209,10 @@ JOIN::optimize()
/* Create all structures needed for materialized subquery execution. */
if (setup_subquery_materialization())
DBUG_RETURN(1);
-
- /*
- is this simple IN subquery?
- */
- if (!group_list && !order &&
- unit->item && unit->item->substype() == Item_subselect::IN_SUBS &&
- tables == 1 && conds &&
- !unit->is_union())
- {
- if (!having)
- {
- Item *where= conds;
- if (join_tab[0].type == JT_EQ_REF &&
- join_tab[0].ref.items[0]->name == in_left_expr_name)
- {
- remove_subq_pushed_predicates(&where);
- save_index_subquery_explain_info(join_tab, where);
- join_tab[0].type= JT_UNIQUE_SUBQUERY;
- error= 0;
- DBUG_RETURN(unit->item->
- change_engine(new
- subselect_uniquesubquery_engine(thd,
- join_tab,
- unit->item,
- where)));
- }
- else if (join_tab[0].type == JT_REF &&
- join_tab[0].ref.items[0]->name == in_left_expr_name)
- {
- remove_subq_pushed_predicates(&where);
- save_index_subquery_explain_info(join_tab, where);
- join_tab[0].type= JT_INDEX_SUBQUERY;
- error= 0;
- DBUG_RETURN(unit->item->
- change_engine(new
- subselect_indexsubquery_engine(thd,
- join_tab,
- unit->item,
- where,
- NULL,
- 0)));
- }
- } else if (join_tab[0].type == JT_REF_OR_NULL &&
- join_tab[0].ref.items[0]->name == in_left_expr_name &&
- having->name == in_having_cond)
- {
- join_tab[0].type= JT_INDEX_SUBQUERY;
- error= 0;
- conds= remove_additional_cond(conds);
- save_index_subquery_explain_info(join_tab, conds);
- DBUG_RETURN(unit->item->
- change_engine(new subselect_indexsubquery_engine(thd,
- join_tab,
- unit->item,
- conds,
- having,
- 1)));
- }
-
- }
+
+ int res;
+ if ((res= rewrite_to_index_subquery_engine(this)) != -1)
+ DBUG_RETURN(res);
/*
Need to tell handlers that to play it safe, it should fetch all
columns of the primary key of the tables: this is because MySQL may
@@ -3118,537 +2345,6 @@ err:
}
-int subq_sj_candidate_cmp(Item_in_subselect* const *el1,
- Item_in_subselect* const *el2)
-{
- return ((*el1)->sj_convert_priority < (*el2)->sj_convert_priority) ? 1 :
- ( ((*el1)->sj_convert_priority == (*el2)->sj_convert_priority)? 0 : -1);
-}
-
-
-inline Item * and_items(Item* cond, Item *item)
-{
- return (cond? (new Item_cond_and(cond, item)) : item);
-}
-
-
-static TABLE_LIST *alloc_join_nest(THD *thd)
-{
- TABLE_LIST *tbl;
- if (!(tbl= (TABLE_LIST*) thd->calloc(ALIGN_SIZE(sizeof(TABLE_LIST))+
- sizeof(NESTED_JOIN))))
- return NULL;
- tbl->nested_join= (NESTED_JOIN*) ((uchar*)tbl +
- ALIGN_SIZE(sizeof(TABLE_LIST)));
- return tbl;
-}
-
-
-void fix_list_after_tbl_changes(SELECT_LEX *new_parent, List<TABLE_LIST> *tlist)
-{
- List_iterator<TABLE_LIST> it(*tlist);
- TABLE_LIST *table;
- while ((table= it++))
- {
- if (table->on_expr)
- table->on_expr->fix_after_pullout(new_parent, &table->on_expr);
- if (table->nested_join)
- fix_list_after_tbl_changes(new_parent, &table->nested_join->join_list);
- }
-}
-
-
-/*
- Convert a subquery predicate into a TABLE_LIST semi-join nest
-
- SYNOPSIS
- convert_subq_to_sj()
- parent_join Parent join, the one that has subq_pred in its WHERE/ON
- clause
- subq_pred Subquery predicate to be converted
-
- DESCRIPTION
- Convert a subquery predicate into a TABLE_LIST semi-join nest. All the
- prerequisites are already checked, so the conversion is always successfull.
-
- Prepared Statements: the transformation is permanent:
- - Changes in TABLE_LIST structures are naturally permanent
- - Item tree changes are performed on statement MEM_ROOT:
- = we activate statement MEM_ROOT
- = this function is called before the first fix_prepare_information
- call.
-
- This is intended because the criteria for subquery-to-sj conversion remain
- constant for the lifetime of the Prepared Statement.
-
- RETURN
- FALSE OK
- TRUE Out of memory error
-*/
-
-bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
-{
- SELECT_LEX *parent_lex= parent_join->select_lex;
- TABLE_LIST *emb_tbl_nest= NULL;
- List<TABLE_LIST> *emb_join_list= &parent_lex->top_join_list;
- THD *thd= parent_join->thd;
- DBUG_ENTER("convert_subq_to_sj");
-
- /*
- 1. Find out where to put the predicate into.
- Note: for "t1 LEFT JOIN t2" this will be t2, a leaf.
- */
- if ((void*)subq_pred->expr_join_nest != (void*)1)
- {
- if (subq_pred->expr_join_nest->nested_join)
- {
- /*
- We're dealing with
-
- ... [LEFT] JOIN ( ... ) ON (subquery AND whatever) ...
-
- The sj-nest will be inserted into the brackets nest.
- */
- emb_tbl_nest= subq_pred->expr_join_nest;
- emb_join_list= &emb_tbl_nest->nested_join->join_list;
- }
- else if (!subq_pred->expr_join_nest->outer_join)
- {
- /*
- We're dealing with
-
- ... INNER JOIN tblX ON (subquery AND whatever) ...
-
- The sj-nest will be tblX's "sibling", i.e. another child of its
- parent. This is ok because tblX is joined as an inner join.
- */
- emb_tbl_nest= subq_pred->expr_join_nest->embedding;
- if (emb_tbl_nest)
- emb_join_list= &emb_tbl_nest->nested_join->join_list;
- }
- else if (!subq_pred->expr_join_nest->nested_join)
- {
- TABLE_LIST *outer_tbl= subq_pred->expr_join_nest;
- TABLE_LIST *wrap_nest;
- /*
- We're dealing with
-
- ... LEFT JOIN tbl ON (on_expr AND subq_pred) ...
-
- we'll need to convert it into:
-
- ... LEFT JOIN ( tbl SJ (subq_tables) ) ON (on_expr AND subq_pred) ...
- | |
- |<----- wrap_nest ---->|
-
- Q: other subqueries may be pointing to this element. What to do?
- A1: simple solution: copy *subq_pred->expr_join_nest= *parent_nest.
- But we'll need to fix other pointers.
- A2: Another way: have TABLE_LIST::next_ptr so the following
- subqueries know the table has been nested.
- A3: changes in the TABLE_LIST::outer_join will make everything work
- automatically.
- */
- if (!(wrap_nest= alloc_join_nest(parent_join->thd)))
- {
- DBUG_RETURN(TRUE);
- }
- wrap_nest->embedding= outer_tbl->embedding;
- wrap_nest->join_list= outer_tbl->join_list;
- wrap_nest->alias= (char*) "(sj-wrap)";
-
- wrap_nest->nested_join->join_list.empty();
- wrap_nest->nested_join->join_list.push_back(outer_tbl);
-
- outer_tbl->embedding= wrap_nest;
- outer_tbl->join_list= &wrap_nest->nested_join->join_list;
-
- /*
- wrap_nest will take place of outer_tbl, so move the outer join flag
- and on_expr
- */
- wrap_nest->outer_join= outer_tbl->outer_join;
- outer_tbl->outer_join= 0;
-
- wrap_nest->on_expr= outer_tbl->on_expr;
- outer_tbl->on_expr= NULL;
-
- List_iterator<TABLE_LIST> li(*wrap_nest->join_list);
- TABLE_LIST *tbl;
- while ((tbl= li++))
- {
- if (tbl == outer_tbl)
- {
- li.replace(wrap_nest);
- break;
- }
- }
- /*
- Ok now wrap_nest 'contains' outer_tbl and we're ready to add the
- semi-join nest into it
- */
- emb_join_list= &wrap_nest->nested_join->join_list;
- emb_tbl_nest= wrap_nest;
- }
- }
-
- TABLE_LIST *sj_nest;
- NESTED_JOIN *nested_join;
- if (!(sj_nest= alloc_join_nest(parent_join->thd)))
- {
- DBUG_RETURN(TRUE);
- }
- nested_join= sj_nest->nested_join;
-
- sj_nest->join_list= emb_join_list;
- sj_nest->embedding= emb_tbl_nest;
- sj_nest->alias= (char*) "(sj-nest)";
- sj_nest->sj_subq_pred= subq_pred;
- /* Nests do not participate in those 'chains', so: */
- /* sj_nest->next_leaf= sj_nest->next_local= sj_nest->next_global == NULL*/
- emb_join_list->push_back(sj_nest);
-
- /*
- nested_join->used_tables and nested_join->not_null_tables are
- initialized in simplify_joins().
- */
-
- /*
- 2. Walk through subquery's top list and set 'embedding' to point to the
- sj-nest.
- */
- st_select_lex *subq_lex= subq_pred->unit->first_select();
- nested_join->join_list.empty();
- List_iterator_fast<TABLE_LIST> li(subq_lex->top_join_list);
- TABLE_LIST *tl, *last_leaf;
- while ((tl= li++))
- {
- tl->embedding= sj_nest;
- tl->join_list= &nested_join->join_list;
- nested_join->join_list.push_back(tl);
- }
-
- /*
- Reconnect the next_leaf chain.
- TODO: Do we have to put subquery's tables at the end of the chain?
- Inserting them at the beginning would be a bit faster.
- NOTE: We actually insert them at the front! That's because the order is
- reversed in this list.
- */
- for (tl= parent_lex->leaf_tables; tl->next_leaf; tl= tl->next_leaf) ;
- tl->next_leaf= subq_lex->leaf_tables;
- last_leaf= tl;
-
- /*
- Same as above for next_local chain
- (a theory: a next_local chain always starts with ::leaf_tables
- because view's tables are inserted after the view)
- */
- for (tl= parent_lex->leaf_tables; tl->next_local; tl= tl->next_local) ;
- tl->next_local= subq_lex->leaf_tables;
-
- /* A theory: no need to re-connect the next_global chain */
-
- /* 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
- /*TODO: also reset the 'with_subselect' there. */
-
- /* n. Adjust the parent_join->tables counter */
- uint table_no= parent_join->tables;
- /* n. Walk through child's tables and adjust table->map */
- for (tl= subq_lex->leaf_tables; tl; tl= tl->next_leaf, table_no++)
- {
- tl->table->tablenr= table_no;
- tl->table->map= ((table_map)1) << table_no;
- SELECT_LEX *old_sl= tl->select_lex;
- tl->select_lex= parent_join->select_lex;
- for (TABLE_LIST *emb= tl->embedding;
- emb && emb->select_lex == old_sl;
- emb= emb->embedding)
- emb->select_lex= parent_join->select_lex;
- }
- parent_join->tables += subq_lex->join->tables;
-
- /*
- Put the subquery's WHERE into semi-join's sj_on_expr
- Add the subquery-induced equalities too.
- */
- SELECT_LEX *save_lex= thd->lex->current_select;
- thd->lex->current_select=subq_lex;
- if (!subq_pred->left_expr->fixed &&
- subq_pred->left_expr->fix_fields(thd, &subq_pred->left_expr))
- DBUG_RETURN(TRUE);
- thd->lex->current_select=save_lex;
-
- sj_nest->nested_join->sj_corr_tables= subq_pred->used_tables();
- sj_nest->nested_join->sj_depends_on= subq_pred->used_tables() |
- subq_pred->left_expr->used_tables();
- sj_nest->sj_on_expr= subq_lex->where;
-
- /*
- Create the IN-equalities and inject them into semi-join's ON expression.
- Additionally, for LooseScan strategy
- - Record the number of IN-equalities.
- - Create list of pointers to (oe1, ..., ieN). We'll need the list to
- see which of the expressions are bound and which are not (for those
- we'll produce a distinct stream of (ie_i1,...ie_ik).
-
- (TODO: can we just create a list of pointers and hope the expressions
- will not substitute themselves on fix_fields()? or we need to wrap
- them into Item_direct_view_refs and store pointers to those. The
- pointers to Item_direct_view_refs are guaranteed to be stable as
- Item_direct_view_refs doesn't substitute itself with anything in
- Item_direct_view_ref::fix_fields.
- */
- sj_nest->sj_in_exprs= subq_pred->left_expr->cols();
- sj_nest->nested_join->sj_outer_expr_list.empty();
-
- if (subq_pred->left_expr->cols() == 1)
- {
- nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr);
- Item_func_eq *item_eq=
- new Item_func_eq(subq_pred->left_expr, subq_lex->ref_pointer_array[0]);
- item_eq->in_equality_no= 0;
- sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
- }
- else
- {
- for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
- {
- nested_join->sj_outer_expr_list.push_back(subq_pred->left_expr->
- element_index(i));
- Item_func_eq *item_eq=
- new Item_func_eq(subq_pred->left_expr->element_index(i),
- subq_lex->ref_pointer_array[i]);
- item_eq->in_equality_no= i;
- sj_nest->sj_on_expr= and_items(sj_nest->sj_on_expr, item_eq);
- }
- }
- /* Fix the created equality and AND */
- sj_nest->sj_on_expr->fix_fields(parent_join->thd, &sj_nest->sj_on_expr);
-
- /*
- Walk through sj nest's WHERE and ON expressions and call
- item->fix_table_changes() for all items.
- */
- sj_nest->sj_on_expr->fix_after_pullout(parent_lex, &sj_nest->sj_on_expr);
- fix_list_after_tbl_changes(parent_lex, &sj_nest->nested_join->join_list);
-
-
- /* Unlink the child select_lex so it doesn't show up in EXPLAIN: */
- subq_lex->master_unit()->exclude_level();
-
- DBUG_EXECUTE("where",
- print_where(sj_nest->sj_on_expr,"SJ-EXPR", QT_ORDINARY););
-
- /* Inject sj_on_expr into the parent's WHERE or ON */
- if (emb_tbl_nest)
- {
- emb_tbl_nest->on_expr= and_items(emb_tbl_nest->on_expr,
- sj_nest->sj_on_expr);
- emb_tbl_nest->on_expr->fix_fields(parent_join->thd, &emb_tbl_nest->on_expr);
- }
- else
- {
- /* Inject into the WHERE */
- parent_join->conds= and_items(parent_join->conds, sj_nest->sj_on_expr);
- parent_join->conds->fix_fields(parent_join->thd, &parent_join->conds);
- parent_join->select_lex->where= parent_join->conds;
- }
-
- if (subq_lex->ftfunc_list->elements)
- {
- Item_func_match *ifm;
- List_iterator_fast<Item_func_match> li(*(subq_lex->ftfunc_list));
- while ((ifm= li++))
- parent_lex->ftfunc_list->push_front(ifm);
- }
-
- DBUG_RETURN(FALSE);
-}
-
-
-
-/*
- Convert semi-join subquery predicates into semi-join join nests
-
- SYNOPSIS
- JOIN::flatten_subqueries()
-
- DESCRIPTION
-
- Convert candidate subquery predicates into semi-join join nests. This
- transformation is performed once in query lifetime and is irreversible.
-
- Conversion of one subquery predicate
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- We start with a join that has a semi-join subquery:
-
- SELECT ...
- FROM ot, ...
- WHERE oe IN (SELECT ie FROM it1 ... itN WHERE subq_where) AND outer_where
-
- and convert it into a semi-join nest:
-
- SELECT ...
- FROM ot SEMI JOIN (it1 ... itN), ...
- WHERE outer_where AND subq_where AND oe=ie
-
- that is, in order to do the conversion, we need to
-
- * Create the "SEMI JOIN (it1 .. itN)" part and add it into the parent
- query's FROM structure.
- * Add "AND subq_where AND oe=ie" into parent query's WHERE (or ON if
- the subquery predicate was in an ON expression)
- * Remove the subquery predicate from the parent query's WHERE
-
- Considerations when converting many predicates
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- A join may have at most MAX_TABLES tables. This may prevent us from
- flattening all subqueries when the total number of tables in parent and
- child selects exceeds MAX_TABLES.
- We deal with this problem by flattening children's subqueries first and
- then using a heuristic rule to determine each subquery predicate's
- "priority".
-
- RETURN
- FALSE OK
- TRUE Error
-*/
-
-bool JOIN::flatten_subqueries()
-{
- Query_arena *arena, backup;
- Item_in_subselect **in_subq;
- Item_in_subselect **in_subq_end;
- DBUG_ENTER("JOIN::flatten_subqueries");
-
- if (sj_subselects.elements() == 0)
- DBUG_RETURN(FALSE);
-
- /* First, convert child join's subqueries. We proceed bottom-up here */
- for (in_subq= sj_subselects.front(), in_subq_end= sj_subselects.back();
- in_subq != in_subq_end; in_subq++)
- {
- st_select_lex *child_select= (*in_subq)->get_select_lex();
- JOIN *child_join= child_select->join;
- child_join->outer_tables = child_join->tables;
-
- /*
- child_select->where contains only the WHERE predicate of the
- subquery itself here. We may be selecting from a VIEW, which has its
- own predicate. The combined predicates are available in child_join->conds,
- which was built by setup_conds() doing prepare_where() for all views.
- */
- child_select->where= child_join->conds;
-
- if (child_join->flatten_subqueries())
- DBUG_RETURN(TRUE);
- (*in_subq)->sj_convert_priority=
- (*in_subq)->is_correlated * MAX_TABLES + child_join->outer_tables;
- }
-
- // Temporary measure: disable semi-joins when they are together with outer
- // joins.
- for (TABLE_LIST *tbl= select_lex->leaf_tables; tbl; tbl=tbl->next_leaf)
- {
- TABLE_LIST *embedding= tbl->embedding;
- if (tbl->on_expr || (tbl->embedding && !(embedding->sj_on_expr &&
- !embedding->embedding)))
- {
- in_subq= sj_subselects.front();
- arena= thd->activate_stmt_arena_if_needed(&backup);
- goto skip_conversion;
- }
- }
-
- //dump_TABLE_LIST_struct(select_lex, select_lex->leaf_tables);
- /*
- 2. Pick which subqueries to convert:
- sort the subquery array
- - prefer correlated subqueries over uncorrelated;
- - prefer subqueries that have greater number of outer tables;
- */
- sj_subselects.sort(subq_sj_candidate_cmp);
- // #tables-in-parent-query + #tables-in-subquery < MAX_TABLES
- /* Replace all subqueries to be flattened with Item_int(1) */
- arena= thd->activate_stmt_arena_if_needed(&backup);
- for (in_subq= sj_subselects.front();
- in_subq != in_subq_end &&
- tables + (*in_subq)->unit->first_select()->join->tables < MAX_TABLES;
- in_subq++)
- {
- Item **tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
- &conds : &((*in_subq)->emb_on_expr_nest->on_expr);
- if (replace_where_subcondition(this, tree, *in_subq, new Item_int(1),
- FALSE))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- }
-
- for (in_subq= sj_subselects.front();
- in_subq != in_subq_end &&
- tables + (*in_subq)->unit->first_select()->join->tables < MAX_TABLES;
- in_subq++)
- {
- if (convert_subq_to_sj(this, *in_subq))
- DBUG_RETURN(TRUE);
- }
-skip_conversion:
- /*
- 3. Finalize (perform IN->EXISTS rewrite) the subqueries that we didn't
- convert:
- */
- for (; in_subq!= in_subq_end; in_subq++)
- {
- JOIN *child_join= (*in_subq)->unit->first_select()->join;
- Item_subselect::trans_res res;
- (*in_subq)->changed= 0;
- (*in_subq)->fixed= 0;
-
- SELECT_LEX *save_select_lex= thd->lex->current_select;
- thd->lex->current_select= (*in_subq)->unit->first_select();
-
- res= (*in_subq)->select_transformer(child_join);
-
- thd->lex->current_select= save_select_lex;
-
- if (res == Item_subselect::RES_ERROR)
- DBUG_RETURN(TRUE);
-
- (*in_subq)->changed= 1;
- (*in_subq)->fixed= 1;
-
- Item *substitute= (*in_subq)->substitution;
- bool do_fix_fields= !(*in_subq)->substitution->fixed;
- Item **tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
- &conds : &((*in_subq)->emb_on_expr_nest->on_expr);
- if (replace_where_subcondition(this, tree, *in_subq, substitute,
- do_fix_fields))
- DBUG_RETURN(TRUE);
- (*in_subq)->substitution= NULL;
-
- if (!thd->stmt_arena->is_conventional())
- {
- tree= ((*in_subq)->emb_on_expr_nest == (TABLE_LIST*)1)?
- &select_lex->prep_where : &((*in_subq)->emb_on_expr_nest->prep_on_expr);
-
- if (replace_where_subcondition(this, tree, *in_subq, substitute,
- FALSE))
- DBUG_RETURN(TRUE);
- }
- }
-
- if (arena)
- thd->restore_active_arena(arena, &backup);
- sj_subselects.clear();
- DBUG_RETURN(FALSE);
-}
-
-
-
/**
Setup for execution all subqueries of a query, for which the optimizer
chose hash semi-join.
@@ -3674,7 +2370,6 @@ skip_conversion:
bool JOIN::setup_subquery_materialization()
{
- //psergey-merge: we don't have materialization so dont need that:
for (SELECT_LEX_UNIT *un= select_lex->first_inner_unit(); un;
un= un->next_unit())
{
@@ -3695,243 +2390,6 @@ bool JOIN::setup_subquery_materialization()
}
-/*
- Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
-
- SYNOPSIS
- find_eq_ref_candidate()
- table Table to be checked
- sj_inner_tables Bitmap of inner tables. eq_ref(inner_table) doesn't
- count.
-
- DESCRIPTION
- Check if table's KEYUSE elements have an eq_ref(outer_tables) candidate
-
- TODO
- Check again if it is feasible to factor common parts with constant table
- search
-
- RETURN
- TRUE - There exists an eq_ref(outer-tables) candidate
- FALSE - Otherwise
-*/
-
-bool find_eq_ref_candidate(TABLE *table, table_map sj_inner_tables)
-{
- KEYUSE *keyuse= table->reginfo.join_tab->keyuse;
- uint key;
-
- if (keyuse)
- {
- while (1) /* For each key */
- {
- key= keyuse->key;
- KEY *keyinfo= table->key_info + key;
- key_part_map bound_parts= 0;
- if ((keyinfo->flags & (HA_NOSAME | HA_END_SPACE_KEY)) == HA_NOSAME)
- {
- do /* For all equalities on all key parts */
- {
- /* Check if this is "t.keypart = expr(outer_tables) */
- if (!(keyuse->used_tables & sj_inner_tables) &&
- !(keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL))
- {
- bound_parts |= 1 << keyuse->keypart;
- }
- keyuse++;
- } while (keyuse->key == key && keyuse->table == table);
-
- if (bound_parts == PREV_BITS(uint, keyinfo->key_parts))
- return TRUE;
- if (keyuse->table != table)
- return FALSE;
- }
- else
- {
- do
- {
- keyuse++;
- if (keyuse->table != table)
- return FALSE;
- }
- while (keyuse->key == key);
- }
- }
- }
- return FALSE;
-}
-
-
-/*
- Pull tables out of semi-join nests, if possible
-
- SYNOPSIS
- pull_out_semijoin_tables()
- join The join where to do the semi-join flattening
-
- DESCRIPTION
- Try to pull tables out of semi-join nests.
-
- PRECONDITIONS
- When this function is called, the join may have several semi-join nests
- but it is guaranteed that one semi-join nest does not contain another.
-
- ACTION
- A table can be pulled out of the semi-join nest if
- - It is a constant table, or
- - It is accessed via eq_ref(outer_tables)
-
- POSTCONDITIONS
- * Tables that were pulled out have JOIN_TAB::emb_sj_nest == NULL
- * Tables that were not pulled out have JOIN_TAB::emb_sj_nest pointing
- to semi-join nest they are in.
- * Semi-join nests' TABLE_LIST::sj_inner_tables is updated accordingly
-
- This operation is (and should be) performed at each PS execution since
- tables may become/cease to be constant across PS reexecutions.
-
- NOTE
- Table pullout may make uncorrelated subquery correlated. Consider this
- example:
-
- ... WHERE oe IN (SELECT it1.primary_key WHERE p(it1, it2) ... )
-
- here table it1 can be pulled out (we have it1.primary_key=oe which gives
- us functional dependency). Once it1 is pulled out, all references to it1
- from p(it1, it2) become references to outside of the subquery and thus
- make the subquery (i.e. its semi-join nest) correlated.
- Making the subquery (i.e. its semi-join nest) correlated prevents us from
- using Materialization or LooseScan to execute it.
-
- RETURN
- 0 - OK
- 1 - Out of memory error
-*/
-
-int pull_out_semijoin_tables(JOIN *join)
-{
- TABLE_LIST *sj_nest;
- DBUG_ENTER("pull_out_semijoin_tables");
- List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
-
- /* Try pulling out of the each of the semi-joins */
- while ((sj_nest= sj_list_it++))
- {
- /* Action #1: Mark the constant tables to be pulled out */
- table_map pulled_tables= 0;
-
- List_iterator<TABLE_LIST> child_li(sj_nest->nested_join->join_list);
- TABLE_LIST *tbl;
- while ((tbl= child_li++))
- {
- if (tbl->table)
- {
- tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
- if (tbl->table->map & join->const_table_map)
- {
- pulled_tables |= tbl->table->map;
- DBUG_PRINT("info", ("Table %s pulled out (reason: constant)",
- tbl->table->alias));
- }
- }
- }
-
- /*
- Action #2: Find which tables we can pull out based on
- update_ref_and_keys() data. Note that pulling one table out can allow
- us to pull out some other tables too.
- */
- bool pulled_a_table;
- do
- {
- pulled_a_table= FALSE;
- child_li.rewind();
- while ((tbl= child_li++))
- {
- if (tbl->table && !(pulled_tables & tbl->table->map))
- {
- if (find_eq_ref_candidate(tbl->table,
- sj_nest->nested_join->used_tables &
- ~pulled_tables))
- {
- pulled_a_table= TRUE;
- pulled_tables |= tbl->table->map;
- DBUG_PRINT("info", ("Table %s pulled out (reason: func dep)",
- tbl->table->alias));
- /*
- Pulling a table out of uncorrelated subquery in general makes
- makes it correlated. See the NOTE to this funtion.
- */
- sj_nest->sj_subq_pred->is_correlated= TRUE;
- sj_nest->nested_join->sj_corr_tables|= tbl->table->map;
- sj_nest->nested_join->sj_depends_on|= tbl->table->map;
- }
- }
- }
- } while (pulled_a_table);
-
- child_li.rewind();
- /*
- Action #3: Move the pulled out TABLE_LIST elements to the parents.
- */
- table_map inner_tables= sj_nest->nested_join->used_tables &
- ~pulled_tables;
- /* Record the bitmap of inner tables */
- sj_nest->sj_inner_tables= inner_tables;
- if (pulled_tables)
- {
- List<TABLE_LIST> *upper_join_list= (sj_nest->embedding != NULL)?
- (&sj_nest->embedding->nested_join->join_list):
- (&join->select_lex->top_join_list);
- Query_arena *arena, backup;
- arena= join->thd->activate_stmt_arena_if_needed(&backup);
- while ((tbl= child_li++))
- {
- if (tbl->table)
- {
- if (inner_tables & tbl->table->map)
- {
- /* This table is not pulled out */
- tbl->table->reginfo.join_tab->emb_sj_nest= sj_nest;
- }
- else
- {
- /* This table has been pulled out of the semi-join nest */
- tbl->table->reginfo.join_tab->emb_sj_nest= NULL;
- /*
- Pull the table up in the same way as simplify_joins() does:
- update join_list and embedding pointers but keep next[_local]
- pointers.
- */
- child_li.remove();
- upper_join_list->push_back(tbl);
- tbl->join_list= upper_join_list;
- tbl->embedding= sj_nest->embedding;
- }
- }
- }
-
- /* Remove the sj-nest itself if we've removed everything from it */
- if (!inner_tables)
- {
- List_iterator<TABLE_LIST> li(*upper_join_list);
- /* Find the sj_nest in the list. */
- while (sj_nest != li++) ;
- li.remove();
- /* Also remove it from the list of SJ-nests: */
- sj_list_it.remove();
- }
-
- if (arena)
- join->thd->restore_active_arena(arena, &backup);
- }
- }
- DBUG_RETURN(0);
-}
-
-
-
-
/*****************************************************************************
Create JOIN_TABS, make a guess about the table types,
Approximate how many records will be used in each table
@@ -3980,63 +2438,6 @@ typedef struct st_sargable_param
} SARGABLE_PARAM;
-/*
- Get estimated record length for semi-join materialization temptable
-
- SYNOPSIS
- get_tmp_table_rec_length()
- items IN subquery's select list.
-
- DESCRIPTION
- Calculate estimated record length for semi-join materialization
- temptable. It's an estimate because we don't follow every bit of
- create_tmp_table()'s logic. This isn't necessary as the return value of
- this function is used only for cost calculations.
-
- RETURN
- Length of the temptable record, in bytes
-*/
-
-static uint get_tmp_table_rec_length(List<Item> &items)
-{
- uint len= 0;
- Item *item;
- List_iterator<Item> it(items);
- while ((item= it++))
- {
- switch (item->result_type()) {
- case REAL_RESULT:
- len += sizeof(double);
- break;
- case INT_RESULT:
- if (item->max_length >= (MY_INT32_NUM_DECIMAL_DIGITS - 1))
- len += 8;
- else
- len += 4;
- break;
- case STRING_RESULT:
- enum enum_field_types type;
- /* DATE/TIME and GEOMETRY fields have STRING_RESULT result type. */
- if ((type= item->field_type()) == MYSQL_TYPE_DATETIME ||
- type == MYSQL_TYPE_TIME || type == MYSQL_TYPE_DATE ||
- type == MYSQL_TYPE_TIMESTAMP || type == MYSQL_TYPE_GEOMETRY)
- len += 8;
- else
- len += item->max_length;
- break;
- case DECIMAL_RESULT:
- len += 10;
- break;
- case ROW_RESULT:
- default:
- DBUG_ASSERT(0); /* purecov: deadcode */
- break;
- }
- }
- return len;
-}
-
-
/**
Calculate the best possible join and initialize the join structure.
@@ -4518,147 +2919,6 @@ error:
}
-/*
- Optimize semi-join nests that could be run with sj-materialization
-
- SYNOPSIS
- optimize_semijoin_nests()
- join The join to optimize semi-join nests for
- all_table_map Bitmap of all tables in the join
-
- DESCRIPTION
- Optimize each of the semi-join nests that can be run with
- materialization. For each of the nests, we
- - Generate the best join order for this "sub-join" and remember it;
- - Remember the sub-join execution cost (it's part of materialization
- cost);
- - Calculate other costs that will be incurred if we decide
- to use materialization strategy for this semi-join nest.
-
- All obtained information is saved and will be used by the main join
- optimization pass.
-
- RETURN
- FALSE Ok
- TRUE Out of memory error
-*/
-
-static bool optimize_semijoin_nests(JOIN *join, table_map all_table_map)
-{
- DBUG_ENTER("optimize_semijoin_nests");
- List_iterator<TABLE_LIST> sj_list_it(join->select_lex->sj_nests);
- TABLE_LIST *sj_nest;
- /*
- The statement may have been executed with 'semijoin=on' earlier.
- We need to verify that 'semijoin=on' still holds.
- */
- if (optimizer_flag(join->thd, OPTIMIZER_SWITCH_SEMIJOIN) &&
- optimizer_flag(join->thd, OPTIMIZER_SWITCH_MATERIALIZATION))
- {
- while ((sj_nest= sj_list_it++))
- {
- /* semi-join nests with only constant tables are not valid */
- DBUG_ASSERT(sj_nest->sj_inner_tables & ~join->const_table_map);
-
- sj_nest->sj_mat_info= NULL;
- if (sj_nest->sj_inner_tables && /* not everything was pulled out */
- !sj_nest->sj_subq_pred->is_correlated &&
- sj_nest->sj_subq_pred->types_allow_materialization)
- {
- join->emb_sjm_nest= sj_nest;
- if (choose_plan(join, all_table_map))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- /*
- The best plan to run the subquery is now in join->best_positions,
- save it.
- */
- uint n_tables= my_count_bits(sj_nest->sj_inner_tables);
- SJ_MATERIALIZATION_INFO* sjm;
- if (!(sjm= new SJ_MATERIALIZATION_INFO) ||
- !(sjm->positions= (POSITION*)join->thd->alloc(sizeof(POSITION)*
- n_tables)))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- 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);
-
- sjm->materialization_cost.convert_from_cost(subjoin_read_time);
- sjm->rows= subjoin_out_rows;
-
- List<Item> &right_expr_list=
- sj_nest->sj_subq_pred->unit->first_select()->item_list;
- /*
- Adjust output cardinality estimates. If the subquery has form
-
- ... oe IN (SELECT t1.colX, t2.colY, func(X,Y,Z) )
-
- then the number of distinct output record combinations has an
- upper bound of product of number of records matching the tables
- that are used by the SELECT clause.
- TODO:
- We can get a more precise estimate if we
- - use rec_per_key cardinality estimates. For simple cases like
- "oe IN (SELECT t.key ...)" it is trivial.
- - Functional dependencies between the tables in the semi-join
- nest (the payoff is probably less here?)
- */
- {
- for (uint i=0 ; i < join->const_tables + sjm->tables ; i++)
- {
- JOIN_TAB *tab= join->best_positions[i].table;
- join->map2table[tab->table->tablenr]= tab;
- }
- List_iterator<Item> it(right_expr_list);
- Item *item;
- table_map map= 0;
- while ((item= it++))
- map |= item->used_tables();
- map= map & ~PSEUDO_TABLE_BITS;
- Table_map_iterator tm_it(map);
- int tableno;
- double rows= 1.0;
- while ((tableno = tm_it.next_bit()) != Table_map_iterator::BITMAP_END)
- rows *= join->map2table[tableno]->table->quick_condition_rows;
- sjm->rows= min(sjm->rows, rows);
- }
- memcpy(sjm->positions, join->best_positions + join->const_tables,
- sizeof(POSITION) * n_tables);
-
- /*
- Calculate temporary table parameters and usage costs
- */
- uint rowlen= get_tmp_table_rec_length(right_expr_list);
- double lookup_cost;
- if (rowlen * subjoin_out_rows< join->thd->variables.max_heap_table_size)
- lookup_cost= HEAP_TEMPTABLE_LOOKUP_COST;
- else
- lookup_cost= DISK_TEMPTABLE_LOOKUP_COST;
-
- /*
- Let materialization cost include the cost to write the data into the
- temporary table:
- */
- sjm->materialization_cost.add_io(subjoin_out_rows, lookup_cost);
-
- /*
- Set the cost to do a full scan of the temptable (will need this to
- consider doing sjm-scan):
- */
- sjm->scan_cost.zero();
- sjm->scan_cost.add_io(sjm->rows, lookup_cost);
-
- sjm->lookup_cost.convert_from_cost(lookup_cost);
- sj_nest->sj_mat_info= sjm;
- DBUG_EXECUTE("opt", print_sjm(sjm););
- }
- }
- }
- join->emb_sjm_nest= NULL;
- DBUG_RETURN(FALSE);
-}
-
/*****************************************************************************
Check with keys are used and with tables references with tables
Updates in stat:
@@ -5887,308 +4147,6 @@ void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key)
}
-/*
- Given a semi-join nest, find out which of the IN-equalities are bound
-
- SYNOPSIS
- get_bound_sj_equalities()
- sj_nest Semi-join nest
- remaining_tables Tables that are not yet bound
-
- DESCRIPTION
- Given a semi-join nest, find out which of the IN-equalities have their
- left part expression bound (i.e. the said expression doesn't refer to
- any of remaining_tables and can be evaluated).
-
- RETURN
- Bitmap of bound IN-equalities.
-*/
-
-ulonglong get_bound_sj_equalities(TABLE_LIST *sj_nest,
- table_map remaining_tables)
-{
- List_iterator<Item> li(sj_nest->nested_join->sj_outer_expr_list);
- Item *item;
- uint i= 0;
- ulonglong res= 0;
- while ((item= li++))
- {
- /*
- Q: should this take into account equality propagation and how?
- A: If e->outer_side is an Item_field, walk over the equality
- class and see if there is an element that is bound?
- (this is an optional feature)
- */
- if (!(item->used_tables() & remaining_tables))
- {
- res |= 1ULL << i;
- }
- }
- return res;
-}
-
-
-/*
- This is a class for considering possible loose index scan optimizations.
- It's usage pattern is as follows:
- best_access_path()
- {
- Loose_scan_opt opt;
-
- opt.init()
- for each index we can do ref access with
- {
- opt.next_ref_key();
- for each keyuse
- opt.add_keyuse();
- opt.check_ref_access();
- }
-
- if (some criteria for range scans)
- opt.check_range_access();
-
- opt.get_best_option();
- }
-*/
-
-class Loose_scan_opt
-{
-public:
- /* All methods must check this before doing anything else */
- bool try_loosescan;
-
- /*
- If we consider (oe1, .. oeN) IN (SELECT ie1, .. ieN) then ieK=oeK is
- called sj-equality. If oeK depends only on preceding tables then such
- equality is called 'bound'.
- */
- ulonglong bound_sj_equalities;
-
- /* Accumulated properties of ref access we're now considering: */
- ulonglong handled_sj_equalities;
- key_part_map loose_scan_keyparts;
- uint max_loose_keypart;
- bool part1_conds_met;
-
- /*
- Use of quick select is a special case. Some of its properties:
- */
- uint quick_uses_applicable_index;
- uint quick_max_loose_keypart;
-
- /* Best loose scan method so far */
- uint best_loose_scan_key;
- double best_loose_scan_cost;
- double best_loose_scan_records;
- KEYUSE *best_loose_scan_start_key;
-
- uint best_max_loose_keypart;
-
- Loose_scan_opt():
- try_loosescan(FALSE),
- bound_sj_equalities(0),
- quick_uses_applicable_index(FALSE)
- {
- UNINIT_VAR(quick_max_loose_keypart); /* Protected by quick_uses_applicable_index */
- /* The following are protected by best_loose_scan_cost!= DBL_MAX */
- UNINIT_VAR(best_loose_scan_key);
- UNINIT_VAR(best_loose_scan_records);
- UNINIT_VAR(best_max_loose_keypart);
- UNINIT_VAR(best_loose_scan_start_key);
- }
-
- void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
- {
- /*
- Discover the bound equalities. We need to do this if
- 1. The next table is an SJ-inner table, and
- 2. It is the first table from that semijoin, and
- 3. We're not within a semi-join range (i.e. all semi-joins either have
- all or none of their tables in join_table_map), except
- s->emb_sj_nest (which we've just entered, see #2).
- 4. All non-IN-equality correlation references from this sj-nest are
- bound
- 5. But some of the IN-equalities aren't (so this can't be handled by
- FirstMatch strategy)
- */
- best_loose_scan_cost= DBL_MAX;
- if (!join->emb_sjm_nest && s->emb_sj_nest && // (1)
- s->emb_sj_nest->sj_in_exprs < 64 &&
- ((remaining_tables & s->emb_sj_nest->sj_inner_tables) == // (2)
- s->emb_sj_nest->sj_inner_tables) && // (2)
- join->cur_sj_inner_tables == 0 && // (3)
- !(remaining_tables &
- s->emb_sj_nest->nested_join->sj_corr_tables) && // (4)
- remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on &&// (5)
- optimizer_flag(join->thd, OPTIMIZER_SWITCH_LOOSE_SCAN))
- {
- /* This table is an LooseScan scan candidate */
- bound_sj_equalities= get_bound_sj_equalities(s->emb_sj_nest,
- remaining_tables);
- try_loosescan= TRUE;
- DBUG_PRINT("info", ("Will try LooseScan scan, bound_map=%llx",
- (longlong)bound_sj_equalities));
- }
- }
-
- void next_ref_key()
- {
- handled_sj_equalities=0;
- loose_scan_keyparts= 0;
- max_loose_keypart= 0;
- part1_conds_met= FALSE;
- }
-
- void add_keyuse(table_map remaining_tables, KEYUSE *keyuse)
- {
- if (try_loosescan && keyuse->sj_pred_no != UINT_MAX)
- {
- if (!(remaining_tables & keyuse->used_tables))
- {
- /*
- This allows to use equality propagation to infer that some
- sj-equalities are bound.
- */
- bound_sj_equalities |= 1ULL << keyuse->sj_pred_no;
- }
- else
- {
- handled_sj_equalities |= 1ULL << keyuse->sj_pred_no;
- loose_scan_keyparts |= ((key_part_map)1) << keyuse->keypart;
- set_if_bigger(max_loose_keypart, keyuse->keypart);
- }
- }
- }
-
- bool have_a_case() { return test(handled_sj_equalities); }
-
- void check_ref_access_part1(JOIN_TAB *s, uint key, KEYUSE *start_key,
- table_map found_part)
- {
- /*
- Check if we can use LooseScan semi-join strategy. We can if
- 1. This is the right table at right location
- 2. All IN-equalities are either
- - "bound", ie. the outer_expr part refers to the preceding tables
- - "handled", ie. covered by the index we're considering
- 3. Index order allows to enumerate subquery's duplicate groups in
- order. This happens when the index definition matches this
- pattern:
-
- (handled_col|bound_col)* (other_col|bound_col)
-
- */
- if (try_loosescan && // (1)
- (handled_sj_equalities | bound_sj_equalities) == // (2)
- PREV_BITS(ulonglong, s->emb_sj_nest->sj_in_exprs) && // (2)
- (PREV_BITS(key_part_map, max_loose_keypart+1) & // (3)
- (found_part | loose_scan_keyparts)) == // (3)
- (found_part | loose_scan_keyparts) && // (3)
- !key_uses_partial_cols(s->table, key))
- {
- /* Ok, can use the strategy */
- part1_conds_met= TRUE;
- if (s->quick && s->quick->index == key &&
- s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
- {
- quick_uses_applicable_index= TRUE;
- quick_max_loose_keypart= max_loose_keypart;
- }
- DBUG_PRINT("info", ("Can use LooseScan scan"));
-
- /*
- Check if this is a confluent where there are no usable bound
- IN-equalities, e.g. we have
-
- outer_expr IN (SELECT innertbl.key FROM ...)
-
- and outer_expr cannot be evaluated yet, so it's actually full
- index scan and not a ref access
- */
- if (!(found_part & 1 ) && /* no usable ref access for 1st key part */
- s->table->covering_keys.is_set(key))
- {
- DBUG_PRINT("info", ("Can use full index scan for LooseScan"));
-
- /* Calculate the cost of complete loose index scan. */
- double records= rows2double(s->table->file->stats.records);
-
- /* The cost is entire index scan cost (divided by 2) */
- double read_time= s->table->file->index_only_read_time(key, records);
-
- /*
- Now find out how many different keys we will get (for now we
- ignore the fact that we have "keypart_i=const" restriction for
- some key components, that may make us think think that loose
- scan will produce more distinct records than it actually will)
- */
- ulong rpc;
- if ((rpc= s->table->key_info[key].rec_per_key[max_loose_keypart]))
- records= records / rpc;
-
- // TODO: previous version also did /2
- if (read_time < best_loose_scan_cost)
- {
- best_loose_scan_key= key;
- best_loose_scan_cost= read_time;
- best_loose_scan_records= records;
- best_max_loose_keypart= max_loose_keypart;
- best_loose_scan_start_key= start_key;
- }
- }
- }
- }
-
- void check_ref_access_part2(uint key, KEYUSE *start_key, double records,
- double read_time)
- {
- if (part1_conds_met && read_time < best_loose_scan_cost)
- {
- /* TODO use rec-per-key-based fanout calculations */
- best_loose_scan_key= key;
- best_loose_scan_cost= read_time;
- best_loose_scan_records= records;
- best_max_loose_keypart= max_loose_keypart;
- best_loose_scan_start_key= start_key;
- }
- }
-
- void check_range_access(JOIN *join, uint idx, QUICK_SELECT_I *quick)
- {
- /* TODO: this the right part restriction: */
- if (quick_uses_applicable_index && idx == join->const_tables &&
- quick->read_time < best_loose_scan_cost)
- {
- best_loose_scan_key= quick->index;
- best_loose_scan_cost= quick->read_time;
- /* this is ok because idx == join->const_tables */
- best_loose_scan_records= rows2double(quick->records);
- best_max_loose_keypart= quick_max_loose_keypart;
- best_loose_scan_start_key= NULL;
- }
- }
-
- void save_to_position(JOIN_TAB *tab, POSITION *pos)
- {
- pos->read_time= best_loose_scan_cost;
- if (best_loose_scan_cost != DBL_MAX)
- {
- pos->records_read= best_loose_scan_records;
- pos->key= best_loose_scan_start_key;
- pos->loosescan_key= best_loose_scan_key;
- pos->loosescan_parts= best_max_loose_keypart + 1;
- pos->use_join_buffer= FALSE;
- pos->table= tab;
- // todo need ref_depend_map ?
- DBUG_PRINT("info", ("Produced a LooseScan plan, key %s, %s",
- tab->table->key_info[best_loose_scan_key].name,
- best_loose_scan_start_key? "(ref access)":
- "(range/index access)"));
- }
- }
-};
-
-
/**
Find the best access path for an extension of a partial execution
plan and add this path to the plan.
@@ -6217,7 +4175,7 @@ public:
None
*/
-static void
+void
best_access_path(JOIN *join,
JOIN_TAB *s,
table_map remaining_tables,
@@ -6783,7 +4741,7 @@ best_access_path(JOIN *join,
TRUE Fatal error
*/
-static bool
+bool
choose_plan(JOIN *join, table_map join_tables)
{
uint search_depth= join->thd->variables.optimizer_search_depth;
@@ -7068,60 +5026,6 @@ optimize_straight_join(JOIN *join, table_map join_tables)
}
-
-/*
- Check if the last tables of the partial join order allow to use
- sj-materialization strategy for them
-
- SYNOPSIS
- at_sjmat_pos()
- join
- remaining_tables
- tab the last table's join tab
- idx last table's index
- loose_scan OUT TRUE <=> use LooseScan
-
- RETURN
- TRUE Yes, can apply sj-materialization
- FALSE No, some of the requirements are not met
-*/
-
-SJ_MATERIALIZATION_INFO *
-at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
- uint idx, bool *loose_scan)
-{
- /*
- Check if
- 1. We're in a semi-join nest that can be run with SJ-materialization
- 2. All the tables correlated through the IN subquery are in the prefix
- */
- TABLE_LIST *emb_sj_nest= tab->emb_sj_nest;
- table_map suffix= remaining_tables & ~tab->table->map;
- if (emb_sj_nest && emb_sj_nest->sj_mat_info &&
- !(suffix & emb_sj_nest->sj_inner_tables))
- {
- /*
- Walk back and check if all immediately preceding tables are from
- this semi-join.
- */
- uint n_tables= my_count_bits(tab->emb_sj_nest->sj_inner_tables);
- for (uint i= 1; i < n_tables ; i++)
- {
- if (join->positions[idx - i].table->emb_sj_nest != tab->emb_sj_nest)
- return NULL;
- }
- *loose_scan= test(remaining_tables & ~tab->table->map &
- (emb_sj_nest->sj_inner_tables |
- emb_sj_nest->nested_join->sj_depends_on));
- if (*loose_scan && !emb_sj_nest->sj_subq_pred->sjm_scan_allowed)
- return NULL;
- else
- return emb_sj_nest->sj_mat_info;
- }
- return NULL;
-}
-
-
/**
Find a good, possibly optimal, query execution plan (QEP) by a greedy search.
@@ -7292,8 +5196,6 @@ greedy_search(JOIN *join,
}
-
-
/*
Calculate a cost of given partial join order
@@ -7845,213 +5747,6 @@ prev_record_reads(JOIN *join, uint idx, table_map found_ref)
/*
- Fix semi-join strategies for the picked join order
-
- SYNOPSIS
- fix_semijoin_strategies_for_picked_join_order()
- join The join with the picked join order
-
- DESCRIPTION
- Fix semi-join strategies for the picked join order. This is a step that
- needs to be done right after we have fixed the join order. What we do
- here is switch join's semi-join strategy description from backward-based
- to forwards based.
-
- When join optimization is in progress, we re-consider semi-join
- strategies after we've added another table. Here's an illustration.
- Suppose the join optimization is underway:
-
- 1) ot1 it1 it2
- sjX -- looking at (ot1, it1, it2) join prefix, we decide
- to use semi-join strategy sjX.
-
- 2) ot1 it1 it2 ot2
- sjX sjY -- Having added table ot2, we now may consider
- another semi-join strategy and decide to use a
- different strategy sjY. Note that the record
- of sjX has remained under it2. That is
- necessary because we need to be able to get
- back to (ot1, it1, it2) join prefix.
- what makes things even worse is that there are cases where the choice
- of sjY changes the way we should access it2.
-
- 3) [ot1 it1 it2 ot2 ot3]
- sjX sjY -- This means that after join optimization is
- finished, semi-join info should be read
- right-to-left (while nearly all plan refinement
- functions, EXPLAIN, etc proceed from left to
- right)
-
- This function does the needed reversal, making it possible to read the
- join and semi-join order from left to right.
-*/
-
-static void fix_semijoin_strategies_for_picked_join_order(JOIN *join)
-{
- uint table_count=join->tables;
- uint tablenr;
- table_map remaining_tables= 0;
- table_map handled_tabs= 0;
- for (tablenr= table_count - 1 ; tablenr != join->const_tables - 1; tablenr--)
- {
- POSITION *pos= join->best_positions + tablenr;
- JOIN_TAB *s= pos->table;
- uint first;
- LINT_INIT(first); // Set by every branch except SJ_OPT_NONE which doesn't use it
-
- if ((handled_tabs & s->table->map) || pos->sj_strategy == SJ_OPT_NONE)
- {
- remaining_tables |= s->table->map;
- continue;
- }
-
- if (pos->sj_strategy == SJ_OPT_MATERIALIZE)
- {
- SJ_MATERIALIZATION_INFO *sjm= s->emb_sj_nest->sj_mat_info;
- sjm->is_used= TRUE;
- sjm->is_sj_scan= FALSE;
- memcpy(pos - sjm->tables + 1, sjm->positions,
- sizeof(POSITION) * sjm->tables);
- first= tablenr - sjm->tables + 1;
- join->best_positions[first].n_sj_tables= sjm->tables;
- join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE;
- }
- else if (pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
- {
- POSITION *first_inner= join->best_positions + pos->sjm_scan_last_inner;
- SJ_MATERIALIZATION_INFO *sjm= first_inner->table->emb_sj_nest->sj_mat_info;
- sjm->is_used= TRUE;
- sjm->is_sj_scan= TRUE;
- first= pos->sjm_scan_last_inner - sjm->tables + 1;
- memcpy(join->best_positions + first,
- sjm->positions, sizeof(POSITION) * sjm->tables);
- join->best_positions[first].sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
- join->best_positions[first].n_sj_tables= sjm->tables;
- /*
- Do what advance_sj_state did: re-run best_access_path for every table
- in the [last_inner_table + 1; pos..) range
- */
- double prefix_rec_count;
- /* Get the prefix record count */
- if (first == join->const_tables)
- prefix_rec_count= 1.0;
- else
- prefix_rec_count= join->best_positions[first-1].prefix_record_count;
-
- /* Add materialization record count*/
- prefix_rec_count *= sjm->rows;
-
- uint i;
- table_map rem_tables= remaining_tables;
- for (i= tablenr; i != (first + sjm->tables - 1); i--)
- rem_tables |= join->best_positions[i].table->table->map;
-
- POSITION dummy;
- join->cur_sj_inner_tables= 0;
- for (i= first + sjm->tables; i <= tablenr; i++)
- {
- best_access_path(join, join->best_positions[i].table, rem_tables, i, FALSE,
- prefix_rec_count, join->best_positions + i, &dummy);
- prefix_rec_count *= join->best_positions[i].records_read;
- rem_tables &= ~join->best_positions[i].table->table->map;
- }
- }
-
- if (pos->sj_strategy == SJ_OPT_FIRST_MATCH)
- {
- first= pos->first_firstmatch_table;
- join->best_positions[first].sj_strategy= SJ_OPT_FIRST_MATCH;
- join->best_positions[first].n_sj_tables= tablenr - first + 1;
- POSITION dummy; // For loose scan paths
- double record_count= (first== join->const_tables)? 1.0:
- join->best_positions[tablenr - 1].prefix_record_count;
-
- table_map rem_tables= remaining_tables;
- uint idx;
- for (idx= first; idx <= tablenr; idx++)
- {
- rem_tables |= join->best_positions[idx].table->table->map;
- }
- /*
- Re-run best_access_path to produce best access methods that do not use
- join buffering
- */
- join->cur_sj_inner_tables= 0;
- for (idx= first; idx <= tablenr; idx++)
- {
- if (join->best_positions[idx].use_join_buffer)
- {
- best_access_path(join, join->best_positions[idx].table,
- rem_tables, idx, TRUE /* no jbuf */,
- record_count, join->best_positions + idx, &dummy);
- }
- record_count *= join->best_positions[idx].records_read;
- rem_tables &= ~join->best_positions[idx].table->table->map;
- }
- }
-
- if (pos->sj_strategy == SJ_OPT_LOOSE_SCAN)
- {
- first= pos->first_loosescan_table;
- POSITION *first_pos= join->best_positions + first;
- POSITION loose_scan_pos; // For loose scan paths
- double record_count= (first== join->const_tables)? 1.0:
- join->best_positions[tablenr - 1].prefix_record_count;
-
- table_map rem_tables= remaining_tables;
- uint idx;
- for (idx= first; idx <= tablenr; idx++)
- rem_tables |= join->best_positions[idx].table->table->map;
- /*
- Re-run best_access_path to produce best access methods that do not use
- join buffering
- */
- join->cur_sj_inner_tables= 0;
- for (idx= first; idx <= tablenr; idx++)
- {
- if (join->best_positions[idx].use_join_buffer || (idx == first))
- {
- best_access_path(join, join->best_positions[idx].table,
- rem_tables, idx, TRUE /* no jbuf */,
- record_count, join->best_positions + idx,
- &loose_scan_pos);
- if (idx==first)
- join->best_positions[idx]= loose_scan_pos;
- }
- rem_tables &= ~join->best_positions[idx].table->table->map;
- record_count *= join->best_positions[idx].records_read;
- }
- first_pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
- first_pos->n_sj_tables= my_count_bits(first_pos->table->emb_sj_nest->sj_inner_tables);
- }
-
- if (pos->sj_strategy == SJ_OPT_DUPS_WEEDOUT)
- {
- /*
- Duplicate Weedout starting at pos->first_dupsweedout_table, ending at
- this table.
- */
- first= pos->first_dupsweedout_table;
- join->best_positions[first].sj_strategy= SJ_OPT_DUPS_WEEDOUT;
- join->best_positions[first].n_sj_tables= tablenr - first + 1;
- }
-
- uint i_end= first + join->best_positions[first].n_sj_tables;
- for (uint i= first; i < i_end; i++)
- {
- if (i != first)
- join->best_positions[i].sj_strategy= SJ_OPT_NONE;
- handled_tabs |= join->best_positions[i].table->table->map;
- }
-
- if (tablenr != first)
- pos->sj_strategy= SJ_OPT_NONE;
- remaining_tables |= s->table->map;
- }
-}
-
-
-/*
Set up join struct according to the picked join order in
SYNOPSIS
@@ -9297,308 +6992,6 @@ end_sj_materialize(JOIN *join, JOIN_TAB *join_tab, bool end_of_records)
}
-/* Check if given Item was injected by semi-join equality */
-static bool is_cond_sj_in_equality(Item *item)
-{
- if (item->type() == Item::FUNC_ITEM &&
- ((Item_func*)item)->functype()== Item_func::EQ_FUNC)
- {
- Item_func_eq *item_eq= (Item_func_eq*)item;
- return test(item_eq->in_equality_no != UINT_MAX);
- }
- return FALSE;
-}
-
-
-
-
-void remove_sj_conds(Item **tree)
-{
- if (*tree)
- {
- if (is_cond_sj_in_equality(*tree))
- {
- *tree= NULL;
- return;
- }
- else if ((*tree)->type() == Item::COND_ITEM)
- {
- Item *item;
- List_iterator<Item> li(*(((Item_cond*)*tree)->argument_list()));
- while ((item= li++))
- {
- if (is_cond_sj_in_equality(item))
- li.replace(new Item_int(1));
- }
- }
- }
-}
-
-
-/*
- Create subquery IN-equalities assuming use of materialization strategy
-
- SYNOPSIS
- create_subq_in_equalities()
- thd Thread handle
- sjm Semi-join materialization structure
- subq_pred The subquery predicate
-
- DESCRIPTION
- Create subquery IN-equality predicates. That is, for a subquery
-
- (oe1, oe2, ...) IN (SELECT ie1, ie2, ... FROM ...)
-
- create "oe1=ie1 AND ie1=ie2 AND ..." expression, such that ie1, ie2, ..
- refer to the columns of the table that's used to materialize the
- subquery.
-
- RETURN
- Created condition
-*/
-
-Item *create_subq_in_equalities(THD *thd, SJ_MATERIALIZATION_INFO *sjm,
- Item_in_subselect *subq_pred)
-{
- Item *res= NULL;
- if (subq_pred->left_expr->cols() == 1)
- {
- if (!(res= new Item_func_eq(subq_pred->left_expr,
- new Item_field(sjm->table->field[0]))))
- return NULL; /* purecov: inspected */
- }
- else
- {
- Item *conj;
- for (uint i= 0; i < subq_pred->left_expr->cols(); i++)
- {
- if (!(conj= new Item_func_eq(subq_pred->left_expr->element_index(i),
- new Item_field(sjm->table->field[i]))) ||
- !(res= and_items(res, conj)))
- return NULL; /* purecov: inspected */
- }
- }
- if (res->fix_fields(thd, &res))
- return NULL; /* purecov: inspected */
- return res;
-}
-
-
-/*
- Setup semi-join materialization strategy for one semi-join nest
-
- SYNOPSIS
-
- setup_sj_materialization()
- tab The first tab in the semi-join
-
- DESCRIPTION
- Setup execution structures for one semi-join materialization nest:
- - Create the materialization temporary table
- - If we're going to do index lookups
- create TABLE_REF structure to make the lookus
- - else (if we're going to do a full scan of the temptable)
- create Copy_field structures to do copying.
-
- RETURN
- FALSE Ok
- TRUE Error
-*/
-
-bool setup_sj_materialization(JOIN_TAB *tab)
-{
- uint i;
- DBUG_ENTER("setup_sj_materialization");
- TABLE_LIST *emb_sj_nest= tab->table->pos_in_table_list->embedding;
- SJ_MATERIALIZATION_INFO *sjm= emb_sj_nest->sj_mat_info;
- THD *thd= tab->join->thd;
- /* First the calls come to the materialization function */
- List<Item> &item_list= emb_sj_nest->sj_subq_pred->unit->first_select()->item_list;
-
- /*
- Set up the table to write to, do as select_union::create_result_table does
- */
- sjm->sjm_table_param.init();
- sjm->sjm_table_param.field_count= item_list.elements;
- // psergey-merge: the following is not in 5.x: sjm->sjm_table_param.bit_fields_as_long= TRUE;
- List_iterator<Item> it(item_list);
- Item *right_expr;
- while((right_expr= it++))
- sjm->sjm_table_cols.push_back(right_expr);
-
- if (!(sjm->table= create_tmp_table(thd, &sjm->sjm_table_param,
- sjm->sjm_table_cols, (ORDER*) 0,
- TRUE /* distinct */,
- 1, /*save_sum_fields*/
- thd->options | TMP_TABLE_ALL_COLUMNS,
- HA_POS_ERROR /*rows_limit */,
- (char*)"sj-materialize")))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- sjm->table->file->extra(HA_EXTRA_WRITE_CACHE);
- sjm->table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
- tab->join->sj_tmp_tables.push_back(sjm->table);
- tab->join->sjm_info_list.push_back(sjm);
-
- sjm->materialized= FALSE;
- if (!sjm->is_sj_scan)
- {
- KEY *tmp_key; /* The only index on the temporary table. */
- uint tmp_key_parts; /* Number of keyparts in tmp_key. */
- tmp_key= sjm->table->key_info;
- tmp_key_parts= tmp_key->key_parts;
-
- /*
- Create/initialize everything we will need to index lookups into the
- temptable.
- */
- TABLE_REF *tab_ref;
- if (!(tab_ref= (TABLE_REF*) thd->alloc(sizeof(TABLE_REF))))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- tab_ref->key= 0; /* The only temp table index. */
- tab_ref->key_length= tmp_key->key_length;
- if (!(tab_ref->key_buff=
- (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
- !(tab_ref->key_copy=
- (store_key**) thd->alloc((sizeof(store_key*) *
- (tmp_key_parts + 1)))) ||
- !(tab_ref->items=
- (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
- DBUG_RETURN(TRUE); /* purecov: inspected */
-
- tab_ref->key_buff2=tab_ref->key_buff+ALIGN_SIZE(tmp_key->key_length);
- tab_ref->key_err=1;
- tab_ref->null_rejecting= 1;
- tab_ref->disable_cache= FALSE;
-
- KEY_PART_INFO *cur_key_part= tmp_key->key_part;
- store_key **ref_key= tab_ref->key_copy;
- uchar *cur_ref_buff= tab_ref->key_buff;
-
- for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
- {
- tab_ref->items[i]= emb_sj_nest->sj_subq_pred->left_expr->element_index(i);
- int null_count= test(cur_key_part->field->real_maybe_null());
- *ref_key= new store_key_item(thd, cur_key_part->field,
- /* TODO:
- the NULL byte is taken into account in
- cur_key_part->store_length, so instead of
- cur_ref_buff + test(maybe_null), we could
- use that information instead.
- */
- cur_ref_buff + null_count,
- null_count ? tab_ref->key_buff : 0,
- cur_key_part->length, tab_ref->items[i]);
- cur_ref_buff+= cur_key_part->store_length;
- }
- *ref_key= NULL; /* End marker. */
- tab_ref->key_err= 1;
- tab_ref->key_parts= tmp_key_parts;
- sjm->tab_ref= tab_ref;
-
- /*
- Remove the injected semi-join IN-equalities from join_tab conds. This
- needs to be done because the IN-equalities refer to columns of
- sj-inner tables which are not available after the materialization
- has been finished.
- */
- for (i= 0; i < sjm->tables; i++)
- {
- remove_sj_conds(&tab[i].select_cond);
- if (tab[i].select)
- remove_sj_conds(&tab[i].select->cond);
- }
- if (!(sjm->in_equality= create_subq_in_equalities(thd, sjm,
- emb_sj_nest->sj_subq_pred)))
- DBUG_RETURN(TRUE); /* purecov: inspected */
- }
- else
- {
- /*
- We'll be doing full scan of the temptable.
- Setup copying of temptable columns back to the record buffers
- for their source tables. We need this because IN-equalities
- refer to the original tables.
-
- EXAMPLE
-
- Consider the query:
- SELECT * FROM ot WHERE ot.col1 IN (SELECT it.col2 FROM it)
-
- Suppose it's executed with SJ-Materialization-scan. We choose to do scan
- if we can't do the lookup, i.e. the join order is (it, ot). The plan
- would look as follows:
-
- table access method condition
- it materialize+scan -
- ot (whatever) ot1.col1=it.col2 (C2)
-
- The condition C2 refers to current row of table it. The problem is
- that by the time we evaluate C2, we would have finished with scanning
- it itself and will be scanning the temptable.
-
- At the moment, our solution is to copy back: when we get the next
- temptable record, we copy its columns to their corresponding columns
- in the record buffers for the source tables.
- */
- sjm->copy_field= new Copy_field[sjm->sjm_table_cols.elements];
- it.rewind();
- for (uint i=0; i < sjm->sjm_table_cols.elements; i++)
- {
- bool dummy;
- Item_equal *item_eq;
- Item *item= (it++)->real_item();
- DBUG_ASSERT(item->type() == Item::FIELD_ITEM);
- Field *copy_to= ((Item_field*)item)->field;
- /*
- Tricks with Item_equal are due to the following: suppose we have a
- query:
-
- ... WHERE cond(ot.col) AND ot.col IN (SELECT it2.col FROM it1,it2
- WHERE it1.col= it2.col)
- then equality propagation will create an
-
- Item_equal(it1.col, it2.col, ot.col)
-
- then substitute_for_best_equal_field() will change the conditions
- according to the join order:
-
- it1
- it2 it1.col=it2.col
- ot cond(it1.col)
-
- although we've originally had "SELECT it2.col", conditions attached
- to subsequent outer tables will refer to it1.col, so SJM-Scan will
- need to unpack data to there.
- That is, if an element from subquery's select list participates in
- equality propagation, then we need to unpack it to the first
- element equality propagation member that refers to table that is
- within the subquery.
- */
- item_eq= find_item_equal(tab->join->cond_equal, copy_to, &dummy);
-
- if (item_eq)
- {
- List_iterator<Item_field> it(item_eq->fields);
- Item_field *item;
- while ((item= it++))
- {
- if (!(item->used_tables() & ~emb_sj_nest->sj_inner_tables))
- {
- copy_to= item->field;
- break;
- }
- }
- }
- sjm->copy_field[i].set(copy_to, sjm->table->field[i], FALSE);
- /* The write_set for source tables must be set up to allow the copying */
- bitmap_set_bit(copy_to->table->write_set, copy_to->field_index);
- }
- }
-
- DBUG_RETURN(FALSE);
-}
-
-
/*
Check whether a join buffer can be used to join the specified table
@@ -11875,37 +9268,6 @@ change_cond_ref_to_const(THD *thd, I_List<COND_CMP> *save_list,
}
}
-/**
- Remove additional condition inserted by IN/ALL/ANY transformation.
-
- @param conds condition for processing
-
- @return
- new conditions
-*/
-
-static Item *remove_additional_cond(Item* conds)
-{
- if (conds->name == in_additional_cond)
- return 0;
- if (conds->type() == Item::COND_ITEM)
- {
- Item_cond *cnd= (Item_cond*) conds;
- List_iterator<Item> li(*(cnd->argument_list()));
- Item *item;
- while ((item= li++))
- {
- if (item->name == in_additional_cond)
- {
- li.remove();
- if (cnd->argument_list()->elements == 1)
- return cnd->argument_list()->head();
- return conds;
- }
- }
- }
- return conds;
-}
static void
propagate_cond_constants(THD *thd, I_List<COND_CMP> *save_list,
@@ -12275,8 +9637,8 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top,
/**
Assign each nested join structure a bit in nested_join_map.
- Assign each nested join structure (except "confluent" ones - those that
- embed only one element) a bit in nested_join_map.
+ Assign each nested join structure (except ones that embed only one element
+ and so are redundant) a bit in nested_join_map.
@param join Join being processed
@param join_list List of tables
@@ -12285,7 +9647,7 @@ simplify_joins(JOIN *join, List<TABLE_LIST> *join_list, COND *conds, bool top,
@note
This function is called after simplify_joins(), when there are no
- redundant nested joins, #non_confluent_nested_joins <= #tables_in_join so
+ redundant nested joins, #non_redundant_nested_joins <= #tables_in_join so
we will not run out of bits in nested_join_map.
@return
@@ -12611,530 +9973,6 @@ void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab,
}
-/*
- Do semi-join optimization step after we've added a new tab to join prefix
-
- SYNOPSIS
- advance_sj_state()
- join The join we're optimizing
- remaining_tables Tables not in the join prefix
- new_join_tab Join tab we've just added to the join prefix
- idx Index of this join tab (i.e. number of tables
- in the prefix minus one)
- current_record_count INOUT Estimate of #records in join prefix's output
- current_read_time INOUT Cost to execute the join prefix
- loose_scan_pos IN A POSITION with LooseScan plan to access
- table new_join_tab
- (produced by the last best_access_path call)
-
- DESCRIPTION
- Update semi-join optimization state after we've added another tab (table
- and access method) to the join prefix.
-
- The state is maintained in join->positions[#prefix_size]. Each of the
- available strategies has its own state variables.
-
- for each semi-join strategy
- {
- update strategy's state variables;
-
- if (join prefix has all the tables that are needed to consider
- using this strategy for the semi-join(s))
- {
- calculate cost of using the strategy
- if ((this is the first strategy to handle the semi-join nest(s) ||
- the cost is less than other strategies))
- {
- // Pick this strategy
- pos->sj_strategy= ..
- ..
- }
- }
-
- Most of the new state is saved join->positions[idx] (and hence no undo
- is necessary). Several members of class JOIN are updated also, these
- changes can be rolled back with restore_prev_sj_state().
-
- See setup_semijoin_dups_elimination() for a description of what kinds of
- join prefixes each strategy can handle.
-*/
-
-static
-void advance_sj_state(JOIN *join, table_map remaining_tables,
- const JOIN_TAB *new_join_tab, uint idx,
- double *current_record_count, double *current_read_time,
- POSITION *loose_scan_pos)
-{
- TABLE_LIST *emb_sj_nest;
- POSITION *pos= join->positions + idx;
- remaining_tables &= ~new_join_tab->table->map;
-
- pos->prefix_cost.convert_from_cost(*current_read_time);
- pos->prefix_record_count= *current_record_count;
- pos->sj_strategy= SJ_OPT_NONE;
-
- /* Initialize the state or copy it from prev. tables */
- if (idx == join->const_tables)
- {
- pos->first_firstmatch_table= MAX_TABLES;
- pos->first_loosescan_table= MAX_TABLES;
- pos->dupsweedout_tables= 0;
- pos->sjm_scan_need_tables= 0;
- LINT_INIT(pos->sjm_scan_last_inner);
- }
- else
- {
- // FirstMatch
- pos->first_firstmatch_table=
- (pos[-1].sj_strategy == SJ_OPT_FIRST_MATCH) ?
- MAX_TABLES : pos[-1].first_firstmatch_table;
- pos->first_firstmatch_rtbl= pos[-1].first_firstmatch_rtbl;
- pos->firstmatch_need_tables= pos[-1].firstmatch_need_tables;
-
- // LooseScan
- pos->first_loosescan_table=
- (pos[-1].sj_strategy == SJ_OPT_LOOSE_SCAN) ?
- MAX_TABLES : pos[-1].first_loosescan_table;
- pos->loosescan_need_tables= pos[-1].loosescan_need_tables;
-
- // SJ-Materialization Scan
- pos->sjm_scan_need_tables=
- (pos[-1].sj_strategy == SJ_OPT_MATERIALIZE_SCAN) ?
- 0 : pos[-1].sjm_scan_need_tables;
- pos->sjm_scan_last_inner= pos[-1].sjm_scan_last_inner;
-
- // Duplicate Weedout
- pos->dupsweedout_tables= pos[-1].dupsweedout_tables;
- pos->first_dupsweedout_table= pos[-1].first_dupsweedout_table;
- }
-
- table_map handled_by_fm_or_ls= 0;
- /* FirstMatch Strategy */
- if (new_join_tab->emb_sj_nest &&
- optimizer_flag(join->thd, OPTIMIZER_SWITCH_FIRSTMATCH))
- {
- const table_map outer_corr_tables=
- new_join_tab->emb_sj_nest->nested_join->sj_corr_tables |
- new_join_tab->emb_sj_nest->nested_join->sj_depends_on;
- const table_map sj_inner_tables=
- new_join_tab->emb_sj_nest->sj_inner_tables;
-
- /*
- Enter condition:
- 1. The next join tab belongs to semi-join nest
- (verified for the encompassing code block above).
- 2. We're not in a duplicate producer range yet
- 3. All outer tables that
- - the subquery is correlated with, or
- - referred to from the outer_expr
- are in the join prefix
- 4. All inner tables are still part of remaining_tables.
- */
- if (!join->cur_sj_inner_tables && // (2)
- !(remaining_tables & outer_corr_tables) && // (3)
- (sj_inner_tables == // (4)
- ((remaining_tables | new_join_tab->table->map) & sj_inner_tables)))
- {
- /* Start tracking potential FirstMatch range */
- pos->first_firstmatch_table= idx;
- pos->firstmatch_need_tables= sj_inner_tables;
- pos->first_firstmatch_rtbl= remaining_tables;
- }
-
- if (pos->first_firstmatch_table != MAX_TABLES)
- {
- if (outer_corr_tables & pos->first_firstmatch_rtbl)
- {
- /*
- Trying to add an sj-inner table whose sj-nest has an outer correlated
- table that was not in the prefix. This means FirstMatch can't be used.
- */
- pos->first_firstmatch_table= MAX_TABLES;
- }
- else
- {
- /* Record that we need all of this semi-join's inner tables, too */
- pos->firstmatch_need_tables|= sj_inner_tables;
- }
-
- if (!(pos->firstmatch_need_tables & remaining_tables))
- {
- /*
- Got a complete FirstMatch range.
- Calculate correct costs and fanout
- */
- double reopt_cost, reopt_rec_count, sj_inner_fanout;
- optimize_wo_join_buffering(join, pos->first_firstmatch_table, idx,
- remaining_tables, FALSE, idx,
- &reopt_rec_count, &reopt_cost,
- &sj_inner_fanout);
- /*
- We don't yet know what are the other strategies, so pick the
- FirstMatch.
-
- We ought to save the alternate POSITIONs produced by
- optimize_wo_join_buffering but the problem is that providing save
- space uses too much space. Instead, we will re-calculate the
- alternate POSITIONs after we've picked the best QEP.
- */
- pos->sj_strategy= SJ_OPT_FIRST_MATCH;
- *current_read_time= reopt_cost;
- *current_record_count= reopt_rec_count / sj_inner_fanout;
- handled_by_fm_or_ls= pos->firstmatch_need_tables;
- }
- }
- }
-
- /* LooseScan Strategy */
- {
- POSITION *first=join->positions+pos->first_loosescan_table;
- /*
- LooseScan strategy can't handle interleaving between tables from the
- semi-join that LooseScan is handling and any other tables.
-
- If we were considering LooseScan for the join prefix (1)
- and the table we're adding creates an interleaving (2)
- then
- stop considering loose scan
- */
- if ((pos->first_loosescan_table != MAX_TABLES) && // (1)
- (first->table->emb_sj_nest->sj_inner_tables & remaining_tables) && //(2)
- new_join_tab->emb_sj_nest != first->table->emb_sj_nest) //(2)
- {
- pos->first_loosescan_table= MAX_TABLES;
- }
-
- /*
- If we got an option to use LooseScan for the current table, start
- considering using LooseScan strategy
- */
- if (loose_scan_pos->read_time != DBL_MAX)
- {
- pos->first_loosescan_table= idx;
- pos->loosescan_need_tables=
- new_join_tab->emb_sj_nest->sj_inner_tables |
- new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
- new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
- }
-
- if ((pos->first_loosescan_table != MAX_TABLES) &&
- !(remaining_tables & pos->loosescan_need_tables))
- {
- /*
- Ok we have LooseScan plan and also have all LooseScan sj-nest's
- inner tables and outer correlated tables into the prefix.
- */
-
- first=join->positions + pos->first_loosescan_table;
- uint n_tables= my_count_bits(first->table->emb_sj_nest->sj_inner_tables);
- /* Got a complete LooseScan range. Calculate its cost */
- double reopt_cost, reopt_rec_count, sj_inner_fanout;
- /*
- The same problem as with FirstMatch - we need to save POSITIONs
- somewhere but reserving space for all cases would require too
- much space. We will re-calculate POSITION structures later on.
- */
- optimize_wo_join_buffering(join, pos->first_loosescan_table, idx,
- remaining_tables,
- TRUE, //first_alt
- pos->first_loosescan_table + n_tables,
- &reopt_rec_count,
- &reopt_cost, &sj_inner_fanout);
- /*
- We don't yet have any other strategies that could handle this
- semi-join nest (the other options are Duplicate Elimination or
- Materialization, which need at least the same set of tables in
- the join prefix to be considered) so unconditionally pick the
- LooseScan.
- */
- pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
- *current_read_time= reopt_cost;
- *current_record_count= reopt_rec_count / sj_inner_fanout;
- handled_by_fm_or_ls= first->table->emb_sj_nest->sj_inner_tables;
- }
- }
-
- /*
- Update join->cur_sj_inner_tables (Used by FirstMatch in this function and
- LooseScan detector in best_access_path)
- */
- if ((emb_sj_nest= new_join_tab->emb_sj_nest))
- {
- join->cur_sj_inner_tables |= emb_sj_nest->sj_inner_tables;
- join->cur_dups_producing_tables |= emb_sj_nest->sj_inner_tables;
-
- /* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
- if (!(remaining_tables &
- emb_sj_nest->sj_inner_tables & ~new_join_tab->table->map))
- join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
- }
- join->cur_dups_producing_tables &= ~handled_by_fm_or_ls;
-
- /* 4. SJ-Materialization and SJ-Materialization-scan strategy handler */
- bool sjm_scan;
- SJ_MATERIALIZATION_INFO *mat_info;
- if ((mat_info= at_sjmat_pos(join, remaining_tables,
- new_join_tab, idx, &sjm_scan)))
- {
- if (sjm_scan)
- {
- /*
- We can't yet evaluate this option yet. This is because we can't
- accout for fanout of sj-inner tables yet:
-
- ntX SJM-SCAN(it1 ... itN) | ot1 ... otN |
- ^(1) ^(2)
-
- we're now at position (1). SJM temptable in general has multiple
- records, so at point (1) we'll get the fanout from sj-inner tables (ie
- there will be multiple record combinations).
-
- The final join result will not contain any semi-join produced
- fanout, i.e. tables within SJM-SCAN(...) will not contribute to
- the cardinality of the join output. Extra fanout produced by
- SJM-SCAN(...) will be 'absorbed' into fanout produced by ot1 ... otN.
-
- The simple way to model this is to remove SJM-SCAN(...) fanout once
- we reach the point #2.
- */
- pos->sjm_scan_need_tables=
- new_join_tab->emb_sj_nest->sj_inner_tables |
- new_join_tab->emb_sj_nest->nested_join->sj_depends_on |
- new_join_tab->emb_sj_nest->nested_join->sj_corr_tables;
- pos->sjm_scan_last_inner= idx;
- }
- else
- {
- /* This is SJ-Materialization with lookups */
- COST_VECT prefix_cost;
- signed int first_tab= (int)idx - mat_info->tables;
- double prefix_rec_count;
- if (first_tab < (int)join->const_tables)
- {
- prefix_cost.zero();
- prefix_rec_count= 1.0;
- }
- else
- {
- prefix_cost= join->positions[first_tab].prefix_cost;
- prefix_rec_count= join->positions[first_tab].prefix_record_count;
- }
-
- double mat_read_time= prefix_cost.total_cost();
- mat_read_time += mat_info->materialization_cost.total_cost() +
- prefix_rec_count * mat_info->lookup_cost.total_cost();
-
- if (mat_read_time < *current_read_time || join->cur_dups_producing_tables)
- {
- /*
- NOTE: When we pick to use SJM[-Scan] we don't memcpy its POSITION
- elements to join->positions as that makes it hard to return things
- back when making one step back in join optimization. That's done
- after the QEP has been chosen.
- */
- pos->sj_strategy= SJ_OPT_MATERIALIZE;
- *current_read_time= mat_read_time;
- *current_record_count= prefix_rec_count;
- join->cur_dups_producing_tables&=
- ~new_join_tab->emb_sj_nest->sj_inner_tables;
- }
- }
- }
-
- /* 4.A SJM-Scan second phase check */
- if (pos->sjm_scan_need_tables && /* Have SJM-Scan prefix */
- !(pos->sjm_scan_need_tables & remaining_tables))
- {
- TABLE_LIST *mat_nest=
- join->positions[pos->sjm_scan_last_inner].table->emb_sj_nest;
- SJ_MATERIALIZATION_INFO *mat_info= mat_nest->sj_mat_info;
-
- double prefix_cost;
- double prefix_rec_count;
- int first_tab= pos->sjm_scan_last_inner + 1 - mat_info->tables;
- /* Get the prefix cost */
- if (first_tab == (int)join->const_tables)
- {
- prefix_rec_count= 1.0;
- prefix_cost= 0.0;
- }
- else
- {
- prefix_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
- prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
- }
-
- /* Add materialization cost */
- prefix_cost += mat_info->materialization_cost.total_cost() +
- prefix_rec_count * mat_info->scan_cost.total_cost();
- prefix_rec_count *= mat_info->rows;
-
- uint i;
- table_map rem_tables= remaining_tables;
- for (i= idx; i != (first_tab + mat_info->tables - 1); i--)
- rem_tables |= join->positions[i].table->table->map;
-
- POSITION curpos, dummy;
- /* Need to re-run best-access-path as we prefix_rec_count has changed */
- for (i= first_tab + mat_info->tables; i <= idx; i++)
- {
- best_access_path(join, join->positions[i].table, rem_tables, i, FALSE,
- prefix_rec_count, &curpos, &dummy);
- prefix_rec_count *= curpos.records_read;
- prefix_cost += curpos.read_time;
- }
-
- /*
- Use the strategy if
- * it is cheaper then what we've had, or
- * we haven't picked any other semi-join strategy yet
- In the second case, we pick this strategy unconditionally because
- comparing cost without semi-join duplicate removal with cost with
- duplicate removal is not an apples-to-apples comparison.
- */
- if (prefix_cost < *current_read_time || join->cur_dups_producing_tables)
- {
- pos->sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
- *current_read_time= prefix_cost;
- *current_record_count= prefix_rec_count;
- join->cur_dups_producing_tables&= ~mat_nest->sj_inner_tables;
-
- }
- }
-
- /* 5. Duplicate Weedout strategy handler */
- {
- /*
- Duplicate weedout can be applied after all ON-correlated and
- correlated
- */
- TABLE_LIST *nest;
- if ((nest= new_join_tab->emb_sj_nest))
- {
- if (!pos->dupsweedout_tables)
- pos->first_dupsweedout_table= idx;
-
- pos->dupsweedout_tables |= nest->sj_inner_tables |
- nest->nested_join->sj_depends_on |
- nest->nested_join->sj_corr_tables;
- }
-
- if (pos->dupsweedout_tables &&
- !(remaining_tables &
- ~new_join_tab->table->map & pos->dupsweedout_tables))
- {
- /*
- Ok, reached a state where we could put a dups weedout point.
- Walk back and calculate
- - the join cost (this is needed as the accumulated cost may assume
- some other duplicate elimination method)
- - extra fanout that will be removed by duplicate elimination
- - duplicate elimination cost
- There are two cases:
- 1. We have other strategy/ies to remove all of the duplicates.
- 2. We don't.
-
- We need to calculate the cost in case #2 also because we need to make
- choice between this join order and others.
- */
- uint first_tab= pos->first_dupsweedout_table;
- double dups_cost;
- double prefix_rec_count;
- double sj_inner_fanout= 1.0;
- double sj_outer_fanout= 1.0;
- uint temptable_rec_size;
- if (first_tab == join->const_tables)
- {
- prefix_rec_count= 1.0;
- temptable_rec_size= 0;
- dups_cost= 0.0;
- }
- else
- {
- dups_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
- prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
- temptable_rec_size= 8; /* This is not true but we'll make it so */
- }
-
- table_map dups_removed_fanout= 0;
- for (uint j= pos->first_dupsweedout_table; j <= idx; j++)
- {
- POSITION *p= join->positions + j;
- dups_cost += p->read_time;
- if (p->table->emb_sj_nest)
- {
- sj_inner_fanout *= p->records_read;
- dups_removed_fanout |= p->table->table->map;
- }
- else
- {
- sj_outer_fanout *= p->records_read;
- temptable_rec_size += p->table->table->file->ref_length;
- }
- }
-
- /*
- Add the cost of temptable use. The table will have sj_outer_fanout
- records, and we will make
- - sj_outer_fanout table writes
- - sj_inner_fanout*sj_outer_fanout lookups.
-
- */
- double one_lookup_cost;
- if (sj_outer_fanout*temptable_rec_size >
- join->thd->variables.max_heap_table_size)
- one_lookup_cost= DISK_TEMPTABLE_LOOKUP_COST;
- else
- one_lookup_cost= HEAP_TEMPTABLE_LOOKUP_COST;
-
- double write_cost= join->positions[first_tab].prefix_record_count*
- sj_outer_fanout * one_lookup_cost;
- double full_lookup_cost= join->positions[first_tab].prefix_record_count*
- sj_outer_fanout* sj_inner_fanout *
- one_lookup_cost;
- dups_cost += write_cost + full_lookup_cost;
-
- /*
- Use the strategy if
- * it is cheaper then what we've had, or
- * we haven't picked any other semi-join strategy yet
- The second part is necessary because this strategy is the last one
- to consider (it needs "the most" tables in the prefix) and we can't
- leave duplicate-producing tables not handled by any strategy.
- */
- if (dups_cost < *current_read_time || join->cur_dups_producing_tables)
- {
- pos->sj_strategy= SJ_OPT_DUPS_WEEDOUT;
- *current_read_time= dups_cost;
- *current_record_count= *current_record_count / sj_inner_fanout;
- join->cur_dups_producing_tables &= ~dups_removed_fanout;
- }
- }
- }
-}
-
-
-/*
- Remove the last join tab from from join->cur_sj_inner_tables bitmap
- we assume remaining_tables doesnt contain @tab.
-*/
-
-static void restore_prev_sj_state(const table_map remaining_tables,
- const JOIN_TAB *tab, uint idx)
-{
- TABLE_LIST *emb_sj_nest;
- if ((emb_sj_nest= tab->emb_sj_nest))
- {
- /* If we're removing the last SJ-inner table, remove the sj-nest */
- if ((remaining_tables & emb_sj_nest->sj_inner_tables) ==
- (emb_sj_nest->sj_inner_tables & ~tab->table->map))
- {
- tab->join->cur_sj_inner_tables &= ~emb_sj_nest->sj_inner_tables;
- }
- }
-}
-
-
static COND *
optimize_cond(JOIN *join, COND *conds, List<TABLE_LIST> *join_list,
Item::cond_result *cond_value)
@@ -13855,11 +10693,6 @@ void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps)
be used for name resolving; can be "".
*/
-#define STRING_TOTAL_LENGTH_TO_PACK_ROWS 128
-#define AVG_STRING_LENGTH_TO_PACK_ROWS 64
-#define RATIO_TO_PACK_ROWS 2
-#define MIN_STRING_LENGTH_TO_PACK_ROWS 10
-
TABLE *
create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
ORDER *group, bool distinct, bool save_sum_fields,
@@ -14570,328 +11403,6 @@ err:
}
-/*
- Create a temporary table to weed out duplicate rowid combinations
-
- SYNOPSIS
-
- create_duplicate_weedout_tmp_table()
- thd Thread handle
- uniq_tuple_length_arg Length of the table's column
- sjtbl Update sjtbl->[start_]recinfo values which
- will be needed if we'll need to convert the
- created temptable from HEAP to MyISAM/Maria.
-
- DESCRIPTION
- Create a temporary table to weed out duplicate rowid combinations. The
- table has a single column that is a concatenation of all rowids in the
- combination.
-
- Depending on the needed length, there are two cases:
-
- 1. When the length of the column < max_key_length:
-
- CREATE TABLE tmp (col VARBINARY(n) NOT NULL, UNIQUE KEY(col));
-
- 2. Otherwise (not a valid SQL syntax but internally supported):
-
- CREATE TABLE tmp (col VARBINARY NOT NULL, UNIQUE CONSTRAINT(col));
-
- The code in this function was produced by extraction of relevant parts
- from create_tmp_table().
-
- RETURN
- created table
- NULL on error
-*/
-
-TABLE *create_duplicate_weedout_tmp_table(THD *thd,
- uint uniq_tuple_length_arg,
- SJ_TMP_TABLE *sjtbl)
-{
- MEM_ROOT *mem_root_save, own_root;
- TABLE *table;
- TABLE_SHARE *share;
- uint temp_pool_slot=MY_BIT_NONE;
- char *tmpname,path[FN_REFLEN];
- Field **reg_field;
- KEY_PART_INFO *key_part_info;
- KEY *keyinfo;
- uchar *group_buff;
- uchar *bitmaps;
- uint *blob_field;
- ENGINE_COLUMNDEF *recinfo, *start_recinfo;
- bool using_unique_constraint=FALSE;
- bool use_packed_rows= FALSE;
- Field *field, *key_field;
- uint blob_count, null_pack_length, null_count;
- uchar *null_flags;
- uchar *pos;
- DBUG_ENTER("create_duplicate_weedout_tmp_table");
- DBUG_ASSERT(!sjtbl->is_confluent);
- /*
- STEP 1: Get temporary table name
- */
- statistic_increment(thd->status_var.created_tmp_tables, &LOCK_status);
- if (use_temp_pool && !(test_flags & TEST_KEEP_TMP_TABLES))
- temp_pool_slot = bitmap_lock_set_next(&temp_pool);
-
- if (temp_pool_slot != MY_BIT_NONE) // we got a slot
- sprintf(path, "%s_%lx_%i", tmp_file_prefix,
- current_pid, temp_pool_slot);
- else
- {
- /* if we run out of slots or we are not using tempool */
- sprintf(path,"%s%lx_%lx_%x", tmp_file_prefix,current_pid,
- thd->thread_id, thd->tmp_table++);
- }
- fn_format(path, path, mysql_tmpdir, "", MY_REPLACE_EXT|MY_UNPACK_FILENAME);
-
- /* STEP 2: Figure if we'll be using a key or blob+constraint */
- if (uniq_tuple_length_arg >= CONVERT_IF_BIGGER_TO_BLOB)
- using_unique_constraint= TRUE;
-
- /* STEP 3: Allocate memory for temptable description */
- init_sql_alloc(&own_root, TABLE_ALLOC_BLOCK_SIZE, 0);
- if (!multi_alloc_root(&own_root,
- &table, sizeof(*table),
- &share, sizeof(*share),
- &reg_field, sizeof(Field*) * (1+1),
- &blob_field, sizeof(uint)*2,
- &keyinfo, sizeof(*keyinfo),
- &key_part_info, sizeof(*key_part_info) * 2,
- &start_recinfo,
- sizeof(*recinfo)*(1*2+4),
- &tmpname, (uint) strlen(path)+1,
- &group_buff, (!using_unique_constraint ?
- uniq_tuple_length_arg : 0),
- &bitmaps, bitmap_buffer_size(1)*3,
- NullS))
- {
- if (temp_pool_slot != MY_BIT_NONE)
- bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
- DBUG_RETURN(NULL);
- }
- strmov(tmpname,path);
-
-
- /* STEP 4: Create TABLE description */
- bzero((char*) table,sizeof(*table));
- bzero((char*) reg_field,sizeof(Field*)*2);
-
- table->mem_root= own_root;
- mem_root_save= thd->mem_root;
- thd->mem_root= &table->mem_root;
-
- table->field=reg_field;
- table->alias= "weedout-tmp";
- table->reginfo.lock_type=TL_WRITE; /* Will be updated */
- table->db_stat=HA_OPEN_KEYFILE+HA_OPEN_RNDFILE;
- table->map=1;
- table->temp_pool_slot = temp_pool_slot;
- table->copy_blobs= 1;
- table->in_use= thd;
- table->quick_keys.init();
- table->covering_keys.init();
- table->keys_in_use_for_query.init();
-
- table->s= share;
- init_tmp_table_share(thd, share, "", 0, tmpname, tmpname);
- share->blob_field= blob_field;
- share->blob_ptr_size= portable_sizeof_char_ptr;
- share->db_low_byte_first=1; // True for HEAP and MyISAM
- share->table_charset= NULL;
- share->primary_key= MAX_KEY; // Indicate no primary key
- share->keys_for_keyread.init();
- share->keys_in_use.init();
-
- blob_count= 0;
-
- /* Create the field */
- {
- /*
- For the sake of uniformity, always use Field_varstring (altough we could
- use Field_string for shorter keys)
- */
- field= new Field_varstring(uniq_tuple_length_arg, FALSE, "rowids", share,
- &my_charset_bin);
- if (!field)
- DBUG_RETURN(0);
- field->table= table;
- field->key_start.init(0);
- field->part_of_key.init(0);
- field->part_of_sortkey.init(0);
- field->unireg_check= Field::NONE;
- field->flags= (NOT_NULL_FLAG | BINARY_FLAG | NO_DEFAULT_VALUE_FLAG);
- field->reset_fields();
- field->init(table);
- field->orig_table= NULL;
-
- field->field_index= 0;
-
- *(reg_field++)= field;
- *blob_field= 0;
- *reg_field= 0;
-
- share->fields= 1;
- share->blob_fields= 0;
- }
-
- uint reclength= field->pack_length();
- if (using_unique_constraint)
- {
- share->db_plugin= ha_lock_engine(0, TMP_ENGINE_HTON);
- table->file= get_new_handler(share, &table->mem_root,
- share->db_type());
- DBUG_ASSERT(uniq_tuple_length_arg <= table->file->max_key_length());
- }
- else
- {
- share->db_plugin= ha_lock_engine(0, heap_hton);
- table->file= get_new_handler(share, &table->mem_root,
- share->db_type());
- }
- if (!table->file)
- goto err;
-
- null_count=1;
-
- null_pack_length= 1;
- reclength += null_pack_length;
-
- share->reclength= reclength;
- {
- uint alloc_length=ALIGN_SIZE(share->reclength + MI_UNIQUE_HASH_LENGTH+1);
- share->rec_buff_length= alloc_length;
- if (!(table->record[0]= (uchar*)
- alloc_root(&table->mem_root, alloc_length*3)))
- goto err;
- table->record[1]= table->record[0]+alloc_length;
- share->default_values= table->record[1]+alloc_length;
- }
- setup_tmp_table_column_bitmaps(table, bitmaps);
-
- recinfo= start_recinfo;
- null_flags=(uchar*) table->record[0];
- pos=table->record[0]+ null_pack_length;
- if (null_pack_length)
- {
- bzero((uchar*) recinfo,sizeof(*recinfo));
- recinfo->type=FIELD_NORMAL;
- recinfo->length=null_pack_length;
- recinfo++;
- bfill(null_flags,null_pack_length,255); // Set null fields
-
- table->null_flags= (uchar*) table->record[0];
- share->null_fields= null_count;
- share->null_bytes= null_pack_length;
- }
- null_count=1;
-
- {
- //Field *field= *reg_field;
- uint length;
- bzero((uchar*) recinfo,sizeof(*recinfo));
- field->move_field(pos,(uchar*) 0,0);
-
- field->reset();
- /*
- Test if there is a default field value. The test for ->ptr is to skip
- 'offset' fields generated by initalize_tables
- */
- // Initialize the table field:
- bzero(field->ptr, field->pack_length());
-
- length=field->pack_length();
- pos+= length;
-
- /* Make entry for create table */
- recinfo->length=length;
- if (field->flags & BLOB_FLAG)
- recinfo->type= FIELD_BLOB;
- else if (use_packed_rows &&
- field->real_type() == MYSQL_TYPE_STRING &&
- length >= MIN_STRING_LENGTH_TO_PACK_ROWS)
- recinfo->type=FIELD_SKIP_ENDSPACE;
- else
- recinfo->type=FIELD_NORMAL;
-
- field->table_name= &table->alias;
- }
-
- //param->recinfo=recinfo;
- //store_record(table,s->default_values); // Make empty default record
-
- if (thd->variables.tmp_table_size == ~ (ulonglong) 0) // No limit
- share->max_rows= ~(ha_rows) 0;
- else
- share->max_rows= (ha_rows) (((share->db_type() == heap_hton) ?
- min(thd->variables.tmp_table_size,
- thd->variables.max_heap_table_size) :
- thd->variables.tmp_table_size) /
- share->reclength);
- set_if_bigger(share->max_rows,1); // For dummy start options
-
-
- //// keyinfo= param->keyinfo;
- if (TRUE)
- {
- DBUG_PRINT("info",("Creating group key in temporary table"));
- share->keys=1;
- share->uniques= test(using_unique_constraint);
- table->key_info=keyinfo;
- keyinfo->key_part=key_part_info;
- keyinfo->flags=HA_NOSAME;
- keyinfo->usable_key_parts= keyinfo->key_parts= 1;
- keyinfo->key_length=0;
- keyinfo->rec_per_key=0;
- keyinfo->algorithm= HA_KEY_ALG_UNDEF;
- keyinfo->name= (char*) "weedout_key";
- {
- key_part_info->null_bit=0;
- key_part_info->field= field;
- key_part_info->offset= field->offset(table->record[0]);
- key_part_info->length= (uint16) field->key_length();
- key_part_info->type= (uint8) field->key_type();
- key_part_info->key_type = FIELDFLAG_BINARY;
- if (!using_unique_constraint)
- {
- if (!(key_field= field->new_key_field(thd->mem_root, table,
- group_buff,
- field->null_ptr,
- field->null_bit)))
- goto err;
- key_part_info->key_part_flag|= HA_END_SPACE_ARE_EQUAL; //todo need this?
- }
- keyinfo->key_length+= key_part_info->length;
- }
- }
-
- if (thd->is_fatal_error) // If end of memory
- goto err;
- share->db_record_offset= 1;
- if (share->db_type() == TMP_ENGINE_HTON)
- {
- recinfo++;
- if (create_internal_tmp_table(table, keyinfo, start_recinfo, &recinfo, 0))
- goto err;
- }
- sjtbl->start_recinfo= start_recinfo;
- sjtbl->recinfo= recinfo;
- if (open_tmp_table(table))
- goto err;
-
- thd->mem_root= mem_root_save;
- DBUG_RETURN(table);
-
-err:
- thd->mem_root= mem_root_save;
- free_tmp_table(thd,table); /* purecov: inspected */
- if (temp_pool_slot != MY_BIT_NONE)
- bitmap_lock_clear_bit(&temp_pool, temp_pool_slot);
- DBUG_RETURN(NULL); /* purecov: inspected */
-}
-
/****************************************************************************/
@@ -15021,7 +11532,7 @@ error:
}
-static bool open_tmp_table(TABLE *table)
+bool open_tmp_table(TABLE *table)
{
int error;
if ((error= table->file->ha_open(table, table->s->table_name.str, O_RDWR,
@@ -15071,10 +11582,10 @@ static bool open_tmp_table(TABLE *table)
/* Create internal Maria temporary table */
-static bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
- ENGINE_COLUMNDEF *start_recinfo,
- ENGINE_COLUMNDEF **recinfo,
- ulonglong options)
+bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
+ ENGINE_COLUMNDEF *start_recinfo,
+ ENGINE_COLUMNDEF **recinfo,
+ ulonglong options)
{
int error;
MARIA_KEYDEF keydef;
@@ -16167,117 +12678,6 @@ sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
}
-/*
- SemiJoinDuplicateElimination: Weed out duplicate row combinations
-
- SYNPOSIS
- do_sj_dups_weedout()
- thd Thread handle
- sjtbl Duplicate weedout table
-
- DESCRIPTION
- Try storing current record combination of outer tables (i.e. their
- rowids) in the temporary table. This records the fact that we've seen
- this record combination and also tells us if we've seen it before.
-
- RETURN
- -1 Error
- 1 The row combination is a duplicate (discard it)
- 0 The row combination is not a duplicate (continue)
-*/
-
-int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl)
-{
- int error;
- SJ_TMP_TABLE::TAB *tab= sjtbl->tabs;
- SJ_TMP_TABLE::TAB *tab_end= sjtbl->tabs_end;
- uchar *ptr;
- uchar *nulls_ptr;
-
- DBUG_ENTER("do_sj_dups_weedout");
-
- if (sjtbl->is_confluent)
- {
- if (sjtbl->have_confluent_row)
- DBUG_RETURN(1);
-
- sjtbl->have_confluent_row= TRUE;
- DBUG_RETURN(0);
- }
-
- ptr= sjtbl->tmp_table->record[0] + 1;
- nulls_ptr= ptr;
-
- /* Put the the rowids tuple into table->record[0]: */
-
- // 1. Store the length
- if (((Field_varstring*)(sjtbl->tmp_table->field[0]))->length_bytes == 1)
- {
- *ptr= (uchar)(sjtbl->rowid_len + sjtbl->null_bytes);
- ptr++;
- }
- else
- {
- int2store(ptr, sjtbl->rowid_len + sjtbl->null_bytes);
- ptr += 2;
- }
-
- // 2. Zero the null bytes
- if (sjtbl->null_bytes)
- {
- bzero(ptr, sjtbl->null_bytes);
- ptr += sjtbl->null_bytes;
- }
-
- // 3. Put the rowids
- for (uint i=0; tab != tab_end; tab++, i++)
- {
- handler *h= tab->join_tab->table->file;
- if (tab->join_tab->table->maybe_null && tab->join_tab->table->null_row)
- {
- /* It's a NULL-complemented row */
- *(nulls_ptr + tab->null_byte) |= tab->null_bit;
- bzero(ptr + tab->rowid_offset, h->ref_length);
- }
- else
- {
- /* Copy the rowid value */
- memcpy(ptr + tab->rowid_offset, h->ref, h->ref_length);
- }
- }
-
- error= sjtbl->tmp_table->file->ha_write_row(sjtbl->tmp_table->record[0]);
- if (error)
- {
- /* create_internal_tmp_table_from_heap will generate error if needed */
- if (!sjtbl->tmp_table->file->is_fatal_error(error, HA_CHECK_DUP))
- DBUG_RETURN(1); /* Duplicate */
- if (create_internal_tmp_table_from_heap(thd, sjtbl->tmp_table,
- sjtbl->start_recinfo,
- &sjtbl->recinfo, error, 1))
- DBUG_RETURN(-1);
- }
- DBUG_RETURN(0);
-}
-
-
-/*
- SemiJoinDuplicateElimination: Reset the temporary table
-*/
-
-int do_sj_reset(SJ_TMP_TABLE *sj_tbl)
-{
- DBUG_ENTER("do_sj_reset");
- if (sj_tbl->tmp_table)
- {
- int rc= sj_tbl->tmp_table->file->ha_delete_all_rows();
- DBUG_RETURN(rc);
- }
- sj_tbl->have_confluent_row= FALSE;
- DBUG_RETURN(0);
-}
-
-
/**
@brief Process one row of the nested loop join.
@@ -17619,7 +14019,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
1 if right_item is used removable reference key on left_item
*/
-static bool test_if_ref(Item *root_cond, Item_field *left_item,Item *right_item)
+bool test_if_ref(Item *root_cond, Item_field *left_item,Item *right_item)
{
Field *field=left_item->field;
JOIN_TAB *join_tab= field->table->reginfo.join_tab;
@@ -17667,56 +14067,6 @@ static bool test_if_ref(Item *root_cond, Item_field *left_item,Item *right_item)
}
-/**
- @brief Replaces an expression destructively inside the expression tree of
- the WHERE clase.
-
- @note Because of current requirements for semijoin flattening, we do not
- need to recurse here, hence this function will only examine the top-level
- AND conditions. (see JOIN::prepare, comment starting with "Check if the
- subquery predicate can be executed via materialization".
-
- @param join The top-level query.
- @param old_cond The expression to be replaced.
- @param new_cond The expression to be substituted.
- @param do_fix_fields If true, Item::fix_fields(THD*, Item**) is called for
- the new expression.
- @return <code>true</code> if there was an error, <code>false</code> if
- successful.
-*/
-static bool replace_where_subcondition(JOIN *join, Item **expr,
- Item *old_cond, Item *new_cond,
- bool do_fix_fields)
-{
- //Item **expr= (emb_nest == (TABLE_LIST*)1)? &join->conds : &emb_nest->on_expr;
- if (*expr == old_cond)
- {
- *expr= new_cond;
- if (do_fix_fields)
- new_cond->fix_fields(join->thd, expr);
- join->select_lex->where= *expr;
- return FALSE;
- }
-
- if ((*expr)->type() == Item::COND_ITEM)
- {
- List_iterator<Item> li(*((Item_cond*)(*expr))->argument_list());
- Item *item;
- while ((item= li++))
- {
- if (item == old_cond)
- {
- li.replace(new_cond);
- if (do_fix_fields)
- new_cond->fix_fields(join->thd, li.ref());
- return FALSE;
- }
- }
- }
- // If we came here it means there were an error during prerequisites check.
- DBUG_ASSERT(0);
- return TRUE;
-}
/*
Extract a condition that can be checked after reading given table
diff --git a/sql/sql_select.h b/sql/sql_select.h
index 4af1de01f67..8f509a9d270 100644
--- a/sql/sql_select.h
+++ b/sql/sql_select.h
@@ -28,6 +28,12 @@
#include "procedure.h"
#include <myisam.h>
+#if defined(WITH_MARIA_STORAGE_ENGINE) && defined(USE_MARIA_FOR_TMP_TABLES)
+#include "../storage/maria/ha_maria.h"
+#define TMP_ENGINE_HTON maria_hton
+#else
+#define TMP_ENGINE_HTON myisam_hton
+#endif
/* Values in optimize */
#define KEY_OPTIMIZE_EXISTS 1
#define KEY_OPTIMIZE_REF_OR_NULL 2
@@ -1195,7 +1201,6 @@ enum_nested_loop_state sub_select(JOIN *join,JOIN_TAB *join_tab, bool
end_of_records);
enum_nested_loop_state sub_select_sjm(JOIN *join, JOIN_TAB *join_tab,
bool end_of_records);
-int do_sj_dups_weedout(THD *thd, SJ_TMP_TABLE *sjtbl);
enum_nested_loop_state
end_send_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
@@ -1329,74 +1334,6 @@ typedef struct st_rollup
List<Item> *fields;
} ROLLUP;
-/*
- Temporary table used by semi-join DuplicateElimination strategy
-
- This consists of the temptable itself and data needed to put records
- into it. The table's DDL is as follows:
-
- CREATE TABLE tmptable (col VARCHAR(n) BINARY, PRIMARY KEY(col));
-
- where the primary key can be replaced with unique constraint if n exceeds
- the limit (as it is always done for query execution-time temptables).
-
- The record value is a concatenation of rowids of tables from the join we're
- executing. If a join table is on the inner side of the outer join, we
- assume that its rowid can be NULL and provide means to store this rowid in
- the tuple.
-*/
-
-class SJ_TMP_TABLE : public Sql_alloc
-{
-public:
- /*
- Array of pointers to tables whose rowids compose the temporary table
- record.
- */
- class TAB
- {
- public:
- JOIN_TAB *join_tab;
- uint rowid_offset;
- ushort null_byte;
- uchar null_bit;
- };
- TAB *tabs;
- TAB *tabs_end;
-
- /*
- is_confluent==TRUE means this is a special case where the temptable record
- has zero length (and presence of a unique key means that the temptable can
- have either 0 or 1 records).
- In this case we don't create the physical temptable but instead record
- its state in SJ_TMP_TABLE::have_confluent_record.
- */
- bool is_confluent;
-
- /*
- When is_confluent==TRUE: the contents of the table (whether it has the
- record or not).
- */
- bool have_confluent_row;
-
- /* table record parameters */
- uint null_bits;
- uint null_bytes;
- uint rowid_len;
-
- /* The temporary table itself (NULL means not created yet) */
- TABLE *tmp_table;
-
- /*
- These are the members we got from temptable creation code. We'll need
- them if we'll need to convert table from HEAP to MyISAM/Maria.
- */
- ENGINE_COLUMNDEF *start_recinfo;
- ENGINE_COLUMNDEF *recinfo;
-
- /* Pointer to next table (next->start_idx > this->end_idx) */
- SJ_TMP_TABLE *next;
-};
#define SJ_OPT_NONE 0
#define SJ_OPT_DUPS_WEEDOUT 1
@@ -1711,7 +1648,6 @@ public:
Item_sum ***func);
int rollup_send_data(uint idx);
int rollup_write_data(uint idx, TABLE *table);
- void remove_subq_pushed_predicates(Item **where);
/**
Release memory and, if possible, the open tables held by this execution
plan (and nested plans). It's used to release some tables before
@@ -1763,11 +1699,6 @@ void TEST_join(JOIN *join);
/* Extern functions in sql_select.cc */
bool store_val_in_field(Field *field, Item *val, enum_check_fields check_flag);
-TABLE *create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
- ORDER *group, bool distinct, bool save_sum_fields,
- ulonglong select_options, ha_rows rows_limit,
- char* alias);
-void free_tmp_table(THD *thd, TABLE *entry);
void count_field_types(SELECT_LEX *select_lex, TMP_TABLE_PARAM *param,
List<Item> &fields, bool reset_with_sum_func);
bool setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param,
@@ -1776,10 +1707,6 @@ bool setup_copy_fields(THD *thd, TMP_TABLE_PARAM *param,
uint elements, List<Item> &fields);
void copy_fields(TMP_TABLE_PARAM *param);
void copy_funcs(Item **func_ptr);
-bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table,
- ENGINE_COLUMNDEF *start_recinfo,
- ENGINE_COLUMNDEF **recinfo,
- int error, bool ignore_last_dupp_key_error);
uint find_shortest_key(TABLE *table, const key_map *usable_keys);
Field* create_tmp_field_from_field(THD *thd, Field* org_field,
const char *name, TABLE *table,
@@ -1955,13 +1882,59 @@ int test_if_item_cache_changed(List<Cached_item> &list);
void calc_used_field_length(THD *thd, JOIN_TAB *join_tab);
int join_init_read_record(JOIN_TAB *tab);
void set_position(JOIN *join,uint idx,JOIN_TAB *table,KEYUSE *key);
+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,
+ double *reopt_rec_count, double *reopt_cost,
+ double *sj_inner_fanout);
+Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
+ bool *inherited_fl);
+bool test_if_ref(COND *root_cond,
+ Item_field *left_item,Item *right_item);
inline bool optimizer_flag(THD *thd, uint flag)
{
return (thd->variables.optimizer_switch & flag);
}
+/* Table elimination entry point function */
void eliminate_tables(JOIN *join);
+/* Index Condition Pushdown entry point function */
void push_index_cond(JOIN_TAB *tab, uint keyno, bool other_tbls_ok);
+/****************************************************************************
+ Temporary table support for SQL Runtime
+ ***************************************************************************/
+
+#define STRING_TOTAL_LENGTH_TO_PACK_ROWS 128
+#define AVG_STRING_LENGTH_TO_PACK_ROWS 64
+#define RATIO_TO_PACK_ROWS 2
+#define MIN_STRING_LENGTH_TO_PACK_ROWS 10
+
+TABLE *create_tmp_table(THD *thd,TMP_TABLE_PARAM *param,List<Item> &fields,
+ ORDER *group, bool distinct, bool save_sum_fields,
+ ulonglong select_options, ha_rows rows_limit,
+ char* alias);
+void free_tmp_table(THD *thd, TABLE *entry);
+bool create_internal_tmp_table_from_heap(THD *thd, TABLE *table,
+ ENGINE_COLUMNDEF *start_recinfo,
+ ENGINE_COLUMNDEF **recinfo,
+ int error, bool ignore_last_dupp_key_error);
+bool create_internal_tmp_table(TABLE *table, KEY *keyinfo,
+ ENGINE_COLUMNDEF *start_recinfo,
+ ENGINE_COLUMNDEF **recinfo,
+ ulonglong options);
+bool open_tmp_table(TABLE *table);
+void setup_tmp_table_column_bitmaps(TABLE *table, uchar *bitmaps);
+
+
+
+