summaryrefslogtreecommitdiff
path: root/sql/sql_explain.cc
diff options
context:
space:
mode:
authorSergei Golubchik <serg@mariadb.org>2014-12-03 11:37:26 +0100
committerSergei Golubchik <serg@mariadb.org>2014-12-03 11:37:26 +0100
commitec4137c62b00a2bad91dd108f5782b206fe86a8b (patch)
treeb284d929d1a498df14aafffd0c31831f2b4e9fd5 /sql/sql_explain.cc
parent1caee393076dc642a7d8e1283377b59ef8f52dbd (diff)
parentbafe529af76a915f43dbf6a3fb8dc610a4ea121b (diff)
downloadmariadb-git-ec4137c62b00a2bad91dd108f5782b206fe86a8b.tar.gz
Merge branch '10.1' into bb-10.1-merge
Diffstat (limited to 'sql/sql_explain.cc')
-rw-r--r--sql/sql_explain.cc1081
1 files changed, 981 insertions, 100 deletions
diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc
index f713e74259d..59f38f7f167 100644
--- a/sql/sql_explain.cc
+++ b/sql/sql_explain.cc
@@ -21,7 +21,11 @@
#include <my_global.h>
#include "sql_priv.h"
#include "sql_select.h"
+#include "my_json_writer.h"
+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";
Explain_query::Explain_query(THD *thd_arg) :
upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false)
@@ -140,8 +144,13 @@ int Explain_query::send_explain(THD *thd)
thd->send_explain_fields(result, lex->describe, lex->analyze_stmt))
return 1;
- int res;
- if ((res= print_explain(result, lex->describe, lex->analyze_stmt)))
+ 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 (res)
result->abort_result_set();
else
result->send_eof();
@@ -178,6 +187,34 @@ int Explain_query::print_explain(select_result_sink *output,
}
+void Explain_query::print_explain_json(select_result_sink *output, bool is_analyze)
+{
+ Json_writer writer;
+ writer.start_object();
+
+ 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 node with id=1 */
+ Explain_node *node= get_node(1);
+ if (!node)
+ return; /* No query plan */
+ node->print_explain_json(this, &writer, is_analyze);
+ }
+
+ writer.end_object();
+
+ const CHARSET_INFO *cs= system_charset_info;
+ List<Item> item_list;
+ String *buf= &writer.output;
+ item_list.push_back(new Item_string(buf->ptr(), buf->length(), cs));
+ 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);
@@ -213,12 +250,173 @@ static void push_string(List<Item> *item_list, String *str)
item_list->push_back(new Item_string_sys(str->ptr(), str->length()));
}
+static void push_string_list(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);
+ }
+ push_string(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,
+ ha_rows *r_rows,
+ double r_filtered,
+ const char *extra)
+{
+ Item *item_null= new Item_null();
+ List<Item> item_list;
+ Item *item;
+
+ item_list.push_back(new Item_int((int32) select_number));
+ item_list.push_back(new Item_string_sys(select_type));
+ item_list.push_back(new Item_string_sys(table_name));
+ if (options & DESCRIBE_PARTITIONS)
+ {
+ if (partitions)
+ {
+ item_list.push_back(new Item_string_sys(partitions));
+ }
+ else
+ item_list.push_back(item_null);
+ }
+
+ const char *jtype_str= join_type_str[jtype];
+ item_list.push_back(new Item_string_sys(jtype_str));
+
+ /* 'possible_keys' */
+ if (possible_keys && !possible_keys->is_empty())
+ {
+ StringBuffer<64> possible_keys_buf;
+ push_string_list(&item_list, *possible_keys, &possible_keys_buf);
+ }
+ else
+ item_list.push_back(item_null);
+
+ /* 'index */
+ item= index ? new Item_string_sys(index) : item_null;
+ item_list.push_back(item);
+
+ /* 'key_len */
+ item= key_len ? new Item_string_sys(key_len) : item_null;
+ item_list.push_back(item);
+
+ /* 'ref' */
+ item= ref ? new Item_string_sys(ref) : item_null;
+ item_list.push_back(item);
+
+ /* 'rows' */
+ if (rows)
+ {
+ item_list.push_back(new Item_int(*rows,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else
+ item_list.push_back(item_null);
+
+ /* 'r_rows' */
+ if (is_analyze)
+ {
+ if (r_rows)
+ {
+ item_list.push_back(new Item_int(*r_rows,
+ MY_INT64_NUM_DECIMAL_DIGITS));
+ }
+ else
+ item_list.push_back(item_null);
+ }
+
+ /* 'filtered' */
+ const double filtered=100.0;
+ if (options & DESCRIBE_EXTENDED || is_analyze)
+ item_list.push_back(new Item_float(filtered, 2));
+
+ /* 'r_filtered' */
+ if (is_analyze)
+ item_list.push_back(new Item_float(r_filtered, 2));
+
+ /* 'Extra' */
+ if (extra)
+ item_list.push_back(new Item_string_sys(extra));
+ else
+ item_list.push_back(item_null);
+
+ if (result->send_data(item_list))
+ return 1;
+ return 0;
+}
+
+
+
+
+uint Explain_union::make_union_table_name(char *buf)
+{
+ uint childno= 0;
+ uint len= 6, lastop= 0;
+ memcpy(buf, STRING_WITH_LEN("<union"));
+
+ for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
+ childno++)
+ {
+ len+= lastop;
+ lastop= 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)
{
+ //const CHARSET_INFO *cs= system_charset_info;
char table_name_buffer[SAFE_NAME_LEN];
/* print all UNION children, in order */
@@ -242,31 +440,8 @@ int Explain_union::print_explain(Explain_query *query,
push_str(&item_list, fake_select_type);
/* `table` column: something like "<union1,2>" */
- {
- uint childno= 0;
- uint len= 6, lastop= 0;
- memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
-
- for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN;
- childno++)
- {
- len+= lastop;
- lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
- "%u,", union_members.at(childno));
- }
-
- if (childno < union_members.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 '>'
- }
- item_list.push_back(new Item_string_sys(table_name_buffer, len));
- }
+ uint len= make_union_table_name(table_name_buffer);
+ item_list.push_back(new Item_string_sys(table_name_buffer, len));
/* `partitions` column */
if (explain_flags & DESCRIBE_PARTITIONS)
@@ -326,14 +501,46 @@ int Explain_union::print_explain(Explain_query *query,
}
+void Explain_union::print_explain_json(Explain_query *query,
+ Json_writer *writer, bool is_analyze)
+{
+ Json_writer_nesting_guard guard(writer);
+ char table_name_buffer[SAFE_NAME_LEN];
+
+ writer->add_member("query_block").start_object();
+ writer->add_member("union_result").start_object();
+ // using_temporary_table
+ 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
+ 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
+}
+
+
/*
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)
+ select_result_sink *output,
+ uint8 explain_flags,
+ bool is_analyze)
{
for (int i= 0; i < (int) children.elements(); i++)
{
@@ -345,6 +552,50 @@ int Explain_node::print_explain_for_children(Explain_query *query,
}
+/*
+ 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));
+ /* 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();
+}
+
+
void Explain_select::replace_table(uint idx, Explain_table_access *new_tab)
{
delete join_tabs[idx];
@@ -352,7 +603,7 @@ void Explain_select::replace_table(uint idx, Explain_table_access *new_tab)
}
-Explain_select::~Explain_select()
+Explain_basic_join::~Explain_basic_join()
{
if (join_tabs)
{
@@ -413,111 +664,163 @@ int Explain_select::print_explain(Explain_query *query,
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);
}
-void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
+int Explain_basic_join::print_explain(Explain_query *query,
+ select_result_sink *output,
+ uint8 explain_flags, bool is_analyze)
{
- extra_tags.append(extra_tag);
+ 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;
+ }
+ return 0;
}
-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)
+void Explain_select::print_explain_json(Explain_query *query,
+ Json_writer *writer, bool is_analyze)
{
- const CHARSET_INFO *cs= system_charset_info;
- const char *hash_key_prefix= "#hash#";
- bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
- type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
+ Json_writer_nesting_guard guard(writer);
- List<Item> item_list;
- Item *item_null= new Item_null();
-
- if (sjm_nest_select_id)
- select_id= sjm_nest_select_id;
+ if (message)
+ {
+ writer->add_member("query_block").start_object();
+ writer->add_member("select_id").add_ll(select_id);
- /* `id` column */
- item_list.push_back(new Item_int((int32) select_id));
+ writer->add_member("table").start_object();
+ writer->add_member("message").add_str(message);
+ writer->end_object();
- /* `select_type` column */
- if (sjm_nest_select_id)
- push_str(&item_list, "MATERIALIZED");
+ print_explain_json_for_children(query, writer, is_analyze);
+ writer->end_object();
+ }
else
- push_str(&item_list, select_type);
+ {
+ /*
+ TODO: how does this approach allow to print ORDER BY members?
+ Explain_basic_join does not have ORDER/GROUP.
+ A: factor out join tab printing loop into a common func.
+ */
+ Explain_basic_join::print_explain_json(query, writer, is_analyze);
+ }
- /* `table` column */
- push_string(&item_list, &table_name);
-
- /* `partitions` column */
- if (explain_flags & DESCRIBE_PARTITIONS)
+}
+
+
+void Explain_basic_join::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(select_id);
+ for (uint i=0; i< n_join_tabs; i++)
{
- if (used_partitions_set)
- {
- push_string(&item_list, &used_partitions);
- }
- else
- item_list.push_back(item_null);
+ if (join_tabs[i]->start_dups_weedout)
+ writer->add_member("duplicates_removal").start_object();
+
+ join_tabs[i]->print_explain_json(query, writer, is_analyze);
+
+ if (join_tabs[i]->end_dups_weedout)
+ writer->end_object();
}
+ print_explain_json_for_children(query, writer, is_analyze);
+ writer->end_object();
+}
- /* `type` column */
- push_str(&item_list, join_type_str[type]);
- /* `possible_keys` column */
- if (possible_keys_str.length() > 0)
- push_string(&item_list, &possible_keys_str);
- else
- item_list.push_back(item_null);
+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 otuput 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
+{
+ const CHARSET_INFO *cs= system_charset_info;
+ bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT ||
+ type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE);
+ const char *hash_key_prefix= "#hash#";
- /* `key` */
- StringBuffer<64> key_str;
if (key.get_key_name())
{
if (is_hj)
- key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs);
+ key_str->append(hash_key_prefix, strlen(hash_key_prefix), cs);
- key_str.append(key.get_key_name());
+ key_str->append(key.get_key_name());
if (is_hj && type != JT_HASH)
- key_str.append(':');
+ key_str->append(':');
}
if (quick_info)
{
StringBuffer<64> buf2;
- quick_info->print_key(&buf2);
- key_str.append(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_str.append(hash_next_key.get_key_name());
-
- if (key_str.length() > 0)
- push_string(&item_list, &key_str);
- else
- item_list.push_back(item_null);
+ key_str->append(hash_next_key.get_key_name());
+}
- /* `key_len` */
- StringBuffer<64> key_len_str;
+/*
+ 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
+
+ The column looks identical in tabular and json forms. In JSON, we consider
+ the column legacy, it is superceded by used_key_parts.
+*/
+
+void Explain_table_access::fill_key_len_str(String *key_len_str) 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);
+ key_len_str->append(buf, length);
if (is_hj && type != JT_HASH)
- key_len_str.append(':');
+ key_len_str->append(':');
}
if (quick_info)
{
StringBuffer<64> buf2;
quick_info->print_key_len(&buf2);
- key_len_str.append(buf2);
+ key_len_str->append(buf2);
}
if (type == JT_HASH_NEXT)
@@ -525,19 +828,127 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
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);
+ key_len_str->append(buf, length);
+ }
+}
+
+
+void Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg)
+{
+ set_pseudo_key(mem_root, key->name);
+ key_len= key_len_arg;
+ uint len= 0;
+ for (uint i= 0; i < key->usable_key_parts; i++)
+ {
+ key_parts_list.append_str(mem_root, key->key_part[i].field->field_name);
+ len += key->key_part[i].store_length;
+ if (len >= key_len_arg)
+ break;
+ }
+}
+
+
+void Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg)
+{
+ if (key_name_arg)
+ {
+ size_t name_len= strlen(key_name_arg);
+ if ((key_name= (char*)alloc_root(root, name_len+1)))
+ memcpy(key_name, key_name_arg, name_len+1);
+ }
+ else
+ key_name= NULL;
+ key_len= -1;
+}
+
+
+/*
+ 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)
+{
+ //const CHARSET_INFO *cs= system_charset_info;
+
+ List<Item> item_list;
+ Item *item_null= new Item_null();
+
+ /* `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 */
+ StringBuffer<64> possible_keys_buf;
+ if (possible_keys.is_empty())
+ item_list.push_back(item_null);
+ else
+ push_string_list(&item_list, possible_keys, &possible_keys_buf);
+
+ /* `key` */
+ StringBuffer<64> key_str;
+ fill_key_str(&key_str, false);
+
+ if (key_str.length() > 0)
+ push_string(&item_list, &key_str);
+ else
+ item_list.push_back(item_null);
+
+ /* `key_len` */
+ StringBuffer<64> key_len_str;
+ fill_key_len_str(&key_len_str);
+
if (key_len_str.length() > 0)
push_string(&item_list, &key_len_str);
else
item_list.push_back(item_null);
/* `ref` */
- if (ref_set)
- push_string(&item_list, &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(&item_list, "");
+ }
+ else
+ item_list.push_back(item_null);
+ }
else
- item_list.push_back(item_null);
+ push_string_list(&item_list, ref_list, &ref_list_buf);
/* `rows` */
if (rows_set)
@@ -629,6 +1040,276 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
}
+bool 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 1;
+ memcpy(cp, str, len+1);
+ push_back(cp);
+ return 0;
+}
+
+
+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_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());
+}
+
+
+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:
+ writer->add_member("index_condition");
+ write_item(writer, pushed_index_cond);
+ break;
+ case ET_USING_WHERE:
+ {
+ /*
+ 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);
+ }
+ }
+ 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_USING_JOIN_BUFFER:
+ /* Do nothing. Join buffer is handled differently */
+ case ET_START_TEMPORARY:
+ case ET_END_TEMPORARY:
+ /* Handled as "duplicates_removal: { ... } */
+ break;
+ case ET_FIRST_MATCH:
+ writer->add_member("first_match").add_str(firstmatch_table_name.c_ptr());
+ break;
+ case ET_LOOSESCAN:
+ writer->add_member("loose_scan").add_bool(true);
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+}
+
+
+void Explain_table_access::print_explain_json(Explain_query *query,
+ Json_writer *writer,
+ bool is_analyze)
+{
+ Json_writer_nesting_guard guard(writer);
+
+ if (bka_type.is_using_jbuf())
+ {
+ writer->add_member("block-nl-join").start_object();
+ }
+
+ writer->add_member("table").start_object();
+
+ writer->add_member("table_name").add_str(table_name);
+ // partitions
+ writer->add_member("access_type").add_str(join_type_str[type]);
+ 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` */
+ /* 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);
+ 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())
+ {
+ 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();
+ }
+
+ /* `ref` */
+ if (!ref_list.is_empty())
+ {
+ List_iterator_fast<char> it(ref_list);
+ const char *str;
+ writer->add_member("ref").start_array();
+ while ((str= it++))
+ writer->add_str(str);
+ writer->end_array();
+ }
+
+ /* r_loops (not present in tabular output) */
+ if (is_analyze)
+ {
+ writer->add_member("r_loops").add_ll(tracker.get_loops());
+ }
+
+ /* `rows` */
+ if (rows_set)
+ writer->add_member("rows").add_ll(rows);
+
+ /* `r_rows` */
+ if (is_analyze)
+ {
+ writer->add_member("r_rows");
+ if (tracker.has_scans())
+ {
+ ha_rows avg_rows= tracker.get_avg_rows();
+ writer->add_ll(avg_rows);
+ }
+ else
+ writer->add_null();
+ }
+
+ /* `filtered` */
+ if (filtered_set)
+ writer->add_member("filtered").add_double(filtered);
+
+ /* `r_filtered` */
+ if (is_analyze)
+ {
+ writer->add_member("r_filtered");
+ if (tracker.has_scans())
+ writer->add_double(tracker.get_filtered_after_where()*100.0);
+ else
+ writer->add_null();
+ }
+
+ for (int i=0; i < (int)extra_tags.elements(); i++)
+ {
+ tag_to_json(writer, extra_tags.at(i));
+ }
+
+ if (bka_type.is_using_jbuf())
+ {
+ writer->end_object();
+ writer->add_member("buffer_type").add_str(bka_type.incremental?
+ "incremental":"flat");
+ 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_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();
+ }
+ }
+
+ 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);
+ 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();
+ }
+
+ writer->end_object();
+}
+
+
/*
Elements in this array match members of enum Extra_tag, defined in
sql_explain.h
@@ -758,6 +1439,35 @@ void Explain_quick_select::print_extra(String *str)
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());
+
+ List_iterator_fast<char> it(range.key_parts_list);
+ const char *name;
+ writer->add_member("used_key_parts").start_array();
+ while ((name= it++))
+ writer->add_str(name);
+ writer->end_array();
+
+ writer->end_object();
+ }
+ else
+ {
+ writer->add_member(get_name_by_type()).start_object();
+
+ List_iterator_fast<Explain_quick_select> it (children);
+ Explain_quick_select* child;
+ while ((child = it++))
+ child->print_json(writer);
+
+ writer->end_object();
+ }
+}
void Explain_quick_select::print_extra_recursive(String *str)
{
@@ -867,7 +1577,7 @@ int Explain_delete::print_explain(Explain_query *query,
{
if (deleting_all_rows)
{
- const char *msg= "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);
@@ -882,6 +1592,27 @@ int Explain_delete::print_explain(Explain_query *query,
}
+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,
@@ -893,8 +1624,8 @@ int Explain_update::print_explain(Explain_query *query,
if (impossible_where || no_partitions)
{
const char *msg= impossible_where ?
- "Impossible WHERE" :
- "No matching rows after partition pruning";
+ STR_IMPOSSIBLE_WHERE :
+ STR_NO_ROWS_AFTER_PRUNING;
int res= print_explain_message_line(output, explain_flags, is_analyze,
1 /*select number*/,
select_type,
@@ -903,7 +1634,6 @@ int Explain_update::print_explain(Explain_query *query,
return res;
}
-
if (quick_info)
{
quick_info->print_key(&key_buf);
@@ -917,10 +1647,13 @@ int Explain_update::print_explain(Explain_query *query,
extra_str.append(quick_buf);
}
}
- else
+ else if (key.get_key_name())
{
- key_buf.copy(key_str);
- key_len_buf.copy(key_len_str);
+ const char *name= key.get_key_name();
+ key_buf.set(name, strlen(name), &my_charset_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_bin);
}
if (using_where)
@@ -964,7 +1697,7 @@ int Explain_update::print_explain(Explain_query *query,
table_name.c_ptr(),
used_partitions_set? used_partitions.c_ptr() : NULL,
jtype,
- possible_keys_line.length()? possible_keys_line.c_ptr(): NULL,
+ &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 */
@@ -977,6 +1710,140 @@ int Explain_update::print_explain(Explain_query *query,
}
+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);
+
+ 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;
+ }
+
+ 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);
+ 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();
+ }
+
+#if 0
+ /* `ref` */
+ if (!ref_list.is_empty())
+ {
+ List_iterator_fast<char> it(ref_list);
+ const char *str;
+ writer->add_member("ref").start_array();
+ while ((str= it++))
+ writer->add_str(str);
+ writer->end_array();
+ }
+#endif
+
+ /* `rows` */
+ writer->add_member("rows").add_ll(rows);
+
+ /* `r_rows` */
+ if (is_analyze && tracker.has_scans())
+ {
+ ha_rows avg_rows= tracker.get_avg_rows();
+ writer->add_member("r_rows").add_ll(avg_rows);
+ }
+
+ /* UPDATE/DELETE do not produce `filtered` estimate */
+
+ /* `r_filtered` */
+ if (is_analyze)
+ {
+ double r_filtered= tracker.get_filtered_after_where();
+ writer->add_member("r_filtered").add_double(r_filtered);
+ }
+
+ if (mrr_type.length() != 0)
+ writer->add_member("mrr_type").add_str(mrr_type.ptr());
+
+ if (using_filesort)
+ writer->add_member("using_filesort").add_ll(1);
+
+ if (using_io_buffer)
+ writer->add_member("using_io_buffer").add_ll(1);
+
+ if (where_cond)
+ {
+ writer->add_member("attached_condition");
+ write_item(writer, where_cond);
+ }
+
+ 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,
@@ -1001,6 +1868,20 @@ int Explain_insert::print_explain(Explain_query *query,
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.c_ptr());
+ writer->end_object(); // table
+ print_explain_json_for_children(query, writer, is_analyze);
+ writer->end_object(); // query_block
+}
+
void delete_explain_query(LEX *lex)
{