SHOW EXPLAIN UPDATE/DELETE
- Introduce "Query Plan Footprints" (abbrev. QPFs) QPF is a part of query plan that is 1. sufficient to produce EXPLAIN output, 2. can be used to produce EXPLAIN output even after its subquery/union was executed and deleted 3. is cheap to save so that we can always save query plans - This patch doesn't fully address #2, we make/save strings for a number of EXPLAIN's columns. This will be fixed.
This commit is contained in:
parent
9718b9763d
commit
03691a7771
@ -99,6 +99,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
|
|||||||
../sql/sql_expression_cache.cc
|
../sql/sql_expression_cache.cc
|
||||||
../sql/my_apc.cc ../sql/my_apc.h
|
../sql/my_apc.cc ../sql/my_apc.h
|
||||||
../sql/rpl_gtid.cc
|
../sql/rpl_gtid.cc
|
||||||
|
../sql/opt_qpf.cc ../sql/opt_qpf.h
|
||||||
${GEN_SOURCES}
|
${GEN_SOURCES}
|
||||||
${MYSYS_LIBWRAP_SOURCE}
|
${MYSYS_LIBWRAP_SOURCE}
|
||||||
)
|
)
|
||||||
|
@ -80,6 +80,7 @@ SET (SQL_SOURCE
|
|||||||
sql_reload.cc
|
sql_reload.cc
|
||||||
|
|
||||||
# added in MariaDB:
|
# added in MariaDB:
|
||||||
|
opt_qpf.h opt_qpf.cc
|
||||||
sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc
|
sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc
|
||||||
create_options.cc multi_range_read.cc
|
create_options.cc multi_range_read.cc
|
||||||
opt_index_cond_pushdown.cc opt_subselect.cc
|
opt_index_cond_pushdown.cc opt_subselect.cc
|
||||||
|
@ -286,7 +286,8 @@
|
|||||||
(yes, the sum is deliberately inaccurate)
|
(yes, the sum is deliberately inaccurate)
|
||||||
TODO remove the limit, use dynarrays
|
TODO remove the limit, use dynarrays
|
||||||
*/
|
*/
|
||||||
#define MAX_HA 15
|
/*#define MAX_HA 15*/
|
||||||
|
#define MAX_HA 32
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Use this instead of 0 as the initial value for the slot number of
|
Use this instead of 0 as the initial value for the slot number of
|
||||||
|
417
sql/opt_qpf.cc
Normal file
417
sql/opt_qpf.cc
Normal file
@ -0,0 +1,417 @@
|
|||||||
|
/*
|
||||||
|
TODO MP AB copyright
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef USE_PRAGMA_IMPLEMENTATION
|
||||||
|
#pragma implementation // gcc: Class implementation
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "sql_priv.h"
|
||||||
|
#include "sql_select.h"
|
||||||
|
|
||||||
|
|
||||||
|
QPF_query::QPF_query()
|
||||||
|
{
|
||||||
|
memset(&unions, 0, sizeof(unions));
|
||||||
|
memset(&selects, 0, sizeof(selects));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QPF_node *QPF_query::get_node(uint select_id)
|
||||||
|
{
|
||||||
|
if (unions[select_id])
|
||||||
|
return unions[select_id];
|
||||||
|
else
|
||||||
|
return selects[select_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QPF_select *QPF_query::get_select(uint select_id)
|
||||||
|
{
|
||||||
|
return selects[select_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QPF_query::add_node(QPF_node *node)
|
||||||
|
{
|
||||||
|
if (node->get_type() == QPF_node::QPF_UNION)
|
||||||
|
{
|
||||||
|
QPF_union *u= (QPF_union*)node;
|
||||||
|
unions[u->get_select_id()]= u;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QPF_select *sel= (QPF_select*)node;
|
||||||
|
if (sel->select_id == (int)UINT_MAX)
|
||||||
|
{
|
||||||
|
//TODO this is a "fake select" from a UNION.
|
||||||
|
DBUG_ASSERT(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
selects[sel->select_id] = sel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
The main entry point to print EXPLAIN of the entire query
|
||||||
|
*/
|
||||||
|
|
||||||
|
int QPF_query::print_explain(select_result_sink *output,
|
||||||
|
uint8 explain_flags)
|
||||||
|
{
|
||||||
|
// Start with id=1
|
||||||
|
QPF_node *node= get_node(1);
|
||||||
|
return node->print_explain(this, output, explain_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QPF_union::push_table_name(List<Item> *item_list)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void push_str(List<Item> *item_list, const char *str)
|
||||||
|
{
|
||||||
|
item_list->push_back(new Item_string(str,
|
||||||
|
strlen(str), system_charset_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void push_string(List<Item> *item_list, String *str)
|
||||||
|
{
|
||||||
|
item_list->push_back(new Item_string(str->ptr(), str->length(),
|
||||||
|
system_charset_info));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int QPF_union::print_explain(QPF_query *query, select_result_sink *output,
|
||||||
|
uint8 explain_flags)
|
||||||
|
{
|
||||||
|
// print all children, in order
|
||||||
|
for (int i= 0; i < (int) children.elements(); i++)
|
||||||
|
{
|
||||||
|
QPF_select *sel= query->get_select(children.at(i));
|
||||||
|
sel->print_explain(query, output, explain_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print a line with "UNION RESULT" */
|
||||||
|
List<Item> item_list;
|
||||||
|
Item *item_null= new Item_null();
|
||||||
|
|
||||||
|
/* `id` column */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `select_type` column */
|
||||||
|
push_str(&item_list, fake_select_type);
|
||||||
|
|
||||||
|
/* `table` column: something like "<union1,2>" */
|
||||||
|
//
|
||||||
|
{
|
||||||
|
char table_name_buffer[SAFE_NAME_LEN];
|
||||||
|
uint childno= 0;
|
||||||
|
uint len= 6, lastop= 0;
|
||||||
|
memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
|
||||||
|
|
||||||
|
for (; childno < children.elements() && len + lastop + 5 < NAME_LEN;
|
||||||
|
childno++)
|
||||||
|
{
|
||||||
|
len+= lastop;
|
||||||
|
lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
|
||||||
|
"%u,", children.at(childno));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (childno < children.elements() || len + lastop >= NAME_LEN)
|
||||||
|
{
|
||||||
|
memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1);
|
||||||
|
len+= 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
len+= lastop;
|
||||||
|
table_name_buffer[len - 1]= '>'; // change ',' to '>'
|
||||||
|
}
|
||||||
|
const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
item_list.push_back(new Item_string(table_name_buffer, len, cs));
|
||||||
|
}
|
||||||
|
//
|
||||||
|
push_table_name(&item_list);
|
||||||
|
|
||||||
|
/* `partitions` column */
|
||||||
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `type` column */
|
||||||
|
push_str(&item_list, join_type_str[JT_ALL]);
|
||||||
|
|
||||||
|
/* `possible_keys` column */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `key` */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `key_len` */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `ref` */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `rows` */
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `filtered` */
|
||||||
|
if (explain_flags & DESCRIBE_EXTENDED)
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `Extra` */
|
||||||
|
StringBuffer<256> extra_buf;
|
||||||
|
if (using_filesort)
|
||||||
|
{
|
||||||
|
extra_buf.append(STRING_WITH_LEN("Using filesort"));
|
||||||
|
}
|
||||||
|
const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs));
|
||||||
|
|
||||||
|
if (output->send_data(item_list))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int QPF_select::print_explain(QPF_query *query, select_result_sink *output,
|
||||||
|
uint8 explain_flags)
|
||||||
|
{
|
||||||
|
if (message)
|
||||||
|
{
|
||||||
|
List<Item> item_list;
|
||||||
|
const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
Item *item_null= new Item_null();
|
||||||
|
|
||||||
|
item_list.push_back(new Item_int((int32) select_id));
|
||||||
|
item_list.push_back(new Item_string(select_type,
|
||||||
|
strlen(select_type), cs));
|
||||||
|
for (uint i=0 ; i < 7; i++)
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
if (explain_flags & DESCRIBE_EXTENDED)
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
item_list.push_back(new Item_string(message,strlen(message),cs));
|
||||||
|
|
||||||
|
if (output->send_data(item_list))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool using_tmp= using_temporary;
|
||||||
|
bool using_fs= using_filesort;
|
||||||
|
for (uint i=0; i< n_join_tabs; i++)
|
||||||
|
{
|
||||||
|
join_tabs[i]->print_explain(output, explain_flags, select_id,
|
||||||
|
select_type, using_tmp, using_fs);
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
"Using temporary; Using filesort" should only be shown near the 1st
|
||||||
|
table
|
||||||
|
*/
|
||||||
|
using_tmp= false;
|
||||||
|
using_fs= false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags,
|
||||||
|
uint select_id, const char *select_type,
|
||||||
|
bool using_temporary, bool using_filesort)
|
||||||
|
{
|
||||||
|
List<Item> item_list;
|
||||||
|
Item *item_null= new Item_null();
|
||||||
|
//const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
|
||||||
|
/* `id` column */
|
||||||
|
item_list.push_back(new Item_int((int32) select_id));
|
||||||
|
|
||||||
|
/* `select_type` column */
|
||||||
|
push_str(&item_list, select_type);
|
||||||
|
|
||||||
|
/* `table` column */
|
||||||
|
push_string(&item_list, &table_name);
|
||||||
|
|
||||||
|
/* `partitions` column */
|
||||||
|
if (explain_flags & DESCRIBE_PARTITIONS)
|
||||||
|
{
|
||||||
|
if (used_partitions_set)
|
||||||
|
{
|
||||||
|
push_string(&item_list, &used_partitions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* `type` column */
|
||||||
|
push_str(&item_list, join_type_str[type]);
|
||||||
|
|
||||||
|
/* `possible_keys` column */
|
||||||
|
//push_str(item_list, "TODO");
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `key` */
|
||||||
|
if (key_set)
|
||||||
|
push_string(&item_list, &key);
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `key_len` */
|
||||||
|
if (key_len_set)
|
||||||
|
push_string(&item_list, &key_len);
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `ref` */
|
||||||
|
if (ref_set)
|
||||||
|
push_string(&item_list, &ref);
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `rows` */
|
||||||
|
if (rows_set)
|
||||||
|
{
|
||||||
|
item_list.push_back(new Item_int((longlong) (ulonglong) rows,
|
||||||
|
MY_INT64_NUM_DECIMAL_DIGITS));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
|
||||||
|
/* `filtered` */
|
||||||
|
if (explain_flags & DESCRIBE_EXTENDED)
|
||||||
|
{
|
||||||
|
if (filtered_set)
|
||||||
|
{
|
||||||
|
item_list.push_back(new Item_float(filtered, 2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
item_list.push_back(item_null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* `Extra` */
|
||||||
|
StringBuffer<256> extra_buf;
|
||||||
|
bool first= true;
|
||||||
|
for (int i=0; i < (int)extra_tags.elements(); i++)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
first= false;
|
||||||
|
else
|
||||||
|
extra_buf.append(STRING_WITH_LEN("; "));
|
||||||
|
append_tag_name(&extra_buf, extra_tags.at(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_temporary)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
first= false;
|
||||||
|
else
|
||||||
|
extra_buf.append(STRING_WITH_LEN("; "));
|
||||||
|
extra_buf.append(STRING_WITH_LEN("Using temporary"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (using_filesort)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
first= false;
|
||||||
|
else
|
||||||
|
extra_buf.append(STRING_WITH_LEN("; "));
|
||||||
|
extra_buf.append(STRING_WITH_LEN("Using filesort"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs));
|
||||||
|
|
||||||
|
if (output->send_data(item_list))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const char * extra_tag_text[]=
|
||||||
|
{
|
||||||
|
"ET_none",
|
||||||
|
"Using index condition",
|
||||||
|
"Using index condition(BKA)",
|
||||||
|
"Using ", //special
|
||||||
|
"Range checked for each record (index map: 0x", //special
|
||||||
|
"Using where with pushed condition",
|
||||||
|
"Using where",
|
||||||
|
"Not exists",
|
||||||
|
|
||||||
|
"Using index",
|
||||||
|
"Full scan on NULL key",
|
||||||
|
"Skip_open_table",
|
||||||
|
"Open_frm_only",
|
||||||
|
"Open_full_table",
|
||||||
|
|
||||||
|
"Scanned 0 databases",
|
||||||
|
"Scanned 1 database",
|
||||||
|
"Scanned all databases",
|
||||||
|
|
||||||
|
"Using index for group-by", // Special?
|
||||||
|
|
||||||
|
"USING MRR: DONT PRINT ME", // Special!
|
||||||
|
|
||||||
|
"Distinct",
|
||||||
|
"LooseScan",
|
||||||
|
"Start temporary",
|
||||||
|
"End temporary",
|
||||||
|
"FirstMatch", //TODO: also handle special variant!
|
||||||
|
|
||||||
|
"Using join buffer", // Special!,
|
||||||
|
|
||||||
|
"const row not found",
|
||||||
|
"unique row not found",
|
||||||
|
"Impossible ON condition"
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag)
|
||||||
|
{
|
||||||
|
switch (tag) {
|
||||||
|
case ET_USING:
|
||||||
|
{
|
||||||
|
// quick select
|
||||||
|
str->append(STRING_WITH_LEN("Using "));
|
||||||
|
str->append(quick_info);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ET_RANGE_CHECKED_FOR_EACH_RECORD:
|
||||||
|
{
|
||||||
|
/* 4 bits per 1 hex digit + terminating '\0' */
|
||||||
|
char buf[MAX_KEY / 4 + 1];
|
||||||
|
str->append(STRING_WITH_LEN("Range checked for each "
|
||||||
|
"record (index map: 0x"));
|
||||||
|
str->append(range_checked_map.print(buf));
|
||||||
|
str->append(')');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ET_USING_MRR:
|
||||||
|
{
|
||||||
|
str->append(mrr_type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ET_USING_JOIN_BUFFER:
|
||||||
|
{
|
||||||
|
str->append(extra_tag_text[tag]);
|
||||||
|
str->append(join_buffer_type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
str->append(extra_tag_text[tag]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
274
sql/opt_qpf.h
Normal file
274
sql/opt_qpf.h
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
/**************************************************************************************
|
||||||
|
|
||||||
|
Query Plan Footprint (QPF) structures
|
||||||
|
|
||||||
|
These structures
|
||||||
|
- Can be produced in-expensively from query plan.
|
||||||
|
- Store sufficient information to produce either a tabular or a json EXPLAIN
|
||||||
|
output
|
||||||
|
- Have methods that produce a tabular output.
|
||||||
|
|
||||||
|
*************************************************************************************/
|
||||||
|
|
||||||
|
class QPF_query;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A node can be either a SELECT, or a UNION.
|
||||||
|
*/
|
||||||
|
class QPF_node : public Sql_alloc
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum qpf_node_type {QPF_UNION, QPF_SELECT};
|
||||||
|
|
||||||
|
virtual enum qpf_node_type get_type()= 0;
|
||||||
|
virtual int print_explain(QPF_query *query, select_result_sink *output,
|
||||||
|
uint8 explain_flags)=0;
|
||||||
|
virtual ~QPF_node(){}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Nesting.
|
||||||
|
QPF_select may have children QPF_select-s.
|
||||||
|
(these can be FROM-subqueries, or subqueries from other clauses)
|
||||||
|
|
||||||
|
As for unions, the standard approach is:
|
||||||
|
- UNION node can be where the select node can be;
|
||||||
|
- the union has a select that retrieves results from temptable (a special
|
||||||
|
kind of child)
|
||||||
|
- and it has regular children selects that are merged into the union.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class QPF_table_access;
|
||||||
|
|
||||||
|
class QPF_select : public QPF_node
|
||||||
|
{
|
||||||
|
/*Construction interface */
|
||||||
|
public:
|
||||||
|
enum qpf_node_type get_type() { return QPF_SELECT; }
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Constructs a finished degenerate join plan */
|
||||||
|
QPF_select(int select_id_arg, const char *select_type_arg, const char* msg) :
|
||||||
|
select_id(select_id_arg),
|
||||||
|
select_type(select_type_arg),
|
||||||
|
message(msg),
|
||||||
|
join_tabs(NULL), n_join_tabs(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/* Constructs an un-finished, non degenerate join plan. */
|
||||||
|
QPF_select(int select_id_arg, const char *select_type_arg) :
|
||||||
|
select_id(select_id_arg),
|
||||||
|
select_type(select_type_arg),
|
||||||
|
message(NULL),
|
||||||
|
join_tabs(NULL), n_join_tabs(0)
|
||||||
|
{}
|
||||||
|
#endif
|
||||||
|
QPF_select() :
|
||||||
|
message(NULL), join_tabs(NULL),
|
||||||
|
using_temporary(false), using_filesort(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool add_table(QPF_table_access *tab)
|
||||||
|
{
|
||||||
|
if (!join_tabs)
|
||||||
|
{
|
||||||
|
join_tabs= (QPF_table_access**) malloc(sizeof(QPF_table_access*) * MAX_TABLES);
|
||||||
|
n_join_tabs= 0;
|
||||||
|
}
|
||||||
|
join_tabs[n_join_tabs++]= tab;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
int select_id; /* -1 means NULL. */
|
||||||
|
const char *select_type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
If message != NULL, this is a degenerate join plan, and all subsequent
|
||||||
|
members have no info
|
||||||
|
*/
|
||||||
|
const char *message;
|
||||||
|
|
||||||
|
/*
|
||||||
|
According to the discussion: this should be an array of "table
|
||||||
|
descriptors".
|
||||||
|
|
||||||
|
As for SJ-Materialization. Start_materialize/end_materialize markers?
|
||||||
|
*/
|
||||||
|
QPF_table_access** join_tabs;
|
||||||
|
uint n_join_tabs;
|
||||||
|
|
||||||
|
/* Global join attributes. In tabular form, they are printed on the first row */
|
||||||
|
bool using_temporary;
|
||||||
|
bool using_filesort;
|
||||||
|
|
||||||
|
void print_tabular(select_result_sink *output, uint8 explain_flags//,
|
||||||
|
//bool *printed_anything
|
||||||
|
);
|
||||||
|
|
||||||
|
int print_explain(QPF_query *query, select_result_sink *output,
|
||||||
|
uint8 explain_flags);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class QPF_union : public QPF_node
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum qpf_node_type get_type() { return QPF_UNION; }
|
||||||
|
|
||||||
|
int get_select_id()
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(children.elements() > 0);
|
||||||
|
return children.at(0);
|
||||||
|
}
|
||||||
|
// This has QPF_select children
|
||||||
|
Dynamic_array<int> children;
|
||||||
|
|
||||||
|
void add_select(int select_no)
|
||||||
|
{
|
||||||
|
children.append(select_no);
|
||||||
|
}
|
||||||
|
void push_table_name(List<Item> *item_list);
|
||||||
|
int print_explain(QPF_query *query, select_result_sink *output,
|
||||||
|
uint8 explain_flags);
|
||||||
|
|
||||||
|
const char *fake_select_type;
|
||||||
|
bool using_filesort;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is the whole query.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class QPF_query
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QPF_query();
|
||||||
|
void add_node(QPF_node *node);
|
||||||
|
int print_explain(select_result_sink *output, uint8 explain_flags);
|
||||||
|
|
||||||
|
/* This will return a select, or a union */
|
||||||
|
QPF_node *get_node(uint select_id);
|
||||||
|
|
||||||
|
/* This will return a select (even if there is a union with this id) */
|
||||||
|
QPF_select *get_select(uint select_id);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPF_union *unions[MAX_TABLES];
|
||||||
|
QPF_select *selects[MAX_TABLES];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum Extra_tag
|
||||||
|
{
|
||||||
|
ET_none= 0, /* not-a-tag */
|
||||||
|
ET_USING_INDEX_CONDITION,
|
||||||
|
ET_USING_INDEX_CONDITION_BKA,
|
||||||
|
ET_USING, /* For quick selects of various kinds */
|
||||||
|
ET_RANGE_CHECKED_FOR_EACH_RECORD,
|
||||||
|
ET_USING_WHERE_WITH_PUSHED_CONDITION,
|
||||||
|
ET_USING_WHERE,
|
||||||
|
ET_NOT_EXISTS,
|
||||||
|
|
||||||
|
ET_USING_INDEX,
|
||||||
|
ET_FULL_SCAN_ON_NULL_KEY,
|
||||||
|
ET_SKIP_OPEN_TABLE,
|
||||||
|
ET_OPEN_FRM_ONLY,
|
||||||
|
ET_OPEN_FULL_TABLE,
|
||||||
|
|
||||||
|
ET_SCANNED_0_DATABASES,
|
||||||
|
ET_SCANNED_1_DATABASE,
|
||||||
|
ET_SCANNED_ALL_DATABASES,
|
||||||
|
|
||||||
|
ET_USING_INDEX_FOR_GROUP_BY,
|
||||||
|
|
||||||
|
ET_USING_MRR, // does not print "Using mrr".
|
||||||
|
|
||||||
|
ET_DISTINCT,
|
||||||
|
ET_LOOSESCAN,
|
||||||
|
ET_START_TEMPORARY,
|
||||||
|
ET_END_TEMPORARY,
|
||||||
|
ET_FIRST_MATCH,
|
||||||
|
|
||||||
|
ET_USING_JOIN_BUFFER,
|
||||||
|
|
||||||
|
ET_CONST_ROW_NOT_FOUND,
|
||||||
|
ET_UNIQUE_ROW_NOT_FOUND,
|
||||||
|
ET_IMPOSSIBLE_ON_CONDITION,
|
||||||
|
|
||||||
|
ET_total
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class QPF_table_access
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void push_extra(enum Extra_tag extra_tag);
|
||||||
|
|
||||||
|
/* Internals */
|
||||||
|
public:
|
||||||
|
/* id and 'select_type' are cared-of by the parent QPF_select */
|
||||||
|
TABLE *table;
|
||||||
|
StringBuffer<256> table_name;
|
||||||
|
|
||||||
|
enum join_type type;
|
||||||
|
|
||||||
|
StringBuffer<256> used_partitions;
|
||||||
|
bool used_partitions_set;
|
||||||
|
|
||||||
|
key_map possible_keys;
|
||||||
|
|
||||||
|
uint key_no;
|
||||||
|
uint key_length;
|
||||||
|
|
||||||
|
Dynamic_array<enum Extra_tag> extra_tags;
|
||||||
|
|
||||||
|
//temporary:
|
||||||
|
StringBuffer<256> key;
|
||||||
|
StringBuffer<256> key_len;
|
||||||
|
StringBuffer<256> ref;
|
||||||
|
bool key_set;
|
||||||
|
bool key_len_set;
|
||||||
|
bool ref_set;
|
||||||
|
|
||||||
|
bool rows_set;
|
||||||
|
ha_rows rows;
|
||||||
|
|
||||||
|
double filtered;
|
||||||
|
bool filtered_set;
|
||||||
|
|
||||||
|
/* Various stuff for 'Extra' column*/
|
||||||
|
uint join_cache_level;
|
||||||
|
|
||||||
|
// Valid if ET_USING tag is present
|
||||||
|
StringBuffer<256> quick_info;
|
||||||
|
|
||||||
|
// Valid if ET_USING_INDEX_FOR_GROUP_BY is present
|
||||||
|
StringBuffer<256> loose_scan_type;
|
||||||
|
|
||||||
|
// valid with ET_RANGE_CHECKED_FOR_EACH_RECORD
|
||||||
|
key_map range_checked_map;
|
||||||
|
|
||||||
|
// valid with ET_USING_MRR
|
||||||
|
StringBuffer <256> mrr_type;
|
||||||
|
|
||||||
|
// valid with ET_USING_JOIN_BUFFER
|
||||||
|
StringBuffer <256> join_buffer_type;
|
||||||
|
|
||||||
|
TABLE *firstmatch_table;
|
||||||
|
|
||||||
|
int print_explain(select_result_sink *output, uint8 explain_flags,
|
||||||
|
uint select_id, const char *select_type,
|
||||||
|
bool using_temporary, bool using_filesort);
|
||||||
|
private:
|
||||||
|
void append_tag_name(String *str, enum Extra_tag tag);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update_plan and Delete_plan belong to this kind of structures, too.
|
||||||
|
|
||||||
|
// TODO: should Update_plan inherit from QPF_table_access?
|
||||||
|
|
||||||
|
|
@ -169,7 +169,9 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags,
|
|||||||
extra_str.c_ptr());
|
extra_str.c_ptr());
|
||||||
|
|
||||||
*printed_anything= true;
|
*printed_anything= true;
|
||||||
|
/*
|
||||||
|
psergey-todo: handle all this through saving QPF.
|
||||||
|
|
||||||
for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
|
for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit();
|
||||||
unit;
|
unit;
|
||||||
unit= unit->next_unit())
|
unit= unit->next_unit())
|
||||||
@ -177,6 +179,7 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags,
|
|||||||
if (unit->print_explain(output, explain_flags, printed_anything))
|
if (unit->print_explain(output, explain_flags, printed_anything))
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
135
sql/sql_lex.cc
135
sql/sql_lex.cc
@ -4172,6 +4172,7 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor)
|
|||||||
return all_merged;
|
return all_merged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int LEX::print_explain(select_result_sink *output, uint8 explain_flags,
|
int LEX::print_explain(select_result_sink *output, uint8 explain_flags,
|
||||||
bool *printed_anything)
|
bool *printed_anything)
|
||||||
{
|
{
|
||||||
@ -4180,11 +4181,87 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags,
|
|||||||
upd_del_plan->print_explain(output, explain_flags, printed_anything);
|
upd_del_plan->print_explain(output, explain_flags, printed_anything);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int res= unit.print_explain(output, explain_flags, printed_anything);
|
//int res= unit.print_explain(output, explain_flags, printed_anything);
|
||||||
return res;
|
|
||||||
|
//psergey-todo: here, we should make Query Plan Footprint, and then produce
|
||||||
|
// an EXPLAIN output from it.
|
||||||
|
/*
|
||||||
|
The new, QueryPlanFootprint way:
|
||||||
|
*/
|
||||||
|
QPF_query qpf;
|
||||||
|
unit.save_qpf(&qpf);
|
||||||
|
//return res;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void st_select_lex::save_qpf(QPF_query *output)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
if (join && join->have_query_plan == JOIN::QEP_AVAILABLE)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
There is a number of reasons join can be marked as degenerate, so all
|
||||||
|
three conditions below can happen simultaneously, or individually:
|
||||||
|
*/
|
||||||
|
if (!join->table_count || !join->tables_list || join->zero_result_cause)
|
||||||
|
{
|
||||||
|
/* It's a degenerate join */
|
||||||
|
const char *cause= join->zero_result_cause ? join-> zero_result_cause :
|
||||||
|
"No tables used";
|
||||||
|
res= join->save_qpf(output, FALSE, FALSE, FALSE, cause);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
join->save_qpf(output, join->need_tmp, // need_tmp_table
|
||||||
|
!join->skip_sort_order && !join->no_order &&
|
||||||
|
(join->order || join->group_list), // bool need_order
|
||||||
|
join->select_distinct, // bool distinct
|
||||||
|
NULL); //const char *message
|
||||||
|
}
|
||||||
|
if (res)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
|
||||||
|
unit;
|
||||||
|
unit= unit->next_unit())
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Display subqueries only if they are not parts of eliminated WHERE/ON
|
||||||
|
clauses.
|
||||||
|
*/
|
||||||
|
if (!(unit->item && unit->item->eliminated))
|
||||||
|
{
|
||||||
|
unit->save_qpf(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char *msg;
|
||||||
|
if (!join)
|
||||||
|
DBUG_ASSERT(0); /* Seems not to be possible */
|
||||||
|
|
||||||
|
/* Not printing anything useful, don't touch *printed_anything here */
|
||||||
|
if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET)
|
||||||
|
msg= "Not yet optimized";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED);
|
||||||
|
msg= "Query plan already deleted";
|
||||||
|
}
|
||||||
|
set_explain_type(TRUE/* on_the_fly */);
|
||||||
|
QPF_select *qp_sel= new QPF_select;
|
||||||
|
qp_sel->select_id= select_number;
|
||||||
|
qp_sel->select_type= type;
|
||||||
|
qp_sel->message= msg;
|
||||||
|
output->add_node(qp_sel);
|
||||||
|
}
|
||||||
|
err:
|
||||||
|
return ;//res;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
int st_select_lex::print_explain(select_result_sink *output,
|
int st_select_lex::print_explain(select_result_sink *output,
|
||||||
uint8 explain_flags,
|
uint8 explain_flags,
|
||||||
bool *printed_anything)
|
bool *printed_anything)
|
||||||
@ -4253,8 +4330,60 @@ int st_select_lex::print_explain(select_result_sink *output,
|
|||||||
err:
|
err:
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
int st_select_lex_unit::save_qpf(QPF_query *output)
|
||||||
|
{
|
||||||
|
//int res= 0;
|
||||||
|
SELECT_LEX *first= first_select();
|
||||||
|
|
||||||
|
QPF_union *qpfu= new QPF_union;
|
||||||
|
/*
|
||||||
|
TODO: The following code should be eliminated. If we have a capability to
|
||||||
|
save Query Plan Footprints, we should just save them, and never need to
|
||||||
|
print "query plan already deleted".
|
||||||
|
*/
|
||||||
|
if (first && !first->next_select() && !first->join)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
If there is only one child, 'first', and it has join==NULL, emit "not in
|
||||||
|
EXPLAIN state" error.
|
||||||
|
*/
|
||||||
|
const char *msg="Query plan already deleted";
|
||||||
|
first->set_explain_type(TRUE/* on_the_fly */);
|
||||||
|
|
||||||
|
QPF_select *qp_sel= new QPF_select;
|
||||||
|
qp_sel->select_id= first->select_number;
|
||||||
|
qp_sel->select_type= first->type;
|
||||||
|
qp_sel->message= msg;
|
||||||
|
output->add_node(qp_sel);
|
||||||
|
qpfu->add_select(qp_sel->select_id);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
|
||||||
|
{
|
||||||
|
sl->save_qpf(output);
|
||||||
|
qpfu->add_select(sl->select_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the UNION node
|
||||||
|
output->add_node(qpfu);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Note: fake_select_lex->join may be NULL or non-NULL at this point */
|
||||||
|
if (fake_select_lex)
|
||||||
|
{
|
||||||
|
res= print_fake_select_lex_join(output, TRUE /* on the fly */,
|
||||||
|
fake_select_lex, explain_flags);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
int st_select_lex_unit::print_explain(select_result_sink *output,
|
int st_select_lex_unit::print_explain(select_result_sink *output,
|
||||||
uint8 explain_flags, bool *printed_anything)
|
uint8 explain_flags, bool *printed_anything)
|
||||||
{
|
{
|
||||||
@ -4288,7 +4417,7 @@ int st_select_lex_unit::print_explain(select_result_sink *output,
|
|||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A routine used by the parser to decide whether we are specifying a full
|
A routine used by the parser to decide whether we are specifying a full
|
||||||
|
@ -617,7 +617,7 @@ class select_result;
|
|||||||
class JOIN;
|
class JOIN;
|
||||||
class select_union;
|
class select_union;
|
||||||
class Procedure;
|
class Procedure;
|
||||||
|
class QPF_query;
|
||||||
|
|
||||||
class st_select_lex_unit: public st_select_lex_node {
|
class st_select_lex_unit: public st_select_lex_node {
|
||||||
protected:
|
protected:
|
||||||
@ -728,8 +728,11 @@ public:
|
|||||||
friend int subselect_union_engine::exec();
|
friend int subselect_union_engine::exec();
|
||||||
|
|
||||||
List<Item> *get_unit_column_types();
|
List<Item> *get_unit_column_types();
|
||||||
|
#if 0
|
||||||
int print_explain(select_result_sink *output, uint8 explain_flags,
|
int print_explain(select_result_sink *output, uint8 explain_flags,
|
||||||
bool *printed_anything);
|
bool *printed_anything);
|
||||||
|
#endif
|
||||||
|
int save_qpf(QPF_query *output);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef class st_select_lex_unit SELECT_LEX_UNIT;
|
typedef class st_select_lex_unit SELECT_LEX_UNIT;
|
||||||
@ -1048,8 +1051,11 @@ public:
|
|||||||
bool save_prep_leaf_tables(THD *thd);
|
bool save_prep_leaf_tables(THD *thd);
|
||||||
|
|
||||||
bool is_merged_child_of(st_select_lex *ancestor);
|
bool is_merged_child_of(st_select_lex *ancestor);
|
||||||
|
#if 0
|
||||||
int print_explain(select_result_sink *output, uint8 explain_flags,
|
int print_explain(select_result_sink *output, uint8 explain_flags,
|
||||||
bool *printed_anything);
|
bool *printed_anything);
|
||||||
|
#endif
|
||||||
|
void save_qpf(QPF_query *output);
|
||||||
/*
|
/*
|
||||||
For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags:
|
For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags:
|
||||||
- Non-aggregated fields are used in this select.
|
- Non-aggregated fields are used in this select.
|
||||||
@ -2360,6 +2366,8 @@ class SQL_SELECT;
|
|||||||
/*
|
/*
|
||||||
Query plan of a single-table UPDATE.
|
Query plan of a single-table UPDATE.
|
||||||
(This is actually a plan for single-table DELETE also)
|
(This is actually a plan for single-table DELETE also)
|
||||||
|
|
||||||
|
TODO: this should be a query plan footprint, not a query plan.
|
||||||
*/
|
*/
|
||||||
class Update_plan
|
class Update_plan
|
||||||
{
|
{
|
||||||
@ -2411,6 +2419,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class QPF_query;
|
||||||
/* The state of the lex parsing. This is saved in the THD struct */
|
/* The state of the lex parsing. This is saved in the THD struct */
|
||||||
|
|
||||||
struct LEX: public Query_tables_list
|
struct LEX: public Query_tables_list
|
||||||
@ -2424,6 +2433,7 @@ struct LEX: public Query_tables_list
|
|||||||
|
|
||||||
/* For single-table DELETE: its query plan */
|
/* For single-table DELETE: its query plan */
|
||||||
Update_plan *upd_del_plan;
|
Update_plan *upd_del_plan;
|
||||||
|
QPF_query *query_plan_footprint;
|
||||||
|
|
||||||
char *length,*dec,*change;
|
char *length,*dec,*change;
|
||||||
LEX_STRING name;
|
LEX_STRING name;
|
||||||
|
@ -4811,7 +4811,15 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
|
|||||||
if (!(result= new select_send()))
|
if (!(result= new select_send()))
|
||||||
return 1; /* purecov: inspected */
|
return 1; /* purecov: inspected */
|
||||||
thd->send_explain_fields(result);
|
thd->send_explain_fields(result);
|
||||||
|
thd->lex->query_plan_footprint= new QPF_query;
|
||||||
res= mysql_explain_union(thd, &thd->lex->unit, result);
|
res= mysql_explain_union(thd, &thd->lex->unit, result);
|
||||||
|
|
||||||
|
thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe);
|
||||||
|
|
||||||
|
//psergey-todo: here, produce the EXPLAIN output.
|
||||||
|
// mysql_explain_union() itself is only responsible for calling
|
||||||
|
// optimize() for all parts of the query.
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The code which prints the extended description is not robust
|
The code which prints the extended description is not robust
|
||||||
against malformed queries, so skip it if we have an error.
|
against malformed queries, so skip it if we have an error.
|
||||||
|
@ -3856,7 +3856,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
|
|||||||
if (*s->on_expr_ref)
|
if (*s->on_expr_ref)
|
||||||
{
|
{
|
||||||
/* Generate empty row */
|
/* Generate empty row */
|
||||||
s->info= "Impossible ON condition";
|
s->info= ET_IMPOSSIBLE_ON_CONDITION;
|
||||||
found_const_table_map|= s->table->map;
|
found_const_table_map|= s->table->map;
|
||||||
s->type= JT_CONST;
|
s->type= JT_CONST;
|
||||||
mark_as_null_row(s->table); // All fields are NULL
|
mark_as_null_row(s->table); // All fields are NULL
|
||||||
@ -17219,7 +17219,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos)
|
|||||||
{
|
{
|
||||||
if ((error=join_read_system(tab)))
|
if ((error=join_read_system(tab)))
|
||||||
{ // Info for DESCRIBE
|
{ // Info for DESCRIBE
|
||||||
tab->info="const row not found";
|
tab->info= ET_CONST_ROW_NOT_FOUND;
|
||||||
/* Mark for EXPLAIN that the row was not found */
|
/* Mark for EXPLAIN that the row was not found */
|
||||||
pos->records_read=0.0;
|
pos->records_read=0.0;
|
||||||
pos->ref_depend_map= 0;
|
pos->ref_depend_map= 0;
|
||||||
@ -17245,7 +17245,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos)
|
|||||||
table->disable_keyread();
|
table->disable_keyread();
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
tab->info="unique row not found";
|
tab->info= ET_UNIQUE_ROW_NOT_FOUND;
|
||||||
/* Mark for EXPLAIN that the row was not found */
|
/* Mark for EXPLAIN that the row was not found */
|
||||||
pos->records_read=0.0;
|
pos->records_read=0.0;
|
||||||
pos->ref_depend_map= 0;
|
pos->ref_depend_map= 0;
|
||||||
@ -22321,8 +22321,11 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res)
|
|||||||
selects)
|
selects)
|
||||||
@param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make
|
@param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make
|
||||||
modifications to any select's data structures
|
modifications to any select's data structures
|
||||||
*/
|
|
||||||
|
|
||||||
|
psergey-todo: should this produce a data structure with a query plan? Or, the
|
||||||
|
data structure with the query plan should be produced in any way?
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
||||||
bool on_the_fly,
|
bool on_the_fly,
|
||||||
bool need_tmp_table, bool need_order,
|
bool need_tmp_table, bool need_order,
|
||||||
@ -22381,6 +22384,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
|||||||
|
|
||||||
bool printing_materialize_nest= FALSE;
|
bool printing_materialize_nest= FALSE;
|
||||||
uint select_id= join->select_lex->select_number;
|
uint select_id= join->select_lex->select_number;
|
||||||
|
|
||||||
JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
|
JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
|
||||||
|
|
||||||
for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
|
for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
|
||||||
@ -22392,7 +22396,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
|||||||
select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier();
|
select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier();
|
||||||
printing_materialize_nest= TRUE;
|
printing_materialize_nest= TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
TABLE *table=tab->table;
|
TABLE *table=tab->table;
|
||||||
TABLE_LIST *table_list= tab->table->pos_in_table_list;
|
TABLE_LIST *table_list= tab->table->pos_in_table_list;
|
||||||
char buff[512];
|
char buff[512];
|
||||||
@ -22672,7 +22676,23 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
|||||||
key_read=1;
|
key_read=1;
|
||||||
|
|
||||||
if (tab->info)
|
if (tab->info)
|
||||||
item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs));
|
{
|
||||||
|
const char *reason;
|
||||||
|
switch (tab->info) {
|
||||||
|
case ET_CONST_ROW_NOT_FOUND:
|
||||||
|
reason= "const row not found";
|
||||||
|
break;
|
||||||
|
case ET_UNIQUE_ROW_NOT_FOUND:
|
||||||
|
reason= "unique row not found";
|
||||||
|
break;
|
||||||
|
case ET_IMPOSSIBLE_ON_CONDITION:
|
||||||
|
reason= "Impossible ON condition";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DBUG_ASSERT(0);
|
||||||
|
}
|
||||||
|
item_list.push_back(new Item_string(reason,strlen(reason),cs));
|
||||||
|
}
|
||||||
else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
|
else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
|
||||||
{
|
{
|
||||||
if (tab->packed_info & TAB_INFO_USING_INDEX)
|
if (tab->packed_info & TAB_INFO_USING_INDEX)
|
||||||
@ -22876,6 +22896,577 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags,
|
|||||||
}
|
}
|
||||||
DBUG_RETURN(error);
|
DBUG_RETURN(error);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
void QPF_table_access::push_extra(enum Extra_tag extra_tag)
|
||||||
|
{
|
||||||
|
extra_tags.append(extra_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Save Query Plan Footprint
|
||||||
|
push_extra
|
||||||
|
|
||||||
|
P
|
||||||
|
*/
|
||||||
|
|
||||||
|
int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order,
|
||||||
|
bool distinct, const char *message)
|
||||||
|
{
|
||||||
|
QPF_select *qp_sel;
|
||||||
|
const bool on_the_fly= true;
|
||||||
|
|
||||||
|
JOIN *join= this; /* Legacy: this code used to be a non-member function */
|
||||||
|
THD *thd=join->thd;
|
||||||
|
const CHARSET_INFO *cs= system_charset_info;
|
||||||
|
int quick_type;
|
||||||
|
int error= 0;
|
||||||
|
DBUG_ENTER("JOIN::print_explain");
|
||||||
|
DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
|
||||||
|
(ulong)join->select_lex, join->select_lex->type,
|
||||||
|
message ? message : "NULL"));
|
||||||
|
DBUG_ASSERT(have_query_plan == QEP_AVAILABLE);
|
||||||
|
/* Don't log this into the slow query log */
|
||||||
|
|
||||||
|
qp_sel= new QPF_select;
|
||||||
|
|
||||||
|
/*
|
||||||
|
NOTE: the number/types of items pushed into item_list must be in sync with
|
||||||
|
EXPLAIN column types as they're "defined" in THD::send_explain_fields()
|
||||||
|
*/
|
||||||
|
if (message)
|
||||||
|
{
|
||||||
|
join->select_lex->set_explain_type(on_the_fly);
|
||||||
|
|
||||||
|
qp_sel->select_id= join->select_lex->select_number;
|
||||||
|
qp_sel->select_type= join->select_lex->type;
|
||||||
|
qp_sel->message= message;
|
||||||
|
/* Setting qp_sel->message means that all other members are invalid */
|
||||||
|
output->add_node(qp_sel);
|
||||||
|
}
|
||||||
|
else if (join->select_lex == join->unit->fake_select_lex)
|
||||||
|
{
|
||||||
|
select_lex->set_explain_type(on_the_fly);
|
||||||
|
QPF_union *qp_union= new QPF_union;
|
||||||
|
|
||||||
|
SELECT_LEX *child;
|
||||||
|
for (child= select_lex->master_unit()->first_select(); child;
|
||||||
|
child=child->next_select())
|
||||||
|
{
|
||||||
|
qp_union->add_select(child->select_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
qp_union->fake_select_type= select_lex->type;
|
||||||
|
qp_union->using_filesort=
|
||||||
|
test(select_lex->master_unit()->global_parameters->order_list.first);
|
||||||
|
|
||||||
|
output->add_node(qp_union);
|
||||||
|
}
|
||||||
|
else if (!join->select_lex->master_unit()->derived ||
|
||||||
|
join->select_lex->master_unit()->derived->is_materialized_derived())
|
||||||
|
{
|
||||||
|
table_map used_tables=0;
|
||||||
|
|
||||||
|
if (on_the_fly)
|
||||||
|
join->select_lex->set_explain_type(on_the_fly);
|
||||||
|
|
||||||
|
bool printing_materialize_nest= FALSE;
|
||||||
|
uint select_id= join->select_lex->select_number;
|
||||||
|
|
||||||
|
qp_sel->select_id= join->select_lex->select_number;
|
||||||
|
qp_sel->select_type= join->select_lex->type;
|
||||||
|
|
||||||
|
JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS);
|
||||||
|
|
||||||
|
for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab;
|
||||||
|
tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab))
|
||||||
|
{
|
||||||
|
QPF_table_access *qpt= new QPF_table_access;
|
||||||
|
qp_sel->add_table(qpt);
|
||||||
|
|
||||||
|
if (tab->bush_root_tab)
|
||||||
|
{
|
||||||
|
JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start;
|
||||||
|
select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier();
|
||||||
|
printing_materialize_nest= TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
TABLE *table=tab->table;
|
||||||
|
TABLE_LIST *table_list= tab->table->pos_in_table_list;
|
||||||
|
char buff2[512], buff3[512], buff4[512];
|
||||||
|
char keylen_str_buf[64];
|
||||||
|
my_bool key_read;
|
||||||
|
char table_name_buffer[SAFE_NAME_LEN];
|
||||||
|
String tmp2(buff2,sizeof(buff2),cs);
|
||||||
|
String tmp3(buff3,sizeof(buff3),cs);
|
||||||
|
String tmp4(buff4,sizeof(buff4),cs);
|
||||||
|
char hash_key_prefix[]= "#hash#";
|
||||||
|
KEY *key_info= 0;
|
||||||
|
uint key_len= 0;
|
||||||
|
bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT;
|
||||||
|
|
||||||
|
tmp2.length(0);
|
||||||
|
tmp3.length(0);
|
||||||
|
tmp4.length(0);
|
||||||
|
quick_type= -1;
|
||||||
|
QUICK_SELECT_I *quick= NULL;
|
||||||
|
JOIN_TAB *saved_join_tab= NULL;
|
||||||
|
|
||||||
|
/* Don't show eliminated tables */
|
||||||
|
if (table->map & join->eliminated_tables)
|
||||||
|
{
|
||||||
|
used_tables|=table->map;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (join->table_access_tabs == join->join_tab &&
|
||||||
|
tab == (first_top_tab + join->const_tables) && pre_sort_join_tab)
|
||||||
|
{
|
||||||
|
saved_join_tab= tab;
|
||||||
|
tab= pre_sort_join_tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* id */
|
||||||
|
qp_sel->select_id= select_id;
|
||||||
|
|
||||||
|
/* select_type */
|
||||||
|
//const char* stype= printing_materialize_nest? "MATERIALIZED" :
|
||||||
|
// join->select_lex->type;
|
||||||
|
//item_list.push_back(new Item_string(stype, strlen(stype), cs));
|
||||||
|
qp_sel->select_type= join->select_lex->type;
|
||||||
|
|
||||||
|
/* table */
|
||||||
|
if (table->derived_select_number)
|
||||||
|
{
|
||||||
|
/* Derived table name generation */
|
||||||
|
int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1,
|
||||||
|
"<derived%u>",
|
||||||
|
table->derived_select_number);
|
||||||
|
qpt->table_name.set(table_name_buffer, len, cs);
|
||||||
|
}
|
||||||
|
else if (tab->bush_children)
|
||||||
|
{
|
||||||
|
JOIN_TAB *ctab= tab->bush_children->start;
|
||||||
|
/* table */
|
||||||
|
int len= my_snprintf(table_name_buffer,
|
||||||
|
sizeof(table_name_buffer)-1,
|
||||||
|
"<subquery%d>",
|
||||||
|
ctab->emb_sj_nest->sj_subq_pred->get_identifier());
|
||||||
|
qpt->table_name.set(table_name_buffer, len, cs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TABLE_LIST *real_table= table->pos_in_table_list;
|
||||||
|
qpt->table_name.set(real_table->alias, strlen(real_table->alias), cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "partitions" column */
|
||||||
|
{
|
||||||
|
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
||||||
|
partition_info *part_info;
|
||||||
|
if (!table->derived_select_number &&
|
||||||
|
(part_info= table->part_info))
|
||||||
|
{
|
||||||
|
make_used_partitions_str(part_info, &qpt->used_partitions);
|
||||||
|
qpt->used_partitions_set= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->used_partitions_set= false;
|
||||||
|
#else
|
||||||
|
/* just produce empty column if partitioning is not compiled in */
|
||||||
|
qpt->used_partitions_set= false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "type" column */
|
||||||
|
enum join_type tab_type= tab->type;
|
||||||
|
if ((tab->type == JT_ALL || tab->type == JT_HASH) &&
|
||||||
|
tab->select && tab->select->quick && tab->use_quick != 2)
|
||||||
|
{
|
||||||
|
quick= tab->select->quick;
|
||||||
|
quick_type= tab->select->quick->get_type();
|
||||||
|
if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
|
||||||
|
(quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) ||
|
||||||
|
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
|
||||||
|
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
|
||||||
|
tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE;
|
||||||
|
else
|
||||||
|
tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE;
|
||||||
|
}
|
||||||
|
qpt->type= tab_type;
|
||||||
|
|
||||||
|
/* Build "possible_keys" value */
|
||||||
|
qpt->possible_keys= tab->keys;
|
||||||
|
|
||||||
|
/* Build "key", "key_len", and "ref" */
|
||||||
|
|
||||||
|
// tmp2 holds key_name
|
||||||
|
// tmp3 holds key_length
|
||||||
|
// tmp4 holds ref?
|
||||||
|
if (tab_type == JT_NEXT)
|
||||||
|
{
|
||||||
|
key_info= table->key_info+tab->index;
|
||||||
|
key_len= key_info->key_length;
|
||||||
|
}
|
||||||
|
else if (tab->ref.key_parts)
|
||||||
|
{
|
||||||
|
key_info= tab->get_keyinfo_by_key_no(tab->ref.key);
|
||||||
|
key_len= tab->ref.key_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key_info)
|
||||||
|
{
|
||||||
|
register uint length;
|
||||||
|
if (is_hj)
|
||||||
|
tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs);
|
||||||
|
tmp2.append(key_info->name, strlen(key_info->name), cs);
|
||||||
|
length= (longlong10_to_str(key_len, keylen_str_buf, 10) -
|
||||||
|
keylen_str_buf);
|
||||||
|
tmp3.append(keylen_str_buf, length, cs);
|
||||||
|
if (tab->ref.key_parts && tab_type != JT_FT)
|
||||||
|
{
|
||||||
|
store_key **ref=tab->ref.key_copy;
|
||||||
|
for (uint kp= 0; kp < tab->ref.key_parts; kp++)
|
||||||
|
{
|
||||||
|
if (tmp4.length())
|
||||||
|
tmp4.append(',');
|
||||||
|
|
||||||
|
if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map)
|
||||||
|
tmp4.append("const");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tmp4.append((*ref)->name(), strlen((*ref)->name()), cs);
|
||||||
|
ref++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_hj && tab_type != JT_HASH)
|
||||||
|
{
|
||||||
|
tmp2.append(':');
|
||||||
|
tmp3.append(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab_type == JT_HASH_NEXT)
|
||||||
|
{
|
||||||
|
register uint length;
|
||||||
|
key_info= table->key_info+tab->index;
|
||||||
|
key_len= key_info->key_length;
|
||||||
|
tmp2.append(key_info->name, strlen(key_info->name), cs);
|
||||||
|
length= (longlong10_to_str(key_len, keylen_str_buf, 10) -
|
||||||
|
keylen_str_buf);
|
||||||
|
tmp3.append(keylen_str_buf, length, cs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab->type != JT_CONST && tab->select && quick)
|
||||||
|
tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3);
|
||||||
|
|
||||||
|
if (key_info || (tab->select && quick))
|
||||||
|
{
|
||||||
|
if (tmp2.length())
|
||||||
|
{
|
||||||
|
qpt->key.copy(tmp2);
|
||||||
|
qpt->key_set= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->key_set= false;
|
||||||
|
|
||||||
|
if (tmp3.length())
|
||||||
|
{
|
||||||
|
qpt->key_len.copy(tmp3);
|
||||||
|
qpt->key_len_set= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->key_len_set= false;
|
||||||
|
|
||||||
|
if (key_info && tab_type != JT_NEXT)
|
||||||
|
{
|
||||||
|
qpt->ref.copy(tmp4);
|
||||||
|
qpt->ref_set= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->ref_set= false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (table_list && /* SJM bushes don't have table_list */
|
||||||
|
table_list->schema_table &&
|
||||||
|
table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
|
||||||
|
{
|
||||||
|
const char *tmp_buff;
|
||||||
|
int f_idx;
|
||||||
|
if (table_list->has_db_lookup_value)
|
||||||
|
{
|
||||||
|
f_idx= table_list->schema_table->idx_field1;
|
||||||
|
tmp_buff= table_list->schema_table->fields_info[f_idx].field_name;
|
||||||
|
tmp2.append(tmp_buff, strlen(tmp_buff), cs);
|
||||||
|
}
|
||||||
|
if (table_list->has_table_lookup_value)
|
||||||
|
{
|
||||||
|
if (table_list->has_db_lookup_value)
|
||||||
|
tmp2.append(',');
|
||||||
|
f_idx= table_list->schema_table->idx_field2;
|
||||||
|
tmp_buff= table_list->schema_table->fields_info[f_idx].field_name;
|
||||||
|
tmp2.append(tmp_buff, strlen(tmp_buff), cs);
|
||||||
|
}
|
||||||
|
if (tmp2.length())
|
||||||
|
{
|
||||||
|
qpt->key.copy(tmp2);
|
||||||
|
qpt->key_set= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->key_set= false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->key_set= false;
|
||||||
|
|
||||||
|
qpt->key_len_set= false;
|
||||||
|
qpt->ref_set= false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* "rows" */
|
||||||
|
|
||||||
|
if (table_list /* SJM bushes don't have table_list */ &&
|
||||||
|
table_list->schema_table)
|
||||||
|
{
|
||||||
|
/* I_S tables have rows=extra=NULL */
|
||||||
|
qpt->rows_set= false;
|
||||||
|
qpt->filtered_set= false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ha_rows examined_rows= tab->get_examined_rows();
|
||||||
|
|
||||||
|
qpt->rows_set= true;
|
||||||
|
qpt->rows= examined_rows;
|
||||||
|
|
||||||
|
/* "filtered" */
|
||||||
|
float f= 0.0;
|
||||||
|
if (examined_rows)
|
||||||
|
{
|
||||||
|
double pushdown_cond_selectivity= tab->cond_selectivity;
|
||||||
|
if (pushdown_cond_selectivity == 1.0)
|
||||||
|
f= (float) (100.0 * tab->records_read / examined_rows);
|
||||||
|
else
|
||||||
|
f= (float) (100.0 * pushdown_cond_selectivity);
|
||||||
|
}
|
||||||
|
set_if_smaller(f, 100.0);
|
||||||
|
qpt->filtered_set= true;
|
||||||
|
qpt->filtered= f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Build "Extra" field and save it */
|
||||||
|
key_read=table->key_read;
|
||||||
|
if ((tab_type == JT_NEXT || tab_type == JT_CONST) &&
|
||||||
|
table->covering_keys.is_set(tab->index))
|
||||||
|
key_read=1;
|
||||||
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
|
||||||
|
!((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row)
|
||||||
|
key_read=1;
|
||||||
|
|
||||||
|
if (tab->info)
|
||||||
|
{
|
||||||
|
qpt->push_extra(tab->info);
|
||||||
|
}
|
||||||
|
else if (tab->packed_info & TAB_INFO_HAVE_VALUE)
|
||||||
|
{
|
||||||
|
if (tab->packed_info & TAB_INFO_USING_INDEX)
|
||||||
|
qpt->push_extra(ET_USING_INDEX);
|
||||||
|
if (tab->packed_info & TAB_INFO_USING_WHERE)
|
||||||
|
qpt->push_extra(ET_USING_WHERE);
|
||||||
|
if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
|
||||||
|
qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint keyno= MAX_KEY;
|
||||||
|
if (tab->ref.key_parts)
|
||||||
|
keyno= tab->ref.key;
|
||||||
|
else if (tab->select && quick)
|
||||||
|
keyno = quick->index;
|
||||||
|
|
||||||
|
if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno &&
|
||||||
|
table->file->pushed_idx_cond)
|
||||||
|
qpt->push_extra(ET_USING_INDEX_CONDITION);
|
||||||
|
else if (tab->cache_idx_cond)
|
||||||
|
qpt->push_extra(ET_USING_INDEX_CONDITION_BKA);
|
||||||
|
|
||||||
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
|
||||||
|
quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
|
||||||
|
quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT ||
|
||||||
|
quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_USING);
|
||||||
|
tab->select->quick->add_info_string(&qpt->quick_info);
|
||||||
|
}
|
||||||
|
if (tab->select)
|
||||||
|
{
|
||||||
|
if (tab->use_quick == 2)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD);
|
||||||
|
qpt->range_checked_map= tab->keys;
|
||||||
|
}
|
||||||
|
else if (tab->select->cond)
|
||||||
|
{
|
||||||
|
const COND *pushed_cond= tab->table->file->pushed_cond;
|
||||||
|
|
||||||
|
if (((thd->variables.optimizer_switch &
|
||||||
|
OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) ||
|
||||||
|
(tab->table->file->ha_table_flags() &
|
||||||
|
HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) &&
|
||||||
|
pushed_cond)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION);
|
||||||
|
/*
|
||||||
|
psergey-todo: what to do? This was useful with NDB only.
|
||||||
|
|
||||||
|
if (explain_flags & DESCRIBE_EXTENDED)
|
||||||
|
{
|
||||||
|
extra.append(STRING_WITH_LEN(": "));
|
||||||
|
((COND *)pushed_cond)->print(&extra, QT_ORDINARY);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->push_extra(ET_USING_WHERE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (table_list /* SJM bushes don't have table_list */ &&
|
||||||
|
table_list->schema_table &&
|
||||||
|
table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE)
|
||||||
|
{
|
||||||
|
if (!table_list->table_open_method)
|
||||||
|
qpt->push_extra(ET_SKIP_OPEN_TABLE);
|
||||||
|
else if (table_list->table_open_method == OPEN_FRM_ONLY)
|
||||||
|
qpt->push_extra(ET_OPEN_FRM_ONLY);
|
||||||
|
else
|
||||||
|
qpt->push_extra(ET_OPEN_FULL_TABLE);
|
||||||
|
/* psergey-note: the following has a bug.*/
|
||||||
|
if (table_list->has_db_lookup_value &&
|
||||||
|
table_list->has_table_lookup_value)
|
||||||
|
qpt->push_extra(ET_SCANNED_0_DATABASES);
|
||||||
|
else if (table_list->has_db_lookup_value ||
|
||||||
|
table_list->has_table_lookup_value)
|
||||||
|
qpt->push_extra(ET_SCANNED_1_DATABASE);
|
||||||
|
else
|
||||||
|
qpt->push_extra(ET_SCANNED_ALL_DATABASES);
|
||||||
|
}
|
||||||
|
if (key_read)
|
||||||
|
{
|
||||||
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
|
||||||
|
{
|
||||||
|
QUICK_GROUP_MIN_MAX_SELECT *qgs=
|
||||||
|
(QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick;
|
||||||
|
qpt->push_extra(ET_USING_INDEX_FOR_GROUP_BY);
|
||||||
|
qgs->append_loose_scan_type(&qpt->loose_scan_type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
qpt->push_extra(ET_USING_INDEX);
|
||||||
|
}
|
||||||
|
if (table->reginfo.not_exists_optimize)
|
||||||
|
qpt->push_extra(ET_NOT_EXISTS);
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE &&
|
||||||
|
!(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags &
|
||||||
|
HA_MRR_USE_DEFAULT_IMPL))
|
||||||
|
{
|
||||||
|
extra.append(STRING_WITH_LEN("; Using MRR"));
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_USING_MRR);
|
||||||
|
explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick),
|
||||||
|
&qpt->mrr_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_tmp_table)
|
||||||
|
{
|
||||||
|
need_tmp_table=0;
|
||||||
|
qp_sel->using_temporary= true;
|
||||||
|
///extra.append(STRING_WITH_LEN("; Using temporary"));
|
||||||
|
}
|
||||||
|
if (need_order)
|
||||||
|
{
|
||||||
|
need_order=0;
|
||||||
|
qp_sel->using_filesort= true;
|
||||||
|
///extra.append(STRING_WITH_LEN("; Using filesort"));
|
||||||
|
}
|
||||||
|
if (distinct & test_all_bits(used_tables,
|
||||||
|
join->select_list_used_tables))
|
||||||
|
qpt->push_extra(ET_DISTINCT);
|
||||||
|
if (tab->loosescan_match_tab)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_LOOSESCAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab->first_weedout_table)
|
||||||
|
qpt->push_extra(ET_START_TEMPORARY);
|
||||||
|
if (tab->check_weed_out_table)
|
||||||
|
qpt->push_extra(ET_END_TEMPORARY);
|
||||||
|
else if (tab->do_firstmatch)
|
||||||
|
{
|
||||||
|
if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1)
|
||||||
|
qpt->push_extra(ET_FIRST_MATCH);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_FIRST_MATCH);
|
||||||
|
//TABLE *prev_table=tab->do_firstmatch->table;
|
||||||
|
/*
|
||||||
|
TODO: qpt->firstmatch_table...
|
||||||
|
This must be a reference to another QPF element. Or, its index.
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
extra.append(STRING_WITH_LEN("; FirstMatch("));
|
||||||
|
if (prev_table->derived_select_number)
|
||||||
|
{
|
||||||
|
char namebuf[NAME_LEN];
|
||||||
|
/* Derived table name generation */
|
||||||
|
int len= my_snprintf(namebuf, sizeof(namebuf)-1,
|
||||||
|
"<derived%u>",
|
||||||
|
prev_table->derived_select_number);
|
||||||
|
extra.append(namebuf, len);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
extra.append(prev_table->pos_in_table_list->alias);
|
||||||
|
extra.append(STRING_WITH_LEN(")"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint part= 0; part < tab->ref.key_parts; part++)
|
||||||
|
{
|
||||||
|
if (tab->ref.cond_guards[part])
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tab->cache)
|
||||||
|
{
|
||||||
|
qpt->push_extra(ET_USING_JOIN_BUFFER);
|
||||||
|
tab->cache->print_explain_comment(&qpt->join_buffer_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (saved_join_tab)
|
||||||
|
tab= saved_join_tab;
|
||||||
|
|
||||||
|
// For next iteration
|
||||||
|
used_tables|=table->map;
|
||||||
|
}
|
||||||
|
output->add_node(qp_sel);
|
||||||
|
}
|
||||||
|
DBUG_RETURN(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -22888,10 +23479,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
|
|||||||
THD *thd=join->thd;
|
THD *thd=join->thd;
|
||||||
select_result *result=join->result;
|
select_result *result=join->result;
|
||||||
DBUG_ENTER("select_describe");
|
DBUG_ENTER("select_describe");
|
||||||
|
#if 0
|
||||||
join->error= join->print_explain(result, thd->lex->describe,
|
join->error= join->print_explain(result, thd->lex->describe,
|
||||||
FALSE, /* Not on-the-fly */
|
FALSE, /* Not on-the-fly */
|
||||||
need_tmp_table, need_order, distinct,
|
need_tmp_table, need_order, distinct,
|
||||||
message);
|
message);
|
||||||
|
#endif
|
||||||
|
//psergey-todo: save QPF here, too.
|
||||||
|
join->save_qpf(thd->lex->query_plan_footprint, need_tmp_table, need_order,
|
||||||
|
distinct, message);
|
||||||
|
|
||||||
for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
|
for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
|
||||||
unit;
|
unit;
|
||||||
|
@ -198,6 +198,12 @@ int rr_sequential(READ_RECORD *info);
|
|||||||
int rr_sequential_and_unpack(READ_RECORD *info);
|
int rr_sequential_and_unpack(READ_RECORD *info);
|
||||||
|
|
||||||
|
|
||||||
|
#include "opt_qpf.h"
|
||||||
|
|
||||||
|
/**************************************************************************************
|
||||||
|
* New EXPLAIN structures END
|
||||||
|
*************************************************************************************/
|
||||||
|
|
||||||
class JOIN_CACHE;
|
class JOIN_CACHE;
|
||||||
class SJ_TMP_TABLE;
|
class SJ_TMP_TABLE;
|
||||||
class JOIN_TAB_RANGE;
|
class JOIN_TAB_RANGE;
|
||||||
@ -252,7 +258,9 @@ typedef struct st_join_table {
|
|||||||
JOIN_TAB_RANGE *bush_children;
|
JOIN_TAB_RANGE *bush_children;
|
||||||
|
|
||||||
/* Special content for EXPLAIN 'Extra' column or NULL if none */
|
/* Special content for EXPLAIN 'Extra' column or NULL if none */
|
||||||
const char *info;
|
enum Extra_tag info;
|
||||||
|
//const char *info;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra'
|
Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra'
|
||||||
column, or 0 if there is no info.
|
column, or 0 if there is no info.
|
||||||
@ -1454,11 +1462,14 @@ public:
|
|||||||
{
|
{
|
||||||
return (unit->item && unit->item->is_in_predicate());
|
return (unit->item && unit->item->is_in_predicate());
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
int print_explain(select_result_sink *result, uint8 explain_flags,
|
int print_explain(select_result_sink *result, uint8 explain_flags,
|
||||||
bool on_the_fly,
|
bool on_the_fly,
|
||||||
bool need_tmp_table, bool need_order,
|
bool need_tmp_table, bool need_order,
|
||||||
bool distinct,const char *message);
|
bool distinct,const char *message);
|
||||||
|
*/
|
||||||
|
int save_qpf(QPF_query *output, bool need_tmp_table, bool need_order,
|
||||||
|
bool distinct, const char *message);
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
TRUE if the query contains an aggregate function but has no GROUP
|
TRUE if the query contains an aggregate function but has no GROUP
|
||||||
|
@ -6,7 +6,7 @@ filter='\.cc$\|\.c$\|\.h$\|\.yy$'
|
|||||||
list="find . -type f"
|
list="find . -type f"
|
||||||
bzr root >/dev/null 2>/dev/null && list="bzr ls --from-root -R --kind=file --versioned"
|
bzr root >/dev/null 2>/dev/null && list="bzr ls --from-root -R --kind=file --versioned"
|
||||||
|
|
||||||
$list |grep $filter |while read f;
|
$list |grep $filter | grep -v gen-cpp |while read f;
|
||||||
do
|
do
|
||||||
etags -o TAGS --append $f
|
etags -o TAGS --append $f
|
||||||
done
|
done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user