diff options
author | Sergei Petrunia <psergey@askmonty.org> | 2014-11-27 00:51:54 +0300 |
---|---|---|
committer | Sergei Petrunia <psergey@askmonty.org> | 2014-11-27 00:51:54 +0300 |
commit | 3d5f97fd708e12201636179baee2c8bc0093c109 (patch) | |
tree | 04a2ea21f8312ecc4100e179f32ce58e719e82fb /sql/sql_explain.cc | |
parent | 55e99b29339dae833448ded6d0ca3d31f673d02a (diff) | |
parent | 3c5ce8a0a32a74cd8e0ddc81bcfacf7c85f0d90a (diff) | |
download | mariadb-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.cc | 559 |
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) { |