From 98a8642fe827fd9ac16bdfaf556599fa509d4180 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Mon, 7 Oct 2013 17:29:51 +0400 Subject: [PATCH] MDEV-3798: EXPLAIN UPDATE/DELETE - Add support for EXPLAIN INSERT. --- mysql-test/r/explain_non_select.result | 17 ++++++++ mysql-test/t/explain_non_select.test | 15 +++++++ sql/sp_head.cc | 4 ++ sql/sql_delete.cc | 14 +----- sql/sql_explain.cc | 59 +++++++++++++++++++++++--- sql/sql_explain.h | 48 ++++++++++++++++++--- sql/sql_insert.cc | 47 +++++++++++++++++++- sql/sql_parse.cc | 18 +------- sql/sql_select.cc | 22 +++++++--- sql/sql_select.h | 2 +- sql/sql_update.cc | 17 ++------ 11 files changed, 201 insertions(+), 62 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index 3cc4d4f1e26..1c5d545fd99 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -181,3 +181,20 @@ explain partitions update t1 set b=12345 where a in (32,33); id select_type table partitions type possible_keys key key_len ref rows Extra 1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL No matching rows after partition pruning drop table t1; +# +# Tests for EXPLAIN INSERT ... VALUES +# +create table t1 (a int, key(a)); +explain insert into t1 values (1),(2),(3); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t1 ALL NULL NULL NULL NULL NULL NULL +insert into t1 values (1),(2),(3); +create table t2 (a int, b int); +explain insert into t2 values +(10, 1+(select max(a) from t1)), +(11, 1+(select max(a+1) from t1)); +id select_type table type possible_keys key key_len ref rows Extra +1 INSERT t2 ALL NULL NULL NULL NULL NULL NULL +3 SUBQUERY t1 index NULL a 5 NULL 3 Using index +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Select tables optimized away +drop table t1,t2; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index 3c4c2d0739d..7220a00bd2a 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -155,3 +155,18 @@ explain partitions delete from t1 where a in (32,33); explain partitions update t1 set b=12345 where a in (32,33); drop table t1; + +--echo # +--echo # Tests for EXPLAIN INSERT ... VALUES +--echo # +create table t1 (a int, key(a)); +explain insert into t1 values (1),(2),(3); +insert into t1 values (1),(2),(3); + +create table t2 (a int, b int); +explain insert into t2 values + (10, 1+(select max(a) from t1)), + (11, 1+(select max(a+1) from t1)); + +drop table t1,t2; + diff --git a/sql/sp_head.cc b/sql/sp_head.cc index 31fd0d7248e..ffc77224c12 100644 --- a/sql/sp_head.cc +++ b/sql/sp_head.cc @@ -328,6 +328,10 @@ sp_get_flags_for_command(LEX *lex) } case SQLCOM_UPDATE: case SQLCOM_UPDATE_MULTI: + case SQLCOM_INSERT: + case SQLCOM_REPLACE: + case SQLCOM_REPLACE_SELECT: + case SQLCOM_INSERT_SELECT: { if (!lex->describe) flags= 0; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index a9d97a6debc..c023a1eebf4 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -680,20 +680,8 @@ cleanup: /* Special exits */ exit_without_my_ok: query_plan.save_explain_data(thd->lex->explain); + int err2= thd->lex->explain->send_explain(thd); - select_send *result2; - if (!(result2= new select_send())) - return 1; /* purecov: inspected */ - List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ - result2->prepare(dummy, &thd->lex->unit); - thd->send_explain_fields(result2); - int err2= thd->lex->explain->print_explain(result2, thd->lex->describe); - - if (err2) - result2->abort_result_set(); - else - result2->send_eof(); - delete select; free_underlaid_joins(thd, select_lex); //table->set_keyread(false); diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index 28774ef2e99..2bd6a6bef25 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -22,9 +22,8 @@ #include "sql_select.h" -Explain_query::Explain_query() +Explain_query::Explain_query() : upd_del_plan(NULL), insert_plan(NULL) { - upd_del_plan= NULL; operations= 0; } @@ -32,6 +31,7 @@ Explain_query::Explain_query() Explain_query::~Explain_query() { delete upd_del_plan; + delete insert_plan; uint i; for (i= 0 ; i < unions.elements(); i++) delete unions.at(i); @@ -100,18 +100,46 @@ void Explain_query::add_node(Explain_node *node) } +/* + Send EXPLAIN output to the client. +*/ + +int Explain_query::send_explain(THD *thd) +{ + select_result *result; + LEX *lex= thd->lex; + + if (!(result= new select_send()) || + thd->send_explain_fields(result)) + return 1; + + int res; + if ((res= print_explain(result, lex->describe))) + result->abort_result_set(); + else + result->send_eof(); + + return res; +} + + /* The main entry point to print EXPLAIN of the entire query */ int Explain_query::print_explain(select_result_sink *output, - uint8 explain_flags) + uint8 explain_flags) { if (upd_del_plan) { upd_del_plan->print_explain(this, output, explain_flags); return 0; } + else if (insert_plan) + { + insert_plan->print_explain(this, output, explain_flags); + return 0; + } else { /* Start printing from node with id=1 */ @@ -681,7 +709,7 @@ const char * Explain_quick_select::get_name_by_type() return "sort_intersect"; default: DBUG_ASSERT(0); - return "Oops"; + return "unknown quick select type"; } } @@ -809,13 +837,34 @@ int Explain_update::print_explain(Explain_query *query, key_str.length()? key_str.c_ptr() : NULL, key_len_str.length() ? key_len_str.c_ptr() : NULL, NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ - rows, + &rows, extra_str.c_ptr()); return print_explain_for_children(query, output, explain_flags); } +int Explain_insert::print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags) +{ + const char *select_type="INSERT"; + print_explain_row(output, explain_flags, + 1, /* id */ + select_type, + table_name.c_ptr(), + NULL, // partitions + JT_ALL, + NULL, // possible_keys + NULL, // key + NULL, // key_len + NULL, // ref + NULL, // rows + NULL); + + return print_explain_for_children(query, output, explain_flags); +} + + void delete_explain_query(LEX *lex) { delete lex->explain; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 1eea61d98fb..07493cfa14d 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -37,10 +37,16 @@ class Explain_query; class Explain_node : public Sql_alloc { public: - enum explain_node_type {EXPLAIN_UNION, EXPLAIN_SELECT, EXPLAIN_UPDATE, EXPLAIN_DELETE }; + enum explain_node_type + { + EXPLAIN_UNION, + EXPLAIN_SELECT, + EXPLAIN_UPDATE, + EXPLAIN_DELETE, + EXPLAIN_INSERT + }; + virtual enum explain_node_type get_type()= 0; - - virtual int get_select_id()= 0; /* @@ -172,8 +178,10 @@ public: bool using_filesort; }; -class Explain_delete; +class Explain_update; +class Explain_delete; +class Explain_insert; /* Explain structure for a query (i.e. a statement). @@ -229,14 +237,20 @@ public: /* Explain_delete inherits from Explain_update */ Explain_update *upd_del_plan; + /* Query "plan" for INSERTs */ + Explain_insert *insert_plan; + /* Produce a tabular EXPLAIN output */ int print_explain(select_result_sink *output, uint8 explain_flags); + /* Send tabular EXPLAIN to the client */ + int send_explain(THD *thd); + /* Return tabular EXPLAIN output as a text string */ bool print_explain_str(THD *thd, String *out_str); /* If true, at least part of EXPLAIN can be printed */ - bool have_query_plan() { return upd_del_plan!= NULL || get_node(1) != NULL; } + bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } MEM_ROOT *mem_root; private: Dynamic_array unions; @@ -445,7 +459,7 @@ private: /* - Query Plan Footprint for single-table UPDATE. + EXPLAIN structure for single-table UPDATE. This is similar to Explain_table_access, except that it is more restrictive. Also, it can have UPDATE operation options, but currently there aren't any. @@ -482,8 +496,28 @@ public: }; +/* + EXPLAIN data structure for an INSERT. + + At the moment this doesn't do much as we don't really have any query plans + for INSERT statements. +*/ + +class Explain_insert : public Explain_node +{ +public: + StringBuffer<64> table_name; + + enum explain_node_type get_type() { return EXPLAIN_INSERT; } + int get_select_id() { return 1; /* always root */ } + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + /* - Explain data of a single-table DELETE. + EXPLAIN data of a single-table DELETE. */ class Explain_delete: public Explain_update diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 982524e7e32..10cd8a6a7f6 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -644,6 +644,36 @@ create_insert_stmt_from_insert_delayed(THD *thd, String *buf) } +static void save_insert_query_plan(THD* thd, TABLE_LIST *table_list) +{ + Explain_insert* explain= new Explain_insert; + explain->table_name.append(table_list->table->alias); + + thd->lex->explain->insert_plan= explain; + + /* See Update_plan::updating_a_view for details */ + bool skip= test(table_list->view); + + /* Save subquery children */ + for (SELECT_LEX_UNIT *unit= thd->lex->select_lex.first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (skip) + { + skip= false; + continue; + } + /* + Table elimination doesn't work for INSERTS, but let's still have this + here for consistency + */ + if (!(unit->item && unit->item->eliminated)) + explain->add_child(unit->first_select()->select_number); + } +} + + /** INSERT statement implementation @@ -660,6 +690,7 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, enum_duplicates duplic, bool ignore) { + bool retval= true; int error, res; bool transactional_table, joins_freed= FALSE; bool changed; @@ -780,6 +811,17 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, /* Restore the current context. */ ctx_state.restore_state(context, table_list); + + if (thd->lex->unit.first_select()->optimize_unflattened_subqueries(false)) + { + goto abort; + } + save_insert_query_plan(thd, table_list); + if (thd->lex->describe) + { + retval= 0; + goto exit_without_my_ok; + } /* Fill in the given fields and dump it to the table file @@ -1128,16 +1170,19 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list, DBUG_RETURN(FALSE); abort: +exit_without_my_ok: #ifndef EMBEDDED_LIBRARY if (lock_type == TL_WRITE_DELAYED) end_delayed_insert(thd); #endif if (table != NULL) table->file->ha_release_auto_increment(); + retval= thd->lex->explain->send_explain(thd); + if (!joins_freed) free_underlaid_joins(thd, &thd->lex->select_lex); thd->abort_on_warning= 0; - DBUG_RETURN(TRUE); + DBUG_RETURN(retval); } diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 808c2cdb421..3139cf4404f 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3245,14 +3245,7 @@ end_with_restore_list: } if (!res && explain) - { - select_result *result= new select_send(); - LEX *lex= thd->lex; - if (thd->send_explain_fields(result) || - lex->explain->print_explain(result, lex->describe) || - result->send_eof()) - res= 1; - } + res= thd->lex->explain->send_explain(thd); /* revert changes for SP */ MYSQL_INSERT_SELECT_DONE(res, (ulong) thd->get_row_count_func()); @@ -3341,14 +3334,7 @@ end_with_restore_list: else { if (explain) - { - select_result *result= new select_send(); - LEX *lex= thd->lex; - if (thd->send_explain_fields(result) || - lex->explain->print_explain(result, lex->describe) || - result->send_eof()) - res= 1; - } + res= thd->lex->explain->send_explain(thd); } delete result; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 00a9eb53fb9..72dc10a8917 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22238,7 +22238,10 @@ int print_explain_message_line(select_result_sink *result, if (options & DESCRIBE_EXTENDED) item_list.push_back(item_null); - item_list.push_back(new Item_string(message,strlen(message),cs)); + if (message) + item_list.push_back(new Item_string(message,strlen(message),cs)); + else + item_list.push_back(item_null); if (result->send_data(item_list)) return 1; @@ -22292,7 +22295,7 @@ int print_explain_row(select_result_sink *result, const char *index, const char *key_len, const char *ref, - ha_rows rows, + ha_rows *rows, const char *extra) { const CHARSET_INFO *cs= system_charset_info; @@ -22337,15 +22340,24 @@ int print_explain_row(select_result_sink *result, item_list.push_back(item); /* 'rows' */ - item_list.push_back(new Item_int(rows, - MY_INT64_NUM_DECIMAL_DIGITS)); + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + /* 'filtered' */ const double filtered=100.0; if (options & DESCRIBE_EXTENDED) item_list.push_back(new Item_float(filtered, 2)); /* 'Extra' */ - item_list.push_back(new Item_string(extra, strlen(extra), cs)); + if (extra) + item_list.push_back(new Item_string(extra, strlen(extra), cs)); + else + item_list.push_back(item_null); if (result->send_data(item_list)) return 1; diff --git a/sql/sql_select.h b/sql/sql_select.h index d6e133262f1..c615e855606 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1865,7 +1865,7 @@ int print_explain_row(select_result_sink *result, const char *index, const char *key_len, const char *ref, - ha_rows rows, + ha_rows *rows, const char *extra); void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index db32ad2a0c5..bc2c5c69adb 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -948,7 +948,7 @@ int mysql_update(THD *thd, if (!transactional_table && updated > 0) thd->transaction.stmt.modified_non_trans_table= TRUE; - thd->apc_target.disable(); //psergey-todo. + thd->apc_target.disable(); apc_target_enabled= false; end_read_record(&info); delete select; @@ -1035,19 +1035,8 @@ err: exit_without_my_ok: DBUG_ASSERT(!apc_target_enabled); query_plan.save_explain_data(thd->lex->explain); - - select_send *result; - if (!(result= new select_send())) - return 1; /* purecov: inspected */ - List dummy; /* note: looked in 5.6 and they too use a dummy list like this */ - result->prepare(dummy, &thd->lex->unit); - thd->send_explain_fields(result); - int err2= thd->lex->explain->print_explain(result, - thd->lex->describe); - if (err2) - result->abort_result_set(); - else - result->send_eof(); + + int err2= thd->lex->explain->send_explain(thd); delete select; free_underlaid_joins(thd, select_lex);