From 5cfd3270ec79238b27765af3062ae7d97f6f06d0 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Tue, 27 May 2014 21:04:45 +0400 Subject: [PATCH 1/8] MDEV-6109: EXPLAIN JSON - First code, "EXPLAIN FORMAT=JSON stmt" and "ANALYZE FORMAT=JSON stmt" work for basic queries. Complex constructs (e.g subqueries, etc) not yet supported. - No test infrastructure yet --- libmysqld/CMakeLists.txt | 1 + sql/CMakeLists.txt | 1 + sql/lex.h | 1 + sql/my_json_writer.cc | 129 ++++++++++++ sql/my_json_writer.h | 43 ++++ sql/sql_class.cc | 12 +- sql/sql_class.h | 1 + sql/sql_explain.cc | 417 +++++++++++++++++++++++++++++++-------- sql/sql_explain.h | 35 +++- sql/sql_lex.cc | 1 + sql/sql_lex.h | 1 + sql/sql_parse.cc | 43 ++-- sql/sql_select.cc | 28 ++- sql/sql_select.h | 1 + sql/sql_yacc.yy | 39 +++- 15 files changed, 642 insertions(+), 111 deletions(-) create mode 100644 sql/my_json_writer.cc create mode 100644 sql/my_json_writer.h diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index d0c3fafdf69..4da881d947a 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -100,6 +100,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/rpl_reporting.cc ../sql/sql_expression_cache.cc ../sql/my_apc.cc ../sql/my_apc.h + ../sql/my_json_writer.cc ../sql/my_json_writer.h ../sql/rpl_gtid.cc ../sql/sql_explain.cc ../sql/sql_explain.h ../sql/compat56.cc diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 21b4236163b..7b6cd623336 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -90,6 +90,7 @@ SET (SQL_SOURCE threadpool_common.cc ../sql-common/mysql_async.c my_apc.cc my_apc.h + my_json_writer.cc my_json_writer.h rpl_gtid.cc rpl_parallel.cc table_cache.cc ${CMAKE_CURRENT_BINARY_DIR}/sql_builtin.cc diff --git a/sql/lex.h b/sql/lex.h index 10a52160cf0..d66160ffec0 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -242,6 +242,7 @@ static SYMBOL symbols[] = { { "FOR", SYM(FOR_SYM)}, { "FORCE", SYM(FORCE_SYM)}, { "FOREIGN", SYM(FOREIGN)}, + { "FORMAT", SYM(FORMAT_SYM)}, { "FOUND", SYM(FOUND_SYM)}, { "FROM", SYM(FROM)}, { "FULL", SYM(FULL)}, diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc new file mode 100644 index 00000000000..206597da59e --- /dev/null +++ b/sql/my_json_writer.cc @@ -0,0 +1,129 @@ +/* Todo: SkySQL copyrights */ + +#include +#include "sql_priv.h" +#include "sql_string.h" + +#include "my_json_writer.h" + +void Json_writer::append_indent() +{ + if (!document_start) + output.append('\n'); + for (int i=0; i< indent_level; i++) + output.append(' '); +} + +void Json_writer::start_object() +{ + if (!element_started) + start_element(); + + output.append("{"); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + +void Json_writer::start_array() +{ + if (!element_started) + start_element(); + + output.append("["); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + + +void Json_writer::end_object() +{ + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("}"); +} + + +void Json_writer::end_array() +{ + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("]"); +} + + +Json_writer& Json_writer::add_member(const char *name) +{ + // assert that we are in an object + DBUG_ASSERT(!element_started); + start_element(); + + output.append('"'); + output.append(name); + output.append("\": "); + return *this; +} + + +void Json_writer::start_element() +{ + element_started= true; + + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + +void Json_writer::add_ll(longlong val) +{ + if (!element_started) + start_element(); + + char buf[64]; + my_snprintf(buf, sizeof(buf), "%ld", val); + output.append(buf); + element_started= false; +} + + +void Json_writer::add_double(double val) +{ + if (!element_started) + start_element(); + + char buf[64]; + my_snprintf(buf, sizeof(buf), "%lf", val); + output.append(buf); + element_started= false; +} + + +void Json_writer::add_str(const char *str) +{ + if (!element_started) + start_element(); + + output.append('"'); + output.append(str); + output.append('"'); + element_started= false; +} + +void Json_writer::add_bool(bool val) +{ + add_str(val? "true" : "false"); +} + +void Json_writer::add_str(const String &str) +{ + add_str(str.ptr()); +} + diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h new file mode 100644 index 00000000000..403d7e0688c --- /dev/null +++ b/sql/my_json_writer.h @@ -0,0 +1,43 @@ +/* Todo: SkySQL copyrights */ + +class Json_writer +{ +public: + /* Add a member. We must be in an object. */ + Json_writer& add_member(const char *name); + + /* Add atomic values */ + void add_ll(longlong val); + void add_str(const char* val); + void add_str(const String &str); + void add_double(double val); + void add_bool(bool val); + + /* Start a child object */ + void start_object(); + void start_array(); + + void end_object(); + void end_array(); + + Json_writer() : + indent_level(0), document_start(true), element_started(false), + first_child(true) + {} +private: + // stack of (name, bool is_object_or_array) elements. + int indent_level; + enum { INDENT_SIZE = 2 }; + + bool document_start; + bool element_started; + bool first_child; + + void append_indent(); + void start_element(); + + //const char *new_member_name; +public: + String output; +}; + diff --git a/sql/sql_class.cc b/sql/sql_class.cc index 8d6ddc0bb08..3d1476715e5 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2271,7 +2271,10 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) int THD::send_explain_fields(select_result *result) { List field_list; - make_explain_field_list(field_list); + if (lex->explain_json) + make_explain_json_field_list(field_list); + else + make_explain_field_list(field_list); result->prepare(field_list, NULL); return (result->send_result_set_metadata(field_list, Protocol::SEND_NUM_ROWS | @@ -2279,6 +2282,13 @@ int THD::send_explain_fields(select_result *result) } +void THD::make_explain_json_field_list(List &field_list) +{ + Item *item= new Item_empty_string("EXPLAIN", 78, system_charset_info); + field_list.push_back(item); +} + + /* Populate the provided field_list with EXPLAIN output columns. this->lex->describe has the EXPLAIN flags diff --git a/sql/sql_class.h b/sql/sql_class.h index a7991952a79..5118972911e 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3059,6 +3059,7 @@ public: CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length); int send_explain_fields(select_result *result); void make_explain_field_list(List &field_list); + void make_explain_json_field_list(List &field_list); /** Clear the current error, if any. We do not clear is_fatal_error or is_fatal_sub_stmt_error since we diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 47581898e42..357e8f07664 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)) 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_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_query(LEX *lex, THD *thd, String *str) { return lex->explain->print_explain_str(thd, str, false); @@ -214,12 +253,59 @@ static void push_string(List *item_list, String *str) system_charset_info)); } +static void push_string_list(List *item_list, List &lines, + String *buf) +{ + List_iterator_fast 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("= 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 */ @@ -240,32 +326,8 @@ int Explain_union::print_explain(Explain_query *query, push_str(&item_list, fake_select_type); /* `table` column: something like "" */ - { - uint childno= 0; - uint len= 6, lastop= 0; - memcpy(table_name_buffer, STRING_WITH_LEN("= NAME_LEN) - { - memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); - len+= 4; - } - else - { - len+= lastop; - table_name_buffer[len - 1]= '>'; // change ',' to '>' - } - const CHARSET_INFO *cs= system_charset_info; - item_list.push_back(new Item_string(table_name_buffer, len, cs)); - } + uint len= make_union_table_name(table_name_buffer); + item_list.push_back(new Item_string(table_name_buffer, len, cs)); /* `partitions` column */ if (explain_flags & DESCRIBE_PARTITIONS) @@ -307,7 +369,6 @@ int Explain_union::print_explain(Explain_query *query, { extra_buf.append(STRING_WITH_LEN("Using filesort")); } - const CHARSET_INFO *cs= system_charset_info; item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); //output->unit.offset_limit_cnt= 0; @@ -322,6 +383,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) */ @@ -410,21 +501,112 @@ 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) +{ + 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; + quick_info->print_key(&buf2); + key_str->append(buf2); + } + if (type == JT_HASH_NEXT) + key_str->append(hash_next_key.get_key_name()); +} + + +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); + } +} + + +double Explain_table_access::get_r_filtered() +{ + double r_filtered; + if (r_rows > 0) + r_filtered= 100.0 * (double)r_rows_after_table_cond / r_rows; + else + r_filtered= 100.0; + 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_list; Item *item_null= new Item_null(); @@ -459,32 +641,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); if (key_str.length() > 0) push_string(&item_list, &key_str); @@ -493,31 +658,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); @@ -561,12 +702,7 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai /* `r_filtered` */ if (is_analyze) { - double r_filtered; - if (r_rows > 0) - r_filtered= 100.0 * (double)r_rows_after_table_cond / r_rows; - else - r_filtered= 100.0; - item_list.push_back(new Item_float(r_filtered, 2)); + item_list.push_back(new Item_float(get_r_filtered(), 2)); } /* `Extra` */ @@ -608,6 +744,115 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai } +static void write_item(Json_writer *writer, Item *item) +{ + char item_buf[256]; + String str(item_buf, sizeof(item_buf), &my_charset_bin); + str.length(0); + item->print(&str ,QT_ORDINARY); + 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; + 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 it(possible_keys); + const char *name; + writer->add_member("possible_keys").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + /* `key` */ + StringBuffer<64> key_str; + fill_key_str(&key_str); + if (key_str.length()) + writer->add_member("key").add_str(key_str); + + /* `used_key_parts` */ + writer->add_member("used_key_parts").add_str("TODO"); + + 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); + + if (rows_set) + writer->add_member("rows").add_ll(rows); + + /* `r_rows` */ + if (is_analyze) + { + ha_rows avg_rows= r_scans ? round((double) r_rows / r_scans): 0; + writer->add_member("r_rows").add_ll(avg_rows); + } + + 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 diff --git a/sql/sql_explain.h b/sql/sql_explain.h index a6b41414c5d..f46dfffb967 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -31,6 +31,7 @@ const int FAKE_SELECT_LEX_ID= (int)UINT_MAX; class Explain_query; +class Json_writer; /* A node can be either a SELECT, or a UNION. */ @@ -61,7 +62,9 @@ public: virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze)=0; - + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze)= 0; + int print_explain_for_children(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); virtual ~Explain_node(){} @@ -135,6 +138,8 @@ public: int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); }; @@ -173,9 +178,13 @@ public: } int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); const char *fake_select_type; bool using_filesort; +private: + uint make_union_table_name(char *buf); }; @@ -247,6 +256,8 @@ public: /* Return tabular EXPLAIN output as a text string */ bool print_explain_str(THD *thd, String *out_str, bool is_analyze); + void print_explain_json(select_result_sink *output, bool is_analyze); + /* If true, at least part of EXPLAIN can be printed */ bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } @@ -411,7 +422,8 @@ public: bool used_partitions_set; /* Empty string means "NULL" will be printed */ - StringBuffer<32> possible_keys_str; + List possible_keys; + //StringBuffer<32> possible_keys_str; /* Index use: key name and length. @@ -460,11 +472,19 @@ public: EXPLAIN_BKA_TYPE bka_type; StringBuffer<32> firstmatch_table_name; + + /* + Note: lifespan of WHERE condition is less than lifespan of this object. + THe below is valid if tags include "ET_USING_WHERE". + */ + Item *where_cond; + Item *pushed_index_cond; int 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 print_explain_json(Json_writer *writer, bool is_analyze); /* ANALYZE members*/ ha_rows r_scans; /* How many scans were ran on this join_tab */ @@ -479,6 +499,10 @@ public: private: void append_tag_name(String *str, enum explain_extra_tag tag); + void fill_key_str(String *key_str); + void fill_key_len_str(String *key_len_str); + double get_r_filtered(); + void tag_to_json(Json_writer *writer, enum explain_extra_tag tag); }; @@ -529,6 +553,8 @@ public: {} virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, bool is_analyze) + { /* EXPLAIN_JSON_NOT_IMPL */} }; @@ -549,6 +575,9 @@ public: int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze) + { /* EXPLAIN_JSON_NOT_IMPL */} }; @@ -570,6 +599,8 @@ public: virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, bool is_analyze) + { /* EXPLAIN_JSON_NOT_IMPL */} }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index cd9f9238f71..1e095627245 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -484,6 +484,7 @@ void lex_start(THD *thd) lex->select_lex.group_list_ptrs->clear(); lex->describe= 0; lex->analyze_stmt= 0; + lex->explain_json= false; lex->subqueries= FALSE; lex->context_analysis_only= 0; lex->derived_tables= 0; diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 70a793541af..5e5f36172b6 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2461,6 +2461,7 @@ struct LEX: public Query_tables_list uint table_count; uint8 describe; bool analyze_stmt; /* TRUE<=> this is "ANALYZE $stmt" */ + bool explain_json; /* A flag that indicates what kinds of derived tables are present in the query (0 if no derived tables, otherwise a combination of flags diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 42a3045cb1d..04892610fcf 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -97,6 +97,8 @@ #include "log_slow.h" #include "sql_bootstrap.h" +#include "my_json_writer.h" + #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") #ifdef WITH_ARIA_STORAGE_ENGINE @@ -5233,20 +5235,39 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) top-level LIMIT */ result->reset_offset_limit(); - thd->lex->explain->print_explain(result, thd->lex->describe, - thd->lex->analyze_stmt); - if (lex->describe & DESCRIBE_EXTENDED) + if (thd->lex->explain_json) { - char buff[1024]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); /* - The warnings system requires input in utf8, @see - mysqld_show_warnings(). + Json_writer writer; + writer.start_object(); + thd->lex->explain->print_explain_json(&writer, thd->lex->analyze_stmt); + writer.end_object(); + + const CHARSET_INFO *cs= system_charset_info; + List item_list; + String *buf= &writer.output; + item_list.push_back(new Item_string(buf->ptr(), buf->length(), cs)); + result->send_data(item_list); */ - thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); - push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_YES, str.c_ptr_safe()); + thd->lex->explain->print_explain_json(result, thd->lex->analyze_stmt); + } + else + { + thd->lex->explain->print_explain(result, thd->lex->describe, + thd->lex->analyze_stmt); + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + /* + The warnings system requires input in utf8, @see + mysqld_show_warnings(). + */ + thd->lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } } } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 6b8fda7bd82..0a8bcea8e41 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22913,7 +22913,6 @@ void JOIN::clear() /* Print an EXPLAIN line with all NULLs and given message in the 'Extra' column - TODO: is_analyze */ int print_explain_message_line(select_result_sink *result, @@ -23201,20 +23200,24 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) /////////////////////////////////////////////////////////////////////////////// // TODO: join with make_possible_keys_line ? -void append_possible_keys(String *str, TABLE *table, key_map possible_keys) +int append_possible_keys(MEM_ROOT *alloc, List &list, TABLE *table, + key_map possible_keys) { uint j; for (j=0 ; j < table->s->keys ; j++) { if (possible_keys.is_set(j)) { - if (str->length()) - str->append(','); - str->append(table->key_info[j].name, - strlen(table->key_info[j].name), - system_charset_info); + const char *key_name= table->key_info[j].name; + size_t len= strlen(key_name); + char *cp; + if (!(cp = (char*)alloc_root(alloc, len))) + return 1; + memcpy(cp, key_name, len+1); + list.push_back(cp); } } + return 0; } @@ -23387,7 +23390,9 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, eta->type= tab_type; /* Build "possible_keys" value */ - append_possible_keys(&eta->possible_keys_str, table, tab->keys); + if (append_possible_keys(thd->mem_root, eta->possible_keys, table, + tab->keys)) + DBUG_RETURN(1); /* Build "key", "key_len", and "ref" */ if (tab_type == JT_NEXT) @@ -23544,7 +23549,10 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && table->file->pushed_idx_cond) + { eta->push_extra(ET_USING_INDEX_CONDITION); + eta->pushed_index_cond= table->file->pushed_idx_cond; + } else if (tab->cache_idx_cond) eta->push_extra(ET_USING_INDEX_CONDITION_BKA); @@ -23585,7 +23593,11 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, */ } else + { + eta->where_cond= tab->select->cond? tab->select->cond: + tab->cache_select->cond; eta->push_extra(ET_USING_WHERE); + } } } if (table_list /* SJM bushes don't have table_list */ && diff --git a/sql/sql_select.h b/sql/sql_select.h index 66d7b007bbb..0e0d04cd967 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1920,4 +1920,5 @@ ulong check_selectivity(THD *thd, TABLE *table, List *conds); + #endif /* SQL_SELECT_INCLUDED */ diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 68d4347de43..3a69e48fad1 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1178,6 +1178,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token FORCE_SYM %token FOREIGN /* SQL-2003-R */ %token FOR_SYM /* SQL-2003-R */ +%token FORMAT_SYM %token FOUND_SYM /* SQL-2003-R */ %token FROM %token FULL /* SQL-2003-R */ @@ -1847,6 +1848,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); subselect_end select_var_list select_var_list_init help field_length opt_field_length opt_extended_describe shutdown + opt_format_json prepare prepare_src execute deallocate statement sp_suid sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa @@ -9756,6 +9758,18 @@ function_call_conflict: if ($$ == NULL) MYSQL_YYABORT; } + | FORMAT_SYM '(' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format($3, $5); + if ($$ == NULL) + MYSQL_YYABORT; + } + | FORMAT_SYM '(' expr ',' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format($3, $5, $7); + if ($$ == NULL) + MYSQL_YYABORT; + } | LAST_VALUE '(' expr_list ')' { $$= new (thd->mem_root) Item_func_last_value(* $3); @@ -12768,16 +12782,34 @@ describe_command: ; analyze_stmt_command: - ANALYZE_SYM explainable_command + ANALYZE_SYM opt_format_json explainable_command { Lex->analyze_stmt= true; } ; opt_extended_describe: - /* empty */ {} - | EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } + EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } | PARTITIONS_SYM { Lex->describe|= DESCRIBE_PARTITIONS; } + | opt_format_json {} + ; + +opt_format_json: + /* empty */ {} + | FORMAT_SYM EQ ident_or_text + { + if (!my_strcasecmp(system_charset_info, $3.str, "JSON")) + Lex->explain_json= true; + else if (!my_strcasecmp(system_charset_info, $3.str, "TRADITIONAL")) + { + DBUG_ASSERT(Lex->explain_json==false); + } + else + { + my_error(ER_UNKNOWN_EXPLAIN_FORMAT, MYF(0), $3.str); + MYSQL_YYABORT; + } + } ; opt_describe_column: @@ -14063,6 +14095,7 @@ keyword: | EXAMINED_SYM {} | EXECUTE_SYM {} | FLUSH_SYM {} + | FORMAT_SYM {} | GET_SYM {} | HANDLER_SYM {} | HELP_SYM {} From 33d53c4c24881d4906cacc791c2049faa96a0ee6 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Sat, 9 Aug 2014 06:37:56 +0400 Subject: [PATCH 2/8] MDEV-6109: EXPLAIN JSON - Add first testcases - Don't overquote when printing conditions - Other small output fixes --- mysql-test/r/explain_json.result | 41 ++++++++++++++++++++++++++++++++ mysql-test/t/explain_json.test | 27 +++++++++++++++++++++ sql/item.cc | 8 ++++++- sql/my_json_writer.cc | 2 +- sql/my_json_writer.h | 8 ++++++- sql/mysqld.h | 6 ++++- sql/sql_explain.cc | 12 ++++++++-- sql/sql_parse.cc | 16 ++----------- 8 files changed, 100 insertions(+), 20 deletions(-) create mode 100644 mysql-test/r/explain_json.result create mode 100644 mysql-test/t/explain_json.test diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result new file mode 100644 index 00000000000..de902a7c31a --- /dev/null +++ b/mysql-test/r/explain_json.result @@ -0,0 +1,41 @@ +drop table if exists t0,t1; +create table t0(a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); +explain format=json select * from t0; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } +} +explain format=json select * from t0 where 1>2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "message": "Impossible WHERE" + } + } +} +explain format=json select * from t0 where a<3; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "(t0.a < 3)" + } + } +} +drop table t0; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test new file mode 100644 index 00000000000..e9306101f81 --- /dev/null +++ b/mysql-test/t/explain_json.test @@ -0,0 +1,27 @@ +# +# EXPLAIN FORMAT=JSON tests. These are tests developed for MariaDB. +# +--disable_warnings +drop table if exists t0,t1; +--enable_warnings + +create table t0(a int); +insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9); + +explain format=json select * from t0; + +explain format=json select * from t0 where 1>2; + +explain format=json select * from t0 where a<3; + +#create table t1 (a int, b int, filler char(32), key(a)); +#insert into t1 +#select +# A.a + B.a* 10 + C.a * 100, +# A.a + B.a* 10 + C.a * 100, +# 'filler' +#from t0 A, t0 B, t0 C; +# +#explain format=json select * from t0,t1 where t1.a=t0.a; + +drop table t0; diff --git a/sql/item.cc b/sql/item.cc index 21baf779781..8b1ceac7420 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2583,7 +2583,13 @@ void Item_ident::print(String *str, enum_query_type query_type) } if (db_name && db_name[0] && !alias_name_used) { - if (!(cached_table && cached_table->belong_to_view && + /* + When printing EXPLAIN, don't print database name when it's the same as + current database. + */ + bool skip_db= (query_type & QT_EXPLAIN) && !strcmp(thd->db, db_name); + if (!skip_db && + !(cached_table && cached_table->belong_to_view && cached_table->belong_to_view->compact_view_format)) { append_identifier(thd, str, d_name, (uint)strlen(d_name)); diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc index 206597da59e..d07b13a3d65 100644 --- a/sql/my_json_writer.cc +++ b/sql/my_json_writer.cc @@ -100,7 +100,7 @@ void Json_writer::add_double(double val) start_element(); char buf[64]; - my_snprintf(buf, sizeof(buf), "%lf", val); + my_snprintf(buf, sizeof(buf), "%lg", val); output.append(buf); element_started= false; } diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h index 403d7e0688c..101df7c3215 100644 --- a/sql/my_json_writer.h +++ b/sql/my_json_writer.h @@ -1,5 +1,11 @@ /* Todo: SkySQL copyrights */ + +/* + A class to write well-formed JSON documents. The documents are also formatted + for human readability. +*/ + class Json_writer { public: @@ -25,7 +31,7 @@ public: first_child(true) {} private: - // stack of (name, bool is_object_or_array) elements. + // TODO: a stack of (name, bool is_object_or_array) elements. int indent_level; enum { INDENT_SIZE = 2 }; diff --git a/sql/mysqld.h b/sql/mysqld.h index a1656a49047..5a7419bf32b 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -611,9 +611,13 @@ enum enum_query_type /// Without character set introducers. QT_WITHOUT_INTRODUCERS= (1 << 1), /// view internal representation (like QT_ORDINARY except ORDER BY clause) - QT_VIEW_INTERNAL= (1 << 2) + QT_VIEW_INTERNAL= (1 << 2), + /// This value means focus on readability, not on ability to parse back, etc. + QT_EXPLAIN= (1 << 4) }; + + /* query_id */ typedef int64 query_id_t; extern query_id_t global_query_id; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 496e113162a..846868197ac 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -774,10 +774,17 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai 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); - item->print(&str ,QT_ORDINARY); + + 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()); } @@ -848,7 +855,8 @@ void Explain_table_access::print_explain_json(Json_writer *writer, writer->add_member("key").add_str(key_str); /* `used_key_parts` */ - writer->add_member("used_key_parts").add_str("TODO"); + if (key_str.length()) + writer->add_member("used_key_parts").add_str("TODO"); StringBuffer<64> key_len_str; fill_key_len_str(&key_len_str); diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index ab2ef71d22b..ed2f76a223e 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -5269,21 +5269,9 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) top-level LIMIT */ result->reset_offset_limit(); - if (thd->lex->explain_json) + if (lex->explain_json) { - /* - Json_writer writer; - writer.start_object(); - thd->lex->explain->print_explain_json(&writer, thd->lex->analyze_stmt); - writer.end_object(); - - const CHARSET_INFO *cs= system_charset_info; - List item_list; - String *buf= &writer.output; - item_list.push_back(new Item_string(buf->ptr(), buf->length(), cs)); - result->send_data(item_list); - */ - thd->lex->explain->print_explain_json(result, thd->lex->analyze_stmt); + lex->explain->print_explain_json(result, lex->analyze_stmt); } else { From 84485dbe7c994ad938cd2ee90d5dee96059192e2 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Tue, 12 Aug 2014 15:02:09 +0400 Subject: [PATCH 3/8] MDEV-6109: EXPLAIN JSON Add pretty-printing of possible_keys column. --- mysql-test/r/explain_json.result | 31 +++++ mysql-test/t/explain_json.test | 18 +-- sql/my_json_writer.cc | 205 +++++++++++++++++++++++++++++-- sql/my_json_writer.h | 77 +++++++++++- 4 files changed, 306 insertions(+), 25 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index de902a7c31a..6f38892ef05 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -38,4 +38,35 @@ EXPLAIN } } } +create table t1 (a int, b int, filler char(32), key(a)); +insert into t1 +select +A.a + B.a* 10 + C.a * 100, +A.a + B.a* 10 + C.a * 100, +'filler' +from t0 A, t0 B, t0 C; +explain format=json select * from t0,t1 where t1.a=t0.a; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "(t0.a is not null)" + }, + "table": { + "table_name": "t1", + "access_type": "ref", + "possible_keys": ["a"], + "key": "a", + "used_key_parts": "TODO", + "key_length": "5", + "rows": 1, + "filtered": 100 + } + } +} drop table t0; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test index e9306101f81..aedff8d78cb 100644 --- a/mysql-test/t/explain_json.test +++ b/mysql-test/t/explain_json.test @@ -14,14 +14,14 @@ explain format=json select * from t0 where 1>2; explain format=json select * from t0 where a<3; -#create table t1 (a int, b int, filler char(32), key(a)); -#insert into t1 -#select -# A.a + B.a* 10 + C.a * 100, -# A.a + B.a* 10 + C.a * 100, -# 'filler' -#from t0 A, t0 B, t0 C; -# -#explain format=json select * from t0,t1 where t1.a=t0.a; +create table t1 (a int, b int, filler char(32), key(a)); +insert into t1 +select + A.a + B.a* 10 + C.a * 100, + A.a + B.a* 10 + C.a * 100, + 'filler' +from t0 A, t0 B, t0 C; + +explain format=json select * from t0,t1 where t1.a=t0.a; drop table t0; diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc index d07b13a3d65..201b2f955ff 100644 --- a/sql/my_json_writer.cc +++ b/sql/my_json_writer.cc @@ -16,6 +16,8 @@ void Json_writer::append_indent() void Json_writer::start_object() { + fmt_helper.on_start_object(); + if (!element_started) start_element(); @@ -28,6 +30,9 @@ void Json_writer::start_object() void Json_writer::start_array() { + if (fmt_helper.on_start_array()) + return; + if (!element_started) start_element(); @@ -50,6 +55,8 @@ void Json_writer::end_object() void Json_writer::end_array() { + if (fmt_helper.on_end_array()) + return; indent_level-=INDENT_SIZE; if (!first_child) append_indent(); @@ -59,6 +66,9 @@ void Json_writer::end_array() Json_writer& Json_writer::add_member(const char *name) { + if (fmt_helper.on_add_member(name)) + return *this; // handled + // assert that we are in an object DBUG_ASSERT(!element_started); start_element(); @@ -69,6 +79,18 @@ Json_writer& Json_writer::add_member(const char *name) return *this; } +/* Used by formatting helper to print something that is formatted by the helper. */ +void Json_writer::start_sub_element() +{ + //element_started= true; + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + void Json_writer::start_element() { @@ -84,30 +106,44 @@ void Json_writer::start_element() void Json_writer::add_ll(longlong val) { - if (!element_started) - start_element(); - char buf[64]; my_snprintf(buf, sizeof(buf), "%ld", val); - output.append(buf); - element_started= false; + add_unquoted_str(buf); } void Json_writer::add_double(double val) { + char buf[64]; + my_snprintf(buf, sizeof(buf), "%lg", val); + add_unquoted_str(buf); +} + + +void Json_writer::add_bool(bool val) +{ + add_unquoted_str(val? "true" : "false"); +} + + +void Json_writer::add_unquoted_str(const char* str) +{ + if (fmt_helper.on_add_str(str)) + return; + if (!element_started) start_element(); - char buf[64]; - my_snprintf(buf, sizeof(buf), "%lg", val); - output.append(buf); + output.append(str); element_started= false; } void Json_writer::add_str(const char *str) { + if (fmt_helper.on_add_str(str)) + return; + if (!element_started) start_element(); @@ -117,13 +153,158 @@ void Json_writer::add_str(const char *str) element_started= false; } -void Json_writer::add_bool(bool val) -{ - add_str(val? "true" : "false"); -} void Json_writer::add_str(const String &str) { add_str(str.ptr()); } + +bool Single_line_formatting_helper::on_add_member(const char *name) +{ + DBUG_ASSERT(state== INACTIVE || state == DISABLED); + if (state != DISABLED) + { + // remove everything from the array + buf_ptr= buffer; + + //append member name to the array + size_t len= strlen(name); + if (len < MAX_LINE_LEN) + { + memcpy(buf_ptr, name, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + + line_len= owner->indent_level + len + 1; + state= ADD_MEMBER; + return true; // handled + } + } + return false; // not handled +} + +bool Single_line_formatting_helper::on_start_array() +{ + if (state == ADD_MEMBER) + { + state= IN_ARRAY; + return true; // handled + } + else + { + state= INACTIVE; + // TODO: what if we have accumulated some stuff already? shouldn't we + // flush it? + return false; // not handled + } +} + +bool Single_line_formatting_helper::on_end_array() +{ + if (state == IN_ARRAY) + { + flush_on_one_line(); + state= INACTIVE; + return true; // handled + } + return false; // not handled +} + +void Single_line_formatting_helper::on_start_object() +{ + // Nested objects will not be printed on one line + disable_and_flush(); +} + +bool Single_line_formatting_helper::on_add_str(const char *str) +{ + if (state == IN_ARRAY) + { + size_t len= strlen(str); + + // New length will be: + // "$string", + // quote + quote + comma + space = 4 + if (line_len + len + 4 > MAX_LINE_LEN) + { + disable_and_flush(); + return false; // didn't handle the last element + } + + //append string to array + memcpy(buf_ptr, str, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + line_len += len + 4; + return true; // handled + } + + disable_and_flush(); + return false; // not handled +} + +void Single_line_formatting_helper::flush_on_one_line() +{ + // append everything to output on one line + owner->start_sub_element(); + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + + if (nr == 0) + { + owner->output.append('"'); + owner->output.append(str); + owner->output.append("\": "); + owner->output.append('['); + } + else + { + if (nr != 1) + owner->output.append(", "); + owner->output.append('"'); + owner->output.append(str); + owner->output.append('"'); + } + nr++; + + while (*ptr!=0) + ptr++; + ptr++; + } + owner->output.append(']'); +} + + +void Single_line_formatting_helper::disable_and_flush() +{ + state= DISABLED; + // deactivate ourselves and flush all accumulated calls. + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + if (nr == 0) + { + owner->add_member(str); + } + else + { + if (nr == 1) + owner->start_array(); + owner->add_str(str); + } + + nr++; + while (*ptr!=0) + ptr++; + ptr++; + } + buf_ptr= buffer; + state= INACTIVE; +} + diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h index 101df7c3215..d92f98f20d8 100644 --- a/sql/my_json_writer.h +++ b/sql/my_json_writer.h @@ -1,5 +1,64 @@ /* Todo: SkySQL copyrights */ +class Json_writer; + +/* + The idea is to catch arrays that can be printed on one line: + + arrayName : [ "boo", 123, 456 ] + + and actually print them on one line. Arrrays that occupy too much space on + the line, or have nested members cannot be printed on one line. + + We hook into JSON printing functions and try to detect the pattern. While + detecting the pattern, we will accumulate "boo", 123, 456 as strings. + + Then, + - either the pattern is broken, and we print the elements out, + - or the pattern lasts till the end of the array, and we print the + array on one line. + + TODO: + fix the quoting. If we start to accumulate an array and but then it grows + too large to be printed on one line, the elements will be printed as + strings (even if some of them could be initially numbers). +*/ + +class Single_line_formatting_helper +{ + enum enum_state + { + INACTIVE, + ADD_MEMBER, + IN_ARRAY, + DISABLED + }; + + enum enum_state state; + enum { MAX_LINE_LEN= 80 }; + char buffer[80]; + char *buf_ptr; + uint line_len; + + Json_writer *owner; +public: + Single_line_formatting_helper() : state(INACTIVE), buf_ptr(buffer) {} + + void init(Json_writer *owner_arg) { owner= owner_arg; } + + bool on_add_member(const char *name); + + bool on_start_array(); + bool on_end_array(); + void on_start_object(); + // on_end_object() is not needed. + + bool on_add_str(const char *str); + + void flush_on_one_line(); + void disable_and_flush(); +}; + /* A class to write well-formed JSON documents. The documents are also formatted @@ -13,12 +72,16 @@ public: Json_writer& add_member(const char *name); /* Add atomic values */ - void add_ll(longlong val); void add_str(const char* val); void add_str(const String &str); + + void add_ll(longlong val); void add_double(double val); void add_bool(bool val); - + +private: + void add_unquoted_str(const char* val); +public: /* Start a child object */ void start_object(); void start_array(); @@ -29,18 +92,24 @@ public: Json_writer() : indent_level(0), document_start(true), element_started(false), first_child(true) - {} + { + fmt_helper.init(this); + } private: // TODO: a stack of (name, bool is_object_or_array) elements. int indent_level; enum { INDENT_SIZE = 2 }; - + + friend class Single_line_formatting_helper; bool document_start; bool element_started; bool first_child; + Single_line_formatting_helper fmt_helper; + void append_indent(); void start_element(); + void start_sub_element(); //const char *new_member_name; public: From a9d43d70f5d83ac652fd970f5b2b8dfdb77c1136 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Tue, 12 Aug 2014 18:14:56 +0400 Subject: [PATCH 4/8] EXPLAIN FORMAT=JSON: produce the 'ref' column. --- mysql-test/r/explain_json.result | 1 + sql/sql_explain.cc | 38 +++++++++++++++++++++++++++----- sql/sql_explain.h | 15 ++++++++----- sql/sql_select.cc | 36 +++++------------------------- 4 files changed, 49 insertions(+), 41 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index 6f38892ef05..8ef2b9f2e8c 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -64,6 +64,7 @@ EXPLAIN "key": "a", "used_key_parts": "TODO", "key_length": "5", + "ref": ["test.t0.a"], "rows": 1, "filtered": 100 } diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 846868197ac..f7fc3b106ce 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -253,7 +253,7 @@ static void push_string(List *item_list, String *str) system_charset_info)); } -static void push_string_list(List *item_list, List &lines, +static void push_string_list(List *item_list, String_list &lines, String *buf) { List_iterator_fast it(lines); @@ -677,10 +677,11 @@ 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); - else + StringBuffer<64> ref_list_buf; + if (ref_list.is_empty()) item_list.push_back(item_null); + else + push_string_list(&item_list, ref_list, &ref_list_buf); /* `rows` */ if (rows_set) @@ -772,6 +773,18 @@ 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))) + 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; @@ -857,12 +870,26 @@ void Explain_table_access::print_explain_json(Json_writer *writer, /* `used_key_parts` */ if (key_str.length()) writer->add_member("used_key_parts").add_str("TODO"); - + + /* `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); + + /* `ref` */ + // TODO: need to print this as an array. + if (!ref_list.is_empty()) + { + List_iterator_fast 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); @@ -873,6 +900,7 @@ void Explain_table_access::print_explain_json(Json_writer *writer, writer->add_member("r_rows").add_ll(avg_rows); } + /* `filtered` */ if (filtered_set) writer->add_member("filtered").add_double(filtered); diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 8708a74e661..26aa5753900 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -464,6 +464,13 @@ private: }; +class String_list: public List +{ +public: + bool append_str(MEM_ROOT *mem_root, const char *str); +}; + + /* EXPLAIN data structure for a single JOIN_TAB. */ @@ -489,9 +496,8 @@ public: StringBuffer<32> used_partitions; bool used_partitions_set; - /* Empty string means "NULL" will be printed */ - List possible_keys; - //StringBuffer<32> possible_keys_str; + /* Empty means "NULL" will be printed */ + String_list possible_keys; /* Index use: key name and length. @@ -509,8 +515,7 @@ public: */ Explain_index_use hash_next_key; - bool ref_set; /* not set means 'NULL' should be printed */ - StringBuffer<32> ref; + String_list ref_list; bool rows_set; /* not set means 'NULL' should be printed */ ha_rows rows; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index f772c53ebb9..213a7eae647 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -23261,23 +23261,14 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) /////////////////////////////////////////////////////////////////////////////// -// TODO: join with make_possible_keys_line ? -int append_possible_keys(MEM_ROOT *alloc, List &list, TABLE *table, +int append_possible_keys(MEM_ROOT *alloc, String_list &list, TABLE *table, key_map possible_keys) { uint j; for (j=0 ; j < table->s->keys ; j++) { if (possible_keys.is_set(j)) - { - const char *key_name= table->key_info[j].name; - size_t len= strlen(key_name); - char *cp; - if (!(cp = (char*)alloc_root(alloc, len))) - return 1; - memcpy(cp, key_name, len+1); - list.push_back(cp); - } + list.append_str(alloc, table->key_info[j].name); } return 0; } @@ -23312,13 +23303,10 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; - char buff4[512]; my_bool key_read; char table_name_buffer[SAFE_NAME_LEN]; - String tmp4(buff4,sizeof(buff4),cs); KEY *key_info= 0; uint key_len= 0; - tmp4.length(0); quick_type= -1; QUICK_SELECT_I *quick= NULL; @@ -23456,14 +23444,11 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab store_key **ref=tab->ref.key_copy; for (uint kp= 0; kp < tab->ref.key_parts; kp++) { - if (tmp4.length()) - tmp4.append(','); - if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map) - tmp4.append("const"); + eta->ref_list.append_str(thd->mem_root, "const"); else { - tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); + eta->ref_list.append_str(thd->mem_root, (*ref)->name()); ref++; } } @@ -23477,17 +23462,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab table->key_info[tab->index].key_length); } - if (key_info) - { - if (key_info && tab_type != JT_NEXT) - { - eta->ref.copy(tmp4); - eta->ref_set= true; - } - else - eta->ref_set= false; - } - else + if (!key_info) { if (table_list && /* SJM bushes don't have table_list */ table_list->schema_table && @@ -23517,7 +23492,6 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (key_name_buf.length()) eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); } - eta->ref_set= false; } /* "rows" */ From 041e03e251e783d51ca86e53112e3b87bd2da146 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Thu, 14 Aug 2014 01:12:05 +0400 Subject: [PATCH 5/8] EXPLAIN FORMAT=JSON: produce used_key_parts, JSON-ish output for index_merge. --- mysql-test/r/explain_json.result | 113 +++++++++++++++++++++++++++- mysql-test/t/explain_json.test | 21 +++++- sql/opt_range.cc | 4 +- sql/sql_explain.cc | 124 +++++++++++++++++++++++++++---- sql/sql_explain.h | 49 ++++++------ sql/sql_select.cc | 10 ++- 6 files changed, 274 insertions(+), 47 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index 8ef2b9f2e8c..2b2a4bf8fc9 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -38,13 +38,14 @@ EXPLAIN } } } +# Try a basic join create table t1 (a int, b int, filler char(32), key(a)); insert into t1 select -A.a + B.a* 10 + C.a * 100, -A.a + B.a* 10 + C.a * 100, +a.a + b.a* 10 + c.a * 100, +a.a + b.a* 10 + c.a * 100, 'filler' -from t0 A, t0 B, t0 C; +from t0 a, t0 b, t0 c; explain format=json select * from t0,t1 where t1.a=t0.a; EXPLAIN { @@ -62,12 +63,116 @@ EXPLAIN "access_type": "ref", "possible_keys": ["a"], "key": "a", - "used_key_parts": "TODO", "key_length": "5", + "used_key_parts": ["a"], "ref": ["test.t0.a"], "rows": 1, "filtered": 100 } } } +# Try range and index_merge +create table t2 (a1 int, a2 int, b1 int, b2 int, key(a1,a2), key(b1,b2)); +insert into t2 select a,a,a,a from t1; +explain format=json select * from t2 where a1<5; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "range", + "possible_keys": ["a1"], + "key": "a1", + "key_length": "5", + "used_key_parts": ["a1"], + "rows": 5, + "filtered": 100, + "index_condition": "(t2.a1 < 5)" + } + } +} +explain format=json select * from t2 where a1=1 or b1=2; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "index_merge", + "possible_keys": ["a1", "b1"], + "key_length": "5,5", + "index_merge": { + "sort_union": { + "range": { + "key": "a1", + "used_key_parts": ["a1"] + }, + "range": { + "key": "b1", + "used_key_parts": ["b1"] + } + } + }, + "rows": 2, + "filtered": 100, + "attached_condition": "((t2.a1 = 1) or (t2.b1 = 2))" + } + } +} +explain format=json select * from t2 where a1=1 or (b1=2 and b2=3); +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t2", + "access_type": "index_merge", + "possible_keys": ["a1", "b1"], + "key_length": "5,10", + "index_merge": { + "sort_union": { + "range": { + "key": "a1", + "used_key_parts": ["a1"] + }, + "range": { + "key": "b1", + "used_key_parts": ["b1", "b2"] + } + } + }, + "rows": 2, + "filtered": 100, + "attached_condition": "((t2.a1 = 1) or ((t2.b1 = 2) and (t2.b2 = 3)))" + } + } +} +# Try ref access on two key components +explain format=json select * from t0,t2 where t2.b1=t0.a and t2.b2=4; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "(t0.a is not null)" + }, + "table": { + "table_name": "t2", + "access_type": "ref", + "possible_keys": ["b1"], + "key": "b1", + "key_length": "10", + "used_key_parts": ["b1", "b2"], + "ref": ["test.t0.a", "const"], + "rows": 1, + "filtered": 100 + } + } +} +drop table t1; drop table t0; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test index aedff8d78cb..887ef66bff9 100644 --- a/mysql-test/t/explain_json.test +++ b/mysql-test/t/explain_json.test @@ -14,14 +14,29 @@ explain format=json select * from t0 where 1>2; explain format=json select * from t0 where a<3; +--echo # Try a basic join create table t1 (a int, b int, filler char(32), key(a)); insert into t1 select - A.a + B.a* 10 + C.a * 100, - A.a + B.a* 10 + C.a * 100, + a.a + b.a* 10 + c.a * 100, + a.a + b.a* 10 + c.a * 100, 'filler' -from t0 A, t0 B, t0 C; +from t0 a, t0 b, t0 c; explain format=json select * from t0,t1 where t1.a=t0.a; +--echo # Try range and index_merge +create table t2 (a1 int, a2 int, b1 int, b2 int, key(a1,a2), key(b1,b2)); +insert into t2 select a,a,a,a from t1; + +explain format=json select * from t2 where a1<5; + +explain format=json select * from t2 where a1=1 or b1=2; +explain format=json select * from t2 where a1=1 or (b1=2 and b2=3); + +--echo # Try ref access on two key components + +explain format=json select * from t0,t2 where t2.b1=t0.a and t2.b2=4; + +drop table t1; drop table t0; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index fc2aa75e604..88d8b0551cb 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -12105,7 +12105,7 @@ Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc) { Explain_quick_select *res; if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE))) - res->range.set(alloc, head->key_info[index].name, max_used_key_length); + res->range.set(alloc, &head->key_info[index], max_used_key_length); return res; } @@ -12114,7 +12114,7 @@ Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc) { Explain_quick_select *res; if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX))) - res->range.set(alloc, head->key_info[index].name, max_used_key_length); + res->range.set(alloc, &head->key_info[index], max_used_key_length); return res; } diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index f7fc3b106ce..9571e5a2886 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -541,7 +541,7 @@ void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) } -void Explain_table_access::fill_key_str(String *key_str) +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 || @@ -562,7 +562,10 @@ void Explain_table_access::fill_key_str(String *key_str) if (quick_info) { StringBuffer<64> buf2; - quick_info->print_key(&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) @@ -570,6 +573,16 @@ void Explain_table_access::fill_key_str(String *key_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) { bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || @@ -601,6 +614,35 @@ void Explain_table_access::fill_key_len_str(String *key_len_str) } +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 @@ -660,7 +702,7 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai /* `key` */ StringBuffer<64> key_str; - fill_key_str(&key_str); + fill_key_str(&key_str, false); if (key_str.length() > 0) push_string(&item_list, &key_str); @@ -861,24 +903,51 @@ void Explain_table_access::print_explain_json(Json_writer *writer, writer->add_str(name); writer->end_array(); } - /* `key` */ - StringBuffer<64> key_str; - fill_key_str(&key_str); - if (key_str.length()) - writer->add_member("key").add_str(key_str); - /* `used_key_parts` */ - if (key_str.length()) - writer->add_member("used_key_parts").add_str("TODO"); - + /* `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 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` */ - // TODO: need to print this as an array. if (!ref_list.is_empty()) { List_iterator_fast it(ref_list); @@ -1046,6 +1115,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 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 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) { diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 26aa5753900..2500b3d3c05 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -14,6 +14,15 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +class String_list: public List +{ +public: + bool append_str(MEM_ROOT *mem_root, const char *str); +}; + + + /* Data structures for ANALYZE */ class Table_access_tracker { @@ -417,21 +426,16 @@ class Explain_index_use : public Sql_alloc { char *key_name; uint key_len; - /* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */ public: - - void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_arg) + String_list key_parts_list; + + void clear() { - 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= key_len_arg; + key_name= NULL; + key_len= (uint)-1; } + void set(MEM_ROOT *root, KEY *key_name, uint key_len_arg); + void set_pseudo_key(MEM_ROOT *root, const char *key_name); inline const char *get_key_name() { return key_name; } inline uint get_key_len() { return key_len; } @@ -448,6 +452,13 @@ public: {} const int quick_type; + + bool is_basic() + { + return (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX); + } /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ Explain_index_use range; @@ -458,19 +469,15 @@ public: void print_extra(String *str); void print_key(String *str); void print_key_len(String *str); -private: + + void print_json(Json_writer *writer); + void print_extra_recursive(String *str); +private: const char *get_name_by_type(); }; -class String_list: public List -{ -public: - bool append_str(MEM_ROOT *mem_root, const char *str); -}; - - /* EXPLAIN data structure for a single JOIN_TAB. */ @@ -565,7 +572,7 @@ public: private: void append_tag_name(String *str, enum explain_extra_tag tag); - void fill_key_str(String *key_str); + void fill_key_str(String *key_str, bool is_json); void fill_key_len_str(String *key_len_str); double get_r_filtered(); void tag_to_json(Json_writer *writer, enum explain_extra_tag tag); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 213a7eae647..8ef3f64016e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -23310,7 +23310,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab quick_type= -1; QUICK_SELECT_I *quick= NULL; - eta->key.set(thd->mem_root, NULL, (uint)-1); + eta->key.clear(); eta->quick_info= NULL; tab->tracker= &eta->tracker; @@ -23437,7 +23437,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (key_info) /* 'index' or 'ref' access */ { - eta->key.set(thd->mem_root, key_info->name, key_len); + eta->key.set(thd->mem_root, key_info, key_len); if (tab->ref.key_parts && tab_type != JT_FT) { @@ -23458,8 +23458,10 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { eta->hash_next_key.set(thd->mem_root, - table->key_info[tab->index].name, + & table->key_info[tab->index], table->key_info[tab->index].key_length); + // psergey-todo: ^ is the above correct? are we necessarily joining on all + // columns? } if (!key_info) @@ -23490,7 +23492,7 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab } if (key_name_buf.length()) - eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); + eta->key.set_pseudo_key(thd->mem_root, key_name_buf.c_ptr_safe()); } } From 1f3724a5831cd6529f4917b0a977d456465c68fe Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Thu, 20 Nov 2014 21:09:57 +0300 Subject: [PATCH 6/8] Better comments --- sql/sql_select.cc | 18 ++++++++++++++++++ sql/table.h | 6 ++++++ 2 files changed, 24 insertions(+) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 7c1a9810db0..468f9fb41e3 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -20736,6 +20736,24 @@ static void free_blobs(Field **ptr) } +/* + @brief + Remove duplicates from a temporary table. + + @detail + Remove duplicate rows from a temporary table. This is used for e.g. queries + like + + select distinct count(*) as CNT from tbl group by col + + Here, we get a group table with count(*) values. It is not possible to + prevent duplicates from appearing in the table (as we don't know the values + before we've done the grouping). Because of that, we have this function to + scan the temptable (maybe, multiple times) and remove the duplicate rows + + Rows that do not satisfy 'having' condition are also removed. +*/ + static int remove_duplicates(JOIN *join, TABLE *table, List &fields, Item *having) { diff --git a/sql/table.h b/sql/table.h index eca35d6c52c..a4b3abdd803 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1072,6 +1072,12 @@ public: TABLE_LIST *pos_in_table_list;/* Element referring to this table */ /* Position in thd->locked_table_list under LOCK TABLES */ TABLE_LIST *pos_in_locked_tables; + + /* + Not-null for temporary tables only. Non-null values means this table is + used to compute GROUP BY, it has a unique of GROUP BY columns. + (set by create_tmp_table) + */ ORDER *group; String alias; /* alias or table name */ uchar *null_flags; From 305dd8e5fba804d8006d34ca7cb05bec5a3b45e6 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Fri, 21 Nov 2014 20:45:18 +0300 Subject: [PATCH 7/8] Better comments --- sql/sql_select.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sql/sql_select.h b/sql/sql_select.h index 41ee2cb51d2..cf3e1b353ab 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -982,7 +982,13 @@ public: */ uint top_join_tab_count; uint send_group_parts; - bool group; /**< If query contains GROUP BY clause */ + /* + True if the query has GROUP BY. + (that is, if group_by != NULL. when DISTINCT is converted into GROUP BY, it + will set this, too. It is not clear why we need a separate var from + group_list) + */ + bool group; bool need_distinct; /** From 3c5ce8a0a32a74cd8e0ddc81bcfacf7c85f0d90a Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Fri, 21 Nov 2014 21:44:06 +0300 Subject: [PATCH 8/8] Make testsuite to pass - Drop all tables in explain_json.test - Tabular form should print ref='' when type='fulltext' (another peculiarity of the traditional EXPLAIN format) - String_list::append_str should allocate memory for \0, too - Some temporary code for EXPLAIN JSON and join buffering. --- mysql-test/r/explain_json.result | 2 +- mysql-test/t/explain_json.test | 2 +- sql/sql_explain.cc | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index 2b2a4bf8fc9..7621332f226 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -174,5 +174,5 @@ EXPLAIN } } } -drop table t1; +drop table t1,t2; drop table t0; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test index 887ef66bff9..0b7bfb030d9 100644 --- a/mysql-test/t/explain_json.test +++ b/mysql-test/t/explain_json.test @@ -38,5 +38,5 @@ explain format=json select * from t2 where a1=1 or (b1=2 and b2=3); explain format=json select * from t0,t2 where t2.b1=t0.a and t2.b2=4; -drop table t1; +drop table t1,t2; drop table t0; diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index a468d16bbe4..e8bc907a9fb 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -721,7 +721,15 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai /* `ref` */ StringBuffer<64> ref_list_buf; if (ref_list.is_empty()) - item_list.push_back(item_null); + { + if (type == JT_FT) + { + /* Traditionally, EXPLAIN lines with type=fulltext have ref='' */ + push_str(&item_list, ""); + } + else + item_list.push_back(item_null); + } else push_string_list(&item_list, ref_list, &ref_list_buf); @@ -819,7 +827,7 @@ 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))) + if (!(cp = (char*)alloc_root(mem_root, len+1))) return 1; memcpy(cp, str, len+1); push_back(cp); @@ -880,6 +888,9 @@ void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_t case ET_USING: // index merge: case ET_USING break; + case ET_USING_JOIN_BUFFER: + // TODO TODO + break; default: DBUG_ASSERT(0); }