sql/opt_explain.cc (1,460 lines of code) (raw):
/* Copyright (c) 2011, 2014, Oracle and/or its affiliates. All rights
reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
/** @file "EXPLAIN <command>" implementation */
#include "opt_explain.h"
#include "sql_select.h"
#include "sql_optimizer.h" // JOIN
#include "sql_partition.h" // for make_used_partitions_str()
#include "sql_join_buffer.h" // JOIN_CACHE
#include "filesort.h" // Filesort
#include "opt_explain_format.h"
#include "sql_base.h" // lock_tables
typedef qep_row::extra extra;
static bool mysql_explain_unit(THD *thd, SELECT_LEX_UNIT *unit,
select_result *result);
static void propagate_explain_option(THD *thd, SELECT_LEX_UNIT *unit);
const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
"ALL","range","index","fulltext",
"ref_or_null","unique_subquery","index_subquery",
"index_merge"
};
static const enum_query_type cond_print_flags=
enum_query_type(QT_ORDINARY | QT_SHOW_SELECT_NUMBER);
/**
A base for all Explain_* classes
Explain_* classes collect and output EXPLAIN data.
This class hierarchy is a successor of the old select_describe() function of 5.5.
*/
class Explain
{
protected:
THD *const thd; ///< cached THD pointer
const CHARSET_INFO *const cs; ///< cached pointer to system_charset_info
JOIN *const join; ///< top-level JOIN (if any) provided by caller
select_result *const external_result; ///< stream (if any) provided by caller
Explain_format *const fmt; ///< shortcut for thd->lex->explain_format
Explain_context_enum context_type; ///< associated value for struct. explain
JOIN::ORDER_with_src order_list; //< ORDER BY item tree list
JOIN::ORDER_with_src group_list; //< GROUP BY item tee list
protected:
class Lazy_condition: public Lazy
{
Item *const condition;
public:
Lazy_condition(Item *condition_arg): condition(condition_arg) {}
virtual bool eval(String *ret)
{
ret->length(0);
if (condition)
condition->print(ret, cond_print_flags);
return false;
}
};
explicit Explain(Explain_context_enum context_type_arg,
THD *thd_arg, JOIN *join_arg= NULL)
: thd(thd_arg),
cs(system_charset_info),
join(join_arg),
external_result(join ? join->result : NULL),
fmt(thd->lex->explain_format),
context_type(context_type_arg),
order_list(NULL),
group_list(NULL)
{
if (join)
{
order_list= join->order;
group_list= join->group_list;
}
else
{
if (select_lex()->order_list.elements)
order_list= JOIN::ORDER_with_src(select_lex()->order_list.first,
ESC_ORDER_BY);
if (select_lex()->group_list.elements)
group_list= JOIN::ORDER_with_src(select_lex()->group_list.first,
ESC_GROUP_BY);
}
}
public:
virtual ~Explain() {}
bool send();
protected:
/**
Explain everything but subqueries
*/
virtual bool shallow_explain();
/**
Explain the rest of things after the @c shallow_explain() call
*/
bool explain_subqueries(select_result *result);
bool mark_subqueries(Item *item, qep_row *destination,
Explain_context_enum type);
bool mark_order_subqueries(const JOIN::ORDER_with_src &order);
bool prepare_columns();
bool describe(uint8 mask) const { return thd->lex->describe & mask; }
SELECT_LEX *select_lex() const
{
return join ? join->select_lex : &thd->lex->select_lex;
}
/**
Prepare the self-allocated result object
For queries with top-level JOIN the caller provides pre-allocated
select_send object. Then that JOIN object prepares the select_send
object calling result->prepare() in JOIN::prepare(),
result->initalize_tables() in JOIN::optimize() and result->prepare2()
in JOIN::exec().
However without the presence of the top-level JOIN we have to
prepare/initialize select_send object manually.
*/
bool prepare(select_result *result)
{
DBUG_ASSERT(join == NULL);
List<Item> dummy;
return result->prepare(dummy, select_lex()->master_unit()) ||
result->prepare2();
}
/**
Push a part of the "extra" column into formatter
Traditional formatter outputs traditional_extra_tags[tag] as is.
Hierarchical formatter outputs a property with the json_extra_tags[tag] name
and a boolean value of true.
@param tag type of the "extra" part
@retval false Ok
@retval true Error (OOM)
*/
bool push_extra(Extra_tag tag)
{
extra *e= new extra(tag);
return e == NULL || fmt->entry()->col_extra.push_back(e);
}
/**
Push a part of the "extra" column into formatter
@param tag type of the "extra" part
@param arg for traditional formatter: rest of the part text,
for hierarchical format: string value of the property
@retval false Ok
@retval true Error (OOM)
*/
bool push_extra(Extra_tag tag, const String &arg)
{
if (arg.is_empty())
return push_extra(tag);
extra *e= new extra(tag, arg.dup(thd->mem_root));
return !e || !e->data || fmt->entry()->col_extra.push_back(e);
}
/**
Push a part of the "extra" column into formatter
@param tag type of the "extra" part
@param arg for traditional formatter: rest of the part text,
for hierarchical format: string value of the property
NOTE: arg must be a long-living string constant.
@retval false Ok
@retval true Error (OOM)
*/
bool push_extra(Extra_tag tag, const char *arg)
{
extra *e= new extra(tag, arg);
return !e || fmt->entry()->col_extra.push_back(e);
}
/*
Rest of the functions are overloadable functions, those calculate and fill
"col_*" fields with Items for further sending as EXPLAIN columns.
"explain_*" functions return false on success and true on error (usually OOM).
*/
virtual bool explain_id();
virtual bool explain_select_type();
virtual bool explain_table_name() { return false; }
virtual bool explain_partitions() { return false; }
virtual bool explain_join_type() { return false; }
virtual bool explain_possible_keys() { return false; }
/** fill col_key and and col_key_len fields together */
virtual bool explain_key_and_len() { return false; }
virtual bool explain_ref() { return false; }
/** fill col_rows and col_filtered fields together */
virtual bool explain_rows_and_filtered() { return false; }
virtual bool explain_extra() { return false; }
virtual bool explain_modify_flags() { return false; }
};
/**
Explain_no_table class outputs a trivial EXPLAIN row with "extra" column
This class is intended for simple cases to produce EXPLAIN output
with "No tables used", "No matching records" etc.
Optionally it can output number of estimated rows in the "row"
column.
@note This class also produces EXPLAIN rows for inner units (if any).
*/
class Explain_no_table: public Explain
{
private:
const char *message; ///< cached "message" argument
const ha_rows rows; ///< HA_POS_ERROR or cached "rows" argument
public:
Explain_no_table(THD *thd_arg, JOIN *join_arg, const char *message_arg)
: Explain(CTX_JOIN, thd_arg, join_arg),
message(message_arg), rows(HA_POS_ERROR)
{}
Explain_no_table(THD *thd_arg, const char *message_arg,
ha_rows rows_arg= HA_POS_ERROR)
: Explain(CTX_JOIN, thd_arg),
message(message_arg), rows(rows_arg)
{}
protected:
virtual bool shallow_explain();
virtual bool explain_rows_and_filtered();
virtual bool explain_extra();
};
/**
Explain_union_result class outputs EXPLAIN row for UNION
*/
class Explain_union_result : public Explain
{
public:
Explain_union_result(THD *thd_arg, JOIN *join_arg)
: Explain(CTX_UNION_RESULT, thd_arg, join_arg)
{
/* it's a UNION: */
DBUG_ASSERT(join_arg->select_lex == join_arg->unit->fake_select_lex);
}
protected:
virtual bool explain_id();
virtual bool explain_table_name();
virtual bool explain_join_type();
virtual bool explain_extra();
};
/**
Common base class for Explain_join and Explain_table
*/
class Explain_table_base : public Explain {
protected:
const TABLE *table;
key_map usable_keys;
Explain_table_base(Explain_context_enum context_type_arg,
THD *const thd_arg, JOIN *const join_arg)
: Explain(context_type_arg, thd_arg, join_arg), table(NULL)
{}
Explain_table_base(Explain_context_enum context_type_arg,
THD *const thd_arg, TABLE *const table_arg)
: Explain(context_type_arg, thd_arg), table(table_arg)
{}
virtual bool explain_partitions();
virtual bool explain_possible_keys();
bool explain_key_parts(int key, uint key_parts);
bool explain_key_and_len_quick(const SQL_SELECT *select);
bool explain_key_and_len_index(int key);
bool explain_key_and_len_index(int key, uint key_length, uint key_parts);
bool explain_extra_common(const SQL_SELECT *select,
const JOIN_TAB *tab,
int quick_type,
uint keyno);
bool explain_tmptable_and_filesort(bool need_tmp_table_arg,
bool need_sort_arg);
virtual bool explain_modify_flags();
};
/**
Explain_join class produces EXPLAIN output for JOINs
*/
class Explain_join : public Explain_table_base
{
private:
bool need_tmp_table; ///< add "Using temporary" to "extra" if true
bool need_order; ///< add "Using filesort"" to "extra" if true
const bool distinct; ///< add "Distinct" string to "extra" column if true
uint tabnum; ///< current tab number in join->join_tab[]
JOIN_TAB *tab; ///< current JOIN_TAB
SQL_SELECT *select; ///< current SQL_SELECT
int quick_type; ///< current quick type, see anon. enum at QUICK_SELECT_I
table_map used_tables; ///< accumulate used tables bitmap
public:
Explain_join(THD *thd_arg, JOIN *join_arg,
bool need_tmp_table_arg, bool need_order_arg,
bool distinct_arg)
: Explain_table_base(CTX_JOIN, thd_arg, join_arg),
need_tmp_table(need_tmp_table_arg),
need_order(need_order_arg), distinct(distinct_arg),
tabnum(0), select(0), used_tables(0)
{
/* it is not UNION: */
DBUG_ASSERT(join_arg->select_lex != join_arg->unit->fake_select_lex);
}
private:
// Next 4 functions begin and end context for GROUP BY, ORDER BY and DISTINC
bool begin_sort_context(Explain_sort_clause clause, Explain_context_enum ctx);
bool end_sort_context(Explain_sort_clause clause, Explain_context_enum ctx);
bool begin_simple_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx);
bool end_simple_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx);
bool explain_join_tab(size_t tab_num);
protected:
virtual bool shallow_explain();
virtual bool explain_table_name();
virtual bool explain_join_type();
virtual bool explain_key_and_len();
virtual bool explain_ref();
virtual bool explain_rows_and_filtered();
virtual bool explain_extra();
virtual bool explain_select_type();
virtual bool explain_id();
};
/**
Explain_table class produce EXPLAIN output for queries without top-level JOIN
This class is a simplified version of the Explain_join class. It works in the
context of queries which implementation lacks top-level JOIN object (EXPLAIN
single-table UPDATE and DELETE).
*/
class Explain_table: public Explain_table_base
{
private:
const SQL_SELECT *const select; ///< cached "select" argument
const uint key; ///< cached "key" number argument
const ha_rows limit; ///< HA_POS_ERROR or cached "limit" argument
const bool need_tmp_table; ///< cached need_tmp_table argument
const bool need_sort; ///< cached need_sort argument
const bool is_update; // is_update ? UPDATE command : DELETE command
const bool used_key_is_modified; ///< UPDATE command updates used key
public:
Explain_table(THD *const thd_arg, TABLE *const table_arg,
const SQL_SELECT *select_arg,
uint key_arg, ha_rows limit_arg,
bool need_tmp_table_arg, bool need_sort_arg,
bool is_update_arg, bool used_key_is_modified_arg)
: Explain_table_base(CTX_JOIN, thd_arg, table_arg),
select(select_arg), key(key_arg),
limit(limit_arg),
need_tmp_table(need_tmp_table_arg), need_sort(need_sort_arg),
is_update(is_update_arg), used_key_is_modified(used_key_is_modified_arg)
{
usable_keys= table->possible_quick_keys;
}
virtual bool explain_modify_flags();
private:
virtual bool explain_tmptable_and_filesort(bool need_tmp_table_arg,
bool need_sort_arg);
virtual bool shallow_explain();
virtual bool explain_ref();
virtual bool explain_table_name();
virtual bool explain_join_type();
virtual bool explain_key_and_len();
virtual bool explain_rows_and_filtered();
virtual bool explain_extra();
};
static join_type calc_join_type(int quick_type)
{
if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
return JT_INDEX_MERGE;
else
return JT_RANGE;
}
/* Explain class functions ****************************************************/
bool Explain::shallow_explain()
{
return prepare_columns() || fmt->flush_entry();
}
/**
Qualify subqueries with WHERE/HAVING/ORDER BY/GROUP BY clause type marker
@param item Item tree to find subqueries
@param destination For WHERE clauses
@param type Clause type
@note WHERE clause belongs to TABLE or JOIN_TAB. The @c destination parameter
provides a pointer to QEP data for such a table to associate a future
subquery EXPLAIN output with table QEP provided.
@retval false OK
@retval true Error
*/
bool Explain::mark_subqueries(Item *item, qep_row *destination,
Explain_context_enum type)
{
if (item == NULL || !fmt->is_hierarchical())
return false;
Explain_subquery_marker marker(destination, type);
Explain_subquery_marker *marker_ptr= ▮
item->compile(&Item::explain_subquery_checker,
reinterpret_cast<uchar **>(&marker_ptr),
&Item::explain_subquery_propagator,
NULL);
return false;
}
bool Explain::mark_order_subqueries(const JOIN::ORDER_with_src &order)
{
if (!order)
return false;
Explain_context_enum sq_context;
switch (order.src) {
case ESC_ORDER_BY:
sq_context= CTX_ORDER_BY_SQ;
break;
case ESC_GROUP_BY:
sq_context= CTX_GROUP_BY_SQ;
break;
case ESC_DISTINCT:
// DISTINCT can't have subqueries, but we can get here when
// DISTINCT is converted to GROUP BY
return false;
default:
DBUG_ASSERT(0);
return true;
}
for (const ORDER *o= order; o; o= o->next)
{
if (mark_subqueries(*o->item, NULL, sq_context))
return true;
}
return false;
}
static bool explain_ref_key(Explain_format *fmt,
uint key_parts, store_key *key_copy[])
{
if (key_parts == 0)
return false;
for (uint part_no= 0; part_no < key_parts; part_no++)
{
const store_key *const s_key= key_copy[part_no];
if (s_key == NULL)
continue;
if (fmt->entry()->col_ref.push_back(s_key->name()))
return true;
}
return false;
}
/**
Traverses SQL clauses of this query specification to identify children
subqueries, marks each of them with the clause they belong to.
Then goes though all children subqueries and produces their EXPLAIN
output, attached to the proper clause's context.
@param result result stream
@retval false Ok
@retval true Error (OOM)
*/
bool Explain::explain_subqueries(select_result *result)
{
if (join)
{
if (mark_subqueries(join->having, NULL, CTX_HAVING))
return true;
if (mark_order_subqueries(group_list))
return true;
if (!join->fields_list.is_empty())
{
List_iterator<Item> it(join->fields_list);
Item *item;
while ((item= it++))
{
if (mark_subqueries(item, NULL, CTX_SELECT_LIST))
return true;
}
}
}
if (&thd->lex->select_lex == select_lex() &&
!thd->lex->value_list.is_empty())
{
/*
Collect subqueries from UPDATE ... SET foo=subquery and
INSERT ... SELECT ... ON DUPLICATE KEY UPDATE x=(SELECT...)
*/
DBUG_ASSERT(thd->lex->sql_command == SQLCOM_UPDATE ||
thd->lex->sql_command == SQLCOM_UPDATE_MULTI ||
thd->lex->sql_command == SQLCOM_INSERT ||
thd->lex->sql_command == SQLCOM_INSERT_SELECT);
List_iterator<Item> it(thd->lex->value_list);
Item *item;
while ((item= it++))
{
if (mark_subqueries(item, NULL, CTX_UPDATE_VALUE_LIST))
return true;
}
}
if (mark_order_subqueries(order_list))
return true;
for (SELECT_LEX_UNIT *unit= select_lex()->first_inner_unit();
unit;
unit= unit->next_unit())
{
SELECT_LEX *sl= unit->first_select();
Explain_context_enum context;
if (sl->type(thd) == SELECT_LEX::SLT_DERIVED)
{
DBUG_ASSERT(unit->explain_marker == CTX_NONE);
context= CTX_DERIVED;
}
else if (unit->explain_marker == CTX_NONE)
context= CTX_OPTIMIZED_AWAY_SUBQUERY;
else
context= static_cast<Explain_context_enum>(unit->explain_marker);
if (fmt->begin_context(context, unit))
return true;
if (mysql_explain_unit(thd, unit, result))
return true;
/*
This must be after mysql_explain_unit() so that JOIN::optimize() has run
and had a chance to choose materialization.
*/
if (fmt->is_hierarchical() &&
(context == CTX_WHERE || context == CTX_HAVING ||
context == CTX_SELECT_LIST ||
context == CTX_GROUP_BY_SQ || context == CTX_ORDER_BY_SQ) &&
unit->item &&
(unit->item->get_engine_for_explain()->engine_type() ==
subselect_engine::HASH_SJ_ENGINE))
{
fmt->entry()->is_materialized_from_subquery= true;
fmt->entry()->col_table_name.set_const("<materialized_subquery>");
fmt->entry()->using_temporary= true;
fmt->entry()->col_join_type.set_const(join_type_str[JT_EQ_REF]);
fmt->entry()->col_key.set_const("<auto_key>");
const subselect_hash_sj_engine * const engine=
static_cast<const subselect_hash_sj_engine *>
(unit->item->get_engine_for_explain());
const JOIN_TAB * const tmp_tab= engine->get_join_tab();
char buff_key_len[24];
fmt->entry()->col_key_len.set(buff_key_len,
longlong2str(tmp_tab->table->key_info[0].key_length,
buff_key_len, 10) - buff_key_len);
if (explain_ref_key(fmt, tmp_tab->ref.key_parts,
tmp_tab->ref.key_copy))
return true;
fmt->entry()->col_rows.set(1);
/*
The value to look up depends on the outer value, so the materialized
subquery is dependent and not cacheable:
*/
fmt->entry()->is_dependent= true;
fmt->entry()->is_cacheable= false;
}
if (fmt->end_context(context))
return true;
}
return false;
}
/**
Pre-calculate table property values for further EXPLAIN output
*/
bool Explain::prepare_columns()
{
return explain_id() ||
explain_select_type() ||
explain_table_name() ||
explain_partitions() ||
explain_join_type() ||
explain_possible_keys() ||
explain_key_and_len() ||
explain_ref() ||
explain_rows_and_filtered() ||
explain_extra() ||
explain_modify_flags();
}
/**
Explain class main function
This function:
a) allocates a select_send object (if no one pre-allocated available),
b) calculates and sends whole EXPLAIN data.
@return false if success, true if error
*/
bool Explain::send()
{
DBUG_ENTER("Explain::send");
if (fmt->begin_context(context_type, NULL))
DBUG_RETURN(true);
/* Don't log this into the slow query log */
thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED |
SERVER_QUERY_NO_GOOD_INDEX_USED);
select_result *result;
if (external_result == NULL)
{
/* Create select_result object if the caller doesn't provide one: */
if (!(result= new select_send))
DBUG_RETURN(true); /* purecov: inspected */
if (fmt->send_headers(result) || prepare(result))
DBUG_RETURN(true);
}
else
{
result= external_result;
external_result->reset_offset_limit_cnt();
}
for (SELECT_LEX_UNIT *unit= select_lex()->first_inner_unit();
unit;
unit= unit->next_unit())
propagate_explain_option(thd, unit);
bool ret= shallow_explain() || explain_subqueries(result);
if (!ret)
ret= fmt->end_context(context_type);
if (ret && join)
join->error= 1; /* purecov: inspected */
if (external_result == NULL)
{
if (ret)
result->abort_result_set();
else
result->send_eof();
delete result;
}
DBUG_RETURN(ret);
}
bool Explain::explain_id()
{
fmt->entry()->col_id.set(select_lex()->select_number);
return false;
}
bool Explain::explain_select_type()
{
if (&thd->lex->select_lex != select_lex()) // ignore top-level SELECT_LEXes
{
fmt->entry()->is_dependent= select_lex()->is_dependent();
if (select_lex()->type(thd) != SELECT_LEX::SLT_DERIVED)
fmt->entry()->is_cacheable= select_lex()->is_cacheable();
}
fmt->entry()->col_select_type.set(select_lex()->type(thd));
return false;
}
/* Explain_no_table class functions *******************************************/
bool Explain_no_table::shallow_explain()
{
return (fmt->begin_context(CTX_MESSAGE) ||
Explain::shallow_explain() ||
mark_subqueries(select_lex()->where, fmt->entry(), CTX_WHERE) ||
fmt->end_context(CTX_MESSAGE));
}
bool Explain_no_table::explain_rows_and_filtered()
{
if (rows == HA_POS_ERROR)
return false;
fmt->entry()->col_rows.set(rows);
return false;
}
bool Explain_no_table::explain_extra()
{
return fmt->entry()->col_message.set(message);
}
/* Explain_union_result class functions ****************************************/
bool Explain_union_result::explain_id()
{
return false;
}
bool Explain_union_result::explain_table_name()
{
SELECT_LEX *last_select= join->unit->first_select()->last_select();
// # characters needed to print select_number of last select
int last_length= (int)log10((double)last_select->select_number)+1;
SELECT_LEX *sl= join->unit->first_select();
uint len= 6, lastop= 0;
char table_name_buffer[NAME_LEN];
memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
/*
- len + lastop: current position in table_name_buffer
- 6 + last_length: the number of characters needed to print
'...,'<last_select->select_number>'>\0'
*/
for (;
sl && len + lastop + 6 + last_length < NAME_CHAR_LEN;
sl= sl->next_select())
{
len+= lastop;
lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
"%u,", sl->select_number);
}
if (sl || len + lastop >= NAME_CHAR_LEN)
{
memcpy(table_name_buffer + len, STRING_WITH_LEN("...,"));
len+= 4;
lastop= my_snprintf(table_name_buffer + len, NAME_CHAR_LEN - len,
"%u,", last_select->select_number);
}
len+= lastop;
table_name_buffer[len - 1]= '>'; // change ',' to '>'
return fmt->entry()->col_table_name.set(table_name_buffer, len);
}
bool Explain_union_result::explain_join_type()
{
fmt->entry()->col_join_type.set_const(join_type_str[JT_ALL]);
return false;
}
bool Explain_union_result::explain_extra()
{
if (!fmt->is_hierarchical())
{
/*
Currently we always use temporary table for UNION result
*/
if (push_extra(ET_USING_TEMPORARY))
return true;
/*
here we assume that the query will return at least two rows, so we
show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
and no filesort will be actually done, but executing all selects in
the UNION to provide precise EXPLAIN information will hardly be
appreciated :)
*/
if (join->unit->global_parameters->order_list.first)
{
return push_extra(ET_USING_FILESORT);
}
}
return Explain::explain_extra();
}
/* Explain_table_base class functions *****************************************/
bool Explain_table_base::explain_partitions()
{
#ifdef WITH_PARTITION_STORAGE_ENGINE
if (!table->pos_in_table_list->derived && table->part_info)
return make_used_partitions_str(table->part_info,
&fmt->entry()->col_partitions);
#endif
return false;
}
bool Explain_table_base::explain_possible_keys()
{
if (usable_keys.is_clear_all())
return false;
for (uint j= 0 ; j < table->s->keys ; j++)
{
if (usable_keys.is_set(j) &&
fmt->entry()->col_possible_keys.push_back(table->key_info[j].name))
return true;
}
return false;
}
bool Explain_table_base::explain_key_parts(int key, uint key_parts)
{
KEY_PART_INFO *kp= table->key_info[key].key_part;
for (uint i= 0; i < key_parts; i++, kp++)
if (fmt->entry()->col_key_parts.push_back(kp->field->field_name))
return true;
return false;
}
bool Explain_table_base::explain_key_and_len_quick(const SQL_SELECT *select)
{
DBUG_ASSERT(select && select->quick);
bool ret= false;
StringBuffer<512> str_key(cs);
StringBuffer<512> str_key_len(cs);
if (select->quick->index != MAX_KEY)
ret= explain_key_parts(select->quick->index,
select->quick->used_key_parts);
select->quick->add_keys_and_lengths(&str_key, &str_key_len);
return (ret || fmt->entry()->col_key.set(str_key) ||
fmt->entry()->col_key_len.set(str_key_len));
}
bool Explain_table_base::explain_key_and_len_index(int key)
{
DBUG_ASSERT(key != MAX_KEY);
return explain_key_and_len_index(key, table->key_info[key].key_length,
table->key_info[key].user_defined_key_parts);
}
bool Explain_table_base::explain_key_and_len_index(int key, uint key_length,
uint key_parts)
{
DBUG_ASSERT(key != MAX_KEY);
char buff_key_len[24];
const KEY *key_info= table->key_info + key;
const int length= longlong2str(key_length, buff_key_len, 10) - buff_key_len;
const bool ret= explain_key_parts(key, key_parts);
return (ret || fmt->entry()->col_key.set(key_info->name) ||
fmt->entry()->col_key_len.set(buff_key_len, length));
}
bool Explain_table_base::explain_extra_common(const SQL_SELECT *select,
const JOIN_TAB *tab,
int quick_type,
uint keyno)
{
if (((keyno != MAX_KEY &&
keyno == table->file->pushed_idx_cond_keyno &&
table->file->pushed_idx_cond) ||
(tab && tab->cache_idx_cond)))
{
StringBuffer<160> buff(cs);
if (fmt->is_hierarchical())
{
if (table->file->pushed_idx_cond)
table->file->pushed_idx_cond->print(&buff, cond_print_flags);
else
tab->cache_idx_cond->print(&buff, cond_print_flags);
}
if (push_extra(ET_USING_INDEX_CONDITION, buff))
return true;
}
const TABLE* pushed_root= table->file->root_of_pushed_join();
if (pushed_root)
{
char buf[128];
int len;
int pushed_id= 0;
for (JOIN_TAB* prev= join->join_tab; prev <= tab; prev++)
{
const TABLE* prev_root= prev->table->file->root_of_pushed_join();
if (prev_root == prev->table)
{
pushed_id++;
if (prev_root == pushed_root)
break;
}
}
if (pushed_root == table)
{
uint pushed_count= tab->table->file->number_of_pushed_joins();
len= my_snprintf(buf, sizeof(buf)-1,
"Parent of %d pushed join@%d",
pushed_count, pushed_id);
}
else
{
len= my_snprintf(buf, sizeof(buf)-1,
"Child of '%s' in pushed join@%d",
tab->table->file->parent_of_pushed_join()->alias,
pushed_id);
}
{
StringBuffer<128> buff(cs);
buff.append(buf,len);
if (push_extra(ET_PUSHED_JOIN, buff))
return true;
}
}
switch (quick_type) {
case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
{
StringBuffer<32> buff(cs);
select->quick->add_info_string(&buff);
if (fmt->is_hierarchical())
{
/*
We are replacing existing col_key value with a quickselect info,
but not the reverse:
*/
DBUG_ASSERT(fmt->entry()->col_key.length);
if (fmt->entry()->col_key.set(buff)) // keep col_key_len intact
return true;
}
else
{
if (push_extra(ET_USING, buff))
return true;
}
}
break;
default: ;
}
if (select)
{
if (tab && tab->use_quick == QS_DYNAMIC_RANGE)
{
StringBuffer<64> str(STRING_WITH_LEN("index map: 0x"), cs);
/* 4 bits per 1 hex digit + terminating '\0' */
char buf[MAX_KEY / 4 + 1];
str.append(tab->keys.print(buf));
if (push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD, str))
return true;
}
else if (select->cond)
{
const Item *pushed_cond= table->file->pushed_cond;
if (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) &&
pushed_cond)
{
StringBuffer<64> buff(cs);
if (describe(DESCRIBE_EXTENDED))
((Item *)pushed_cond)->print(&buff, cond_print_flags);
if (push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION, buff))
return true;
}
else
{
if (fmt->is_hierarchical())
{
Lazy_condition *c= new Lazy_condition(tab && !tab->filesort ?
tab->condition() :
select->cond);
if (c == NULL)
return true;
fmt->entry()->col_attached_condition.set(c);
}
else if (push_extra(ET_USING_WHERE))
return true;
}
}
else
DBUG_ASSERT(!tab || !tab->condition());
}
if (table->reginfo.not_exists_optimize && push_extra(ET_NOT_EXISTS))
return true;
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE)
{
uint mrr_flags=
((QUICK_RANGE_SELECT*)(select->quick))->mrr_flags;
/*
During normal execution of a query, multi_range_read_init() is
called to initialize MRR. If HA_MRR_SORTED is set at this point,
multi_range_read_init() for any native MRR implementation will
revert to default MRR if not HA_MRR_SUPPORT_SORTED.
Calling multi_range_read_init() can potentially be costly, so it
is not done when executing an EXPLAIN. We therefore simulate
its effect here:
*/
if (mrr_flags & HA_MRR_SORTED && !(mrr_flags & HA_MRR_SUPPORT_SORTED))
mrr_flags|= HA_MRR_USE_DEFAULT_IMPL;
if (!(mrr_flags & HA_MRR_USE_DEFAULT_IMPL) && push_extra(ET_USING_MRR))
return true;
}
return false;
}
bool Explain_table_base::explain_tmptable_and_filesort(bool need_tmp_table_arg,
bool need_sort_arg)
{
/*
For hierarchical EXPLAIN we output "Using temporary" and
"Using filesort" with related ORDER BY, GROUP BY or DISTINCT
*/
if (fmt->is_hierarchical())
return false;
if (need_tmp_table_arg && push_extra(ET_USING_TEMPORARY))
return true;
if (need_sort_arg && push_extra(ET_USING_FILESORT))
return true;
return false;
}
bool Explain_table_base::explain_modify_flags()
{
if (!fmt->is_hierarchical())
return false;
switch (thd->lex->sql_command) {
case SQLCOM_UPDATE_MULTI:
if (!bitmap_is_clear_all(table->write_set) &&
table->s->table_category != TABLE_CATEGORY_TEMPORARY)
fmt->entry()->is_update= true;
break;
case SQLCOM_DELETE_MULTI:
{
TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first;
for (TABLE_LIST *at= aux_tables; at; at= at->next_local)
{
if (at->table == table)
{
fmt->entry()->is_delete= true;
break;
}
}
break;
}
default: ;
};
return false;
}
/* Explain_join class functions ***********************************************/
bool Explain_join::begin_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx)
{
const Explain_format_flags *flags= &join->explain_flags;
return (flags->get(clause, ESP_EXISTS) &&
!flags->get(clause, ESP_IS_SIMPLE) &&
fmt->begin_context(ctx, NULL, flags));
}
bool Explain_join::end_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx)
{
const Explain_format_flags *flags= &join->explain_flags;
return (flags->get(clause, ESP_EXISTS) &&
!flags->get(clause, ESP_IS_SIMPLE) &&
fmt->end_context(ctx));
}
bool Explain_join::begin_simple_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx)
{
const Explain_format_flags *flags= &join->explain_flags;
return (flags->get(clause, ESP_IS_SIMPLE) &&
fmt->begin_context(ctx, NULL, flags));
}
bool Explain_join::end_simple_sort_context(Explain_sort_clause clause,
Explain_context_enum ctx)
{
const Explain_format_flags *flags= &join->explain_flags;
return (flags->get(clause, ESP_IS_SIMPLE) &&
fmt->end_context(ctx));
}
bool Explain_join::shallow_explain()
{
if (begin_sort_context(ESC_ORDER_BY, CTX_ORDER_BY))
return true;
if (begin_sort_context(ESC_DISTINCT, CTX_DISTINCT))
return true;
if (begin_sort_context(ESC_GROUP_BY, CTX_GROUP_BY))
return true;
if (begin_sort_context(ESC_BUFFER_RESULT, CTX_BUFFER_RESULT))
return true;
for (size_t t= 0,
cnt= fmt->is_hierarchical() ? join->primary_tables : join->tables;
t < cnt; t++)
{
if (explain_join_tab(t))
return true;
}
if (end_sort_context(ESC_BUFFER_RESULT, CTX_BUFFER_RESULT))
return true;
if (end_sort_context(ESC_GROUP_BY, CTX_GROUP_BY))
return true;
if (end_sort_context(ESC_DISTINCT, CTX_DISTINCT))
return true;
if (end_sort_context(ESC_ORDER_BY, CTX_ORDER_BY))
return true;
return false;
}
bool Explain_join::explain_join_tab(size_t tab_num)
{
tabnum= tab_num;
tab= join->join_tab + tabnum;
table= tab->table;
if (!tab->position)
return false;
usable_keys= tab->keys;
quick_type= -1;
select= (tab->filesort && tab->filesort->select) ?
tab->filesort->select : tab->select;
if (tab->type == JT_ALL && select && select->quick)
{
quick_type= select->quick->get_type();
tab->type= calc_join_type(quick_type);
}
if (tab->starts_weedout())
fmt->begin_context(CTX_DUPLICATES_WEEDOUT);
const bool first_non_const= tabnum == join->const_tables;
if (first_non_const)
{
if (begin_simple_sort_context(ESC_ORDER_BY, CTX_SIMPLE_ORDER_BY))
return true;
if (begin_simple_sort_context(ESC_DISTINCT, CTX_SIMPLE_DISTINCT))
return true;
if (begin_simple_sort_context(ESC_GROUP_BY, CTX_SIMPLE_GROUP_BY))
return true;
}
Semijoin_mat_exec *sjm= tab->sj_mat_exec;
Explain_context_enum c= sjm ? CTX_MATERIALIZATION : CTX_JOIN_TAB;
if (fmt->begin_context(c) || prepare_columns())
return true;
fmt->entry()->query_block_id= table->pos_in_table_list->query_block_id();
if (sjm)
{
if (sjm->is_scan)
{
fmt->entry()->col_rows.cleanup(); // TODO: set(something reasonable)
}
else
{
fmt->entry()->col_rows.set(1);
}
}
if (fmt->flush_entry() ||
mark_subqueries(tab->condition(), fmt->entry(), CTX_WHERE))
return true;
if (sjm && fmt->is_hierarchical())
{
for (size_t sjt= sjm->inner_table_index, end= sjt + sjm->table_count;
sjt < end; sjt++)
{
if (explain_join_tab(sjt))
return true;
}
}
if (fmt->end_context(c))
return true;
if (first_non_const)
{
if (end_simple_sort_context(ESC_GROUP_BY, CTX_SIMPLE_GROUP_BY))
return true;
if (end_simple_sort_context(ESC_DISTINCT, CTX_SIMPLE_DISTINCT))
return true;
if (end_simple_sort_context(ESC_ORDER_BY, CTX_SIMPLE_ORDER_BY))
return true;
}
if (tab->check_weed_out_table &&
fmt->end_context(CTX_DUPLICATES_WEEDOUT))
return true;
used_tables|= table->map;
return false;
}
bool Explain_join::explain_table_name()
{
if (table->pos_in_table_list->derived && !fmt->is_hierarchical())
{
/* Derived table name generation */
char table_name_buffer[NAME_LEN];
const size_t len= my_snprintf(table_name_buffer,
sizeof(table_name_buffer) - 1,
"<derived%u>",
table->pos_in_table_list->query_block_id());
return fmt->entry()->col_table_name.set(table_name_buffer, len);
}
else
return fmt->entry()->col_table_name.set(table->pos_in_table_list->alias);
}
bool Explain_join::explain_select_type()
{
if (sj_is_materialize_strategy(tab->get_sj_strategy()))
fmt->entry()->col_select_type.set(st_select_lex::SLT_MATERIALIZED);
else
return Explain::explain_select_type();
return false;
}
bool Explain_join::explain_id()
{
if (sj_is_materialize_strategy(tab->get_sj_strategy()))
fmt->entry()->col_id.set(tab->sjm_query_block_id());
else
return Explain::explain_id();
return false;
}
bool Explain_join::explain_join_type()
{
fmt->entry()->col_join_type.set_const(join_type_str[tab->type]);
return false;
}
bool Explain_join::explain_key_and_len()
{
if (tab->ref.key_parts)
return explain_key_and_len_index(tab->ref.key, tab->ref.key_length,
tab->ref.key_parts);
else if (tab->type == JT_INDEX_SCAN)
return explain_key_and_len_index(tab->index);
else if (select && select->quick)
return explain_key_and_len_quick(select);
else
{
const TABLE_LIST *table_list= table->pos_in_table_list;
if (table_list->schema_table &&
table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
{
StringBuffer<512> str_key(cs);
const char *f_name;
int f_idx;
if (table_list->has_db_lookup_value)
{
f_idx= table_list->schema_table->idx_field1;
f_name= table_list->schema_table->fields_info[f_idx].field_name;
str_key.append(f_name, strlen(f_name), cs);
}
if (table_list->has_table_lookup_value)
{
if (table_list->has_db_lookup_value)
str_key.append(',');
f_idx= table_list->schema_table->idx_field2;
f_name= table_list->schema_table->fields_info[f_idx].field_name;
str_key.append(f_name, strlen(f_name), cs);
}
if (str_key.length())
return fmt->entry()->col_key.set(str_key);
}
}
return false;
}
bool Explain_join::explain_ref()
{
return explain_ref_key(fmt, tab->ref.key_parts, tab->ref.key_copy);
}
bool Explain_join::explain_rows_and_filtered()
{
if (table->pos_in_table_list->schema_table)
return false;
double examined_rows;
if (select && select->quick)
examined_rows= rows2double(select->quick->records);
else if (tab->type == JT_INDEX_SCAN || tab->type == JT_ALL)
{
if (tab->limit)
examined_rows= rows2double(tab->limit);
else
{
table->pos_in_table_list->fetch_number_of_rows();
examined_rows= rows2double(table->file->stats.records);
}
}
else
examined_rows= tab->position->records_read;
fmt->entry()->col_rows.set(static_cast<longlong>(examined_rows));
/* Add "filtered" field */
if (describe(DESCRIBE_EXTENDED))
{
float f= 0.0;
if (examined_rows)
f= 100.0 * tab->position->records_read / examined_rows;
fmt->entry()->col_filtered.set(f);
}
return false;
}
bool Explain_join::explain_extra()
{
if (tab->info)
{
if (push_extra(tab->info))
return true;
}
else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
{
if (tab->packed_info & TAB_INFO_USING_INDEX)
{
if (push_extra(ET_USING_INDEX))
return true;
}
if (tab->packed_info & TAB_INFO_USING_WHERE)
{
if (fmt->is_hierarchical())
{
Lazy_condition *c= new Lazy_condition(tab->condition());
if (c == NULL)
return true;
fmt->entry()->col_attached_condition.set(c);
}
else if (push_extra(ET_USING_WHERE))
return true;
}
if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
{
if (fmt->entry()->col_extra.push_back(new
extra(ET_FULL_SCAN_ON_NULL_KEY)))
return true;
}
}
else
{
uint keyno= MAX_KEY;
if (tab->ref.key_parts)
keyno= tab->ref.key;
else if (select && select->quick)
keyno = select->quick->index;
if (explain_extra_common(select, tab, quick_type, keyno))
return true;
const TABLE_LIST *table_list= table->pos_in_table_list;
if (table_list->schema_table &&
table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
{
if (!table_list->table_open_method)
{
if (push_extra(ET_SKIP_OPEN_TABLE))
return true;
}
else if (table_list->table_open_method == OPEN_FRM_ONLY)
{
if (push_extra(ET_OPEN_FRM_ONLY))
return true;
}
else
{
if (push_extra(ET_OPEN_FULL_TABLE))
return true;
}
StringBuffer<32> buff(cs);
if (table_list->has_db_lookup_value &&
table_list->has_table_lookup_value)
{
if (push_extra(ET_SCANNED_DATABASES, "0"))
return true;
}
else if (table_list->has_db_lookup_value ||
table_list->has_table_lookup_value)
{
if (push_extra(ET_SCANNED_DATABASES, "1"))
return true;
}
else
{
if (push_extra(ET_SCANNED_DATABASES, "all"))
return true;
}
}
if (((tab->type == JT_INDEX_SCAN || tab->type == JT_CONST) &&
table->covering_keys.is_set(tab->index)) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
!((QUICK_ROR_INTERSECT_SELECT*) select->quick)->need_to_fetch_row) ||
table->key_read)
{
if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
{
QUICK_GROUP_MIN_MAX_SELECT *qgs=
(QUICK_GROUP_MIN_MAX_SELECT *) select->quick;
StringBuffer<64> buff(cs);
qgs->append_loose_scan_type(&buff);
if (push_extra(ET_USING_INDEX_FOR_GROUP_BY, buff))
return true;
}
else if (quick_type == QUICK_SELECT_I::QS_TYPE_SKIP_SCAN)
{
if (push_extra(ET_USING_INDEX_FOR_SKIP_SCAN))
return true;
}
else
{
if (push_extra(ET_USING_INDEX))
return true;
}
}
if (explain_tmptable_and_filesort(need_tmp_table, need_order))
return true;
need_tmp_table= need_order= false;
if (distinct && test_all_bits(used_tables, thd->lex->used_tables) &&
push_extra(ET_DISTINCT))
return true;
if (tab->do_loosescan() && push_extra(ET_LOOSESCAN))
return true;
if (tab->starts_weedout())
{
if (!fmt->is_hierarchical() && push_extra(ET_START_TEMPORARY))
return true;
}
if (tab->finishes_weedout())
{
if (!fmt->is_hierarchical() && push_extra(ET_END_TEMPORARY))
return true;
}
else if (tab->do_firstmatch())
{
if (tab->firstmatch_return == join->join_tab - 1)
{
if (push_extra(ET_FIRST_MATCH))
return true;
}
else
{
StringBuffer<64> buff(cs);
TABLE *prev_table= tab->firstmatch_return->table;
if (prev_table->pos_in_table_list->query_block_id() &&
!fmt->is_hierarchical() &&
prev_table->pos_in_table_list->derived)
{
char namebuf[NAME_LEN];
/* Derived table name generation */
int len= my_snprintf(namebuf, sizeof(namebuf)-1,
"<derived%u>",
prev_table->pos_in_table_list->query_block_id());
buff.append(namebuf, len);
}
else
buff.append(prev_table->pos_in_table_list->alias);
if (push_extra(ET_FIRST_MATCH, buff))
return true;
}
}
if (tab->has_guarded_conds() && push_extra(ET_FULL_SCAN_ON_NULL_KEY))
return true;
if (tabnum > 0 && tab->use_join_cache != JOIN_CACHE::ALG_NONE)
{
StringBuffer<64> buff(cs);
if ((tab->use_join_cache & JOIN_CACHE::ALG_BNL))
buff.append("Block Nested Loop");
else if ((tab->use_join_cache & JOIN_CACHE::ALG_BKA))
buff.append("Batched Key Access");
else if ((tab->use_join_cache & JOIN_CACHE::ALG_BKA_UNIQUE))
buff.append("Batched Key Access (unique)");
else
DBUG_ASSERT(0); /* purecov: inspected */
if (push_extra(ET_USING_JOIN_BUFFER, buff))
return true;
}
}
return false;
}
/* Explain_table class functions **********************************************/
bool Explain_table::explain_modify_flags()
{
if (!fmt->is_hierarchical())
return false;
if (is_update)
fmt->entry()->is_update= true;
else
fmt->entry()->is_delete= true;
return false;
}
bool Explain_table::explain_tmptable_and_filesort(bool need_tmp_table_arg,
bool need_sort_arg)
{
if (fmt->is_hierarchical())
{
/*
For hierarchical EXPLAIN we output "using_temporary_table" and
"using_filesort" with related ORDER BY, GROUP BY or DISTINCT
(excluding the single-table UPDATE command that updates used key --
in this case we output "using_temporary_table: for update"
at the "table" node)
*/
if (need_tmp_table_arg)
{
DBUG_ASSERT(used_key_is_modified || order_list);
if (used_key_is_modified && push_extra(ET_USING_TEMPORARY, "for update"))
return true;
}
}
else
{
if (need_tmp_table_arg && push_extra(ET_USING_TEMPORARY))
return true;
if (need_sort_arg && push_extra(ET_USING_FILESORT))
return true;
}
return false;
}
bool Explain_table::shallow_explain()
{
Explain_format_flags flags;
if (order_list)
{
flags.set(ESC_ORDER_BY, ESP_EXISTS);
if (need_sort)
flags.set(ESC_ORDER_BY, ESP_USING_FILESORT);
if (!used_key_is_modified && need_tmp_table)
flags.set(ESC_ORDER_BY, ESP_USING_TMPTABLE);
}
if (order_list && fmt->begin_context(CTX_ORDER_BY, NULL, &flags))
return true;
if (fmt->begin_context(CTX_JOIN_TAB))
return true;
if (Explain::shallow_explain() ||
mark_subqueries(select_lex()->where, fmt->entry(), CTX_WHERE))
return true;
if (fmt->end_context(CTX_JOIN_TAB))
return true;
if (order_list && fmt->end_context(CTX_ORDER_BY))
return true;
return false;
}
bool Explain_table::explain_table_name()
{
return fmt->entry()->col_table_name.set(table->alias);
}
bool Explain_table::explain_join_type()
{
join_type jt;
if (select && select->quick)
jt= calc_join_type(select->quick->get_type());
else if (key != MAX_KEY)
jt= JT_INDEX_SCAN;
else
jt= JT_ALL;
fmt->entry()->col_join_type.set_const(join_type_str[jt]);
return false;
}
bool Explain_table::explain_ref()
{
if (select && select->quick)
{
int key_parts= select->quick->used_key_parts;
while(key_parts--)
{
fmt->entry()->col_ref.push_back("const");
}
}
return false;
}
bool Explain_table::explain_key_and_len()
{
if (select && select->quick)
return explain_key_and_len_quick(select);
else if (key != MAX_KEY)
return explain_key_and_len_index(key);
return false;
}
bool Explain_table::explain_rows_and_filtered()
{
double examined_rows;
if (select && select->quick)
examined_rows= rows2double(select->quick->records);
else if (!select && !need_sort && limit != HA_POS_ERROR)
examined_rows= rows2double(limit);
else
{
table->pos_in_table_list->fetch_number_of_rows();
examined_rows= rows2double(table->file->stats.records);
}
fmt->entry()->col_rows.set(static_cast<long long>(examined_rows));
if (describe(DESCRIBE_EXTENDED))
fmt->entry()->col_filtered.set(100.0);
return false;
}
bool Explain_table::explain_extra()
{
const uint keyno= (select && select->quick) ? select->quick->index : key;
const int quick_type= (select && select->quick) ? select->quick->get_type()
: -1;
return (explain_extra_common(select, NULL, quick_type, keyno) ||
explain_tmptable_and_filesort(need_tmp_table, need_sort));
}
/**
EXPLAIN functionality for insert_select, multi_update and multi_delete
This class objects substitute insert_select, multi_update and multi_delete
data interceptor objects to implement EXPLAIN for INSERT, REPLACE and
multi-table UPDATE and DELETE queries.
explain_send class object initializes tables like insert_select, multi_update
or multi_delete data interceptor do, but it suppress table data modification
by the underlying interceptor object.
Thus, we can use explain_send object in the context of EXPLAIN INSERT/
REPLACE/UPDATE/DELETE query like we use select_send in the context of
EXPLAIN SELECT command:
1) in presence of lex->describe flag we pass explain_send object to the
mysql_select() function,
2) it call prepare(), prepare2() and initialize_tables() functions to
mark modified tables etc.
*/
class explain_send : public select_send {
protected:
/*
As far as we use explain_send object in a place of select_send, explain_send
have to pass multiple invocation of its prepare(), prepare2() and
initialize_tables() functions, since JOIN::exec() of subqueries runs
these functions of select_send multiple times by design.
insert_select, multi_update and multi_delete class functions are not intended
for multiple invocations, so "prepared", "prepared2" and "initialized" flags
guard data interceptor object from function re-invocation.
*/
bool prepared; ///< prepare() is done
bool prepared2; ///< prepare2() is done
bool initialized; ///< initialize_tables() is done
/**
Pointer to underlying insert_select, multi_update or multi_delete object
*/
select_result_interceptor *interceptor;
public:
explain_send(select_result_interceptor *interceptor_arg)
: prepared(false), prepared2(false), initialized(false),
interceptor(interceptor_arg)
{}
protected:
virtual int prepare(List<Item> &list, SELECT_LEX_UNIT *u)
{
if (prepared)
return false;
prepared= true;
return select_send::prepare(list, u) || interceptor->prepare(list, u);
}
virtual int prepare2(void)
{
if (prepared2)
return false;
prepared2= true;
return select_send::prepare2() || interceptor->prepare2();
}
virtual bool initialize_tables(JOIN *join)
{
if (initialized)
return false;
initialized= true;
return select_send::initialize_tables(join) ||
interceptor->initialize_tables(join);
}
virtual void cleanup()
{
select_send::cleanup();
interceptor->cleanup();
}
};
/******************************************************************************
External function implementations
******************************************************************************/
/**
Send a message as an "extra" column value
This function forms the 1st row of the QEP output with a simple text message.
This is useful to explain such trivial cases as "No tables used" etc.
@note Also this function explains the rest of QEP (subqueries or joined
tables if any).
@param thd current THD
@param join JOIN
@param message text message for the "extra" column.
@return false if success, true if error
*/
bool explain_no_table(THD *thd, JOIN *join, const char *message)
{
DBUG_ENTER("explain_no_table");
const bool ret= Explain_no_table(thd, join, message).send();
DBUG_RETURN(ret);
}
/**
Send a message as an "extra" column value
This function forms the 1st row of the QEP output with a simple text message.
This is useful to explain such trivial cases as "No tables used" etc.
@note Also this function explains the rest of QEP (subqueries if any).
@param thd current THD
@param message text message for the "extra" column.
@param rows HA_POS_ERROR or a value for the "rows" column.
@return false if success, true if error
*/
bool explain_no_table(THD *thd, const char *message, ha_rows rows)
{
DBUG_ENTER("explain_no_table");
const bool ret= Explain_no_table(thd, message, rows).send();
DBUG_RETURN(ret);
}
/**
EXPLAIN handling for single-table UPDATE and DELETE queries
Send to the client a QEP data set for single-table EXPLAIN UPDATE/DELETE
queries. As far as single-table UPDATE/DELETE are implemented without
the regular JOIN tree, we can't reuse explain_unit() directly,
thus we deal with this single table in a special way and then call
explain_unit() for subqueries (if any).
@param thd current THD
@param table TABLE object to update/delete rows in the UPDATE/DELETE
query.
@param select SQL_SELECT object that represents quick access functions
and WHERE clause.
@param key MAX_KEY or and index number of the key that was chosen
to access table data.
@param limit HA_POS_ERROR or LIMIT value.
@param need_tmp_table true if it requires temporary table -- "Using temporary"
string in the "extra" column.
@param need_sort true if it requires filesort() -- "Using filesort"
string in the "extra" column.
@param is_update is_update ? UPDATE command : DELETE command
@param used_key_is_modified UPDATE updates used key column
@return false if success, true if error
*/
bool explain_single_table_modification(THD *thd,
TABLE *table,
const SQL_SELECT *select,
uint key,
ha_rows limit,
bool need_tmp_table,
bool need_sort,
bool is_update,
bool used_key_is_modified)
{
DBUG_ENTER("explain_single_table_modification");
const bool ret= Explain_table(thd, table, select, key, limit,
need_tmp_table, need_sort, is_update,
used_key_is_modified).send();
DBUG_RETURN(ret);
}
/**
EXPLAIN handling for EXPLAIN SELECT queries
Send QEP to the client.
@param thd current THD
@param join JOIN
@param need_tmp_table true if it requires a temporary table --
"Using temporary" string in the "extra" column.
@param need_order true if it requires filesort() -- "Using filesort"
string in the "extra" column.
@param distinct true if there is the DISTINCT clause (not optimized
out) -- "Distinct" string in the "extra" column.
@return false if success, true if error
*/
bool explain_query_specification(THD *thd, JOIN *join)
{
const Explain_format_flags *flags= &join->explain_flags;
const bool need_tmp_table= flags->any(ESP_USING_TMPTABLE);
const bool need_order= flags->any(ESP_USING_FILESORT);
const bool distinct= flags->get(ESC_DISTINCT, ESP_EXISTS);
DBUG_ENTER("explain_query_specification");
DBUG_PRINT("info", ("Select %p, type %s",
join->select_lex, join->select_lex->get_type_str(thd)));
bool ret;
if (join->select_lex == join->unit->fake_select_lex)
ret= Explain_union_result(thd, join).send();
else
ret= Explain_join(thd, join, need_tmp_table, need_order, distinct).send();
DBUG_RETURN(ret);
}
/**
EXPLAIN handling for INSERT, REPLACE and multi-table UPDATE/DELETE queries
Send to the client a QEP data set for data-modifying commands those have a
regular JOIN tree (INSERT...SELECT, REPLACE...SELECT and multi-table
UPDATE and DELETE queries) like mysql_select() does for SELECT queries in
the "describe" mode.
@note see explain_single_table_modification() for single-table
UPDATE/DELETE EXPLAIN handling.
@note Unlike the mysql_select function, explain_multi_table_modification
calls abort_result_set() itself in the case of failure (OOM etc.)
since explain_multi_table_modification() uses internally created
select_result stream.
@param thd current THD
@param result pointer to select_insert, multi_delete or multi_update object:
the function uses it to call result->prepare(),
result->prepare2() and result->initialize_tables() only but
not to modify table data or to send a result to client.
@return false if success, true if error
*/
bool explain_multi_table_modification(THD *thd,
select_result_interceptor *result)
{
DBUG_ENTER("explain_multi_table_modification");
explain_send explain(result);
bool res= explain_query_expression(thd, &explain);
DBUG_RETURN(res);
}
/**
EXPLAIN handling for SELECT and table-modifying queries that have JOIN
Send to the client a QEP data set for SELECT or data-modifying commands
those have a regular JOIN tree (INSERT...SELECT, REPLACE...SELECT and
multi-table UPDATE and DELETE queries) like mysql_select() does for SELECT
queries in the "describe" mode.
@note see explain_single_table_modification() for single-table
UPDATE/DELETE EXPLAIN handling.
@note explain_query_expression() calls abort_result_set() itself in the
case of failure (OOM etc.) since explain_multi_table_modification()
uses internally created select_result stream.
@param thd current THD
@param result pointer to select_result, select_insert, multi_delete or
multi_update object: the function uses it to call
result->prepare(), result->prepare2() and
result->initialize_tables() only but not to modify table data
or to send a result to client.
@return false if success, true if error
*/
bool explain_query_expression(THD *thd, select_result *result)
{
DBUG_ENTER("explain_query_expression");
const bool res= thd->lex->explain_format->send_headers(result) ||
mysql_explain_unit(thd, &thd->lex->unit, result) ||
thd->is_error();
/*
The code which prints the extended description is not robust
against malformed queries, so skip it if we have an error.
*/
if (!res && (thd->lex->describe & DESCRIBE_EXTENDED) &&
thd->lex->sql_command == SQLCOM_SELECT) // TODO: implement for INSERT/etc
{
StringBuffer<1024> str;
/*
The warnings system requires input in utf8, see mysqld_show_warnings().
*/
thd->lex->unit.print(&str, enum_query_type(QT_TO_SYSTEM_CHARSET |
QT_SHOW_SELECT_NUMBER));
str.append('\0');
push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_YES, str.ptr());
}
/* Skip if capturing SQL plan */
if (! thd->in_capture_sql_plan())
{
if (res)
result->abort_result_set();
else
result->send_eof();
}
DBUG_RETURN(res);
}
/**
Set SELECT_DESCRIBE flag for all unit's SELECT_LEXes
@param thd THD
@param unit unit of SELECT_LEXes
*/
static void propagate_explain_option(THD *thd, SELECT_LEX_UNIT *unit)
{
for (SELECT_LEX *sl= unit->first_select(); sl; sl= sl->next_select())
sl->options|= SELECT_DESCRIBE;
}
/**
Explain UNION or subqueries of the unit
If the unit is a UNION, explain it as a UNION. Otherwise explain nested
subselects.
@param thd thread object
@param unit unit object
@param result result stream to send QEP dataset
@return false if success, true if error
*/
bool mysql_explain_unit(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
{
DBUG_ENTER("mysql_explain_unit");
bool res= 0;
propagate_explain_option(thd, unit);
if (unit->is_union())
{
if (unit->fake_select_lex != NULL)
{
unit->fake_select_lex->select_number= UINT_MAX; // just for initialization
unit->fake_select_lex->options|= SELECT_DESCRIBE;
}
res= unit->prepare(thd, result, SELECT_NO_UNLOCK | SELECT_DESCRIBE);
if (res)
DBUG_RETURN(res);
/*
If tables are not locked at this point, it means that we have delayed
this step until after prepare stage (now), in order to do better
partition pruning.
We need to lock tables now in order to proceed with the remaning
stages of query optimization.
*/
if (! thd->lex->is_query_tables_locked() &&
lock_tables(thd, thd->lex->query_tables, thd->lex->table_count, 0))
DBUG_RETURN(true);
res= unit->optimize();
if (!res)
res= unit->explain();
}
else
{
SELECT_LEX *first= unit->first_select();
thd->lex->current_select= first;
unit->set_limit(unit->global_parameters);
res= mysql_select(thd,
first->table_list.first,
first->with_wild, first->item_list,
first->where,
&first->order_list,
&first->group_list,
first->having,
first->options | thd->variables.option_bits | SELECT_DESCRIBE,
result, unit, first);
}
DBUG_RETURN(res || thd->is_error());
}