1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00
Files
mariadb/sql/sql_explain.cc
Dave Gosselin 4561ccad91 MDEV-40620: Allow a nested join as a FULL JOIN operand
Phase 2 rejected two shapes.  A FULL JOIN whose right operand was a
nested join raised ER_FULL_JOIN_BASE_TABLES_ONLY, and a FULL JOIN on the
inner side of an enclosing LEFT or RIGHT JOIN raised
ER_FULL_JOIN_NOT_ALLOWED_IN_OUTER_JOIN.  Both shapes are allowed now and
neither error is raised any more.

A FULL JOIN emits the rows of one side that never matched with the other
side null complemented.  An enclosing join's condition has to see those
rows complete, which it cannot do while the operand is only a set of
tables spread through the join order.  An operand that is a nested join
is therefore computed into a temporary table before the enclosing join
runs, so one JOIN_TAB stands for the whole operand and carries the FULL
JOIN's own marks.

The chosen plan has to keep such an operand on an unbroken span of the
join order, and that span becomes a run of JOIN_TABs of its own, the way
a materialized semi join already is.  Nothing outside the operand is
positioned while the operand is computed, so a table inside it can carry
no condition that names an outside table, an equality class that spans
the boundary is split at it, and the outer join scope of a join outside
it stops there.  A nest goes on naming a table that table elimination
removed, and an eliminated table is a const table, so the test for
entering the run leaves the eliminated tables out.  The test for
entering a materialized semi join run reads the same way.

The operand keeps a copy of its own ON expression, since simplifying the
join tree merges an inner join's ON into the enclosing outer join's ON,
which is not equivalent for a FULL JOIN because it changes which rows of
the right side never match.  An operand can itself be built from more
than one join, so an ON expression can sit a level or two below the top
of it, and every such expression is recorded on the operand as a whole,
which is the nest that reads them back.  A predicate over the operand is
checked again at the entry standing for it, once the null complementing
has happened.  A FULL JOIN's left side WHERE predicates are deferred to
the table that completes its right side even when that side is a nest.
The enclosing ON contributes no access path for the operand.

Reading a row back restores what a nest table cannot hold on its own,
whether the row was null complemented for a column declared NOT NULL,
and the status that says whether the table holds a usable row at all.

The JOIN_TAB standing for a run covers every table of the nest, the
eliminated ones included.  The plan output therefore skips only a
JOIN_TAB that is a table of the query, since a JOIN_TAB standing for a
run is never eliminated.

The FULL JOIN code moves out of sql_select.cc, sql_base.cc and table.cc
into opt_full_join.cc and opt_full_join.h.  JOIN::full_joins holds a
Full_join_info, declared in opt_full_join_info.h, for each FULL JOIN
that simplify_joins did not rewrite.  It holds the two sides, the WHERE
conjuncts held back for the FULL JOIN, the tab that reads its right
side, the duplicate filter of the right rows that matched, and the next
FULL JOIN whose null complement pass runs from the same tab.  It
replaces fj_dups, fj_first_target and fj_next_target on JOIN_TAB and
fj_left_cond on TABLE_LIST.  The list is built for each JOIN, so a
prepared statement starts every execution with nothing held.

opt_join_runs.cc and opt_join_runs.h hold the runs.
get_best_combination records every run of the chosen join order in a
Join_run_layout before it writes any JOIN_TAB, and the layout gives the
exact number of top level JOIN_TABs.  make_join_select walks the plan
with Join_tab_walk, which keeps the tables a run sets aside on entry
until the walk reaches the run's placeholder.  A FULL JOIN run can be
inside a materialized semi join, so the walk keeps the runs on a stack.

make_join_select decides again which conditions wait for a FULL JOIN's
right partner, on each condition about to be attached to a table.
Distributing the WHERE builds conditions for one table out of
disjunctions that span both sides, and a HAVING predicate moved into
the WHERE can name the left side alone, so the choice made while the
WHERE is rewritten misses both.  That earlier choice stays, since it
keeps the left side out of ref and range analysis.  Whether a condition
reads a table after the partner is decided from the join order
positions of the tables it reads.

An enclosing join's ON supplies no key value for a table on a FULL
JOIN's left side, because the null complement pass is complete only
once every left row has been read.  A key value that reads only tables
of the same left side is still allowed.  An ORDER BY over a FULL JOIN
that simplify_joins kept sorts through a temporary table, because the
null complement pass emits right rows after the scan of the first
table.  EXPLAIN gathers the tables of each run at its own depth, and an
operand nest inside a materialized semi join reports under that semi
join's id.

Sections 22 to 28 cover the new shapes.  The section covering more than
one matching right side row becomes Section 28, which adds a nest operand
to the same case, and the two sections that came with the fixes before
this one are renumbered to follow it.  Sections 35 to 42 cover an ON
expression below the top of an operand, an enclosing ON that keeps the
left side NULLs, operand nests that table elimination shrinks, a
condition offered to a table inside a run, a prepared WHERE that folds
away on a later execution, a FULL JOIN run inside a materialized semi
join, and a left side condition over a table before the partner.
Sections 8, 18 and 27 gain left side predicates that appear only once
the WHERE is rewritten.  The helper that gives every column an index of
its own comes with them, ahead of the axis that turns it on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 08:02:24 -04:00

3202 lines
88 KiB
C++

/*
Copyright (c) 2013 Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
#include "mariadb.h"
#include "sql_priv.h"
#include "sql_select.h"
#include "my_json_writer.h"
#include "opt_range.h"
#include "sql_expression_cache.h"
#include "item_subselect.h"
#include <stack>
const char * STR_DELETING_ALL_ROWS= "Deleting all rows";
const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE";
const char * STR_NO_ROWS_AFTER_PRUNING= "No matching rows after partition pruning";
const char *unit_operation_text[4]=
{
"UNIT RESULT","UNION RESULT","INTERSECT RESULT","EXCEPT RESULT"
};
const char *pushed_unit_operation_text[4]=
{
"PUSHED UNIT", "PUSHED UNION", "PUSHED INTERSECT", "PUSHED EXCEPT"
};
const char *pushed_derived_text= "PUSHED DERIVED";
const char *pushed_select_text= "PUSHED SELECT";
/* See enum ha_parititon::partition_index_scan_method */
const char *partitions_index_scan_method_str[]=
{NULL, "merge_ordered_scans", "iterate_over_partitions", "both"};
static void write_item(Json_writer *writer, Item *item);
static void append_item_to_str(String *out, Item *item);
Explain_query::Explain_query(THD *thd_arg, MEM_ROOT *root) :
mem_root(root), upd_del_plan(nullptr), insert_plan(nullptr),
unions(root), selects(root), stmt_thd(thd_arg), apc_enabled(false),
operations(0)
{
optimization_time_tracker.start_tracking(stmt_thd);
}
static void print_json_array(Json_writer *writer,
const char *title, String_list &list)
{
List_iterator_fast<char> it(list);
const char *name;
writer->add_member(title).start_array();
while ((name= it++))
writer->add_str(name);
writer->end_array();
}
Explain_query::~Explain_query()
{
if (apc_enabled)
stmt_thd->apc_target.disable();
delete upd_del_plan;
delete insert_plan;
uint i;
for (i= 0 ; i < unions.elements(); i++)
delete unions.at(i);
for (i= 0 ; i < selects.elements(); i++)
delete selects.at(i);
}
Explain_node *Explain_query::get_node(uint select_id)
{
Explain_union *u;
if ((u= get_union(select_id)))
return u;
else
return get_select(select_id);
}
Explain_union *Explain_query::get_union(uint select_id)
{
return (unions.elements() > select_id) ? unions.at(select_id) : NULL;
}
Explain_select *Explain_query::get_select(uint select_id)
{
return (selects.elements() > select_id) ? selects.at(select_id) : NULL;
}
void Explain_query::add_node(Explain_node *node)
{
uint select_id;
operations++;
if (node->get_type() == Explain_node::EXPLAIN_UNION)
{
Explain_union *u= (Explain_union*)node;
select_id= u->get_select_id();
if (unions.elements() <= select_id)
unions.resize(MY_MAX(select_id+1, unions.elements()*2), NULL);
Explain_union *old_node;
if ((old_node= get_union(select_id)))
delete old_node;
unions.at(select_id)= u;
}
else
{
Explain_select *sel= (Explain_select*)node;
if (sel->select_id == FAKE_SELECT_LEX_ID)
{
DBUG_ASSERT(0); // this is a "fake select" from a UNION.
}
else
{
select_id= sel->select_id;
Explain_select *old_node;
if (selects.elements() <= select_id)
selects.resize(MY_MAX(select_id+1, selects.elements()*2), NULL);
if ((old_node= get_select(select_id)))
delete old_node;
selects.at(select_id)= sel;
}
}
}
void Explain_query::add_insert_plan(Explain_insert *insert_plan_arg)
{
insert_plan= insert_plan_arg;
query_plan_ready();
}
void Explain_query::add_upd_del_plan(Explain_update *upd_del_plan_arg)
{
upd_del_plan= upd_del_plan_arg;
query_plan_ready();
}
void Explain_query::query_plan_ready()
{
optimization_time_tracker.stop_tracking(stmt_thd);
if (!apc_enabled)
stmt_thd->apc_target.enable();
apc_enabled= true;
#ifndef DBUG_OFF
can_print_json= true;
#endif
}
bool Explain_query::is_query_plan_ready()
{
return optimization_time_tracker.get_loops() == 1;
}
void Explain_query::notify_tables_are_closed()
{
/*
Disable processing of SHOW EXPLAIN|ANALYZE. The query is about to close
the tables it is using, which will make it impossible to print Item*
values. See sql_explain.h:ExplainDataStructureLifetime for details.
*/
if (apc_enabled)
{
stmt_thd->apc_target.disable();
apc_enabled= false;
#ifndef DBUG_OFF
can_print_json= false;
#endif
}
}
/*
Send EXPLAIN output to the client.
*/
int Explain_query::send_explain(THD *thd, bool extended)
{
select_result *result;
LEX *lex= thd->lex;
if (!(result= new (thd->mem_root) select_send(thd)) ||
thd->send_explain_fields(result, lex->describe, lex->analyze_stmt))
return 1;
int res= 0;
if (thd->lex->explain_json)
print_explain_json(result, thd->lex->analyze_stmt);
else
{
res= print_explain(result, lex->describe, thd->lex->analyze_stmt);
if (extended)
{
char buff[1024];
String str(buff, (uint32) sizeof(buff), &my_charset_utf8mb4_bin);
str.length(0);
/*
The warnings system requires input in utf8, @see
mysqld_show_warnings().
*/
lex->unit.print(&str, QT_EXPLAIN_EXTENDED);
push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_YES,
str.c_ptr_safe());
}
}
if (res)
result->abort_result_set();
else
result->send_eof();
return res;
}
/*
The main entry point to print EXPLAIN of the entire query
*/
int Explain_query::print_explain(select_result_sink *output,
uint8 explain_flags, bool is_analyze)
{
/* A sanity check for ANALYZE: */
DBUG_ASSERT(timer_tracker_frequency() != 0.0);
if (upd_del_plan)
{
upd_del_plan->print_explain(this, output, explain_flags, is_analyze);
return 0;
}
else if (insert_plan)
{
insert_plan->print_explain(this, output, explain_flags, is_analyze);
return 0;
}
else
{
/* Start printing from node with id=1 */
Explain_node *node= get_node(1);
if (!node)
return 1; /* No query plan */
return node->print_explain(this, output, explain_flags, is_analyze);
}
}
int Explain_query::print_explain_json(select_result_sink *output,
bool is_analyze,
ulonglong query_time_in_progress_ms)
{
Json_writer writer;
#ifndef DBUG_OFF
DBUG_ASSERT(can_print_json);
#endif
writer.start_object();
if (is_analyze)
{
if (query_time_in_progress_ms > 0){
writer.add_member("r_query_time_in_progress_ms").
add_ull(query_time_in_progress_ms);
}
print_query_optimization_json(&writer);
}
bool plan_found = print_query_blocks_json(&writer, is_analyze);
writer.end_object();
if( plan_found )
{
send_explain_json_to_output(&writer, output);
}
return 0;
}
void Explain_query::print_query_optimization_json(Json_writer *writer)
{
if (optimization_time_tracker.has_timed_statistics())
{
// if more timers are added, move the query_optimization member
// outside the if statement
writer->add_member("query_optimization").start_object();
writer->add_member("r_total_time_ms").
add_double(optimization_time_tracker.get_time_ms());
writer->end_object();
}
}
bool Explain_query::print_query_blocks_json(Json_writer *writer, const bool is_analyze)
{
if (upd_del_plan)
upd_del_plan->print_explain_json(this, writer, is_analyze);
else if (insert_plan)
insert_plan->print_explain_json(this, writer, is_analyze);
else
{
/* Start printing from root node with id=1 */
Explain_node *node= get_node(1);
if (!node)
return false; /* No query plan */
node->print_explain_json(this, writer, is_analyze);
}
return true;
}
void Explain_query::send_explain_json_to_output(Json_writer *writer,
select_result_sink *output)
{
CHARSET_INFO *cs= &my_charset_utf8mb4_bin;
List<Item> item_list;
const String *buf= writer->output.get_string();
THD *thd= output->thd;
item_list.push_back(new (thd->mem_root)
Item_string(thd, buf->ptr(), buf->length(), cs),
thd->mem_root);
output->send_data(item_list);
}
bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str)
{
return lex->explain->print_explain_str(thd, str, /*is_analyze*/ true);
}
/*
Return tabular EXPLAIN output as a text string
*/
bool Explain_query::print_explain_str(THD *thd, String *out_str,
bool is_analyze)
{
List<Item> fields;
thd->make_explain_field_list(fields, thd->lex->describe, is_analyze);
select_result_text_buffer output_buf(thd);
output_buf.send_result_set_metadata(fields, thd->lex->describe);
if (print_explain(&output_buf, thd->lex->describe, is_analyze))
return true;
output_buf.save_to(out_str);
return false;
}
static void push_str(THD *thd, List<Item> *item_list, const char *str)
{
item_list->push_back(new (thd->mem_root) Item_string_sys(thd, str),
thd->mem_root);
}
static void push_string(THD *thd, List<Item> *item_list, String *str)
{
item_list->push_back(new (thd->mem_root)
Item_string_sys(thd, str->ptr(), str->length()),
thd->mem_root);
}
static void push_string_list(THD *thd, List<Item> *item_list,
String_list &lines, String *buf)
{
List_iterator_fast<char> it(lines);
char *line;
bool first= true;
while ((line= it++))
{
if (first)
first= false;
else
buf->append(',');
buf->append(line, strlen(line));
}
push_string(thd, item_list, buf);
}
/*
Print an EXPLAIN output row, based on information provided in the parameters
@note
Parameters that may have NULL value in EXPLAIN output, should be passed
(char*)NULL.
@return
0 - OK
1 - OOM Error
*/
static
int print_explain_row(select_result_sink *result,
uint8 options, bool is_analyze,
uint select_number,
const char *select_type,
const char *table_name,
const char *partitions,
enum join_type jtype,
String_list *possible_keys,
const char *index,
const char *key_len,
const char *ref,
ha_rows *rows,
double *r_rows,
double r_filtered,
const char *extra)
{
THD *thd= result->thd;
MEM_ROOT *mem_root= thd->mem_root;
Item *item_null= new (mem_root) Item_null(thd);
List<Item> item_list;
Item *item;
if (!select_type[0])
return 0;
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_number),
mem_root);
item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
mem_root);
item_list.push_back(new (mem_root) Item_string_sys(thd, table_name),
mem_root);
if (options & DESCRIBE_PARTITIONS)
{
if (partitions)
{
item_list.push_back(new (mem_root) Item_string_sys(thd, partitions),
mem_root);
}
else
item_list.push_back(item_null, mem_root);
}
const char *jtype_str= join_type_str[jtype];
item_list.push_back(new (mem_root) Item_string_sys(thd, jtype_str),
mem_root);
/* 'possible_keys'
The buffer must not be deallocated before we call send_data, otherwise
we may end up reading freed memory.
*/
StringBuffer<64> possible_keys_buf;
if (possible_keys && !possible_keys->is_empty())
{
push_string_list(thd, &item_list, *possible_keys, &possible_keys_buf);
}
else
item_list.push_back(item_null, mem_root);
/* 'index */
item= index ? new (mem_root) Item_string_sys(thd, index) : item_null;
item_list.push_back(item, mem_root);
/* 'key_len */
item= key_len ? new (mem_root) Item_string_sys(thd, key_len) : item_null;
item_list.push_back(item, mem_root);
/* 'ref' */
item= ref ? new (mem_root) Item_string_sys(thd, ref) : item_null;
item_list.push_back(item, mem_root);
/* 'rows' */
StringBuffer<64> rows_str;
if (rows)
{
rows_str.append_ulonglong((ulonglong)(*rows));
item_list.push_back(new (mem_root)
Item_string_sys(thd, rows_str.ptr(),
rows_str.length()), mem_root);
}
else
item_list.push_back(item_null, mem_root);
/* 'r_rows' */
StringBuffer<64> r_rows_str;
if (is_analyze)
{
if (r_rows)
{
Item_float *fl= new (mem_root) Item_float(thd, *r_rows, 2);
String tmp;
String *res= fl->val_str(&tmp);
r_rows_str.append(*res);
item_list.push_back(new (mem_root)
Item_string_sys(thd, r_rows_str.ptr(),
r_rows_str.length()), mem_root);
}
else
item_list.push_back(item_null, mem_root);
}
/* 'filtered' */
const double filtered=100.0;
if (options & DESCRIBE_EXTENDED || is_analyze)
item_list.push_back(new (mem_root) Item_float(thd, filtered, 2), mem_root);
/* 'r_filtered' */
if (is_analyze)
item_list.push_back(new (mem_root) Item_float(thd, r_filtered, 2),
mem_root);
/* 'Extra' */
if (extra)
item_list.push_back(new (mem_root) Item_string_sys(thd, extra), mem_root);
else
item_list.push_back(item_null, mem_root);
if (result->send_data(item_list))
return 1;
return 0;
}
uint Explain_union::make_union_table_name(char *buf)
{
uint childno= 0;
uint len, lastop= 0;
LEX_CSTRING type;
switch (operation)
{
case OP_MIX:
type= { STRING_WITH_LEN("<unit") };
break;
case OP_UNION:
type= { STRING_WITH_LEN("<union") };
break;
case OP_INTERSECT:
type= { STRING_WITH_LEN("<intersect") };
break;
case OP_EXCEPT:
type= { STRING_WITH_LEN("<except") };
break;
default:
DBUG_ASSERT(0);
type= { NULL, 0 };
}
memcpy(buf, type.str, (len= (uint)type.length));
for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
childno++)
{
len+= lastop;
lastop= (uint)my_snprintf(buf + len, NAME_LEN - len,
"%u,", union_members.at(childno));
}
if (childno < union_members.elements() || len + lastop >= NAME_LEN)
{
memcpy(buf + len, STRING_WITH_LEN("...>") + 1);
len+= 4;
}
else
{
len+= lastop;
buf[len - 1]= '>'; // change ',' to '>'
}
return len;
}
int Explain_union::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags, bool is_analyze)
{
if (is_pushed_down_to_engine)
return print_explain_pushed_down(output, explain_flags, is_analyze);
else
return print_explain_regular(query, output, explain_flags, is_analyze);
}
/*
Prints EXPLAIN plan for a regular UNIT (UNION/EXCEPT/INTERSECT),
i.e. UNIT that has not been pushed down to a storage engine
*/
int Explain_union::print_explain_regular(Explain_query *query,
select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
THD *thd= output->thd;
MEM_ROOT *mem_root= thd->mem_root;
char table_name_buffer[SAFE_NAME_LEN];
/* print all UNION children, in order */
for (int i= 0; i < (int) union_members.elements(); i++)
{
Explain_select *sel= query->get_select(union_members.at(i));
sel->print_explain(query, output, explain_flags, is_analyze);
}
if (!using_tmp)
{
/*
The union operation may not employ a temporary table, for example,
for UNION ALL, in that case the results of the query are sent directly
to the output. So there is no actual UNION operation and we don't need
to print the line in the EXPLAIN output.
*/
return 0;
}
/* Print a line with "UNIT RESULT" */
List<Item> item_list;
Item *item_null= new (mem_root) Item_null(thd);
/* `id` column */
item_list.push_back(item_null, mem_root);
/* `select_type` column */
push_str(thd, &item_list, fake_select_type);
/* `table` column: something like "<union1,2>" */
uint len= make_union_table_name(table_name_buffer);
item_list.push_back(new (mem_root)
Item_string_sys(thd, table_name_buffer, len),
mem_root);
/* `partitions` column */
if (explain_flags & DESCRIBE_PARTITIONS)
item_list.push_back(item_null, mem_root);
/* `type` column */
push_str(thd, &item_list, join_type_str[JT_ALL]);
/* `possible_keys` column */
item_list.push_back(item_null, mem_root);
/* `key` */
item_list.push_back(item_null, mem_root);
/* `key_len` */
item_list.push_back(item_null, mem_root);
/* `ref` */
item_list.push_back(item_null, mem_root);
/* `rows` */
item_list.push_back(item_null, mem_root);
/* `r_rows` */
StringBuffer<64> r_rows_str;
if (is_analyze)
{
double avg_rows= fake_select_lex_tracker.get_avg_rows();
Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
String tmp;
String *res= fl->val_str(&tmp);
r_rows_str.append(*res);
item_list.push_back(new (mem_root)
Item_string_sys(thd, r_rows_str.ptr(),
r_rows_str.length()), mem_root);
}
/* `filtered` */
if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
item_list.push_back(item_null, mem_root);
/* `r_filtered` */
if (is_analyze)
item_list.push_back(item_null, mem_root);
/* `Extra` */
StringBuffer<256> extra_buf;
if (using_filesort)
{
extra_buf.append(STRING_WITH_LEN("Using filesort"));
}
item_list.push_back(new (mem_root)
Item_string_sys(thd, extra_buf.ptr(),
extra_buf.length()),
mem_root);
if (output->send_data(item_list))
return 1;
/*
Print all subquery children (UNION children have already been printed at
the start of this function)
*/
return print_explain_for_children(query, output, explain_flags, is_analyze);
}
/*
Prints EXPLAIN plan for a UNIT (UNION/EXCEPT/INTERSECT) that
has been pushed down to a storage engine
*/
int Explain_union::print_explain_pushed_down(select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
THD *thd= output->thd;
MEM_ROOT *mem_root= thd->mem_root;
List<Item> item_list;
Item *item_null= new (mem_root) Item_null(thd);
/* `id` column */
item_list.push_back(item_null, mem_root);
/* `select_type` column */
push_str(thd, &item_list, fake_select_type);
/* `table` column */
item_list.push_back(item_null, mem_root);
/* `partitions` column */
if (explain_flags & DESCRIBE_PARTITIONS)
item_list.push_back(item_null, mem_root);
/* `type` column */
item_list.push_back(item_null, mem_root);
/* `possible_keys` column */
item_list.push_back(item_null, mem_root);
/* `key` */
item_list.push_back(item_null, mem_root);
/* `key_len` */
item_list.push_back(item_null, mem_root);
/* `ref` */
item_list.push_back(item_null, mem_root);
/* `rows` */
item_list.push_back(item_null, mem_root);
/* `r_rows` */
if (is_analyze)
item_list.push_back(item_null, mem_root);
/* `filtered` */
if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
item_list.push_back(item_null, mem_root);
/* `r_filtered` */
if (is_analyze)
item_list.push_back(item_null, mem_root);
/* `Extra` */
item_list.push_back(item_null, mem_root);
if (output->send_data(item_list))
return 1;
return 0;
}
void Explain_union::print_explain_json(Explain_query *query,
Json_writer *writer, bool is_analyze)
{
if (is_pushed_down_to_engine)
print_explain_json_pushed_down(query, writer, is_analyze);
else
print_explain_json_regular(query, writer, is_analyze);
}
/*
Prints EXPLAIN plan in JSON format for a regular UNIT (UNION/EXCEPT/INTERSECT),
i.e. UNIT that has not been pushed down to a storage engine
*/
void Explain_union::print_explain_json_regular(
Explain_query *query, Json_writer *writer, bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
char table_name_buffer[SAFE_NAME_LEN];
bool started_object= print_explain_json_cache(writer, is_analyze);
writer->add_member("query_block").start_object();
if (is_recursive_cte)
writer->add_member("recursive_union").start_object();
else
writer->add_member("union_result").start_object();
if (using_tmp)
{
make_union_table_name(table_name_buffer);
writer->add_member("table_name").add_str(table_name_buffer);
writer->add_member("access_type").add_str("ALL"); // not very useful
/* r_loops (not present in tabular output) */
if (is_analyze)
{
writer->add_member("r_loops").add_ll(
fake_select_lex_tracker.get_loops());
}
/* `r_rows` */
if (is_analyze)
{
writer->add_member("r_rows");
if (fake_select_lex_tracker.has_scans())
writer->add_double(fake_select_lex_tracker.get_avg_rows());
else
writer->add_null();
}
}
writer->add_member("query_specifications").start_array();
for (int i= 0; i < (int) union_members.elements(); i++)
{
writer->start_object();
//writer->add_member("dependent").add_str("TODO");
//writer->add_member("cacheable").add_str("TODO");
Explain_select *sel= query->get_select(union_members.at(i));
sel->print_explain_json(query, writer, is_analyze);
writer->end_object();
}
writer->end_array();
print_explain_json_for_children(query, writer, is_analyze);
writer->end_object(); // union_result
writer->end_object(); // query_block
if (started_object)
writer->end_object();
}
/*
Prints EXPLAIN plan in JSON format for a UNIT (UNION/EXCEPT/INTERSECT) that
has been pushed down to a storage engine
*/
void Explain_union::print_explain_json_pushed_down(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
writer->add_member("query_block").start_object();
if (is_recursive_cte)
writer->add_member("recursive_union").start_object();
else
writer->add_member("union_result").start_object();
writer->add_member("message").add_str(fake_select_type);
writer->end_object(); // union_result
writer->end_object(); // query_block
}
/*
Print EXPLAINs for all children nodes (i.e. for subqueries)
*/
int Explain_node::print_explain_for_children(Explain_query *query,
select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
for (int i= 0; i < (int) children.elements(); i++)
{
Explain_node *node= query->get_node(children.at(i));
/*
Note: node may not be present because for certain kinds of subqueries,
the optimizer is not able to see that they were eliminated.
*/
if (node && node->print_explain(query, output, explain_flags, is_analyze))
return 1;
}
return 0;
}
bool Explain_basic_join::add_table(Explain_table_access *tab, Explain_query *query)
{
if (!join_tabs)
{
n_join_tabs= 0;
if (!(join_tabs= ((Explain_table_access**)
alloc_root(query->mem_root,
sizeof(Explain_table_access*) *
MAX_TABLES))))
return true;
}
join_tabs[n_join_tabs++]= tab;
return false;
}
/*
This tells whether a child subquery should be printed in JSON output.
Derived tables and Non-merged semi-joins should not be printed, because they
are printed inline in Explain_table_access.
*/
bool is_connection_printable_in_json(enum Explain_node::explain_connection_type type)
{
return (type != Explain_node::EXPLAIN_NODE_DERIVED &&
type != Explain_node::EXPLAIN_NODE_NON_MERGED_SJ);
}
void Explain_node::print_explain_json_for_children(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
bool started= false;
for (int i= 0; i < (int) children.elements(); i++)
{
Explain_node *node= query->get_node(children.at(i));
/*
Note: node may not be present because for certain kinds of subqueries,
the optimizer is not able to see that they were eliminated.
*/
if (!node)
continue;
/* Derived tables are printed inside Explain_table_access objects */
if (!is_connection_printable_in_json(node->connection_type))
continue;
if (!started)
{
writer->add_member("subqueries").start_array();
started= true;
}
writer->start_object();
node->print_explain_json(query, writer, is_analyze);
writer->end_object();
}
if (started)
writer->end_array();
}
bool Explain_node::print_explain_json_cache(Json_writer *writer,
bool is_analyze)
{
if (cache_tracker)
{
cache_tracker->fetch_current_stats();
writer->add_member("subquery_cache").start_object();
if (cache_tracker->state != Expression_cache_tracker::OK)
{
writer->add_member("state").
add_str(Expression_cache_tracker::state_str[cache_tracker->state]);
}
if (is_analyze)
{
longlong cache_reads= cache_tracker->hit + cache_tracker->miss;
writer->add_member("r_loops").add_ll(cache_reads);
if (cache_reads != 0)
{
double hit_ratio= double(cache_tracker->hit) / cache_reads * 100.0;
writer->add_member("r_hit_ratio").add_double(hit_ratio);
}
}
return true;
}
return false;
}
bool Explain_node::print_explain_json_subq_materialization(Json_writer *writer,
bool is_analyze)
{
if (subq_materialization)
{
subq_materialization->print_explain_json(writer, is_analyze);
return true;
}
return false;
}
Explain_basic_join::~Explain_basic_join()
{
if (join_tabs)
{
for (uint i= 0; i< n_join_tabs; i++)
delete join_tabs[i];
}
}
int Explain_select::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags, bool is_analyze)
{
THD *thd= output->thd;
MEM_ROOT *mem_root= thd->mem_root;
if (select_type == pushed_derived_text || select_type == pushed_select_text)
{
print_explain_message_line(output, explain_flags, is_analyze,
select_id /*select number*/,
select_type,
NULL, /* rows */
NULL);
}
else if (message)
{
List<Item> item_list;
Item *item_null= new (mem_root) Item_null(thd);
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
mem_root);
item_list.push_back(new (mem_root) Item_string_sys(thd, select_type),
mem_root);
for (uint i=0 ; i < 7; i++)
item_list.push_back(item_null, mem_root);
if (explain_flags & DESCRIBE_PARTITIONS)
item_list.push_back(item_null, mem_root);
/* filtered */
if (is_analyze || explain_flags & DESCRIBE_EXTENDED)
item_list.push_back(item_null, mem_root);
if (is_analyze)
{
/* r_rows, r_filtered */
item_list.push_back(item_null, mem_root);
item_list.push_back(item_null, mem_root);
}
item_list.push_back(new (mem_root) Item_string_sys(thd, message),
mem_root);
if (output->send_data(item_list))
return 1;
}
else
{
bool using_tmp= false;
bool using_fs= false;
for (Explain_aggr_node *node= aggr_tree; node; node= node->child)
{
switch (node->get_type())
{
case AGGR_OP_TEMP_TABLE:
using_tmp= true;
break;
case AGGR_OP_FILESORT:
using_fs= true;
break;
default:
break;
}
}
for (uint i=0; i< n_join_tabs; i++)
{
join_tabs[i]->print_explain(output, explain_flags, is_analyze, 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;
}
}
for (uint i=0; i< n_join_tabs; i++)
{
Explain_basic_join* nest;
if ((nest= join_tabs[i]->sjm_nest))
nest->print_explain(query, output, explain_flags, is_analyze);
}
}
return print_explain_for_children(query, output, explain_flags, is_analyze);
}
int Explain_basic_join::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags, bool is_analyze)
{
for (uint i=0; i< n_join_tabs; i++)
{
if (join_tabs[i]->print_explain(output, explain_flags, is_analyze,
select_id,
"MATERIALIZED" /*select_type*/,
FALSE /*using temporary*/,
FALSE /*using filesort*/))
return 1;
}
/*
A run held by one of these tables is reported after them, the way a
run held by a table of the join's own plan is reported after that
plan. A run can hold a further run, so this reaches every depth.
*/
for (uint i=0; i< n_join_tabs; i++)
{
Explain_basic_join *nest= join_tabs[i]->sjm_nest;
if (nest && nest->print_explain(query, output, explain_flags, is_analyze))
return 1;
}
return 0;
}
void Explain_select::add_linkage(Json_writer *writer)
{
const char *operation= NULL;
switch (linkage)
{
case UNION_TYPE:
operation= "UNION";
break;
case INTERSECT_TYPE:
operation= "INTERSECT";
break;
case EXCEPT_TYPE:
operation= "EXCEPT";
break;
default:
// It is the first or the only SELECT => no operation
break;
}
if (operation)
writer->add_member("operation").add_str(operation);
}
void Explain_select::print_explain_json(Explain_query *query,
Json_writer *writer, bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
bool started_cache= print_explain_json_cache(writer, is_analyze);
bool started_subq_mat= print_explain_json_subq_materialization(writer,
is_analyze);
if (message ||
select_type == pushed_derived_text ||
select_type == pushed_select_text)
{
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(select_id);
add_linkage(writer);
writer->add_member("table").start_object();
writer->add_member("message").add_str(select_type == pushed_derived_text ?
"Pushed derived" :
select_type == pushed_select_text ?
"Pushed select" :
message);
writer->end_object();
print_explain_json_for_children(query, writer, is_analyze);
writer->end_object();
}
else
{
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(select_id);
add_linkage(writer);
if (cost != 0.0)
writer->add_member("cost").add_double(cost);
if (is_analyze && time_tracker.get_loops())
{
writer->add_member("r_loops").add_ll(time_tracker.get_loops());
if (time_tracker.has_timed_statistics())
{
writer->add_member("r_total_time_ms").
add_double(time_tracker.get_time_ms());
}
}
if (exec_const_cond)
{
writer->add_member("const_condition");
write_item(writer, exec_const_cond);
}
if (outer_ref_cond)
{
writer->add_member("outer_ref_condition");
write_item(writer, outer_ref_cond);
}
if (pseudo_bits_cond)
{
writer->add_member("pseudo_bits_condition");
write_item(writer, pseudo_bits_cond);
}
/* we do not print HAVING which always evaluates to TRUE */
if (having || (having_value == Item::COND_FALSE))
{
writer->add_member("having_condition");
if (likely(having))
write_item(writer, having);
else
{
/* Normally we should not go this branch, left just for safety */
DBUG_ASSERT(having_value == Item::COND_FALSE);
writer->add_str("0");
}
}
int started_objects= 0;
Explain_aggr_node *node= aggr_tree;
for (; node; node= node->child)
{
switch (node->get_type())
{
case AGGR_OP_TEMP_TABLE:
writer->add_member("temporary_table").start_object();
break;
case AGGR_OP_FILESORT:
{
writer->add_member("filesort").start_object();
auto aggr_node= (Explain_aggr_filesort*)node;
aggr_node->print_json_members(writer, is_analyze);
break;
}
case AGGR_OP_REMOVE_DUPLICATES:
writer->add_member("duplicate_removal").start_object();
break;
case AGGR_OP_WINDOW_FUNCS:
{
//TODO: make print_json_members virtual?
writer->add_member("window_functions_computation").start_object();
auto aggr_node= (Explain_aggr_window_funcs*)node;
aggr_node->print_json_members(writer, is_analyze);
break;
}
default:
DBUG_ASSERT(0);
}
started_objects++;
}
Explain_basic_join::print_explain_json_interns(query, writer, is_analyze);
for (;started_objects; started_objects--)
writer->end_object();
writer->end_object();
}
if (started_subq_mat)
writer->end_object();
if (started_cache)
writer->end_object();
}
Explain_aggr_filesort::Explain_aggr_filesort(MEM_ROOT *mem_root,
bool is_analyze,
Filesort *filesort)
: tracker(is_analyze)
{
child= NULL;
for (ORDER *ord= filesort->order; ord; ord= ord->next)
{
sort_items.push_back(ord->item[0], mem_root);
sort_directions.push_back(&ord->direction, mem_root);
}
filesort->tracker= &tracker;
}
void Explain_aggr_filesort::print_json_members(Json_writer *writer,
bool is_analyze)
{
char item_buf[256];
String str(item_buf, sizeof(item_buf), &my_charset_utf8mb4_bin);
str.length(0);
List_iterator_fast<Item> it(sort_items);
List_iterator_fast<ORDER::enum_order> it_dir(sort_directions);
Item* item;
ORDER::enum_order *direction;
bool first= true;
while ((item= it++))
{
direction= it_dir++;
if (first)
first= false;
else
{
str.append(STRING_WITH_LEN(", "));
}
append_item_to_str(&str, item);
if (*direction == ORDER::ORDER_DESC)
str.append(STRING_WITH_LEN(" desc"));
}
writer->add_member("sort_key").add_str(str.c_ptr_safe());
if (is_analyze)
tracker.print_json_members(writer);
}
void Explain_aggr_window_funcs::print_json_members(Json_writer *writer,
bool is_analyze)
{
Explain_aggr_filesort *srt;
List_iterator<Explain_aggr_filesort> it(sorts);
Json_writer_array sorts(writer, "sorts");
while ((srt= it++))
{
Json_writer_object sort(writer);
Json_writer_object filesort(writer, "filesort");
srt->print_json_members(writer, is_analyze);
}
}
void Explain_basic_join::print_explain_json(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(select_id);
print_explain_json_interns(query, writer, is_analyze);
writer->end_object();
}
void Explain_basic_join::
print_explain_json_interns(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
{
Json_writer_array loop(writer, "nested_loop");
for (uint i=0; i< n_join_tabs; i++)
{
if (join_tabs[i]->start_dups_weedout)
{
writer->start_object();
writer->add_member("duplicates_removal");
writer->start_array();
}
join_tabs[i]->print_explain_json(query, writer, is_analyze);
if (join_tabs[i]->end_dups_weedout)
{
writer->end_array();
writer->end_object();
}
}
} // "nested_loop"
print_explain_json_for_children(query, writer, is_analyze);
}
void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
{
extra_tags.append(extra_tag);
}
/*
Put the contents of 'key' field of EXPLAIN output into key_str.
It is surprisingly complex:
- hash join shows #hash#used_key
- quick selects that use single index will print index name
*/
void Explain_table_access::fill_key_str(String *key_str, bool is_json) const
{
CHARSET_INFO *cs= &my_charset_utf8mb4_bin;
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
LEX_CSTRING hash_key_prefix= { STRING_WITH_LEN("#hash#") };
const char *key_name;
if ((key_name= key.get_key_name()))
{
if (is_hj)
key_str->append(hash_key_prefix.str, hash_key_prefix.length, cs);
key_str->append(key_name, strlen(key_name));
if (is_hj && type != JT_HASH)
key_str->append(':');
}
if (quick_info)
{
StringBuffer<64> buf2;
if (is_json)
quick_info->print_extra_recursive(&buf2);
else
quick_info->print_key(&buf2);
key_str->append(buf2);
}
if (type == JT_HASH_NEXT)
{
key_name= hash_next_key.get_key_name();
key_str->append(key_name, strlen(key_name));
}
}
/*
Fill "key_length".
- this is just used key length for ref/range
- for index_merge, it is a comma-separated list of lengths.
- for hash join, it is key_len:pseudo_key_len
- [tabular form only] rowid filter length is added after "|".
In JSON, we consider this column to be legacy, it is superseded by
used_key_parts.
*/
void Explain_table_access::fill_key_len_str(String *key_len_str,
bool is_json) const
{
bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
if (key.get_key_len() != (uint)-1)
{
char buf[64];
size_t length;
length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
key_len_str->append(buf, length);
if (is_hj && type != JT_HASH)
key_len_str->append(':');
}
if (quick_info)
{
StringBuffer<64> buf2;
quick_info->print_key_len(&buf2);
key_len_str->append(buf2);
}
if (type == JT_HASH_NEXT)
{
char buf[64];
size_t length;
length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf;
key_len_str->append(buf, length);
}
if (!is_json && rowid_filter)
{
key_len_str->append('|');
StringBuffer<64> filter_key_len;
rowid_filter->quick->print_key_len(&filter_key_len);
key_len_str->append(filter_key_len);
}
}
bool Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg)
{
if (set_pseudo_key(mem_root, key->name.str))
return 1;
key_len= key_len_arg;
uint len= 0;
for (uint i= 0; i < key->usable_key_parts; i++)
{
if (!key_parts_list.append_str(mem_root,
key->key_part[i].field->field_name.str))
return 1;
len += key->key_part[i].store_length;
if (len >= key_len_arg)
break;
}
return 0;
}
bool Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg)
{
if (key_name_arg)
{
if (!(key_name= strdup_root(root, key_name_arg)))
return 1;
}
else
key_name= NULL;
key_len= ~(uint) 0;
return 0;
}
/*
Given r_filtered% from join buffer condition and join condition, produce a
combined r_filtered% number. This is needed for tabular EXPLAIN output which
has only one cell for r_filtered value.
*/
double Explain_table_access::get_r_filtered()
{
double r_filtered= tracker.get_filtered_after_where();
if (bka_type.is_using_jbuf())
r_filtered *= jbuf_tracker.get_filtered_after_where();
return r_filtered;
}
int Explain_table_access::print_explain(select_result_sink *output,
uint8 explain_flags,
bool is_analyze,
uint select_id, const char *select_type,
bool using_temporary,
bool using_filesort)
{
THD *thd= output->thd; // note: for SHOW EXPLAIN, this is target thd.
MEM_ROOT *mem_root= thd->mem_root;
List<Item> item_list;
Item *item_null= new (mem_root) Item_null(thd);
/* `id` column */
item_list.push_back(new (mem_root) Item_int(thd, (int32) select_id),
mem_root);
/* `select_type` column */
push_str(thd, &item_list, select_type);
/* `table` column */
push_string(thd, &item_list, &table_name);
/* `partitions` column */
if (explain_flags & DESCRIBE_PARTITIONS)
{
if (used_partitions_set)
{
push_string(thd, &item_list, &used_partitions);
}
else
item_list.push_back(item_null, mem_root);
}
/* `type` column */
StringBuffer<64> join_type_buf;
if (rowid_filter == NULL)
push_str(thd, &item_list, join_type_str[type]);
else
{
join_type_buf.append(join_type_str[type], strlen(join_type_str[type]));
join_type_buf.append(STRING_WITH_LEN("|filter"));
item_list.push_back(new (mem_root)
Item_string_sys(thd, join_type_buf.ptr(),
join_type_buf.length()),
mem_root);
}
/* `possible_keys` column */
StringBuffer<64> possible_keys_buf;
if (possible_keys.is_empty())
item_list.push_back(item_null, mem_root);
else
push_string_list(thd, &item_list, possible_keys, &possible_keys_buf);
/* `key` */
StringBuffer<64> key_str;
fill_key_str(&key_str, false);
if (rowid_filter)
{
key_str.append('|');
StringBuffer<64> rowid_key_str;
rowid_filter->quick->print_key(&rowid_key_str);
key_str.append(rowid_key_str);
}
if (key_str.length() > 0)
push_string(thd, &item_list, &key_str);
else
item_list.push_back(item_null, mem_root);
/* `key_len` */
StringBuffer<64> key_len_str;
fill_key_len_str(&key_len_str, false);
if (key_len_str.length() > 0)
push_string(thd, &item_list, &key_len_str);
else
item_list.push_back(item_null, mem_root);
/* `ref` */
StringBuffer<64> ref_list_buf;
if (ref_list.is_empty())
{
if (type == JT_FT)
{
/* Traditionally, EXPLAIN lines with type=fulltext have ref='' */
push_str(thd, &item_list, "");
}
else
item_list.push_back(item_null, mem_root);
}
else
push_string_list(thd, &item_list, ref_list, &ref_list_buf);
/* `rows` */
StringBuffer<64> rows_str;
if (rows_set)
{
rows_str.append_ulonglong((ulonglong)rows);
if (rowid_filter)
{
rows_str.append(STRING_WITH_LEN(" ("));
rows_str.append_ulonglong((ulonglong) (round(rowid_filter->selectivity *
100.0)));
rows_str.append(STRING_WITH_LEN("%)"));
}
item_list.push_back(new (mem_root)
Item_string_sys(thd, rows_str.ptr(),
rows_str.length()), mem_root);
}
else
item_list.push_back(item_null, mem_root);
/* `r_rows` */
StringBuffer<64> r_rows_str;
if (is_analyze)
{
if (!tracker.has_scans())
{
item_list.push_back(item_null, mem_root);
}
else
{
double avg_rows= tracker.get_avg_rows();
Item_float *fl= new (mem_root) Item_float(thd, avg_rows, 2);
String tmp;
String *res= fl->val_str(&tmp);
r_rows_str.append(*res);
if (rowid_filter)
{
r_rows_str.append(STRING_WITH_LEN(" ("));
r_rows_str.append_ulonglong(
(ulonglong) (rowid_filter->tracker->get_r_selectivity_pct() * 100.0));
r_rows_str.append(STRING_WITH_LEN("%)"));
}
item_list.push_back(new (mem_root)
Item_string_sys(thd, r_rows_str.ptr(),
r_rows_str.length()), mem_root);
}
}
/* `filtered` */
if (explain_flags & DESCRIBE_EXTENDED || is_analyze)
{
if (filtered_set)
{
item_list.push_back(new (mem_root) Item_float(thd, filtered, 2),
mem_root);
}
else
item_list.push_back(item_null, mem_root);
}
/* `r_filtered` */
if (is_analyze)
{
if (!tracker.has_scans())
{
item_list.push_back(item_null, mem_root);
}
else
{
double r_filtered= tracker.get_filtered_after_where();
if (bka_type.is_using_jbuf())
r_filtered *= jbuf_tracker.get_filtered_after_where();
item_list.push_back(new (mem_root)
Item_float(thd, r_filtered * 100.0, 2),
mem_root);
}
}
/* `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 || this->pre_join_sort)
{
if (first)
first= false;
else
extra_buf.append(STRING_WITH_LEN("; "));
extra_buf.append(STRING_WITH_LEN("Using filesort"));
}
if (rowid_filter)
{
if (first)
first= false;
else
extra_buf.append(STRING_WITH_LEN("; "));
extra_buf.append(STRING_WITH_LEN("Using rowid filter"));
}
item_list.push_back(new (mem_root)
Item_string_sys(thd, extra_buf.ptr(),
extra_buf.length()),
mem_root);
if (output->send_data(item_list))
return 1;
return 0;
}
/**
Adds copy of the string to the list
@param mem_root where to allocate string
@param str string to copy and add
@return
NULL - out of memory error
pointer on allocated copy of the string
*/
const char *String_list::append_str(MEM_ROOT *mem_root, const char *str)
{
size_t len= strlen(str);
char *cp;
if (!(cp = (char*)alloc_root(mem_root, len+1)))
return NULL;
memcpy(cp, str, len+1);
push_back(cp, mem_root);
return cp;
}
static void write_item(Json_writer *writer, Item *item)
{
THD *thd= current_thd;
char item_buf[256];
String str(item_buf, sizeof(item_buf), &my_charset_utf8mb4_bin);
str.length(0);
ulonglong save_option_bits= thd->variables.option_bits;
thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
item->print(&str, QT_EXPLAIN);
thd->variables.option_bits= save_option_bits;
writer->add_str(str.c_ptr_safe(), str.length());
}
static void append_item_to_str(String *out, Item *item)
{
THD *thd= current_thd;
ulonglong save_option_bits= thd->variables.option_bits;
thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE;
item->print(out, QT_EXPLAIN);
thd->variables.option_bits= save_option_bits;
}
void Explain_table_access::tag_to_json(Json_writer *writer,
enum explain_extra_tag tag)
{
switch (tag)
{
case ET_OPEN_FULL_TABLE:
writer->add_member("open_full_table").add_bool(true);
break;
case ET_SCANNED_0_DATABASES:
writer->add_member("scanned_databases").add_ll(0);
break;
case ET_SCANNED_1_DATABASE:
writer->add_member("scanned_databases").add_ll(1);
break;
case ET_SCANNED_ALL_DATABASES:
writer->add_member("scanned_databases").add_str("all");
break;
case ET_SKIP_OPEN_TABLE:
writer->add_member("skip_open_table").add_bool(true);
break;
case ET_OPEN_FRM_ONLY:
writer->add_member("open_frm_only").add_bool(true);
break;
case ET_USING_INDEX_CONDITION:
case ET_USING_INDEX_CONDITION_BKA:
case ET_USING_WHERE:
/* Conditions are printed outside of this function */
break;
case ET_USING_INDEX:
writer->add_member("using_index").add_bool(true);
break;
case ET_USING:
// index merge: case ET_USING
break;
case ET_RANGE_CHECKED_FOR_EACH_RECORD:
/* Handled as range_checked_fer */
case ET_USING_JOIN_BUFFER:
/* Do nothing. Join buffer is handled differently */
case ET_START_TEMPORARY:
case ET_END_TEMPORARY:
/* Handled as "duplicates_removal: { ... } */
case ET_FULL_SCAN_ON_NULL_KEY:
/* Handled in full_scan_on_null_key */
break;
case ET_FIRST_MATCH:
writer->add_member("first_match").add_str(firstmatch_table_name);
break;
case ET_LOOSESCAN:
writer->add_member("loose_scan").add_bool(true);
break;
case ET_USING_MRR:
writer->add_member("mrr_type").add_str(mrr_type);
break;
case ET_USING_INDEX_FOR_GROUP_BY:
writer->add_member("using_index_for_group_by");
if (loose_scan_is_scanning)
writer->add_str("scanning");
else
writer->add_bool(true);
break;
/*new:*/
case ET_CONST_ROW_NOT_FOUND:
writer->add_member("const_row_not_found").add_bool(true);
break;
case ET_UNIQUE_ROW_NOT_FOUND:
/*
Currently, we never get here. All SELECTs that have
ET_UNIQUE_ROW_NOT_FOUND for a table are converted into degenerate
SELECTs with message="Impossible WHERE ...".
MySQL 5.6 has the same property.
I'm leaving the handling in just for the sake of covering all enum
members and safety.
*/
writer->add_member("unique_row_not_found").add_bool(true);
break;
case ET_IMPOSSIBLE_ON_CONDITION:
writer->add_member("impossible_on_condition").add_bool(true);
break;
case ET_USING_WHERE_WITH_PUSHED_CONDITION:
/*
It would be nice to print the pushed condition, but current Storage
Engine API doesn't provide any way to do that
*/
writer->add_member("pushed_condition").add_bool(true);
break;
case ET_NOT_EXISTS:
writer->add_member("not_exists").add_bool(true);
break;
case ET_DISTINCT:
writer->add_member("distinct").add_bool(true);
break;
case ET_TABLE_FUNCTION:
writer->add_member("table_function").add_str("json_table");
break;
default:
DBUG_ASSERT(0);
}
}
static
void add_json_keyset(Json_writer *writer, const char *elem_name,
String_list *keyset)
{
if (!keyset->is_empty())
print_json_array(writer, elem_name, *keyset);
}
void Explain_rowid_filter::print_explain_json(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
writer->add_member("rowid_filter").start_object();
quick->print_json(writer);
writer->add_member("rows").add_ll(rows);
writer->add_member("selectivity_pct").add_double(selectivity * 100.0);
if (is_analyze)
{
writer->add_member("r_rows").add_double(tracker->get_container_elements());
writer->add_member("r_lookups").add_ll(tracker->get_container_lookups());
writer->add_member("r_selectivity_pct").
add_double(tracker->get_r_selectivity_pct() * 100.0);
writer->add_member("r_buffer_size").
add_double((double) (tracker->get_container_buff_size()));
writer->add_member("r_filling_time_ms").
add_double(tracker->get_time_fill_container_ms());
}
writer->end_object(); // rowid_filter
}
static void trace_engine_stats(handler *file, Json_writer *writer)
{
if (file && file->handler_stats)
{
ha_handler_stats *hs= file->handler_stats;
writer->add_member("r_engine_stats").start_object();
if (hs->pages_accessed)
writer->add_member("pages_accessed").add_ull(hs->pages_accessed);
if (hs->pages_updated)
writer->add_member("pages_updated").add_ull(hs->pages_updated);
if (hs->pages_read_count)
writer->add_member("pages_read_count").add_ull(hs->pages_read_count);
if (hs->pages_read_time)
writer->add_member("pages_read_time_ms").
add_double(hs->pages_read_time * 1000. / timer_tracker_frequency());
if (hs->pages_prefetched)
writer->add_member("pages_prefetch_read_count").add_ull(hs->pages_prefetched);
if (hs->undo_records_read)
writer->add_member("old_rows_read").add_ull(hs->undo_records_read);
if (unlikely(hs->ahi_searches | hs->ahi_searches_btree |
hs->ahi_rows_added | hs->ahi_pages_added))
{
writer->add_member("r_ahi_stats").start_object();
if (hs->ahi_searches)
writer->add_member("ahi_searches").add_ull(hs->ahi_searches);
if (hs->ahi_searches_btree)
writer->add_member("ahi_searches_btree").add_ull(hs->ahi_searches_btree);
if (hs->ahi_rows_added)
writer->add_member("ahi_rows_added").add_ull(hs->ahi_rows_added);
if (hs->ahi_pages_added)
writer->add_member("ahi_pages_added").add_ull(hs->ahi_pages_added);
writer->end_object();
}
writer->end_object();
}
}
static void print_r_icp_filtered(handler *file, Json_writer *writer)
{
if (!file || !file->handler_stats || !file->pushed_idx_cond)
return;
ha_handler_stats *hs= file->handler_stats;
double r_icp_filtered = hs->icp_attempts ?
(double)(hs->icp_match) / (double)(hs->icp_attempts) : 0.0;
writer->add_member("r_icp_filtered").add_double(r_icp_filtered * 100);
}
void Explain_table_access::print_explain_json(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_object jsobj(writer);
if (pre_join_sort)
{
/* filesort was invoked on this join tab before doing the join with the rest */
writer->add_member("read_sorted_file").start_object();
if (is_analyze)
{
writer->add_member("r_rows");
/*
r_rows when reading filesort result. This can be less than the number
of rows produced by filesort due to NL-join having LIMIT.
*/
if (tracker.has_scans())
writer->add_double(tracker.get_avg_rows());
else
writer->add_null();
/*
r_filtered when reading filesort result. We should have checked the
WHERE while doing filesort but lets check just in case.
*/
if (tracker.has_scans() && tracker.get_filtered_after_where() < 1.0)
{
writer->add_member("r_filtered");
writer->add_double(tracker.get_filtered_after_where()*100.0);
}
}
writer->add_member("filesort").start_object();
pre_join_sort->print_json_members(writer, is_analyze);
}
if (bka_type.is_using_jbuf())
{
writer->add_member("block-nl-join").start_object();
}
if (range_checked_fer)
{
range_checked_fer->print_json(writer, is_analyze);
}
if (full_scan_on_null_key)
writer->add_member("full-scan-on-null_key").start_object();
writer->add_member("table").start_object();
writer->add_member("table_name").add_str(table_name);
if (used_partitions_set)
{
print_json_array(writer, "partitions", used_partitions_list);
if (handler_for_stats)
{
assert(handler_for_stats->partition_engine());
const char* pi_scan_str= partitions_index_scan_method_str[
handler_for_stats->partition_index_scan_method()];
if (pi_scan_str)
writer->add_member("partitions_index_scan_method")
.add_str(pi_scan_str);
}
}
writer->add_member("access_type").add_str(join_type_str[type]);
add_json_keyset(writer, "possible_keys", &possible_keys);
/* `key` */
/* For non-basic quick select, 'key' will not be present */
if (!quick_info || quick_info->is_basic())
{
StringBuffer<64> key_str;
fill_key_str(&key_str, true);
if (key_str.length())
writer->add_member("key").add_str(key_str);
}
/* `key_length` */
StringBuffer<64> key_len_str;
fill_key_len_str(&key_len_str, true);
if (key_len_str.length())
writer->add_member("key_length").add_str(key_len_str);
/* `used_key_parts` */
String_list *parts_list= NULL;
if (quick_info && quick_info->is_basic())
parts_list= &quick_info->range.key_parts_list;
else
parts_list= &key.key_parts_list;
if (parts_list && !parts_list->is_empty())
print_json_array(writer, "used_key_parts", *parts_list);
if (quick_info && !quick_info->is_basic())
{
writer->add_member("index_merge").start_object();
quick_info->print_json(writer);
writer->end_object();
}
/* `ref` */
if (!ref_list.is_empty())
print_json_array(writer, "ref", ref_list);
if (rowid_filter)
{
rowid_filter->print_explain_json(query, writer, is_analyze);
}
if (loops != 0.0)
writer->add_member("loops").add_double(loops);
/* r_loops (not present in tabular output) */
if (is_analyze)
{
ha_rows loops= tracker.get_loops();
writer->add_member("r_loops").add_ll(loops);
if (type == JT_EQ_REF) // max one row
{
ha_rows table_loops= op_tracker.get_loops();
if (table_loops != loops)
writer->add_member("r_table_loops").add_ll(table_loops);
}
}
/* `rows` */
if (rows_set)
writer->add_member("rows").add_ull(rows);
double r_index_rows; /* protected by have_icp_or_rowid_filter */
bool r_index_rows_is_zero; /* also protected by have_icp_or_rowid_filter */
bool have_icp_or_rowid_filter= false;
/* `r_index_rows` and `r_rows` */
if (is_analyze)
{
/*
r_index_rows is the number of rows enumerated in the index before
any kind of checking. The number is the average across all scans.
*/
double loops;
if (tracker.get_loops())
loops = rows2double(tracker.get_loops());
else
loops= 1.0;
handler *file= handler_for_stats;
if (file && file->handler_stats && file->pushed_idx_cond)
{
/*
Pushed Index Condition is checked before checking the Rowid Filter,
so try getting it first.
*/
ulonglong val= file->handler_stats->icp_attempts;
r_index_rows_is_zero= (val == 0);
r_index_rows= val / loops;
have_icp_or_rowid_filter= true;
}
else if (rowid_filter)
{
/* If ICP wasn't used, get the number from Rowid Filter */
uint val= rowid_filter->tracker->get_container_lookups();
r_index_rows_is_zero= (val == 0);
r_index_rows= val / loops;
have_icp_or_rowid_filter= true;
}
/* Print r_index_rows only if ICP and/or Rowid Filter were used */
if (have_icp_or_rowid_filter)
writer->add_member("r_index_rows").add_double(r_index_rows);
writer->add_member("r_rows");
if (pre_join_sort)
{
/* Get r_rows value from filesort */
if (pre_join_sort->tracker.get_r_loops())
writer->add_double(pre_join_sort->tracker.get_avg_examined_rows());
else
writer->add_null();
}
else
{
if (tracker.has_scans())
writer->add_double(tracker.get_avg_rows());
else
writer->add_null();
}
}
if (cost != 0.0)
writer->add_member("cost").add_double(cost);
if (is_analyze)
{
if (op_tracker.get_loops())
{
double total_time= op_tracker.get_time_ms();
if (rowid_filter)
total_time+= rowid_filter->tracker->get_time_fill_container_ms();
writer->add_member("r_table_time_ms").add_double(total_time);
writer->add_member("r_other_time_ms").add_double(extra_time_tracker.get_time_ms());
}
trace_engine_stats(handler_for_stats, writer);
}
/* `filtered` */
if (filtered_set)
writer->add_member("filtered").add_double(filtered);
bool have_r_filtered= false;
double r_filtered;
/* Compute value of `r_filtered` - filtered of attached_condition */
if (is_analyze)
{
if (pre_join_sort)
{
/* Get r_filtered value from filesort */
if (pre_join_sort->tracker.get_r_loops())
{
have_r_filtered= true;
r_filtered= pre_join_sort->tracker.get_r_filtered()*100;
}
}
else
{
/* Get r_filtered from the NL-join runtime */
if (tracker.has_scans())
{
have_r_filtered= true;
r_filtered= tracker.get_filtered_after_where()*100.0;
}
}
/*
Add r_total_filtered, as combined "filtered" of all kinds of filtering:
Rowid Filter, Index Condition Pushdown, attached condition.
*/
double r_total_filtered;
if (have_icp_or_rowid_filter)
{
double out_rows;
if (pre_join_sort)
out_rows= pre_join_sort->tracker.get_avg_returned_rows();
else
out_rows= tracker.get_avg_rows_after_where();
if (r_index_rows_is_zero)
r_total_filtered= 100.0;
else
r_total_filtered= out_rows* 100.0 / r_index_rows;
}
else if (have_r_filtered)
r_total_filtered= r_filtered;
writer->add_member("r_total_filtered");
if (have_r_filtered)
writer->add_double(r_total_filtered);
else
writer->add_null();
}
/*
`index_condition[_bka]`
*/
if (pushed_index_cond)
{
writer->add_member(bka_type.is_bka? "index_condition_bka": "index_condition");
write_item(writer, pushed_index_cond);
if (is_analyze)
print_r_icp_filtered(handler_for_stats, writer);
}
/* `attached_condition` */
{
/*
we are printing the condition that is checked when scanning this
table.
- when join buffer is used, it is cache_cond.
- in other cases, it is where_cond.
*/
Item *item= bka_type.is_using_jbuf()? cache_cond: where_cond;
if (item)
{
writer->add_member("attached_condition");
write_item(writer, item);
}
}
/* `r_filtered` - filtered of attached_condition */
if (is_analyze)
{
writer->add_member("r_filtered");
if (have_r_filtered)
writer->add_double(r_filtered);
else
writer->add_null();
}
for (int i=0; i < (int)extra_tags.elements(); i++)
{
tag_to_json(writer, extra_tags.at(i));
}
if (full_scan_on_null_key)
writer->end_object(); //"full-scan-on-null_key"
if (range_checked_fer)
writer->end_object(); // "range-checked-for-each-record"
if (bka_type.is_using_jbuf())
{
writer->end_object(); // "block-nl-join"
writer->add_member("buffer_type").add_str(bka_type.incremental?
"incremental":"flat");
writer->add_member("buffer_size").add_size(bka_type.join_buffer_size);
writer->add_member("join_type").add_str(bka_type.join_alg);
if (bka_type.mrr_type.length())
writer->add_member("mrr_type").add_str(bka_type.mrr_type);
if (where_cond)
{
writer->add_member("attached_condition");
write_item(writer, where_cond);
}
if (is_analyze)
{
writer->add_member("r_loops").add_ll(jbuf_loops_tracker.get_loops());
writer->add_member("r_filtered");
if (jbuf_tracker.has_scans())
writer->add_double(jbuf_tracker.get_filtered_after_where()*100.0);
else
writer->add_null();
writer->add_member("r_unpack_time_ms");
writer->add_double(jbuf_unpack_tracker.get_time_ms());
DBUG_EXECUTE_IF("analyze_print_r_unpack_ops",
{
writer->add_member("r_unpack_ops");
writer->add_ull(jbuf_unpack_tracker.get_loops());
});
writer->add_member("r_other_time_ms").
add_double(jbuf_extra_time_tracker.get_time_ms());
/*
effective_rows is average number of matches we got for an incoming
row. The row is stored in the join buffer and then is read
from there, possibly multiple times. We can't count this number
directly. Infer it as:
total_number_of_row_combinations_considered / r_loops.
*/
writer->add_member("r_effective_rows");
if (jbuf_loops_tracker.has_scans())
{
double loops= (double)jbuf_loops_tracker.get_loops();
double row_combinations= (double)jbuf_tracker.r_rows;
writer->add_double(row_combinations / loops);
}
else
writer->add_null();
}
}
if (derived_select_number)
{
/* This is a derived table. Print its contents here */
writer->add_member("materialized").start_object();
Explain_node *node= query->get_node(derived_select_number);
if (node->get_type() == Explain_node::EXPLAIN_SELECT &&
((Explain_select*)node)->is_lateral)
{
writer->add_member("lateral").add_ll(1);
}
node->print_explain_json(query, writer, is_analyze);
writer->end_object();
}
if (non_merged_sjm_number)
{
/* This is a non-merged semi-join table. Print its contents here */
writer->add_member("materialized").start_object();
writer->add_member("unique").add_ll(1);
Explain_node *node= query->get_node(non_merged_sjm_number);
node->connection_type= Explain_node::EXPLAIN_NODE_NON_MERGED_SJ;
node->print_explain_json(query, writer, is_analyze);
writer->end_object();
}
if (sjm_nest)
{
/* This is a non-merged semi-join table. Print its contents here */
writer->add_member("materialized").start_object();
writer->add_member("unique").add_ll(1);
sjm_nest->print_explain_json(query, writer, is_analyze);
writer->end_object();
}
if (pre_join_sort)
{
writer->end_object(); // filesort
writer->end_object(); // read_sorted_file
}
writer->end_object();
}
/*
Elements in this array match members of enum explain_extra_tag, defined in
sql_explain.h
*/
const LEX_CSTRING extra_tag_text[]=
{
{ STRING_WITH_LEN("ET_none") },
{ STRING_WITH_LEN("Using index condition") },
{ STRING_WITH_LEN("Using index condition(BKA)") },
{ STRING_WITH_LEN("Using ") }, // special handling
{ STRING_WITH_LEN("Range checked for each record (index map: 0x") }, // special handling
{ STRING_WITH_LEN("Using where with pushed condition") },
{ STRING_WITH_LEN("Using where") },
{ STRING_WITH_LEN("Not exists") },
{ STRING_WITH_LEN("Using index") },
{ STRING_WITH_LEN("Full scan on NULL key") },
{ STRING_WITH_LEN("Skip_open_table") },
{ STRING_WITH_LEN("Open_frm_only") },
{ STRING_WITH_LEN("Open_full_table") },
{ STRING_WITH_LEN("Scanned 0 databases") },
{ STRING_WITH_LEN("Scanned 1 database") },
{ STRING_WITH_LEN("Scanned all databases") },
{ STRING_WITH_LEN("Using index for group-by") }, // special handling
{ STRING_WITH_LEN("USING MRR: DONT PRINT ME") }, // special handling
{ STRING_WITH_LEN("Distinct") },
{ STRING_WITH_LEN("LooseScan") },
{ STRING_WITH_LEN("Start temporary") },
{ STRING_WITH_LEN("End temporary") },
{ STRING_WITH_LEN("FirstMatch") }, // special handling
{ STRING_WITH_LEN("Using join buffer") }, // special handling
{ STRING_WITH_LEN("Const row not found") },
{ STRING_WITH_LEN("Unique row not found") },
{ STRING_WITH_LEN("Impossible ON condition") }
};
void Explain_table_access::append_tag_name(String *str, enum explain_extra_tag tag)
{
switch (tag) {
case ET_USING:
{
// quick select
str->append(STRING_WITH_LEN("Using "));
quick_info->print_extra(str);
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"));
range_checked_fer->keys_map.print(buf);
str->append(buf, strlen(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(STRING_WITH_LEN(" ("));
LEX_CSTRING buffer_type;
if (bka_type.incremental)
buffer_type= { STRING_WITH_LEN("incremental") };
else
buffer_type= { STRING_WITH_LEN("flat") };
str->append(buffer_type);
str->append(STRING_WITH_LEN(", "));
str->append(bka_type.join_alg, strlen(bka_type.join_alg));
str->append(STRING_WITH_LEN(" join"));
str->append(')');
if (bka_type.mrr_type.length())
{
str->append(STRING_WITH_LEN("; "));
str->append(bka_type.mrr_type);
}
break;
}
case ET_FIRST_MATCH:
{
if (firstmatch_table_name.length())
{
str->append(STRING_WITH_LEN("FirstMatch("));
str->append(firstmatch_table_name);
str->append(')');
}
else
str->append(extra_tag_text[tag]);
break;
}
case ET_USING_INDEX_FOR_GROUP_BY:
{
str->append(extra_tag_text[tag]);
if (loose_scan_is_scanning)
str->append(STRING_WITH_LEN(" (scanning)"));
break;
case ET_TABLE_FUNCTION:
str->append(STRING_WITH_LEN("Table function: json_table"));
break;
}
default:
str->append(extra_tag_text[tag]);
}
}
/*
This is called for top-level Explain_quick_select only. The point of this
function is:
- index_merge should print $index_merge_type (child, ...)
- 'range' should not print anything.
*/
void Explain_quick_select::print_extra(String *str)
{
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
{
/* print nothing */
}
else
print_extra_recursive(str);
}
void Explain_quick_select::print_json(Json_writer *writer)
{
if (is_basic())
{
writer->add_member("range").start_object();
writer->add_member("key").add_str(range.get_key_name());
print_json_array(writer, "used_key_parts", range.key_parts_list);
writer->end_object();
}
else
{
Json_writer_array ranges(writer, get_name_by_type());
List_iterator_fast<Explain_quick_select> it (children);
Explain_quick_select* child;
while ((child = it++))
{
Json_writer_object obj(writer);
child->print_json(writer);
}
}
}
void Explain_quick_select::print_extra_recursive(String *str)
{
const char *name;
if (is_basic())
{
name= range.get_key_name();
str->append(name, strlen(name));
}
else
{
name= get_name_by_type();
str->append(name, strlen(name));
str->append('(');
List_iterator_fast<Explain_quick_select> it (children);
Explain_quick_select* child;
bool first= true;
while ((child = it++))
{
if (first)
first= false;
else
str->append(',');
child->print_extra_recursive(str);
}
str->append(')');
}
}
const char * Explain_quick_select::get_name_by_type()
{
switch (quick_type) {
case QUICK_SELECT_I::QS_TYPE_INDEX_MERGE:
return "sort_union";
case QUICK_SELECT_I::QS_TYPE_ROR_UNION:
return "union";
case QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT:
return "intersect";
case QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT:
return "sort_intersect";
default:
DBUG_ASSERT(0);
return "unknown quick select type";
}
}
/*
This prints a comma-separated list of used indexes, ignoring nesting
*/
void Explain_quick_select::print_key(String *str)
{
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
{
if (str->length() > 0)
str->append(',');
str->append(range.get_key_name(), strlen(range.get_key_name()));
}
else
{
List_iterator_fast<Explain_quick_select> it (children);
Explain_quick_select* child;
while ((child = it++))
{
child->print_key(str);
}
}
}
/*
This prints a comma-separated list of used key_lengths, ignoring nesting
*/
void Explain_quick_select::print_key_len(String *str)
{
if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE ||
quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC ||
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
{
char buf[64];
size_t length;
length= longlong10_to_str(range.get_key_len(), buf, 10) - buf;
if (str->length() > 0)
str->append(',');
str->append(buf, length);
}
else
{
List_iterator_fast<Explain_quick_select> it (children);
Explain_quick_select* child;
while ((child = it++))
{
child->print_key_len(str);
}
}
}
int Explain_delete::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
if (deleting_all_rows)
{
const char *msg= STR_DELETING_ALL_ROWS;
int res= print_explain_message_line(output, explain_flags, is_analyze,
1 /*select number*/,
select_type, &rows, msg);
return res;
}
else
{
return Explain_update::print_explain(query, output, explain_flags,
is_analyze);
}
}
void Explain_delete::print_explain_json(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
if (deleting_all_rows)
{
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(1);
writer->add_member("table").start_object();
// just like mysql-5.6, we don't print table name. Is this ok?
writer->add_member("message").add_str(STR_DELETING_ALL_ROWS);
writer->end_object(); // table
writer->end_object(); // query_block
return;
}
Explain_update::print_explain_json(query, writer, is_analyze);
}
int Explain_update::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
StringBuffer<64> key_buf;
StringBuffer<64> key_len_buf;
StringBuffer<64> extra_str;
if (impossible_where || no_partitions)
{
const char *msg= impossible_where ?
STR_IMPOSSIBLE_WHERE :
STR_NO_ROWS_AFTER_PRUNING;
int res= print_explain_message_line(output, explain_flags, is_analyze,
1 /*select number*/,
select_type,
NULL, /* rows */
msg);
return res;
}
if (quick_info)
{
quick_info->print_key(&key_buf);
quick_info->print_key_len(&key_len_buf);
StringBuffer<64> quick_buf;
quick_info->print_extra(&quick_buf);
if (quick_buf.length())
{
extra_str.append(STRING_WITH_LEN("Using "));
extra_str.append(quick_buf);
}
}
else if (key.get_key_name())
{
const char *name= key.get_key_name();
key_buf.set(name, strlen(name), &my_charset_utf8mb4_bin);
char buf[64];
size_t length= longlong10_to_str(key.get_key_len(), buf, 10) - buf;
key_len_buf.copy(buf, length, &my_charset_utf8mb4_bin);
}
if (using_where)
{
if (extra_str.length() !=0)
extra_str.append(STRING_WITH_LEN("; "));
extra_str.append(STRING_WITH_LEN("Using where"));
}
if (mrr_type.length() != 0)
{
if (extra_str.length() !=0)
extra_str.append(STRING_WITH_LEN("; "));
extra_str.append(mrr_type);
}
if (is_using_filesort())
{
if (extra_str.length() !=0)
extra_str.append(STRING_WITH_LEN("; "));
extra_str.append(STRING_WITH_LEN("Using filesort"));
}
if (using_io_buffer)
{
if (extra_str.length() !=0)
extra_str.append(STRING_WITH_LEN("; "));
extra_str.append(STRING_WITH_LEN("Using buffer"));
}
/*
Single-table DELETE commands do not do "Using temporary".
"Using index condition" is also not possible (which is an unjustified limitation)
*/
double r_filtered= 100 * tracker.get_filtered_after_where();
double r_rows= tracker.get_avg_rows();
print_explain_row(output, explain_flags, is_analyze,
1, /* id */
select_type,
table_name.c_ptr(),
used_partitions_set? used_partitions.c_ptr() : NULL,
jtype,
&possible_keys,
key_buf.length()? key_buf.c_ptr() : NULL,
key_len_buf.length() ? key_len_buf.c_ptr() : NULL,
NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */
&rows,
tracker.has_scans()? &r_rows : NULL,
r_filtered,
extra_str.c_ptr_safe());
return print_explain_for_children(query, output, explain_flags, is_analyze);
}
void Explain_update::print_explain_json(Explain_query *query,
Json_writer *writer,
bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(1);
/* This is the total time it took to do the UPDATE/DELETE */
if (is_analyze && command_tracker.has_timed_statistics())
{
writer->add_member("r_total_time_ms").
add_double(command_tracker.get_time_ms());
}
if (impossible_where || no_partitions)
{
const char *msg= impossible_where ? STR_IMPOSSIBLE_WHERE :
STR_NO_ROWS_AFTER_PRUNING;
writer->add_member("table").start_object();
writer->add_member("message").add_str(msg);
writer->end_object(); // table
writer->end_object(); // query_block
return;
}
DBUG_ASSERT(!(is_using_filesort() && using_io_buffer));
bool doing_buffering= false;
if (is_using_filesort())
{
writer->add_member("filesort").start_object();
if (is_analyze)
filesort_tracker->print_json_members(writer);
doing_buffering= true;
}
if (using_io_buffer)
{
writer->add_member("buffer").start_object();
doing_buffering= true;
}
/* Produce elements that are common for buffered and un-buffered cases */
writer->add_member("table").start_object();
if (get_type() == EXPLAIN_UPDATE)
writer->add_member("update").add_ll(1);
else
writer->add_member("delete").add_ll(1);
writer->add_member("table_name").add_str(table_name);
if (used_partitions_set)
print_json_array(writer, "partitions", used_partitions_list);
writer->add_member("access_type").add_str(join_type_str[jtype]);
if (!possible_keys.is_empty())
{
List_iterator_fast<char> it(possible_keys);
const char *name;
writer->add_member("possible_keys").start_array();
while ((name= it++))
writer->add_str(name);
writer->end_array();
}
/* `key`, `key_length` */
if (quick_info && quick_info->is_basic())
{
StringBuffer<64> key_buf;
StringBuffer<64> key_len_buf;
quick_info->print_extra_recursive(&key_buf);
quick_info->print_key_len(&key_len_buf);
writer->add_member("key").add_str(key_buf);
writer->add_member("key_length").add_str(key_len_buf);
}
else if (key.get_key_name())
{
writer->add_member("key").add_str(key.get_key_name());
writer->add_member("key_length").add_str(key.get_key_len());
}
/* `used_key_parts` */
String_list *parts_list= NULL;
if (quick_info && quick_info->is_basic())
parts_list= &quick_info->range.key_parts_list;
else
parts_list= &key.key_parts_list;
if (parts_list && !parts_list->is_empty())
{
List_iterator_fast<char> it(*parts_list);
const char *name;
writer->add_member("used_key_parts").start_array();
while ((name= it++))
writer->add_str(name);
writer->end_array();
}
if (quick_info && !quick_info->is_basic())
{
writer->add_member("index_merge").start_object();
quick_info->print_json(writer);
writer->end_object();
}
/* `rows` */
writer->add_member("rows").add_ull(rows);
if (mrr_type.length() != 0)
writer->add_member("mrr_type").add_str(mrr_type.ptr());
double UNINIT_VAR(r_filtered); /* set and used when is_analyze==true */
if (is_analyze)
{
if (doing_buffering)
{
ha_rows r_rows;
if (is_using_filesort())
{
if (filesort_tracker->get_r_loops())
r_rows= (ha_rows) filesort_tracker->get_avg_examined_rows();
else
r_rows= 0;
r_filtered= filesort_tracker->get_r_filtered() * 100.0;
}
else
{
if (buf_tracker.has_scans())
r_rows= (ha_rows) buf_tracker.get_avg_rows();
else
r_rows= 0;
r_filtered= buf_tracker.get_filtered_after_where() * 100.0;
}
writer->add_member("r_rows").add_ull(r_rows);
/* Currently r_total_filtered == r_filtered for DMLs */
writer->add_member("r_total_filtered").add_double(r_filtered);
}
else /* Not doing buffering */
{
writer->add_member("r_rows");
if (tracker.has_scans())
writer->add_double(tracker.get_avg_rows());
else
writer->add_null();
/* There is no 'filtered' estimate in UPDATE/DELETE atm */
r_filtered= tracker.get_filtered_after_where() * 100.0;
/* Currently r_total_filtered == r_filtered for DMLs */
writer->add_member("r_total_filtered").add_double(r_filtered);
}
if (table_tracker.has_timed_statistics())
{
writer->add_member("r_table_time_ms").add_double(table_tracker.get_time_ms());
writer->add_member("r_other_time_ms").add_double(extra_time_tracker.get_time_ms());
}
}
trace_engine_stats(handler_for_stats, writer);
if (where_cond)
{
writer->add_member("attached_condition");
write_item(writer, where_cond);
}
if (is_analyze)
writer->add_member("r_filtered").add_double(r_filtered);
/*** The part of plan that is before the buffering/sorting ends here ***/
if (is_using_filesort())
writer->end_object();
if (using_io_buffer)
writer->end_object();
writer->end_object(); // table
print_explain_json_for_children(query, writer, is_analyze);
writer->end_object(); // query_block
}
int Explain_insert::print_explain(Explain_query *query,
select_result_sink *output,
uint8 explain_flags,
bool is_analyze)
{
const char *select_type="INSERT";
print_explain_row(output, explain_flags, is_analyze,
1, /* id */
select_type,
table_name.c_ptr(),
NULL, // partitions
JT_ALL,
NULL, // possible_keys
NULL, // key
NULL, // key_len
NULL, // ref
NULL, // rows
NULL, // r_rows
100.0, // r_filtered
NULL);
return print_explain_for_children(query, output, explain_flags, is_analyze);
}
void Explain_insert::print_explain_json(Explain_query *query,
Json_writer *writer, bool is_analyze)
{
Json_writer_nesting_guard guard(writer);
writer->add_member("query_block").start_object();
writer->add_member("select_id").add_ll(1);
writer->add_member("table").start_object();
writer->add_member("table_name").add_str(table_name);
writer->end_object(); // table
print_explain_json_for_children(query, writer, is_analyze);
writer->end_object(); // query_block
}
void delete_explain_query(LEX *lex)
{
DBUG_ENTER("delete_explain_query");
delete lex->explain;
lex->explain= NULL;
DBUG_VOID_RETURN;
}
void create_explain_query(LEX *lex, MEM_ROOT *mem_root)
{
DBUG_ASSERT(!lex->explain);
DBUG_ENTER("create_explain_query");
lex->explain= new (mem_root) Explain_query(lex->thd, mem_root);
DBUG_ASSERT(mem_root == current_thd->mem_root);
DBUG_VOID_RETURN;
}
void create_explain_query_if_not_exists(LEX *lex, MEM_ROOT *mem_root)
{
if (!lex->explain)
create_explain_query(lex, mem_root);
}
/**
Build arrays for collecting keys statistics, add possible key names
to the list and name array
@param alloc MEM_ROOT to put data in
@param list list of possible key names to fill
@param table table of the keys
@param possible_keys possible keys map
@retval 0 - OK
@retval 1 - Error
*/
int Explain_range_checked_fer::append_possible_keys_stat(MEM_ROOT *alloc,
TABLE *table,
key_map possible_keys)
{
uint j;
multi_alloc_root(alloc, &keys_stat, sizeof(ha_rows) * table->s->keys,
&keys_stat_names, sizeof(char *) * table->s->keys, NULL);
if ((!keys_stat) || (!keys_stat_names))
{
keys_stat= NULL;
keys_stat_names= NULL;
return 1;
}
keys_map= possible_keys;
keys= table->s->keys;
bzero(keys_stat, sizeof(ha_rows) * table->s->keys);
for (j= 0; j < table->s->keys; j++)
{
if (possible_keys.is_set(j))
{
if (!(keys_stat_names[j]= key_set.append_str(alloc,
table->key_info[j].name.str)))
return 1;
}
else
keys_stat_names[j]= NULL;
}
return 0;
}
void Explain_range_checked_fer::collect_data(QUICK_SELECT_I *quick)
{
if (quick)
{
if (quick->index == MAX_KEY)
index_merge++;
else
{
DBUG_ASSERT(quick->index < keys);
DBUG_ASSERT(keys_stat);
DBUG_ASSERT(keys_stat_names);
DBUG_ASSERT(keys_stat_names[ quick->index]);
keys_stat[quick->index]++;
}
}
else
full_scan++;
}
void Explain_range_checked_fer::print_json(Json_writer *writer,
bool is_analyze)
{
writer->add_member("range-checked-for-each-record").start_object();
add_json_keyset(writer, "keys", &key_set);
if (is_analyze)
{
writer->add_member("r_keys").start_object();
writer->add_member("full_scan").add_ll(full_scan);
writer->add_member("index_merge").add_ll(index_merge);
if (keys_stat)
{
writer->add_member("range").start_object();
for (uint i= 0; i < keys; i++)
{
if (keys_stat_names[i])
{
writer->add_member(keys_stat_names[i]).add_ll(keys_stat[i]);
}
}
writer->end_object();
}
writer->end_object();
}
}
void Explain_subq_materialization::print_explain_json(Json_writer *writer,
bool is_analyze)
{
writer->add_member("materialization").start_object();
if (is_analyze)
tracker.print_json_members(writer);
}
void Subq_materialization_tracker::print_json_members(Json_writer *writer) const
{
writer->add_member("r_strategy").add_str(get_exec_strategy());
if (loops_count)
writer->add_member("r_loops").add_ull(loops_count);
if (index_lookups_count)
writer->add_member("r_index_lookups").add_ull(index_lookups_count);
if (partial_matches_count)
writer->add_member("r_partial_matches").add_ull(partial_matches_count);
if (partial_match_buffer_size)
{
writer->add_member("r_partial_match_buffer_size").
add_size(partial_match_buffer_size);
}
if (partial_match_array_sizes.elements())
{
writer->add_member("r_partial_match_array_sizes").start_array();
for(size_t i= 0; i < partial_match_array_sizes.elements(); i++)
writer->add_ull(partial_match_array_sizes[i]);
writer->end_array();
}
}