[Commits] 37c444e: EXPLAIN FORMAT=JSON: further development

Sergei Petrunia psergey at askmonty.org
Thu Nov 27 18:32:48 EET 2014


revision-id: 37c444e1a079b25d0a34efbbc2fadfae17999966
parent(s): 3d5f97fd708e12201636179baee2c8bc0093c109
committer: Sergei Petrunia
branch nick: 10.1-explain-json-r5
timestamp: 2014-11-27 19:32:48 +0300
message:

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 7621332..d2225c1 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": "<union1,2>",
+      "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": "<union1,2>",
+      "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 0b7bfb0..32932e0 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 6d163d7..b22c213 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 a62bb17..5786c2d 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 92b269d..02e487c 100644
--- a/sql/item_subselect.h
+++ b/sql/item_subselect.h
@@ -871,6 +871,7 @@ class subselect_union_engine: public subselect_engine
   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 201b2f9..99be150 100644
--- a/sql/my_json_writer.cc
+++ b/sql/my_json_writer.cc
@@ -79,7 +79,12 @@ void Json_writer::end_array()
   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 d92f98f..35bd5cb 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 @@ class Json_writer
   // 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 @@ class Json_writer
   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 ecc406b..2e60c65 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> 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 9c0c8ec..39392d3 100644
--- a/sql/sql_explain.h
+++ b/sql/sql_explain.h
@@ -112,6 +112,8 @@ class Explain_node : public Sql_alloc
 
   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(){}
 };
 


More information about the commits mailing list