From 18fec5128b6fd9712f63e306f03f16833f2599b2 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 08:20:14 +0400 Subject: [PATCH 1/3] EXPLAIN DELETE for MariaDB - Backported the code to 10.0-base - Removed incorrect assert --- mysql-test/r/show_explain.result | 14 +- mysql-test/t/show_explain.test | 6 +- sql/my_apc.h | 2 + sql/sql_delete.cc | 221 ++++++++++++++++++++++++++++--- sql/sql_lex.cc | 31 +++-- sql/sql_lex.h | 48 +++++++ sql/sql_parse.cc | 80 ++++++++--- sql/sql_select.cc | 158 ++++++++++++++++++---- sql/sql_select.h | 22 +++ sql/sql_show.cc | 4 +- sql/sql_string.h | 32 +++++ sql/sql_update.cc | 5 +- sql/sql_yacc.yy | 10 +- 13 files changed, 550 insertions(+), 83 deletions(-) diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index da132a102e2..8baf4d40e7c 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -192,16 +192,24 @@ ERROR HY000: Target is not running an EXPLAINable command drop table t2; set debug_dbug=@old_debug; # -# Attempt SHOW EXPLAIN for a DELETE +# Attempt SHOW EXPLAIN for a DELETE (UPD: now works) # create table t2 as select a as a, a as dummy from t0 limit 2; set @show_explain_probe_select_id=2; set debug_dbug='+d,show_explain_probe_join_exec_start'; delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +Warnings: +Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; set debug_dbug=@old_debug; # diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index 57d86ece4f4..d87bf2f14d4 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -249,7 +249,7 @@ drop table t2; set debug_dbug=@old_debug; --echo # ---echo # Attempt SHOW EXPLAIN for a DELETE +--echo # Attempt SHOW EXPLAIN for a DELETE (UPD: now works) --echo # create table t2 as select a as a, a as dummy from t0 limit 2; set @show_explain_probe_select_id=2; @@ -257,10 +257,10 @@ set debug_dbug='+d,show_explain_probe_join_exec_start'; send delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; diff --git a/sql/my_apc.h b/sql/my_apc.h index 7f19809c082..ee94595e78f 100644 --- a/sql/my_apc.h +++ b/sql/my_apc.h @@ -64,6 +64,8 @@ public: { return test(apc_calls); } + + inline bool is_enabled() { return enabled; } /* Functor class for calls you can schedule */ class Apc_call diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 4bcb62ef764..d7a612f3d56 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -40,6 +40,129 @@ #include "records.h" // init_read_record, #include "sql_derived.h" // mysql_handle_list_of_derived // end_read_record + + +/* + @brief + Print query plan of a single-table DELETE command + + @detail + This function is used by EXPLAIN DELETE and by SHOW EXPLAIN when it is + invoked on a running DELETE statement. +*/ + +int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (deleting_all_rows || impossible_where) + { + const char *msg= deleting_all_rows? "Deleting all rows": "Impossible where"; + if (print_explain_message_line(output, explain_flags, 1/*select number*/, + "SIMPLE", msg)) + { + return 1; + } + *printed_anything= true; + return 0; + } + + select_lex->set_explain_type(FALSE); + /* + Print an EXPLAIN line. We dont have join, so we can't directly use + JOIN::print_explain. + We do have a SELECT_LEX (TODO but how is it useful? it has select_type.. + and that's it?) + */ + + enum join_type jtype; + if (select && select->quick) + { + int quick_type= select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + jtype= JT_INDEX_MERGE; + else + jtype= JT_RANGE; + } + else + { + if (index == MAX_KEY) + jtype= JT_ALL; + else + jtype= JT_NEXT; + } + + StringBuffer<128> possible_keys_line; + make_possible_keys_line(table, possible_keys, &possible_keys_line); + + const char *key_name; + const char *key_len; + + StringBuffer<128> key_str; + StringBuffer<128> key_len_str; + StringBuffer<128> extra_str; + + /* Calculate key_len */ + if (select && select->quick) + { + select->quick->add_keys_and_lengths(&key_str, &key_len_str); + key_name= key_str.c_ptr(); + key_len= key_len_str.c_ptr(); + } + else + { + key_name= (index == MAX_KEY)? NULL : table->key_info[index].name; + key_len= NULL; + } + + if (select && select->cond) + extra_str.append(STRING_WITH_LEN("Using where")); + if (select && select->quick && + select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE) + { + explain_append_mrr_info((QUICK_RANGE_SELECT*)select->quick, &extra_str); + } + + if (using_filesort) + { + if (extra_str.length() !=0) + extra_str.append(STRING_WITH_LEN("; ")); + extra_str.append(STRING_WITH_LEN("Using filesort")); + } + + /* + Single-table DELETE commands do not do "Using temporary". + "Using index condition" is also not possible (which is an unjustified limitation) + */ + + print_explain_row(output, explain_flags, + 1, /* id */ + select_lex->type, + table->pos_in_table_list->alias, + // partitions, + jtype, + possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, + key_name, + key_len, + NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ + select ? select->records : table_rows, + extra_str.c_ptr()); + + *printed_anything= true; + + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + if (unit->print_explain(output, explain_flags, printed_anything)) + return 1; + } + return 0; +} + + /** Implement DELETE SQL word. @@ -61,12 +184,16 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, bool const_cond_result; ha_rows deleted= 0; bool reverse= FALSE; + bool err= true; ORDER *order= (ORDER *) ((order_list && order_list->elements) ? order_list->first : NULL); - uint usable_index= MAX_KEY; SELECT_LEX *select_lex= &thd->lex->select_lex; killed_state killed_status= NOT_KILLED; THD::enum_binlog_query_type query_type= THD::ROW_QUERY_TYPE; + + Delete_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_delete"); if (open_and_lock_tables(thd, table_list, TRUE, 0)) @@ -90,6 +217,8 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, } thd_proc_info(thd, "init"); table->map=1; + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; if (mysql_prepare_delete(thd, table_list, &conds)) DBUG_RETURN(TRUE); @@ -163,6 +292,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, table->file->info(HA_STATUS_VARIABLE | HA_STATUS_NO_LOCK); ha_rows const maybe_deleted= table->file->stats.records; DBUG_PRINT("debug", ("Trying to use delete_all_rows()")); + + query_plan.set_delete_all_rows(maybe_deleted); + if (thd->lex->describe) + goto exit_without_my_ok; + if (!(error=table->file->ha_delete_all_rows())) { /* @@ -187,7 +321,12 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, Item::cond_result result; conds= remove_eq_conds(thd, conds, &result); if (result == Item::COND_FALSE) // Impossible where + { limit= 0; + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } #ifdef WITH_PARTITION_STORAGE_ENGINE @@ -195,6 +334,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { free_underlaid_joins(thd, select_lex); // No matching record + //psergey-explain-todo: No-partitions used EXPLAIN here.. my_ok(thd, 0); DBUG_RETURN(0); } @@ -211,6 +351,10 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, DBUG_RETURN(TRUE); if ((select && select->check_quick(thd, safe_update, limit)) || !limit) { + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -243,26 +387,46 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (order) { - uint length= 0; - SORT_FIELD *sortorder; - ha_rows examined_rows; - ha_rows found_rows; - table->update_const_key_parts(conds); order= simple_remove_const(order, conds); - bool need_sort; if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered") - need_sort= FALSE; - usable_index= MAX_KEY; + query_plan.using_filesort= FALSE; + query_plan.index= MAX_KEY; } else - usable_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); - if (need_sort) + query_plan.index= get_index_for_order(order, table, select, limit, + &query_plan.using_filesort, + &reverse); + } + + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; + thd->lex->delete_plan= &query_plan; + + /* + Ok, we have generated a query plan for the DELETE. + - if we're running EXPLAIN DELETE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto exit_without_my_ok; + + thd->apc_target.enable(); + DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", + dbug_serve_apcs(thd, 1);); + + if (query_plan.using_filesort) + { + ha_rows examined_rows; + ha_rows found_rows; + uint length= 0; + SORT_FIELD *sortorder; + { - DBUG_ASSERT(usable_index == MAX_KEY); + DBUG_ASSERT(query_plan.index == MAX_KEY); table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE), MYF(MY_FAE | MY_ZEROFILL | MY_THREAD_SPECIFIC)); @@ -276,6 +440,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, &thd->lex->select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } thd->examined_row_count+= examined_rows; @@ -294,19 +459,21 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, { delete select; free_underlaid_joins(thd, select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } - if (usable_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 1, 1, FALSE)) { delete select; free_underlaid_joins(thd, select_lex); + thd->apc_target.disable(); DBUG_RETURN(TRUE); } } else - init_read_record_idx(&info, thd, table, 1, usable_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); init_ftfuncs(thd, select_lex, 1); thd_proc_info(thd, "updating"); @@ -398,6 +565,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (options & OPTION_QUICK) (void) table->file->extra(HA_EXTRA_NORMAL); + thd->apc_target.disable(); cleanup: /* Invalidate the table in the query cache if something changed. This must @@ -458,6 +626,29 @@ cleanup: DBUG_PRINT("info",("%ld records deleted",(long) deleted)); } DBUG_RETURN(error >= 0 || thd->is_error()); + + /* Special exits */ +exit_without_my_ok: + thd->lex->delete_plan= &query_plan; + + select_send *result; + bool printed_anything; + 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->print_explain(result, 0 /* explain flags*/, &printed_anything); + + if (err2) + result->abort_result_set(); + else + result->send_eof(); + + delete select; + free_underlaid_joins(thd, select_lex); + //table->set_keyread(false); + DBUG_RETURN((err || thd->is_error() || thd->killed) ? 1 : 0); } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index b8ce3b6720e..74e4b3e1162 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,6 +448,7 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; + lex->delete_plan= NULL; lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); @@ -2557,6 +2558,7 @@ LEX::LEX() INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); + delete_plan= NULL; } @@ -4166,12 +4168,17 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } - -int print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, - uint8 options, - const char *message); +int LEX::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (delete_plan) + { + delete_plan->print_explain(output, explain_flags, printed_anything); + return 0; + } + int res= unit.print_explain(output, explain_flags, printed_anything); + return res; +} int st_select_lex::print_explain(select_result_sink *output, @@ -4235,8 +4242,9 @@ int st_select_lex::print_explain(select_result_sink *output, DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); msg= "Query plan already deleted"; } - res= print_explain_message_line(output, this, TRUE /* on_the_fly */, - 0, msg); + set_explain_type(TRUE/* on_the_fly */); + res= print_explain_message_line(output, 0/*options*/, select_number, type, + msg); } err: return res; @@ -4256,9 +4264,10 @@ int st_select_lex_unit::print_explain(select_result_sink *output, EXPLAIN state" error. */ const char *msg="Query plan already deleted"; - res= print_explain_message_line(output, first, TRUE /* on_the_fly */, - 0, msg); - return 0; + first->set_explain_type(TRUE/* on_the_fly */); + res= print_explain_message_line(output, 0/*options*/, first->select_number, + first->type, msg); + return res; } for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) diff --git a/sql/sql_lex.h b/sql/sql_lex.h index be7cdd8ca1d..af35ccb01b0 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2344,6 +2344,48 @@ protected: LEX *m_lex; }; +class Delete_plan; +class SQL_SELECT; + +/* Query plan of a single-table DELETE */ +class Delete_plan +{ + bool deleting_all_rows; + bool impossible_where; +public: + + TABLE *table; + SQL_SELECT *select; + uint index; + ha_rows table_rows; /* Use if select==NULL */ + bool using_filesort; + key_map possible_keys; + + /* + Top-level select_lex. Most of its fields are not used, we need it only to + get to the subqueries. + */ + SELECT_LEX *select_lex; + + /* Construction functions */ + Delete_plan() : + deleting_all_rows(false), impossible_where(false), using_filesort(false) {} + + /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ + void set_delete_all_rows(ha_rows rows_arg) + { + deleting_all_rows= true; + table_rows= rows_arg; + } + + /* Set this plan to be a plan to do nothing because of impossible WHRE*/ + void set_impossible_where() { impossible_where= true; } + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); +}; + + /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2355,6 +2397,9 @@ struct LEX: public Query_tables_list /* list of all SELECT_LEX */ SELECT_LEX *all_selects_list; + /* For single-table DELETE: its query plan */ + Delete_plan *delete_plan; + char *length,*dec,*change; LEX_STRING name; char *help_arg; @@ -2769,6 +2814,9 @@ struct LEX: public Query_tables_list } return FALSE; } + + int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); }; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0abb249d97b..d5fd5c1b32d 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -3212,7 +3212,8 @@ end_with_restore_list: { DBUG_ASSERT(first_table == all_tables && first_table != 0); TABLE_LIST *aux_tables= thd->lex->auxiliary_table_list.first; - multi_delete *del_result; + bool explain= test(lex->describe); + select_result *result; if ((res= multi_delete_precheck(thd, all_tables))) break; @@ -3227,37 +3228,72 @@ end_with_restore_list: if ((res= open_and_lock_tables(thd, all_tables, TRUE, 0))) break; - MYSQL_MULTI_DELETE_START(thd->query()); + if (!explain) + { + MYSQL_MULTI_DELETE_START(thd->query()); + } + if ((res= mysql_multi_delete_prepare(thd))) { - MYSQL_MULTI_DELETE_DONE(1, 0); + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(1, 0); + } goto error; } - if (!thd->is_fatal_error && - (del_result= new multi_delete(aux_tables, lex->table_count))) + if (!thd->is_fatal_error) { - res= mysql_select(thd, &select_lex->ref_pointer_array, - select_lex->get_table_list(), - select_lex->with_wild, - select_lex->item_list, - select_lex->where, - 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, - (ORDER *)NULL, - (select_lex->options | thd->variables.option_bits | - SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | - OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, - del_result, unit, select_lex); - res|= thd->is_error(); - MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); - if (res) - del_result->abort_result_set(); - delete del_result; + if (explain) + { + result= new select_send(); + if (thd->send_explain_fields(result)) + { + delete result; + result= NULL; + } + select_lex->set_explain_type(FALSE); + } + else + result= new multi_delete(aux_tables, lex->table_count); + + if (result) + { + res= mysql_select(thd, &select_lex->ref_pointer_array, + select_lex->get_table_list(), + select_lex->with_wild, + select_lex->item_list, + select_lex->where, + 0, (ORDER *)NULL, (ORDER *)NULL, (Item *)NULL, + (ORDER *)NULL, + (select_lex->options | thd->variables.option_bits | + SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | + OPTION_SETUP_TABLES_DONE) & ~OPTION_BUFFER_RESULT, + result, unit, select_lex); + res|= thd->is_error(); + + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(res, del_result->num_deleted()); + } + + if (res) + result->abort_result_set(); /* for both DELETE and EXPLAIN DELETE */ + else + { + if (explain) + result->send_eof(); + } + delete result; + } } else { res= TRUE; // Error - MYSQL_MULTI_DELETE_DONE(1, 0); + if (!explain) + { + MYSQL_MULTI_DELETE_DONE(1, 0); + } } break; } diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 41c0b07bc49..230355c550e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -21594,23 +21594,20 @@ void JOIN::clear() /* Print an EXPLAIN line with all NULLs and given message in the 'Extra' column */ + int print_explain_message_line(select_result_sink *result, - SELECT_LEX *select_lex, - bool on_the_fly, uint8 options, + uint select_number, + const char *select_type, const char *message) { const CHARSET_INFO *cs= system_charset_info; Item *item_null= new Item_null(); List item_list; - if (on_the_fly) - select_lex->set_explain_type(on_the_fly); - - item_list.push_back(new Item_int((int32) - select_lex->select_number)); - item_list.push_back(new Item_string(select_lex->type, - strlen(select_lex->type), cs)); + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); for (uint i=0 ; i < 7; i++) item_list.push_back(item_null); if (options & DESCRIBE_PARTITIONS) @@ -21626,6 +21623,104 @@ int print_explain_message_line(select_result_sink *result, } +/* + Make a comma-separated list of possible_keys names and add it into the string +*/ + +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line) +{ + if (!possible_keys.is_clear_all()) + { + uint j; + for (j=0 ; j < table->s->keys ; j++) + { + if (possible_keys.is_set(j)) + { + if (line->length()) + line->append(','); + line->append(table->key_info[j].name, + strlen(table->key_info[j].name), + system_charset_info); + } + } + } +} + +/* + Print an EXPLAIN output row, based on information provided in the parameters + + @note + Parameters that may have NULL value in EXPLAIN output, should be passed + (char*)NULL. + + @return + 0 - OK + 1 - OOM Error +*/ + +int print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + //const char *partitions, (todo) + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows rows, + const char *extra) +{ + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + List item_list; + Item *item; + + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + item_list.push_back(new Item_string(table_name, + strlen(table_name), cs)); + if (options & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); // psergey-todo: produce proper value + + const char *jtype_str= join_type_str[jtype]; + item_list.push_back(new Item_string(jtype_str, + strlen(jtype_str), cs)); + + item= possible_keys? new Item_string(possible_keys, strlen(possible_keys), + cs) : item_null; + item_list.push_back(item); + + /* 'index */ + item= index ? new Item_string(index, strlen(index), cs) : item_null; + item_list.push_back(item); + + /* 'key_len */ + item= key_len ? new Item_string(key_len, strlen(key_len), cs) : item_null; + item_list.push_back(item); + + /* 'ref' */ + item= ref ? new Item_string(ref, strlen(ref), cs) : item_null; + item_list.push_back(item); + + /* 'rows' */ + item_list.push_back(new Item_int(rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + /* 'filtered' */ + if (options & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* 'Extra' */ + item_list.push_back(new Item_string(extra, strlen(extra), cs)); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, SELECT_LEX *select_lex, uint8 explain_flags) { @@ -21705,6 +21800,26 @@ int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, } +/* + Append MRR information from quick select to the given string +*/ + +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) +{ + char mrr_str_buf[128]; + mrr_str_buf[0]=0; + int len; + handler *h= quick->head->file; + len= h->multi_range_read_explain_info(quick->mrr_flags, mrr_str_buf, + sizeof(mrr_str_buf)); + if (len > 0) + { + res->append(STRING_WITH_LEN("; ")); + res->append(mrr_str_buf, len); + } +} + + /** EXPLAIN handling. @@ -21746,8 +21861,12 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, */ if (message) { - if (print_explain_message_line(result, join->select_lex, on_the_fly, - explain_flags, message)) + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); + + if (print_explain_message_line(result, explain_flags, + join->select_lex->select_number, + join->select_lex->type, message)) error= 1; } @@ -21762,7 +21881,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, join->select_lex->master_unit()->derived->is_materialized_derived()) { table_map used_tables=0; - //if (!join->select_lex->type) + if (on_the_fly) join->select_lex->set_explain_type(on_the_fly); @@ -22171,19 +22290,8 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, */ if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) { - char mrr_str_buf[128]; - mrr_str_buf[0]=0; - int len; - uint mrr_flags= - ((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags; - len= table->file->multi_range_read_explain_info(mrr_flags, - mrr_str_buf, - sizeof(mrr_str_buf)); - if (len > 0) - { - extra.append(STRING_WITH_LEN("; ")); - extra.append(mrr_str_buf, len); - } + explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), + &extra); } if (need_tmp_table) diff --git a/sql/sql_select.h b/sql/sql_select.h index 638de926d75..61a72357d84 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -1804,6 +1804,28 @@ void eliminate_tables(JOIN *join); /* Index Condition Pushdown entry point function */ void push_index_cond(JOIN_TAB *tab, uint keyno); +/* EXPLAIN-related utility functions */ +int print_explain_message_line(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *message); +void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res); +int print_explain_row(select_result_sink *result, + uint8 options, + uint select_number, + const char *select_type, + const char *table_name, + //const char *partitions, (todo) + enum join_type jtype, + const char *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows rows, + const char *extra); +void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); + /**************************************************************************** Temporary table support for SQL Runtime ***************************************************************************/ diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 095ad409a94..f3bb14b6e9f 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2337,8 +2337,8 @@ void Show_explain_request::call_in_target_thread() DBUG_ASSERT(current_thd == target_thd); set_current_thd(request_thd); - if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/, - &printed_anything)) + if (target_thd->lex->print_explain(explain_buf, 0 /* explain flags*/, + &printed_anything)) { failed_to_produce= TRUE; } diff --git a/sql/sql_string.h b/sql/sql_string.h index 58cda343dac..e08b7a2581f 100644 --- a/sql/sql_string.h +++ b/sql/sql_string.h @@ -500,6 +500,38 @@ public: } }; + +// The following class is a backport from MySQL 5.6: +/** + String class wrapper with a preallocated buffer of size buff_sz + + This class allows to replace sequences of: + char buff[12345]; + String str(buff, sizeof(buff)); + str.length(0); + with a simple equivalent declaration: + StringBuffer<12345> str; +*/ + +template +class StringBuffer : public String +{ + char buff[buff_sz]; + +public: + StringBuffer() : String(buff, buff_sz, &my_charset_bin) { length(0); } + explicit StringBuffer(const CHARSET_INFO *cs) : String(buff, buff_sz, cs) + { + length(0); + } + StringBuffer(const char *str, size_t length, const CHARSET_INFO *cs) + : String(buff, buff_sz, cs) + { + set(str, length, cs); + } +}; + + static inline bool check_if_only_end_space(CHARSET_INFO *cs, const char *str, const char *end) diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 28b9fe7eacd..84adfc2c3f5 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -1394,7 +1394,10 @@ bool mysql_multi_update(THD *thd, thd->abort_on_warning= test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); - +/* + psergey-explain-todo: + This is the place where EXPLAIN should be handled. +*/ List total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9664aad1e19..7bac9c8ded7 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -11881,13 +11881,21 @@ describe: opt_describe_column {} | describe_command opt_extended_describe { Lex->describe|= DESCRIBE_NORMAL; } - select + explanable_command { LEX *lex=Lex; lex->select_lex.options|= SELECT_DESCRIBE; } ; +explanable_command: + select + | insert + | replace + | update + | delete + ; + describe_command: DESC | DESCRIBE From b38b44cfe1c62b4c2211aec7c3f1407f14a2aa21 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 08:24:48 +0400 Subject: [PATCH 2/3] EXPLAIN DELETE for MariaDB - Include the testcases in the backport. --- mysql-test/r/explain_non_select.result | 53 +++++++++++++++++++ mysql-test/r/show_explain_non_select.result | 24 +++++++++ mysql-test/t/explain_non_select.test | 47 +++++++++++++++++ mysql-test/t/show_explain_non_select.test | 57 +++++++++++++++++++++ 4 files changed, 181 insertions(+) create mode 100644 mysql-test/r/explain_non_select.result create mode 100644 mysql-test/r/show_explain_non_select.result create mode 100644 mysql-test/t/explain_non_select.test create mode 100644 mysql-test/t/show_explain_non_select.test diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result new file mode 100644 index 00000000000..aa592f0b198 --- /dev/null +++ b/mysql-test/r/explain_non_select.result @@ -0,0 +1,53 @@ +drop table if exists t0; +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +# +# Tests for single-table DELETE +# +explain select * from t0 where a=3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +explain delete from t0 where a=3; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +# DELETE without WHERE is a special case: +explain delete from t0; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Deleting all rows +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +# This should use an index, possible_keys=NULL because there is no WHERE +explain delete from t1 order by a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 index NULL a NULL NULL 512 +# This should use range, possible_keys={a,b} +explain delete from t1 where a<20 and b < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,b a 5 NULL 1 Using where +# This should use ALL + filesort +explain delete from t1 order by a+1 limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 Using filesort +# This should use range + using filesort +explain delete from t1 where a<20 order by b limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where; Using filesort +# Try some subqueries: +explain delete from t1 where a < (select max(a) from t0); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 range a a 5 NULL 1 Using where +2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +explain delete from t1 where a < (select max(a) from t0 where a < t1.b); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 8 Using where +# +# Tests for multi-table DELETE +# +explain delete t1 from t0, t1 where t0.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +drop table t0, t1; diff --git a/mysql-test/r/show_explain_non_select.result b/mysql-test/r/show_explain_non_select.result new file mode 100644 index 00000000000..5358981e6f2 --- /dev/null +++ b/mysql-test/r/show_explain_non_select.result @@ -0,0 +1,24 @@ +drop table if exists t0, t1; +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +set debug_dbug='d,show_explain_probe_delete_exec_start'; +delete from t1 where a<10 and b+1>1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +Warnings: +Note 1003 delete from t1 where a<10 and b+1>1000 +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using where +Warnings: +Note 1003 delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000 +drop table t0,t1; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test new file mode 100644 index 00000000000..bc3c2bb6c16 --- /dev/null +++ b/mysql-test/t/explain_non_select.test @@ -0,0 +1,47 @@ +# +# MariaDB tests for EXPLAIN UPDATE/DELETE. +# +--disable_warnings +drop table if exists t0; +--enable_warnings + +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +--echo # +--echo # Tests for single-table DELETE +--echo # + +explain select * from t0 where a=3; +explain delete from t0 where a=3; + +--echo # DELETE without WHERE is a special case: +explain delete from t0; + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +--echo # This should use an index, possible_keys=NULL because there is no WHERE +explain delete from t1 order by a limit 2; + +--echo # This should use range, possible_keys={a,b} +explain delete from t1 where a<20 and b < 10; + +--echo # This should use ALL + filesort +explain delete from t1 order by a+1 limit 2; + +--echo # This should use range + using filesort +explain delete from t1 where a<20 order by b limit 2; + +--echo # Try some subqueries: +explain delete from t1 where a < (select max(a) from t0); +explain delete from t1 where a < (select max(a) from t0 where a < t1.b); + +--echo # +--echo # Tests for multi-table DELETE +--echo # +explain delete t1 from t0, t1 where t0.a = t1.a; + +drop table t0, t1; diff --git a/mysql-test/t/show_explain_non_select.test b/mysql-test/t/show_explain_non_select.test new file mode 100644 index 00000000000..da788779626 --- /dev/null +++ b/mysql-test/t/show_explain_non_select.test @@ -0,0 +1,57 @@ +# +# SHOW EXPLAIN tests for non-select subqueries +# +#--source include/have_debug.inc +#--source include/have_innodb.inc +#--source include/not_embedded.inc + +--disable_warnings +drop table if exists t0, t1; +--enable_warnings + +# +# Setup two threads and their ids +# +let $thr1=`select connection_id()`; +connect (con2, localhost, root,,); +connection con2; +let $thr2=`select connection_id()`; +connection default; + +# +# Create tables +# +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; + +# +# Test SHOW EXPLAIN for single-table DELETEs +# +connection con2; +set debug_dbug='d,show_explain_probe_delete_exec_start'; +send delete from t1 where a<10 and b+1>1000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; + +set @show_explain_probe_select_id=1; +set debug_dbug='d,show_explain_probe_do_select'; +send delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; + + +drop table t0,t1; From d2995031d9214206689660069024525808c8a683 Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 12 Feb 2013 14:37:08 +0400 Subject: [PATCH 3/3] SHOW EXPLAIN for MariaDB - Support [SHOW] EXPLAIN UPDATE (needs code cleanup). --- mysql-test/r/explain_non_select.result | 53 +++++++- mysql-test/r/show_explain.result | 18 ++- mysql-test/r/show_explain_non_select.result | 24 +++- mysql-test/t/explain_non_select.test | 39 ++++++ mysql-test/t/show_explain.test | 4 +- mysql-test/t/show_explain_non_select.test | 31 ++++- sql/sql_delete.cc | 26 +++- sql/sql_lex.cc | 8 +- sql/sql_lex.h | 41 ++++-- sql/sql_select.cc | 3 +- sql/sql_update.cc | 134 ++++++++++++++++---- 11 files changed, 318 insertions(+), 63 deletions(-) diff --git a/mysql-test/r/explain_non_select.result b/mysql-test/r/explain_non_select.result index aa592f0b198..1c8e444c4b9 100644 --- a/mysql-test/r/explain_non_select.result +++ b/mysql-test/r/explain_non_select.result @@ -38,7 +38,7 @@ id select_type table type possible_keys key key_len ref rows Extra explain delete from t1 where a < (select max(a) from t0); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 range a a 5 NULL 1 Using where -2 SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 SUBQUERY t0 ALL NULL NULL NULL NULL 8 explain delete from t1 where a < (select max(a) from t0 where a < t1.b); id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where @@ -51,3 +51,54 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where 1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index drop table t0, t1; +# ################################################################### +# ## EXPLAIN UPDATE tests +# ################################################################### +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); +explain update t0 set a=3 where a=4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; +explain update t1 set a=a+1 where 3>4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +explain update t1 set a=a+1 where a=3 and a=4; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Impossible where +# This should use an index, possible_keys=NULL because there is no WHERE +explain update t1 set a=a+1 order by a limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +# This should use range, possible_keys={a,b} +explain update t1 set filler='fooo' where a<20 and b < 10; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a,b a 5 NULL 1 Using where +# This should use ALL + filesort +explain update t1 set filler='fooo' order by a+1 limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ALL NULL NULL NULL NULL 512 +# This should use range + using filesort +explain update t1 set filler='fooo' where a<20 order by b limit 2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +# Try some subqueries: +explain update t1 set filler='fooo' where a < (select max(a) from t0); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 range a a 5 NULL 1 Using where +2 SUBQUERY t0 ALL NULL NULL NULL NULL 8 +explain update t1 set filler='fooo' where a < (select max(a) from t0 where a < t1.b); +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 512 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 8 Using where +# +# Tests for multi-table UPDATE +# +explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t0 ALL NULL NULL NULL NULL 8 Using where +1 SIMPLE t1 ref a a 5 test.t0.a 4 Using index +drop table t0, t1; diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result index 8baf4d40e7c..1152baaf835 100644 --- a/mysql-test/r/show_explain.result +++ b/mysql-test/r/show_explain.result @@ -186,9 +186,17 @@ set @show_explain_probe_select_id=2; set debug_dbug='+d,show_explain_probe_join_exec_start'; update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; -ERROR HY000: Target is not running an EXPLAINable command +id select_type table type possible_keys key key_len ref rows Extra +1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where +Warnings: +Note 1003 update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; set debug_dbug=@old_debug; # @@ -207,7 +215,7 @@ Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 2 Using where -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 delete from t2 where (select max(a) from t0 where t2.a + t0.a <3) >3 drop table t2; @@ -228,13 +236,13 @@ Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2 show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 PRIMARY t2 ALL NULL NULL NULL NULL 3 -2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Query plan already deleted +2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 Using where Warnings: Note 1003 select t2.a, ((select max(a) from t0 where t2.a + t0.a <3) >3) as SUBQ from t2 a SUBQ diff --git a/mysql-test/r/show_explain_non_select.result b/mysql-test/r/show_explain_non_select.result index 5358981e6f2..0bd1e959405 100644 --- a/mysql-test/r/show_explain_non_select.result +++ b/mysql-test/r/show_explain_non_select.result @@ -1,19 +1,27 @@ drop table if exists t0, t1; +SET @old_debug= @@session.debug; +set debug_sync='RESET'; create table t0 (a int) engine=myisam; insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); create table t1 (a int, b int, filler char(100), key(a), key(b)); insert into t1 select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' from t0 A, t0 B, t0 C; -set debug_dbug='d,show_explain_probe_delete_exec_start'; +# +# Test SHOW EXPLAIN for single-table DELETE +# +set debug_dbug='+d,show_explain_probe_delete_exec_start'; delete from t1 where a<10 and b+1>1000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range a a 5 NULL 1 Using where Warnings: Note 1003 delete from t1 where a<10 and b+1>1000 +# +# Test SHOW EXPLAIN for multi-table DELETE +# set @show_explain_probe_select_id=1; -set debug_dbug='d,show_explain_probe_do_select'; +set debug_dbug='+d,show_explain_probe_do_select'; delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; show explain for $thr2; id select_type table type possible_keys key key_len ref rows Extra @@ -21,4 +29,16 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ref a a 5 test.t0.a 4 Using where Warnings: Note 1003 delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000 +# +# Test SHOW EXPLAIN for single-table UPDATE +# +set debug_dbug='+d,show_explain_probe_update_exec_start'; +update t1 set filler='filler-data-2' where a<10 and b+1>1000; +show explain for $thr2; +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range a a 5 NULL 1 Using where +Warnings: +Note 1003 update t1 set filler='filler-data-2' where a<10 and b+1>1000 drop table t0,t1; +set debug_dbug=@old_debug; +set debug_sync='RESET'; diff --git a/mysql-test/t/explain_non_select.test b/mysql-test/t/explain_non_select.test index bc3c2bb6c16..66432a8e4a5 100644 --- a/mysql-test/t/explain_non_select.test +++ b/mysql-test/t/explain_non_select.test @@ -43,5 +43,44 @@ explain delete from t1 where a < (select max(a) from t0 where a < t1.b); --echo # Tests for multi-table DELETE --echo # explain delete t1 from t0, t1 where t0.a = t1.a; +drop table t0, t1; + +--echo # ################################################################### +--echo # ## EXPLAIN UPDATE tests +--echo # ################################################################### +create table t0 (a int) engine=myisam; +insert into t0 values (1),(2),(3),(4),(5),(6),(7),(8); + +explain update t0 set a=3 where a=4; + +create table t1 (a int, b int, filler char(100), key(a), key(b)); +insert into t1 +select A.a+10*B.a + 10*C.a, A.a+10*B.a + 10*C.a, 'filler' +from t0 A, t0 B, t0 C; + +explain update t1 set a=a+1 where 3>4; +explain update t1 set a=a+1 where a=3 and a=4; + +--echo # This should use an index, possible_keys=NULL because there is no WHERE +explain update t1 set a=a+1 order by a limit 2; + +--echo # This should use range, possible_keys={a,b} +explain update t1 set filler='fooo' where a<20 and b < 10; + +--echo # This should use ALL + filesort +explain update t1 set filler='fooo' order by a+1 limit 2; + +--echo # This should use range + using filesort +explain update t1 set filler='fooo' where a<20 order by b limit 2; + +--echo # Try some subqueries: +explain update t1 set filler='fooo' where a < (select max(a) from t0); +explain update t1 set filler='fooo' where a < (select max(a) from t0 where a < t1.b); + +--echo # +--echo # Tests for multi-table UPDATE +--echo # +explain update t0, t1 set t1.a=t1.a+1 where t0.a = t1.a; + drop table t0, t1; diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test index d87bf2f14d4..0694160b151 100644 --- a/mysql-test/t/show_explain.test +++ b/mysql-test/t/show_explain.test @@ -238,10 +238,10 @@ set debug_dbug='+d,show_explain_probe_join_exec_start'; send update t2 set dummy=0 where (select max(a) from t0 where t2.a + t0.a <3) >3 ; connection default; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; --source include/wait_condition.inc ---error ER_TARGET_NOT_EXPLAINABLE +#--error ER_TARGET_NOT_EXPLAINABLE evalp show explain for $thr2; connection con1; reap; diff --git a/mysql-test/t/show_explain_non_select.test b/mysql-test/t/show_explain_non_select.test index da788779626..f71affd8a6e 100644 --- a/mysql-test/t/show_explain_non_select.test +++ b/mysql-test/t/show_explain_non_select.test @@ -9,6 +9,9 @@ drop table if exists t0, t1; --enable_warnings +SET @old_debug= @@session.debug; +set debug_sync='RESET'; + # # Setup two threads and their ids # @@ -31,11 +34,11 @@ from t0 A, t0 B, t0 C; let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2; -# -# Test SHOW EXPLAIN for single-table DELETEs -# +--echo # +--echo # Test SHOW EXPLAIN for single-table DELETE +--echo # connection con2; -set debug_dbug='d,show_explain_probe_delete_exec_start'; +set debug_dbug='+d,show_explain_probe_delete_exec_start'; send delete from t1 where a<10 and b+1>1000; connection default; @@ -44,8 +47,11 @@ evalp show explain for $thr2; connection con2; reap; +--echo # +--echo # Test SHOW EXPLAIN for multi-table DELETE +--echo # set @show_explain_probe_select_id=1; -set debug_dbug='d,show_explain_probe_do_select'; +set debug_dbug='+d,show_explain_probe_do_select'; send delete t1 from t1, t0 where t0.a=t1.a and t1.b +1 > 1000; connection default; --source include/wait_condition.inc @@ -53,5 +59,20 @@ evalp show explain for $thr2; connection con2; reap; +--echo # +--echo # Test SHOW EXPLAIN for single-table UPDATE +--echo # +connection con2; +set debug_dbug='+d,show_explain_probe_update_exec_start'; +send update t1 set filler='filler-data-2' where a<10 and b+1>1000; + +connection default; +--source include/wait_condition.inc +evalp show explain for $thr2; +connection con2; +reap; drop table t0,t1; + +set debug_dbug=@old_debug; +set debug_sync='RESET'; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index d7a612f3d56..df659871a64 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -54,9 +54,27 @@ int Delete_plan::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { - if (deleting_all_rows || impossible_where) + if (deleting_all_rows) { - const char *msg= deleting_all_rows? "Deleting all rows": "Impossible where"; + const char *msg= "Deleting all rows"; + if (print_explain_message_line(output, explain_flags, 1/*select number*/, + "SIMPLE", msg)) + { + return 1; + } + *printed_anything= true; + return 0; + } + return Update_plan::print_explain(output, explain_flags, printed_anything); +} + + +int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything) +{ + if (impossible_where) + { + const char *msg= "Impossible where"; if (print_explain_message_line(output, explain_flags, 1/*select number*/, "SIMPLE", msg)) { @@ -404,7 +422,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, query_plan.select= select; query_plan.possible_keys= table->quick_keys; query_plan.table_rows= table->file->stats.records; - thd->lex->delete_plan= &query_plan; + thd->lex->upd_del_plan= &query_plan; /* Ok, we have generated a query plan for the DELETE. @@ -629,7 +647,7 @@ cleanup: /* Special exits */ exit_without_my_ok: - thd->lex->delete_plan= &query_plan; + thd->lex->upd_del_plan= &query_plan; select_send *result; bool printed_anything; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 74e4b3e1162..714ab373b17 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -448,7 +448,7 @@ void lex_start(THD *thd) lex->thd= lex->unit.thd= thd; - lex->delete_plan= NULL; + lex->upd_del_plan= NULL; lex->context_stack.empty(); lex->unit.init_query(); lex->unit.init_select(); @@ -2558,7 +2558,7 @@ LEX::LEX() INITIAL_LEX_PLUGIN_LIST_SIZE, 0); reset_query_tables_list(TRUE); mi.init(); - delete_plan= NULL; + upd_del_plan= NULL; } @@ -4171,9 +4171,9 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { - if (delete_plan) + if (upd_del_plan) { - delete_plan->print_explain(output, explain_flags, printed_anything); + upd_del_plan->print_explain(output, explain_flags, printed_anything); return 0; } int res= unit.print_explain(output, explain_flags, printed_anything); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index af35ccb01b0..588ad172a3c 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2347,29 +2347,48 @@ protected: class Delete_plan; class SQL_SELECT; -/* Query plan of a single-table DELETE */ -class Delete_plan +/* + Query plan of a single-table UPDATE. + (This is actually a plan for single-table DELETE also) +*/ +class Update_plan { - bool deleting_all_rows; +protected: bool impossible_where; public: - TABLE *table; SQL_SELECT *select; uint index; ha_rows table_rows; /* Use if select==NULL */ - bool using_filesort; - key_map possible_keys; - /* Top-level select_lex. Most of its fields are not used, we need it only to get to the subqueries. */ SELECT_LEX *select_lex; + + key_map possible_keys; + bool using_filesort; + + /* Set this plan to be a plan to do nothing because of impossible WHRE*/ + void set_impossible_where() { impossible_where= true; } + + virtual int print_explain(select_result_sink *output, uint8 explain_flags, + bool *printed_anything); + virtual ~Update_plan() {} + + Update_plan() : impossible_where(false), using_filesort(false) {} +}; + + +/* Query plan of a single-table DELETE */ +class Delete_plan : public Update_plan +{ + bool deleting_all_rows; +public: /* Construction functions */ Delete_plan() : - deleting_all_rows(false), impossible_where(false), using_filesort(false) {} + deleting_all_rows(false) {} /* Set this query plan to be a plan to make a call to h->delete_all_rows() */ void set_delete_all_rows(ha_rows rows_arg) @@ -2377,10 +2396,6 @@ public: deleting_all_rows= true; table_rows= rows_arg; } - - /* Set this plan to be a plan to do nothing because of impossible WHRE*/ - void set_impossible_where() { impossible_where= true; } - int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); }; @@ -2398,7 +2413,7 @@ struct LEX: public Query_tables_list SELECT_LEX *all_selects_list; /* For single-table DELETE: its query plan */ - Delete_plan *delete_plan; + Update_plan *upd_del_plan; char *length,*dec,*change; LEX_STRING name; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 230355c550e..d9177b9240e 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -10756,7 +10756,8 @@ void JOIN::cleanup(bool full) DBUG_ENTER("JOIN::cleanup"); DBUG_PRINT("enter", ("full %u", (uint) full)); - have_query_plan= QEP_DELETED; + if (full) + have_query_plan= QEP_DELETED; //psergey: this is a problem! if (table) { diff --git a/sql/sql_update.cc b/sql/sql_update.cc index 84adfc2c3f5..11cb493da97 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -260,7 +260,7 @@ int mysql_update(THD *thd, bool can_compare_record; int res; int error, loc_error; - uint used_index, dup_key_found; + uint dup_key_found; bool need_sort= TRUE; bool reverse= FALSE; #ifndef NO_EMBEDDED_ACCESS_CHECKS @@ -270,12 +270,15 @@ int mysql_update(THD *thd, ha_rows updated, found; key_map old_covering_keys; TABLE *table; - SQL_SELECT *select; + SQL_SELECT *select= NULL; READ_RECORD info; SELECT_LEX *select_lex= &thd->lex->select_lex; ulonglong id; List all_fields; killed_state killed_status= NOT_KILLED; + Update_plan query_plan; + query_plan.index= MAX_KEY; + query_plan.using_filesort= FALSE; DBUG_ENTER("mysql_update"); if (open_tables(thd, &table_list, &table_count, 0)) @@ -314,6 +317,8 @@ int mysql_update(THD *thd, table->covering_keys= table->s->keys_in_use; table->quick_keys.clear_all(); + query_plan.select_lex= &thd->lex->select_lex; + query_plan.table= table; #ifndef NO_EMBEDDED_ACCESS_CHECKS /* Force privilege re-checking for views after they have been opened. */ want_privilege= (table_list->view ? UPDATE_ACL : @@ -370,7 +375,12 @@ int mysql_update(THD *thd, Item::cond_result cond_value; conds= remove_eq_conds(thd, conds, &cond_value); if (cond_value == Item::COND_FALSE) + { limit= 0; // Impossible WHERE + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + } } /* @@ -400,6 +410,10 @@ int mysql_update(THD *thd, if (error || !limit || thd->is_error() || (select && select->check_quick(thd, safe_update, limit))) { + query_plan.set_impossible_where(); + if (thd->lex->describe) + goto exit_without_my_ok; + delete select; free_underlaid_joins(thd, select_lex); /* @@ -438,16 +452,16 @@ int mysql_update(THD *thd, if (select && select->quick && select->quick->unique_key_range()) { // Single row select (always "ordered"): Ok to use with key field UPDATE need_sort= FALSE; - used_index= MAX_KEY; + query_plan.index= MAX_KEY; used_key_is_modified= FALSE; } else { - used_index= get_index_for_order(order, table, select, limit, - &need_sort, &reverse); + query_plan.index= get_index_for_order(order, table, select, limit, + &need_sort, &reverse); if (select && select->quick) { - DBUG_ASSERT(need_sort || used_index == select->quick->index); + DBUG_ASSERT(need_sort || query_plan.index == select->quick->index); used_key_is_modified= (!select->quick->unique_key_range() && select->quick->is_keys_used(table->write_set)); } @@ -455,14 +469,37 @@ int mysql_update(THD *thd, { if (need_sort) { // Assign table scan index to check below for modified key fields: - used_index= table->file->key_used_on_scan; + query_plan.index= table->file->key_used_on_scan; } - if (used_index != MAX_KEY) + if (query_plan.index != MAX_KEY) { // Check if we are modifying a key that we are used to search with: - used_key_is_modified= is_key_used(table, used_index, table->write_set); + used_key_is_modified= is_key_used(table, query_plan.index, table->write_set); } } } + + /* + Query optimization is finished at this point. + - Save the decisions in the query plan + - if we're running EXPLAIN UPDATE, get out + */ + query_plan.select= select; + query_plan.possible_keys= table->quick_keys; + query_plan.table_rows= table->file->stats.records; + thd->lex->upd_del_plan= &query_plan; + + /* + Ok, we have generated a query plan for the UPDATE. + - if we're running EXPLAIN UPDATE, goto produce explain output + - otherwise, execute the query plan + */ + if (thd->lex->describe) + goto exit_without_my_ok; + + thd->apc_target.enable(); + DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", + dbug_serve_apcs(thd, 1);); + if (used_key_is_modified || order || partition_key_modified(table, table->write_set)) @@ -476,8 +513,8 @@ int mysql_update(THD *thd, DBUG_ASSERT(table->read_set == &table->def_read_set); DBUG_ASSERT(table->write_set == &table->def_write_set); - if (used_index < MAX_KEY && old_covering_keys.is_set(used_index)) - table->add_read_columns_used_by_index(used_index); + if (query_plan.index < MAX_KEY && old_covering_keys.is_set(query_plan.index)) + table->add_read_columns_used_by_index(query_plan.index); else table->use_all_columns(); @@ -534,22 +571,22 @@ int mysql_update(THD *thd, /* When we get here, we have one of the following options: - A. used_index == MAX_KEY + A. query_plan.index == MAX_KEY This means we should use full table scan, and start it with init_read_record call - B. used_index != MAX_KEY + B. query_plan.index != MAX_KEY B.1 quick select is used, start the scan with init_read_record B.2 quick select is not used, this is full index scan (with LIMIT) Full index scan must be started with init_read_record_idx */ - if (used_index == MAX_KEY || (select && select->quick)) + if (query_plan.index == MAX_KEY || (select && select->quick)) { if (init_read_record(&info, thd, table, select, 0, 1, FALSE)) goto err; } else - init_read_record_idx(&info, thd, table, 1, used_index, reverse); + init_read_record_idx(&info, thd, table, 1, query_plan.index, reverse); thd_proc_info(thd, "Searching rows for update"); ha_rows tmp_limit= limit; @@ -610,6 +647,7 @@ int mysql_update(THD *thd, select= new SQL_SELECT; select->head=table; } + //psergey-todo: disable SHOW EXPLAIN because the plan was deleted? if (reinit_io_cache(&tempfile,READ_CACHE,0L,0,0)) error=1; /* purecov: inspected */ select->file=tempfile; // Read row ptrs from this file @@ -884,6 +922,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. end_read_record(&info); delete select; thd_proc_info(thd, "end"); @@ -962,6 +1001,27 @@ err: table->disable_keyread(); thd->abort_on_warning= 0; DBUG_RETURN(1); + +exit_without_my_ok: + thd->lex->upd_del_plan= &query_plan; + + select_send *result; + bool printed_anything; + 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->print_explain(result, 0 /* explain flags*/, &printed_anything); + + if (err2) + result->abort_result_set(); + else + result->send_eof(); + + delete select; + free_underlaid_joins(thd, select_lex); + DBUG_RETURN((error >= 0 || thd->is_error()) ? 1 : 0); } /* @@ -1381,23 +1441,37 @@ bool mysql_multi_update(THD *thd, multi_update **result) { bool res; + select_result *output; + bool explain= test(thd->lex->describe); DBUG_ENTER("mysql_multi_update"); - - if (!(*result= new multi_update(table_list, - &thd->lex->select_lex.leaf_tables, - fields, values, - handle_duplicates, ignore))) + + if (explain) { - DBUG_RETURN(TRUE); + /* Handle EXPLAIN UPDATE */ + if (!(output= new select_send()) || + thd->send_explain_fields(output)) + { + delete output; + DBUG_RETURN(TRUE); + } + select_lex->set_explain_type(FALSE); + *result= NULL; /* no multi_update object */ + } + else + { + if (!(*result= new multi_update(table_list, + &thd->lex->select_lex.leaf_tables, + fields, values, + handle_duplicates, ignore))) + { + DBUG_RETURN(TRUE); + } + output= *result; } thd->abort_on_warning= test(thd->variables.sql_mode & (MODE_STRICT_TRANS_TABLES | MODE_STRICT_ALL_TABLES)); -/* - psergey-explain-todo: - This is the place where EXPLAIN should be handled. -*/ List total_list; res= mysql_select(thd, &select_lex->ref_pointer_array, @@ -1407,12 +1481,20 @@ bool mysql_multi_update(THD *thd, (ORDER *)NULL, options | SELECT_NO_JOIN_CACHE | SELECT_NO_UNLOCK | OPTION_SETUP_TABLES_DONE, - *result, unit, select_lex); + output, unit, select_lex); DBUG_PRINT("info",("res: %d report_error: %d", res, (int) thd->is_error())); res|= thd->is_error(); if (unlikely(res)) (*result)->abort_result_set(); + else + { + if (explain) + { + output->send_eof(); + delete output; + } + } thd->abort_on_warning= 0; DBUG_RETURN(res); }