From 37c444e1a079b25d0a34efbbc2fadfae17999966 Mon Sep 17 00:00:00 2001 From: Sergei Petrunia Date: Thu, 27 Nov 2014 19:32:48 +0300 Subject: [PATCH] EXPLAIN FORMAT=JSON: further development Writing JSON: - Fix a bug in Single_line_formatting_helper - Add Json_writer_nesting_guard - safety class EXPLAIN JSON support - Add basic subquery support - Add tests for UNION/UNION ALL. --- mysql-test/r/explain_json.result | 134 +++++++++++++++++++++++++++++++ mysql-test/t/explain_json.test | 20 +++++ sql/item.cc | 7 ++ sql/item_subselect.cc | 19 +++++ sql/item_subselect.h | 1 + sql/my_json_writer.cc | 24 +++++- sql/my_json_writer.h | 61 +++++++++++++- sql/sql_explain.cc | 44 +++++++--- sql/sql_explain.h | 2 + 9 files changed, 298 insertions(+), 14 deletions(-) diff --git a/mysql-test/r/explain_json.result b/mysql-test/r/explain_json.result index 7621332f226..d2225c1e189 100644 --- a/mysql-test/r/explain_json.result +++ b/mysql-test/r/explain_json.result @@ -175,4 +175,138 @@ EXPLAIN } } drop table t1,t2; +# +# Try a UNION +# +explain format=json select * from t0 A union select * from t0 B; +EXPLAIN +{ + "query_block": { + "union_result": { + "table_name": "", + "access_type": "ALL", + "query_specifications": [ + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "A", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + }, + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "B", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + ] + } + } +} +explain format=json select * from t0 A union all select * from t0 B; +EXPLAIN +{ + "query_block": { + "union_result": { + "table_name": "", + "access_type": "ALL", + "query_specifications": [ + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "A", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + }, + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "B", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + } + } + } + ] + } + } +} +# +# Subqueries +# +create table t1 (a int, b int); +insert into t1 select a,a from t0; +explain format=json select a, a > (select max(b) from t1 where t1.b=t0.a) from t0; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100 + }, + "subqueries": [ + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "(t1.b = t0.a)" + } + } + } + ] + } +} +explain format=json +select * from t0 where +a > (select max(b) from t1 where t1.b=t0.a) or a < 3 ; +EXPLAIN +{ + "query_block": { + "select_id": 1, + "table": { + "table_name": "t0", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "((t0.a > (subquery#2)) or (t0.a < 3))" + }, + "subqueries": [ + { + "query_block": { + "select_id": 1, + "table": { + "table_name": "t1", + "access_type": "ALL", + "rows": 10, + "filtered": 100, + "attached_condition": "(t1.b = t0.a)" + } + } + } + ] + } +} +drop table t1; drop table t0; diff --git a/mysql-test/t/explain_json.test b/mysql-test/t/explain_json.test index 0b7bfb030d9..32932e0ff69 100644 --- a/mysql-test/t/explain_json.test +++ b/mysql-test/t/explain_json.test @@ -39,4 +39,24 @@ 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,t2; + +--echo # +--echo # Try a UNION +--echo # +explain format=json select * from t0 A union select * from t0 B; +explain format=json select * from t0 A union all select * from t0 B; + +--echo # +--echo # Subqueries +--echo # +create table t1 (a int, b int); +insert into t1 select a,a from t0; +explain format=json select a, a > (select max(b) from t1 where t1.b=t0.a) from t0; + +explain format=json +select * from t0 where + a > (select max(b) from t1 where t1.b=t0.a) or a < 3 ; + +drop table t1; drop table t0; + diff --git a/sql/item.cc b/sql/item.cc index 6d163d7956a..b22c21326de 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -7536,6 +7536,13 @@ void Item_cache_wrapper::init_on_demand() void Item_cache_wrapper::print(String *str, enum_query_type query_type) { + if (query_type == QT_EXPLAIN) + { + /* Don't print the cache in EXPLAIN EXTENDED */ + orig_item->print(str, query_type); + return; + } + str->append(func_name()); if (expr_cache) { diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index a62bb175a86..5786c2d5fdd 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -894,6 +894,21 @@ void Item_subselect::update_used_tables() void Item_subselect::print(String *str, enum_query_type query_type) { + if (query_type == QT_EXPLAIN) + { + str->append("(subquery#"); + if (engine) + { + char buf[64]; + ll2str(engine->get_identifier(), buf, 10, 0); + str->append(buf); + } + else + str->append("NULL"); // TODO: what exactly does this mean? + + str->append(")"); + return; + } if (engine) { str->append('('); @@ -3716,6 +3731,10 @@ int subselect_union_engine::exec() return res; } +int subselect_union_engine::get_identifier() +{ + return unit->first_select()->select_number; +} /* Search for at least one row satisfying select condition diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 92b269d02f1..02e487c82d0 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -871,6 +871,7 @@ public: bool is_executed() const; bool no_rows(); virtual enum_engine_type engine_type() { return UNION_ENGINE; } + int get_identifier(); }; diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc index 201b2f955ff..99be150ea93 100644 --- a/sql/my_json_writer.cc +++ b/sql/my_json_writer.cc @@ -79,7 +79,12 @@ Json_writer& Json_writer::add_member(const char *name) return *this; } -/* Used by formatting helper to print something that is formatted by the helper. */ + +/* + Used by formatting helper to print something that is formatted by the helper. + We should only separate it from the previous element. +*/ + void Json_writer::start_sub_element() { //element_started= true; @@ -184,6 +189,7 @@ bool Single_line_formatting_helper::on_add_member(const char *name) return false; // not handled } + bool Single_line_formatting_helper::on_start_array() { if (state == ADD_MEMBER) @@ -200,6 +206,7 @@ bool Single_line_formatting_helper::on_start_array() } } + bool Single_line_formatting_helper::on_end_array() { if (state == IN_ARRAY) @@ -211,12 +218,14 @@ bool Single_line_formatting_helper::on_end_array() 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) @@ -244,9 +253,13 @@ bool Single_line_formatting_helper::on_add_str(const char *str) return false; // not handled } + +/* + Append everything accumulated to the output on one line +*/ + 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; @@ -281,6 +294,7 @@ void Single_line_formatting_helper::flush_on_one_line() void Single_line_formatting_helper::disable_and_flush() { + bool start_array= (state == IN_ARRAY); state= DISABLED; // deactivate ourselves and flush all accumulated calls. char *ptr= buffer; @@ -291,11 +305,13 @@ void Single_line_formatting_helper::disable_and_flush() if (nr == 0) { owner->add_member(str); + if (start_array) + owner->start_array(); } else { - if (nr == 1) - owner->start_array(); + //if (nr == 1) + // owner->start_array(); owner->add_str(str); } diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h index d92f98f20d8..35bd5cba544 100644 --- a/sql/my_json_writer.h +++ b/sql/my_json_writer.h @@ -34,9 +34,30 @@ class Single_line_formatting_helper DISABLED }; + /* + This works like a finite automaton. + + state=DISABLED means the helper is disabled - all on_XXX functions will + return false (which means "not handled") and do nothing. + + +->-+ + | v + INACTIVE ---> ADD_MEMBER ---> IN_ARRAY--->-+ + ^ | + +------------------<--------------------+ + + For other states: + INACTIVE - initial state, we have nothing. + ADD_MEMBER - add_member() was called, the buffer has "member_name\0". + IN_ARRAY - start_array() was called. + + + */ enum enum_state state; enum { MAX_LINE_LEN= 80 }; char buffer[80]; + + /* The data in the buffer is located between buffer[0] and buf_ptr */ char *buf_ptr; uint line_len; @@ -99,8 +120,9 @@ 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; + friend class Json_writer_nesting_guard; bool document_start; bool element_started; bool first_child; @@ -116,3 +138,40 @@ public: String output; }; + +/* + RAII-based helper class to detect incorrect use of Json_writer. + + The idea is that a function typically must leave Json_writer at the same + identation level as it was when it was invoked. Leaving it at a different + level typically means we forgot to close an object or an array + + So, here is a way to guard + void foo(Json_writer *writer) + { + Json_writer_nesting_guard(writer); + .. do something with writer + + // at the end of the function, ~Json_writer_nesting_guard() is called + // and it makes sure that the nesting is the same as when the function was + // entered. + } +*/ + +class Json_writer_nesting_guard +{ + Json_writer* writer; + int indent_level; +public: + Json_writer_nesting_guard(Json_writer *writer_arg) : + writer(writer_arg), + indent_level(writer->indent_level) + {} + + ~Json_writer_nesting_guard() + { + DBUG_ASSERT(indent_level == writer->indent_level); + } +}; + + diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index ecc406bd5e9..2e60c65aa2e 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -303,7 +303,7 @@ int Explain_union::print_explain(Explain_query *query, uint8 explain_flags, bool is_analyze) { - const CHARSET_INFO *cs= system_charset_info; + //const CHARSET_INFO *cs= system_charset_info; char table_name_buffer[SAFE_NAME_LEN]; /* print all UNION children, in order */ @@ -391,6 +391,7 @@ 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(); @@ -404,17 +405,18 @@ void Explain_union::print_explain_json(Explain_query *query, 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"); + //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 + print_explain_json_for_children(query, writer, is_analyze); - writer->end_object(); + writer->end_object(); // union_result + writer->end_object(); // query_block } @@ -423,9 +425,9 @@ void Explain_union::print_explain_json(Explain_query *query, */ 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++) { @@ -437,6 +439,25 @@ int Explain_node::print_explain_for_children(Explain_query *query, } +void Explain_node::print_explain_json_for_children(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + if (!children.elements()) + return; + + writer->add_member("subqueries").start_array(); + for (int i= 0; i < (int) children.elements(); i++) + { + writer->start_object(); + Explain_node *node= query->get_node(children.at(i)); + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + writer->end_array(); +} + + void Explain_select::replace_table(uint idx, Explain_table_access *new_tab) { delete join_tabs[idx]; @@ -514,6 +535,8 @@ int Explain_select::print_explain(Explain_query *query, void Explain_select::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 (message) @@ -530,6 +553,8 @@ void Explain_select::print_explain_json(Explain_query *query, join_tabs[i]->print_explain_json(writer, is_analyze); } } + + print_explain_json_for_children(query, writer, is_analyze); writer->end_object(); } @@ -658,7 +683,7 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai uint select_id, const char *select_type, bool using_temporary, bool using_filesort) { - const CHARSET_INFO *cs= system_charset_info; + //const CHARSET_INFO *cs= system_charset_info; List item_list; Item *item_null= new Item_null(); @@ -899,6 +924,7 @@ void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_t void Explain_table_access::print_explain_json(Json_writer *writer, bool is_analyze) { + Json_writer_nesting_guard guard(writer); writer->add_member("table").start_object(); writer->add_member("table_name").add_str(table_name); diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 9c0c8ec0c11..39392d370d9 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -112,6 +112,8 @@ public: int print_explain_for_children(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json_for_children(Explain_query *query, + Json_writer *writer, bool is_analyze); virtual ~Explain_node(){} };