diff options
author | Sergei Golubchik <serg@mariadb.org> | 2014-12-03 11:37:26 +0100 |
---|---|---|
committer | Sergei Golubchik <serg@mariadb.org> | 2014-12-03 11:37:26 +0100 |
commit | ec4137c62b00a2bad91dd108f5782b206fe86a8b (patch) | |
tree | b284d929d1a498df14aafffd0c31831f2b4e9fd5 /sql/sql_explain.cc | |
parent | 1caee393076dc642a7d8e1283377b59ef8f52dbd (diff) | |
parent | bafe529af76a915f43dbf6a3fb8dc610a4ea121b (diff) | |
download | mariadb-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.cc | 1081 |
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) { |