summaryrefslogtreecommitdiff
path: root/sql/sql_explain.cc
diff options
context:
space:
mode:
authorSergei Petrunia <psergey@askmonty.org>2014-11-27 00:51:54 +0300
committerSergei Petrunia <psergey@askmonty.org>2014-11-27 00:51:54 +0300
commit3d5f97fd708e12201636179baee2c8bc0093c109 (patch)
tree04a2ea21f8312ecc4100e179f32ce58e719e82fb /sql/sql_explain.cc
parent55e99b29339dae833448ded6d0ca3d31f673d02a (diff)
parent3c5ce8a0a32a74cd8e0ddc81bcfacf7c85f0d90a (diff)
downloadmariadb-git-3d5f97fd708e12201636179baee2c8bc0093c109.tar.gz
Merge ../10.1-explain-json-r4 into 10.1
Diffstat (limited to 'sql/sql_explain.cc')
-rw-r--r--sql/sql_explain.cc559
1 files changed, 478 insertions, 81 deletions
diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc
index 7bab96e8ea5..ecc406bd5e9 100644
--- a/sql/sql_explain.cc
+++ b/sql/sql_explain.cc
@@ -20,7 +20,7 @@
#include "sql_priv.h"
#include "sql_select.h"
-
+#include "my_json_writer.h"
Explain_query::Explain_query(THD *thd_arg) :
upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false)
@@ -139,8 +139,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();
@@ -177,6 +182,40 @@ 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(this, output, explain_flags, is_analyze);
+ DBUG_ASSERT(0);
+ }
+ else if (insert_plan)
+ {
+ //insert_plan->print_explain(this, output, explain_flags, is_analyze);
+ DBUG_ASSERT(0);
+ }
+ 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);
@@ -212,12 +251,59 @@ 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);
+}
+
+
+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 */
@@ -241,31 +327,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)
@@ -325,6 +388,36 @@ int Explain_union::print_explain(Explain_query *query,
}
+void Explain_union::print_explain_json(Explain_query *query,
+ Json_writer *writer, bool is_analyze)
+{
+ 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();
+
+ //TODO: print_explain_for_children
+
+ writer->end_object();
+}
+
+
/*
Print EXPLAINs for all children nodes (i.e. for subqueries)
*/
@@ -418,21 +511,154 @@ int Explain_select::print_explain(Explain_query *query,
}
+void Explain_select::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(1);
+ if (message)
+ {
+ writer->add_member("table").start_object();
+ writer->add_member("message").add_str(message);
+ writer->end_object();
+ }
+ else
+ {
+ for (uint i=0; i< n_join_tabs; i++)
+ {
+ // psergey-todo: Need to honor SJM nests...
+ join_tabs[i]->print_explain_json(writer, is_analyze);
+ }
+ }
+ writer->end_object();
+}
+
+
void Explain_table_access::push_extra(enum explain_extra_tag extra_tag)
{
extra_tags.append(extra_tag);
}
+void Explain_table_access::fill_key_str(String *key_str, bool is_json)
+{
+ 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#";
+
+ if (key.get_key_name())
+ {
+ if (is_hj)
+ key_str->append(hash_key_prefix, strlen(hash_key_prefix), cs);
+
+ key_str->append(key.get_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_str->append(hash_next_key.get_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
+
+ 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)
+{
+ 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);
+ }
+}
+
+
+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;
+}
+
+
+double Explain_table_access::get_r_filtered()
+{
+ //psergey-todo: modify this to produce separate filtered% for both parts of
+ //WHERE.
+ 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;
- 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);
List<Item> item_list;
Item *item_null= new Item_null();
@@ -467,32 +693,15 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
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
+ 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;
- if (key.get_key_name())
- {
- if (is_hj)
- key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs);
-
- key_str.append(key.get_key_name());
-
- if (is_hj && type != JT_HASH)
- key_str.append(':');
- }
-
- if (quick_info)
- {
- StringBuffer<64> buf2;
- quick_info->print_key(&buf2);
- key_str.append(buf2);
- }
- if (type == JT_HASH_NEXT)
- key_str.append(hash_next_key.get_key_name());
+ fill_key_str(&key_str, false);
if (key_str.length() > 0)
push_string(&item_list, &key_str);
@@ -501,31 +710,7 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
/* `key_len` */
StringBuffer<64> key_len_str;
-
- 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);
- }
+ fill_key_len_str(&key_len_str);
if (key_len_str.length() > 0)
push_string(&item_list, &key_len_str);
@@ -533,10 +718,19 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai
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)
@@ -628,6 +822,180 @@ 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:
+ writer->add_member("attached_condition");
+ write_item(writer, where_cond);
+ 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:
+ // TODO TODO
+ break;
+ default:
+ DBUG_ASSERT(0);
+ }
+}
+
+
+void Explain_table_access::print_explain_json(Json_writer *writer,
+ bool is_analyze)
+{
+ 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();
+ }
+
+
+ // TODO: here, if quick select is not basic, print its nested form.
+
+ /* `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();
+ }
+
+ /* `rows` */
+ if (rows_set)
+ 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);
+ }
+
+ /* `filtered` */
+ if (filtered_set)
+ writer->add_member("filtered").add_double(filtered);
+
+ /* `r_filtered` */
+ if (is_analyze)
+ writer->add_member("r_filtered").add_double(get_r_filtered());
+
+ for (int i=0; i < (int)extra_tags.elements(); i++)
+ {
+ tag_to_json(writer, extra_tags.at(i));
+ }
+
+ writer->end_object();
+}
+
+
/*
Elements in this array match members of enum Extra_tag, defined in
sql_explain.h
@@ -757,6 +1125,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)
{