diff options
Diffstat (limited to 'sql')
-rw-r--r-- | sql/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/item.cc | 15 | ||||
-rw-r--r-- | sql/item_strfunc.cc | 85 | ||||
-rw-r--r-- | sql/item_strfunc.h | 42 | ||||
-rw-r--r-- | sql/item_subselect.cc | 19 | ||||
-rw-r--r-- | sql/item_subselect.h | 1 | ||||
-rw-r--r-- | sql/lex.h | 1 | ||||
-rw-r--r-- | sql/my_json_writer.cc | 345 | ||||
-rw-r--r-- | sql/my_json_writer.h | 189 | ||||
-rw-r--r-- | sql/mysqld.h | 6 | ||||
-rw-r--r-- | sql/opt_range.cc | 4 | ||||
-rw-r--r-- | sql/sql_class.cc | 13 | ||||
-rw-r--r-- | sql/sql_class.h | 2 | ||||
-rw-r--r-- | sql/sql_delete.cc | 26 | ||||
-rw-r--r-- | sql/sql_explain.cc | 1081 | ||||
-rw-r--r-- | sql/sql_explain.h | 239 | ||||
-rw-r--r-- | sql/sql_lex.cc | 8 | ||||
-rw-r--r-- | sql/sql_lex.h | 8 | ||||
-rw-r--r-- | sql/sql_parse.cc | 37 | ||||
-rw-r--r-- | sql/sql_select.cc | 400 | ||||
-rw-r--r-- | sql/sql_select.h | 44 | ||||
-rw-r--r-- | sql/sql_update.cc | 4 | ||||
-rw-r--r-- | sql/sql_yacc.yy | 70 | ||||
-rw-r--r-- | sql/sys_vars.cc | 4 | ||||
-rw-r--r-- | sql/table.h | 6 |
25 files changed, 2099 insertions, 551 deletions
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 1c41ff481aa..59fe2c32056 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -112,6 +112,7 @@ SET (SQL_SOURCE threadpool_common.cc ../sql-common/mysql_async.c my_apc.cc my_apc.h + my_json_writer.cc my_json_writer.h rpl_gtid.cc rpl_parallel.cc ${WSREP_SOURCES} table_cache.cc diff --git a/sql/item.cc b/sql/item.cc index 099fe1e7f07..d585ace5717 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -2504,7 +2504,13 @@ void Item_ident::print(String *str, enum_query_type query_type) } if (db_name && db_name[0] && !alias_name_used) { - if (!(cached_table && cached_table->belong_to_view && + /* + When printing EXPLAIN, don't print database name when it's the same as + current database. + */ + bool skip_db= (query_type & QT_EXPLAIN) && !strcmp(thd->db, db_name); + if (!skip_db && + !(cached_table && cached_table->belong_to_view && cached_table->belong_to_view->compact_view_format)) { append_identifier(thd, str, d_name, (uint)strlen(d_name)); @@ -7542,6 +7548,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_strfunc.cc b/sql/item_strfunc.cc index ec5740cf3e2..875d9470dac 100644 --- a/sql/item_strfunc.cc +++ b/sql/item_strfunc.cc @@ -2171,57 +2171,68 @@ void Item_func_trim::print(String *str, enum_query_type query_type) /* Item_func_password */ -String *Item_func_password::val_str_ascii(String *str) +bool Item_func_password::fix_fields(THD *thd, Item **ref) { - DBUG_ASSERT(fixed == 1); - String *res= args[0]->val_str(str); - check_password_policy(res); - if (args[0]->null_value || res->length() == 0) - return make_empty_result(); - my_make_scrambled_password(tmp_value, res->ptr(), res->length()); - str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, &my_charset_latin1); - return str; -} - -char *Item_func_password::alloc(THD *thd, const char *password, size_t pass_len) -{ - char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH+1); - if (buff) - { - String *password_str= new (thd->mem_root)String(password, thd->variables. - character_set_client); - check_password_policy(password_str); - my_make_scrambled_password(buff, password, pass_len); - } - return buff; + if (deflt) + alg= (thd->variables.old_passwords ? OLD : NEW); + return Item_str_ascii_func::fix_fields(thd, ref); } - -/* Item_func_old_password */ - -String *Item_func_old_password::val_str_ascii(String *str) +String *Item_func_password::val_str_ascii(String *str) { DBUG_ASSERT(fixed == 1); - String *res= args[0]->val_str(str); - if ((null_value=args[0]->null_value)) - return 0; - if (res->length() == 0) - return make_empty_result(); - my_make_scrambled_password_323(tmp_value, res->ptr(), res->length()); - str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, &my_charset_latin1); + String *res= args[0]->val_str(str); + switch (alg){ + case NEW: + check_password_policy(res); + if (args[0]->null_value || res->length() == 0) + return make_empty_result(); + my_make_scrambled_password(tmp_value, res->ptr(), res->length()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH, &my_charset_latin1); + break; + case OLD: + if ((null_value=args[0]->null_value)) + return 0; + if (res->length() == 0) + return make_empty_result(); + my_make_scrambled_password_323(tmp_value, res->ptr(), res->length()); + str->set(tmp_value, SCRAMBLED_PASSWORD_CHAR_LENGTH_323, &my_charset_latin1); + break; + default: + DBUG_ASSERT(0); + } return str; } -char *Item_func_old_password::alloc(THD *thd, const char *password, - size_t pass_len) +char *Item_func_password::alloc(THD *thd, const char *password, + size_t pass_len, enum PW_Alg al) { - char *buff= (char *) thd->alloc(SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1); - if (buff) + char *buff= (char *) thd->alloc((al==NEW)? + SCRAMBLED_PASSWORD_CHAR_LENGTH + 1: + SCRAMBLED_PASSWORD_CHAR_LENGTH_323 + 1); + if (!buff) + return NULL; + + switch (al) { + case NEW: + { + String *password_str= new (thd->mem_root)String(password, thd->variables. + character_set_client); + check_password_policy(password_str); + my_make_scrambled_password(buff, password, pass_len); + break; + } + case OLD: my_make_scrambled_password_323(buff, password, pass_len); + break; + default: + DBUG_ASSERT(0); + } return buff; } + #define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.') String *Item_func_encrypt::val_str(String *str) diff --git a/sql/item_strfunc.h b/sql/item_strfunc.h index 8377a20e0a4..b79009c6778 100644 --- a/sql/item_strfunc.h +++ b/sql/item_strfunc.h @@ -407,40 +407,32 @@ public: class Item_func_password :public Item_str_ascii_func { +public: + enum PW_Alg {OLD, NEW}; +private: char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH+1]; + enum PW_Alg alg; + bool deflt; public: - Item_func_password(Item *a) :Item_str_ascii_func(a) {} + Item_func_password(Item *a) :Item_str_ascii_func(a), alg(NEW), deflt(1) {} + Item_func_password(Item *a, PW_Alg al) :Item_str_ascii_func(a), + alg(al), deflt(0) {} String *val_str_ascii(String *str); + bool fix_fields(THD *thd, Item **ref); void fix_length_and_dec() { - fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH, default_charset()); + fix_length_and_charset((alg == 1 ? + SCRAMBLED_PASSWORD_CHAR_LENGTH : + SCRAMBLED_PASSWORD_CHAR_LENGTH_323), + default_charset()); } - const char *func_name() const { return "password"; } - static char *alloc(THD *thd, const char *password, size_t pass_len); + const char *func_name() const { return ((deflt || alg == 1) ? + "password" : "old_password"); } + static char *alloc(THD *thd, const char *password, size_t pass_len, + enum PW_Alg al); }; -/* - Item_func_old_password -- PASSWORD() implementation used in MySQL 3.21 - 4.0 - compatibility mode. This item is created in sql_yacc.yy when - 'old_passwords' session variable is set, and to handle OLD_PASSWORD() - function. -*/ - -class Item_func_old_password :public Item_str_ascii_func -{ - char tmp_value[SCRAMBLED_PASSWORD_CHAR_LENGTH_323+1]; -public: - Item_func_old_password(Item *a) :Item_str_ascii_func(a) {} - String *val_str_ascii(String *str); - void fix_length_and_dec() - { - fix_length_and_charset(SCRAMBLED_PASSWORD_CHAR_LENGTH_323, default_charset()); - } - const char *func_name() const { return "old_password"; } - static char *alloc(THD *thd, const char *password, size_t pass_len); -}; - class Item_func_des_encrypt :public Item_str_func { diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 65974fb6155..964be7eb061 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -895,6 +895,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('('); @@ -3717,6 +3732,10 @@ int subselect_union_engine::exec() return res; } +int subselect_union_engine::get_identifier() +{ + return unit->first_select()->select_number; +} /* Search for at least one row satisfying select condition diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 92b269d02f1..02e487c82d0 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -871,6 +871,7 @@ public: bool is_executed() const; bool no_rows(); virtual enum_engine_type engine_type() { return UNION_ENGINE; } + int get_identifier(); }; diff --git a/sql/lex.h b/sql/lex.h index 51b69f00990..137790e6227 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -242,6 +242,7 @@ static SYMBOL symbols[] = { { "FOR", SYM(FOR_SYM)}, { "FORCE", SYM(FORCE_SYM)}, { "FOREIGN", SYM(FOREIGN)}, + { "FORMAT", SYM(FORMAT_SYM)}, { "FOUND", SYM(FOUND_SYM)}, { "FROM", SYM(FROM)}, { "FULL", SYM(FULL)}, diff --git a/sql/my_json_writer.cc b/sql/my_json_writer.cc new file mode 100644 index 00000000000..4f933583347 --- /dev/null +++ b/sql/my_json_writer.cc @@ -0,0 +1,345 @@ +/* Copyright (C) 2014 SkySQL Ab, MariaDB Corporation Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include <my_global.h> +#include "sql_priv.h" +#include "sql_string.h" + +#include "my_json_writer.h" + +void Json_writer::append_indent() +{ + if (!document_start) + output.append('\n'); + for (int i=0; i< indent_level; i++) + output.append(' '); +} + +void Json_writer::start_object() +{ + fmt_helper.on_start_object(); + + if (!element_started) + start_element(); + + output.append("{"); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + +void Json_writer::start_array() +{ + if (fmt_helper.on_start_array()) + return; + + if (!element_started) + start_element(); + + output.append("["); + indent_level+=INDENT_SIZE; + first_child=true; + element_started= false; + document_start= false; +} + + +void Json_writer::end_object() +{ + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("}"); +} + + +void Json_writer::end_array() +{ + if (fmt_helper.on_end_array()) + return; + indent_level-=INDENT_SIZE; + if (!first_child) + append_indent(); + output.append("]"); +} + + +Json_writer& Json_writer::add_member(const char *name) +{ + if (fmt_helper.on_add_member(name)) + return *this; // handled + + // assert that we are in an object + DBUG_ASSERT(!element_started); + start_element(); + + output.append('"'); + output.append(name); + output.append("\": "); + return *this; +} + + +/* + 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; + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + + +void Json_writer::start_element() +{ + element_started= true; + + if (first_child) + first_child= false; + else + output.append(','); + + append_indent(); +} + +void Json_writer::add_ll(longlong val) +{ + char buf[64]; + my_snprintf(buf, sizeof(buf), "%ld", val); + add_unquoted_str(buf); +} + + +void Json_writer::add_double(double val) +{ + char buf[64]; + my_snprintf(buf, sizeof(buf), "%lg", val); + add_unquoted_str(buf); +} + + +void Json_writer::add_bool(bool val) +{ + add_unquoted_str(val? "true" : "false"); +} + + +void Json_writer::add_null() +{ + add_unquoted_str("null"); +} + + +void Json_writer::add_unquoted_str(const char* str) +{ + if (fmt_helper.on_add_str(str)) + return; + + if (!element_started) + start_element(); + + output.append(str); + element_started= false; +} + + +void Json_writer::add_str(const char *str) +{ + if (fmt_helper.on_add_str(str)) + return; + + if (!element_started) + start_element(); + + output.append('"'); + output.append(str); + output.append('"'); + element_started= false; +} + + +void Json_writer::add_str(const String &str) +{ + add_str(str.ptr()); +} + + +bool Single_line_formatting_helper::on_add_member(const char *name) +{ + DBUG_ASSERT(state== INACTIVE || state == DISABLED); + if (state != DISABLED) + { + // remove everything from the array + buf_ptr= buffer; + + //append member name to the array + size_t len= strlen(name); + if (len < MAX_LINE_LEN) + { + memcpy(buf_ptr, name, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + + line_len= owner->indent_level + len + 1; + state= ADD_MEMBER; + return true; // handled + } + } + return false; // not handled +} + + +bool Single_line_formatting_helper::on_start_array() +{ + if (state == ADD_MEMBER) + { + state= IN_ARRAY; + return true; // handled + } + else + { + state= INACTIVE; + // TODO: what if we have accumulated some stuff already? shouldn't we + // flush it? + return false; // not handled + } +} + + +bool Single_line_formatting_helper::on_end_array() +{ + if (state == IN_ARRAY) + { + flush_on_one_line(); + state= INACTIVE; + return true; // handled + } + return false; // not handled +} + + +void Single_line_formatting_helper::on_start_object() +{ + // Nested objects will not be printed on one line + disable_and_flush(); +} + + +bool Single_line_formatting_helper::on_add_str(const char *str) +{ + if (state == IN_ARRAY) + { + size_t len= strlen(str); + + // New length will be: + // "$string", + // quote + quote + comma + space = 4 + if (line_len + len + 4 > MAX_LINE_LEN) + { + disable_and_flush(); + return false; // didn't handle the last element + } + + //append string to array + memcpy(buf_ptr, str, len); + buf_ptr+=len; + *(buf_ptr++)= 0; + line_len += len + 4; + return true; // handled + } + + disable_and_flush(); + return false; // not handled +} + + +/* + Append everything accumulated to the output on one line +*/ + +void Single_line_formatting_helper::flush_on_one_line() +{ + owner->start_sub_element(); + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + + if (nr == 0) + { + owner->output.append('"'); + owner->output.append(str); + owner->output.append("\": "); + owner->output.append('['); + } + else + { + if (nr != 1) + owner->output.append(", "); + owner->output.append('"'); + owner->output.append(str); + owner->output.append('"'); + } + nr++; + + while (*ptr!=0) + ptr++; + ptr++; + } + owner->output.append(']'); +} + + +void Single_line_formatting_helper::disable_and_flush() +{ + bool start_array= (state == IN_ARRAY); + state= DISABLED; + // deactivate ourselves and flush all accumulated calls. + char *ptr= buffer; + int nr= 0; + while (ptr < buf_ptr) + { + char *str= ptr; + if (nr == 0) + { + owner->add_member(str); + if (start_array) + owner->start_array(); + } + else + { + //if (nr == 1) + // owner->start_array(); + owner->add_str(str); + } + + nr++; + while (*ptr!=0) + ptr++; + ptr++; + } + buf_ptr= buffer; + state= INACTIVE; +} + diff --git a/sql/my_json_writer.h b/sql/my_json_writer.h new file mode 100644 index 00000000000..3a7defc3566 --- /dev/null +++ b/sql/my_json_writer.h @@ -0,0 +1,189 @@ +/* Copyright (C) 2014 SkySQL Ab, MariaDB Corporation Ab + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +class Json_writer; + +/* + Single_line_formatting_helper is used by Json_writer to do better formatting + of JSON documents. + + The idea is to catch arrays that can be printed on one line: + + arrayName : [ "boo", 123, 456 ] + + and actually print them on one line. Arrrays that occupy too much space on + the line, or have nested members cannot be printed on one line. + + We hook into JSON printing functions and try to detect the pattern. While + detecting the pattern, we will accumulate "boo", 123, 456 as strings. + + Then, + - either the pattern is broken, and we print the elements out, + - or the pattern lasts till the end of the array, and we print the + array on one line. +*/ + +class Single_line_formatting_helper +{ + enum enum_state + { + INACTIVE, + ADD_MEMBER, + IN_ARRAY, + 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; + + Json_writer *owner; +public: + Single_line_formatting_helper() : state(INACTIVE), buf_ptr(buffer) {} + + void init(Json_writer *owner_arg) { owner= owner_arg; } + + bool on_add_member(const char *name); + + bool on_start_array(); + bool on_end_array(); + void on_start_object(); + // on_end_object() is not needed. + + bool on_add_str(const char *str); + + void flush_on_one_line(); + void disable_and_flush(); +}; + + +/* + A class to write well-formed JSON documents. The documents are also formatted + for human readability. +*/ + +class Json_writer +{ +public: + /* Add a member. We must be in an object. */ + Json_writer& add_member(const char *name); + + /* Add atomic values */ + void add_str(const char* val); + void add_str(const String &str); + + void add_ll(longlong val); + void add_double(double val); + void add_bool(bool val); + void add_null(); + +private: + void add_unquoted_str(const char* val); +public: + /* Start a child object */ + void start_object(); + void start_array(); + + void end_object(); + void end_array(); + + Json_writer() : + indent_level(0), document_start(true), element_started(false), + first_child(true) + { + fmt_helper.init(this); + } +private: + // TODO: a stack of (name, bool is_object_or_array) elements. + int indent_level; + enum { INDENT_SIZE = 2 }; + + friend class Single_line_formatting_helper; + friend class Json_writer_nesting_guard; + bool document_start; + bool element_started; + bool first_child; + + Single_line_formatting_helper fmt_helper; + + void append_indent(); + void start_element(); + void start_sub_element(); + + //const char *new_member_name; +public: + 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/mysqld.h b/sql/mysqld.h index 94077ec01b8..19616867ce9 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -613,9 +613,13 @@ enum enum_query_type /// Without character set introducers. QT_WITHOUT_INTRODUCERS= (1 << 1), /// view internal representation (like QT_ORDINARY except ORDER BY clause) - QT_VIEW_INTERNAL= (1 << 2) + QT_VIEW_INTERNAL= (1 << 2), + /// This value means focus on readability, not on ability to parse back, etc. + QT_EXPLAIN= (1 << 4) }; + + /* query_id */ typedef int64 query_id_t; extern query_id_t global_query_id; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 297a8b91f27..531721750cc 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -12269,7 +12269,7 @@ Explain_quick_select* QUICK_RANGE_SELECT::get_explain(MEM_ROOT *alloc) { Explain_quick_select *res; if ((res= new (alloc) Explain_quick_select(QS_TYPE_RANGE))) - res->range.set(alloc, head->key_info[index].name, max_used_key_length); + res->range.set(alloc, &head->key_info[index], max_used_key_length); return res; } @@ -12278,7 +12278,7 @@ Explain_quick_select* QUICK_GROUP_MIN_MAX_SELECT::get_explain(MEM_ROOT *alloc) { Explain_quick_select *res; if ((res= new (alloc) Explain_quick_select(QS_TYPE_GROUP_MIN_MAX))) - res->range.set(alloc, head->key_info[index].name, max_used_key_length); + res->range.set(alloc, &head->key_info[index], max_used_key_length); return res; } diff --git a/sql/sql_class.cc b/sql/sql_class.cc index dbdf90066d0..f5d51cfeaee 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -2370,7 +2370,11 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length) int THD::send_explain_fields(select_result *result, uint8 explain_flags, bool is_analyze) { List<Item> field_list; - make_explain_field_list(field_list, explain_flags, is_analyze); + if (lex->explain_json) + make_explain_json_field_list(field_list); + else + make_explain_field_list(field_list, explain_flags, is_analyze); + result->prepare(field_list, NULL); return (result->send_result_set_metadata(field_list, Protocol::SEND_NUM_ROWS | @@ -2378,6 +2382,13 @@ int THD::send_explain_fields(select_result *result, uint8 explain_flags, bool is } +void THD::make_explain_json_field_list(List<Item> &field_list) +{ + Item *item= new Item_empty_string("EXPLAIN", 78, system_charset_info); + field_list.push_back(item); +} + + /* Populate the provided field_list with EXPLAIN output columns. this->lex->describe has the EXPLAIN flags diff --git a/sql/sql_class.h b/sql/sql_class.h index 236975a6e94..24eda567dcc 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -3097,6 +3097,8 @@ public: bool is_analyze); void make_explain_field_list(List<Item> &field_list, uint8 explain_flags, bool is_analyze); + void make_explain_json_field_list(List<Item> &field_list); + /** Clear the current error, if any. We do not clear is_fatal_error or is_fatal_sub_stmt_error since we diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index c85068e5d10..65c748cc590 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -52,7 +52,7 @@ invoked on a running DELETE statement. */ -void Delete_plan::save_explain_data(Explain_query *query) +void Delete_plan::save_explain_data(MEM_ROOT *mem_root, Explain_query *query) { Explain_delete* explain= new Explain_delete; @@ -65,22 +65,23 @@ void Delete_plan::save_explain_data(Explain_query *query) else { explain->deleting_all_rows= false; - Update_plan::save_explain_data_intern(query, explain); + Update_plan::save_explain_data_intern(mem_root, query, explain); } query->add_upd_del_plan(explain); } -void Update_plan::save_explain_data(Explain_query *query) +void Update_plan::save_explain_data(MEM_ROOT *mem_root, Explain_query *query) { Explain_update* explain= new Explain_update; - save_explain_data_intern(query, explain); + save_explain_data_intern(mem_root, query, explain); query->add_upd_del_plan(explain); } -void Update_plan::save_explain_data_intern(Explain_query *query, +void Update_plan::save_explain_data_intern(MEM_ROOT *mem_root, + Explain_query *query, Explain_update *explain) { explain->select_type= "SIMPLE"; @@ -142,10 +143,12 @@ void Update_plan::save_explain_data_intern(Explain_query *query, } explain->using_where= MY_TEST(select && select->cond); + explain->where_cond= select? select->cond: NULL; explain->using_filesort= using_filesort; explain->using_io_buffer= using_io_buffer; - make_possible_keys_line(table, possible_keys, &explain->possible_keys_line); + append_possible_keys(mem_root, explain->possible_keys, table, + possible_keys); explain->quick_info= NULL; @@ -158,11 +161,8 @@ void Update_plan::save_explain_data_intern(Explain_query *query, { if (index != MAX_KEY) { - explain->key_str.append(table->key_info[index].name); - char buf[64]; - size_t length; - length= longlong10_to_str(table->key_info[index].key_length, buf, 10) - buf; - explain->key_len_str.append(buf, length); + explain->key.set(mem_root, &table->key_info[index], + table->key_info[index].key_length); } } explain->rows= scanned_rows; @@ -461,7 +461,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, if (thd->lex->describe) goto produce_explain_and_leave; - query_plan.save_explain_data(thd->lex->explain); + query_plan.save_explain_data(thd->mem_root, thd->lex->explain); DBUG_EXECUTE_IF("show_explain_probe_delete_exec_start", dbug_serve_apcs(thd, 1);); @@ -699,7 +699,7 @@ produce_explain_and_leave: We come here for various "degenerate" query plans: impossible WHERE, no-partitions-used, impossible-range, etc. */ - query_plan.save_explain_data(thd->lex->explain); + query_plan.save_explain_data(thd->mem_root, thd->lex->explain); send_nothing_and_leave: /* diff --git a/sql/sql_explain.cc b/sql/sql_explain.cc index f713e74259d..59f38f7f167 100644 --- a/sql/sql_explain.cc +++ b/sql/sql_explain.cc @@ -21,7 +21,11 @@ #include <my_global.h> #include "sql_priv.h" #include "sql_select.h" +#include "my_json_writer.h" +const char * STR_DELETING_ALL_ROWS= "Deleting all rows"; +const char * STR_IMPOSSIBLE_WHERE= "Impossible WHERE"; +const char * STR_NO_ROWS_AFTER_PRUNING= "No matching rows after partition pruning"; Explain_query::Explain_query(THD *thd_arg) : upd_del_plan(NULL), insert_plan(NULL), thd(thd_arg), apc_enabled(false) @@ -140,8 +144,13 @@ int Explain_query::send_explain(THD *thd) thd->send_explain_fields(result, lex->describe, lex->analyze_stmt)) return 1; - int res; - if ((res= print_explain(result, lex->describe, lex->analyze_stmt))) + int res= 0; + if (thd->lex->explain_json) + print_explain_json(result, thd->lex->analyze_stmt); + else + res= print_explain(result, lex->describe, thd->lex->analyze_stmt); + + if (res) result->abort_result_set(); else result->send_eof(); @@ -178,6 +187,34 @@ int Explain_query::print_explain(select_result_sink *output, } +void Explain_query::print_explain_json(select_result_sink *output, bool is_analyze) +{ + Json_writer writer; + writer.start_object(); + + if (upd_del_plan) + upd_del_plan->print_explain_json(this, &writer, is_analyze); + else if (insert_plan) + insert_plan->print_explain_json(this, &writer, is_analyze); + else + { + /* Start printing from node with id=1 */ + Explain_node *node= get_node(1); + if (!node) + return; /* No query plan */ + node->print_explain_json(this, &writer, is_analyze); + } + + writer.end_object(); + + const CHARSET_INFO *cs= system_charset_info; + List<Item> item_list; + String *buf= &writer.output; + item_list.push_back(new Item_string(buf->ptr(), buf->length(), cs)); + output->send_data(item_list); +} + + bool print_explain_for_slow_log(LEX *lex, THD *thd, String *str) { return lex->explain->print_explain_str(thd, str, /*is_analyze*/ true); @@ -213,12 +250,173 @@ static void push_string(List<Item> *item_list, String *str) item_list->push_back(new Item_string_sys(str->ptr(), str->length())); } +static void push_string_list(List<Item> *item_list, String_list &lines, + String *buf) +{ + List_iterator_fast<char> it(lines); + char *line; + bool first= true; + while ((line= it++)) + { + if (first) + first= false; + else + buf->append(','); + + buf->append(line); + } + push_string(item_list, buf); +} + + +/* + 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 +*/ + +static +int print_explain_row(select_result_sink *result, + uint8 options, bool is_analyze, + uint select_number, + const char *select_type, + const char *table_name, + const char *partitions, + enum join_type jtype, + String_list *possible_keys, + const char *index, + const char *key_len, + const char *ref, + ha_rows *rows, + ha_rows *r_rows, + double r_filtered, + const char *extra) +{ + Item *item_null= new Item_null(); + List<Item> item_list; + Item *item; + + item_list.push_back(new Item_int((int32) select_number)); + item_list.push_back(new Item_string_sys(select_type)); + item_list.push_back(new Item_string_sys(table_name)); + if (options & DESCRIBE_PARTITIONS) + { + if (partitions) + { + item_list.push_back(new Item_string_sys(partitions)); + } + else + item_list.push_back(item_null); + } + + const char *jtype_str= join_type_str[jtype]; + item_list.push_back(new Item_string_sys(jtype_str)); + + /* 'possible_keys' */ + if (possible_keys && !possible_keys->is_empty()) + { + StringBuffer<64> possible_keys_buf; + push_string_list(&item_list, *possible_keys, &possible_keys_buf); + } + else + item_list.push_back(item_null); + + /* 'index */ + item= index ? new Item_string_sys(index) : item_null; + item_list.push_back(item); + + /* 'key_len */ + item= key_len ? new Item_string_sys(key_len) : item_null; + item_list.push_back(item); + + /* 'ref' */ + item= ref ? new Item_string_sys(ref) : item_null; + item_list.push_back(item); + + /* 'rows' */ + if (rows) + { + item_list.push_back(new Item_int(*rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* 'r_rows' */ + if (is_analyze) + { + if (r_rows) + { + item_list.push_back(new Item_int(*r_rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + } + + /* 'filtered' */ + const double filtered=100.0; + if (options & DESCRIBE_EXTENDED || is_analyze) + item_list.push_back(new Item_float(filtered, 2)); + + /* 'r_filtered' */ + if (is_analyze) + item_list.push_back(new Item_float(r_filtered, 2)); + + /* 'Extra' */ + if (extra) + item_list.push_back(new Item_string_sys(extra)); + else + item_list.push_back(item_null); + + if (result->send_data(item_list)) + return 1; + return 0; +} + + + + +uint Explain_union::make_union_table_name(char *buf) +{ + uint childno= 0; + uint len= 6, lastop= 0; + memcpy(buf, STRING_WITH_LEN("<union")); + + for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN; + childno++) + { + len+= lastop; + lastop= my_snprintf(buf + len, NAME_LEN - len, + "%u,", union_members.at(childno)); + } + + if (childno < union_members.elements() || len + lastop >= NAME_LEN) + { + memcpy(buf + len, STRING_WITH_LEN("...>") + 1); + len+= 4; + } + else + { + len+= lastop; + buf[len - 1]= '>'; // change ',' to '>' + } + return len; +} + int Explain_union::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze) { + //const CHARSET_INFO *cs= system_charset_info; char table_name_buffer[SAFE_NAME_LEN]; /* print all UNION children, in order */ @@ -242,31 +440,8 @@ int Explain_union::print_explain(Explain_query *query, push_str(&item_list, fake_select_type); /* `table` column: something like "<union1,2>" */ - { - uint childno= 0; - uint len= 6, lastop= 0; - memcpy(table_name_buffer, STRING_WITH_LEN("<union")); - - for (; childno < union_members.elements() && len + lastop + 5 < NAME_LEN; - childno++) - { - len+= lastop; - lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, - "%u,", union_members.at(childno)); - } - - if (childno < union_members.elements() || len + lastop >= NAME_LEN) - { - memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); - len+= 4; - } - else - { - len+= lastop; - table_name_buffer[len - 1]= '>'; // change ',' to '>' - } - item_list.push_back(new Item_string_sys(table_name_buffer, len)); - } + uint len= make_union_table_name(table_name_buffer); + item_list.push_back(new Item_string_sys(table_name_buffer, len)); /* `partitions` column */ if (explain_flags & DESCRIBE_PARTITIONS) @@ -326,14 +501,46 @@ 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(); + writer->add_member("union_result").start_object(); + // using_temporary_table + make_union_table_name(table_name_buffer); + writer->add_member("table_name").add_str(table_name_buffer); + writer->add_member("access_type").add_str("ALL"); // not very useful + writer->add_member("query_specifications").start_array(); + + for (int i= 0; i < (int) union_members.elements(); i++) + { + writer->start_object(); + //writer->add_member("dependent").add_str("TODO"); + //writer->add_member("cacheable").add_str("TODO"); + Explain_select *sel= query->get_select(union_members.at(i)); + sel->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + writer->end_array(); + + print_explain_json_for_children(query, writer, is_analyze); + + writer->end_object(); // union_result + writer->end_object(); // query_block +} + + /* Print EXPLAINs for all children nodes (i.e. for subqueries) */ 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++) { @@ -345,6 +552,50 @@ int Explain_node::print_explain_for_children(Explain_query *query, } +/* + This tells whether a child subquery should be printed in JSON output. + + Derived tables and Non-merged semi-joins should not be printed, because they + are printed inline in Explain_table_access. +*/ +bool is_connection_printable_in_json(enum Explain_node::explain_connection_type type) +{ + return (type != Explain_node::EXPLAIN_NODE_DERIVED && + type != Explain_node::EXPLAIN_NODE_NON_MERGED_SJ); +} + + +void Explain_node::print_explain_json_for_children(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + bool started= false; + for (int i= 0; i < (int) children.elements(); i++) + { + Explain_node *node= query->get_node(children.at(i)); + /* Derived tables are printed inside Explain_table_access objects */ + + if (!is_connection_printable_in_json(node->connection_type)) + continue; + + if (!started) + { + writer->add_member("subqueries").start_array(); + started= true; + } + + writer->start_object(); + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + + if (started) + writer->end_array(); +} + + void Explain_select::replace_table(uint idx, Explain_table_access *new_tab) { delete join_tabs[idx]; @@ -352,7 +603,7 @@ void Explain_select::replace_table(uint idx, Explain_table_access *new_tab) } -Explain_select::~Explain_select() +Explain_basic_join::~Explain_basic_join() { if (join_tabs) { @@ -413,111 +664,163 @@ int Explain_select::print_explain(Explain_query *query, using_fs= false; } } + for (uint i=0; i< n_join_tabs; i++) + { + Explain_basic_join* nest; + if ((nest= join_tabs[i]->sjm_nest)) + nest->print_explain(query, output, explain_flags, is_analyze); + } } return print_explain_for_children(query, output, explain_flags, is_analyze); } -void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) +int Explain_basic_join::print_explain(Explain_query *query, + select_result_sink *output, + uint8 explain_flags, bool is_analyze) { - extra_tags.append(extra_tag); + for (uint i=0; i< n_join_tabs; i++) + { + if (join_tabs[i]->print_explain(output, explain_flags, is_analyze, + select_id, + "MATERIALIZED" /*select_type*/, + FALSE /*using temporary*/, + FALSE /*using filesort*/)) + return 1; + } + return 0; } -int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, - bool is_analyze, - uint select_id, const char *select_type, - bool using_temporary, bool using_filesort) +void Explain_select::print_explain_json(Explain_query *query, + Json_writer *writer, bool is_analyze) { - const CHARSET_INFO *cs= system_charset_info; - const char *hash_key_prefix= "#hash#"; - bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || - type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + Json_writer_nesting_guard guard(writer); - List<Item> item_list; - Item *item_null= new Item_null(); - - if (sjm_nest_select_id) - select_id= sjm_nest_select_id; + if (message) + { + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(select_id); - /* `id` column */ - item_list.push_back(new Item_int((int32) select_id)); + writer->add_member("table").start_object(); + writer->add_member("message").add_str(message); + writer->end_object(); - /* `select_type` column */ - if (sjm_nest_select_id) - push_str(&item_list, "MATERIALIZED"); + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); + } else - push_str(&item_list, select_type); + { + /* + TODO: how does this approach allow to print ORDER BY members? + Explain_basic_join does not have ORDER/GROUP. + A: factor out join tab printing loop into a common func. + */ + Explain_basic_join::print_explain_json(query, writer, is_analyze); + } - /* `table` column */ - push_string(&item_list, &table_name); - - /* `partitions` column */ - if (explain_flags & DESCRIBE_PARTITIONS) +} + + +void Explain_basic_join::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(select_id); + for (uint i=0; i< n_join_tabs; i++) { - if (used_partitions_set) - { - push_string(&item_list, &used_partitions); - } - else - item_list.push_back(item_null); + if (join_tabs[i]->start_dups_weedout) + writer->add_member("duplicates_removal").start_object(); + + join_tabs[i]->print_explain_json(query, writer, is_analyze); + + if (join_tabs[i]->end_dups_weedout) + writer->end_object(); } + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); +} - /* `type` column */ - push_str(&item_list, join_type_str[type]); - /* `possible_keys` column */ - if (possible_keys_str.length() > 0) - push_string(&item_list, &possible_keys_str); - else - item_list.push_back(item_null); +void Explain_table_access::push_extra(enum explain_extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + +/* + Put the contents of 'key' field of EXPLAIN otuput into key_str. + + It is surprisingly complex: + - hash join shows #hash#used_key + - quick selects that use single index will print index name +*/ + +void Explain_table_access::fill_key_str(String *key_str, bool is_json) const +{ + const CHARSET_INFO *cs= system_charset_info; + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); + const char *hash_key_prefix= "#hash#"; - /* `key` */ - StringBuffer<64> key_str; if (key.get_key_name()) { if (is_hj) - key_str.append(hash_key_prefix, strlen(hash_key_prefix), cs); + key_str->append(hash_key_prefix, strlen(hash_key_prefix), cs); - key_str.append(key.get_key_name()); + key_str->append(key.get_key_name()); if (is_hj && type != JT_HASH) - key_str.append(':'); + key_str->append(':'); } if (quick_info) { StringBuffer<64> buf2; - quick_info->print_key(&buf2); - key_str.append(buf2); + if (is_json) + quick_info->print_extra_recursive(&buf2); + else + quick_info->print_key(&buf2); + key_str->append(buf2); } if (type == JT_HASH_NEXT) - key_str.append(hash_next_key.get_key_name()); - - if (key_str.length() > 0) - push_string(&item_list, &key_str); - else - item_list.push_back(item_null); + key_str->append(hash_next_key.get_key_name()); +} - /* `key_len` */ - StringBuffer<64> key_len_str; +/* + Fill "key_length". + - this is just used key length for ref/range + - for index_merge, it is a comma-separated list of lengths. + - for hash join, it is key_len:pseudo_key_len + + The column looks identical in tabular and json forms. In JSON, we consider + the column legacy, it is superceded by used_key_parts. +*/ + +void Explain_table_access::fill_key_len_str(String *key_len_str) const +{ + bool is_hj= (type == JT_HASH || type == JT_HASH_NEXT || + type == JT_HASH_RANGE || type == JT_HASH_INDEX_MERGE); if (key.get_key_len() != (uint)-1) { char buf[64]; size_t length; length= longlong10_to_str(key.get_key_len(), buf, 10) - buf; - key_len_str.append(buf, length); + key_len_str->append(buf, length); if (is_hj && type != JT_HASH) - key_len_str.append(':'); + key_len_str->append(':'); } if (quick_info) { StringBuffer<64> buf2; quick_info->print_key_len(&buf2); - key_len_str.append(buf2); + key_len_str->append(buf2); } if (type == JT_HASH_NEXT) @@ -525,19 +828,127 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai char buf[64]; size_t length; length= longlong10_to_str(hash_next_key.get_key_len(), buf, 10) - buf; - key_len_str.append(buf, length); + key_len_str->append(buf, length); + } +} + + +void Explain_index_use::set(MEM_ROOT *mem_root, KEY *key, uint key_len_arg) +{ + set_pseudo_key(mem_root, key->name); + key_len= key_len_arg; + uint len= 0; + for (uint i= 0; i < key->usable_key_parts; i++) + { + key_parts_list.append_str(mem_root, key->key_part[i].field->field_name); + len += key->key_part[i].store_length; + if (len >= key_len_arg) + break; + } +} + + +void Explain_index_use::set_pseudo_key(MEM_ROOT *root, const char* key_name_arg) +{ + if (key_name_arg) + { + size_t name_len= strlen(key_name_arg); + if ((key_name= (char*)alloc_root(root, name_len+1))) + memcpy(key_name, key_name_arg, name_len+1); + } + else + key_name= NULL; + key_len= -1; +} + + +/* + Given r_filtered% from join buffer condition and join condition, produce a + combined r_filtered% number. This is needed for tabular EXPLAIN output which + has only one cell for r_filtered value. +*/ + +double Explain_table_access::get_r_filtered() +{ + double r_filtered= tracker.get_filtered_after_where(); + if (bka_type.is_using_jbuf()) + r_filtered *= jbuf_tracker.get_filtered_after_where(); + return r_filtered; +} + + +int Explain_table_access::print_explain(select_result_sink *output, uint8 explain_flags, + bool is_analyze, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort) +{ + //const CHARSET_INFO *cs= system_charset_info; + + List<Item> item_list; + Item *item_null= new Item_null(); + + /* `id` column */ + item_list.push_back(new Item_int((int32) select_id)); + + /* `select_type` column */ + push_str(&item_list, select_type); + + /* `table` column */ + push_string(&item_list, &table_name); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + { + if (used_partitions_set) + { + push_string(&item_list, &used_partitions); + } + else + item_list.push_back(item_null); } + /* `type` column */ + push_str(&item_list, join_type_str[type]); + + /* `possible_keys` column */ + StringBuffer<64> possible_keys_buf; + if (possible_keys.is_empty()) + item_list.push_back(item_null); + else + push_string_list(&item_list, possible_keys, &possible_keys_buf); + + /* `key` */ + StringBuffer<64> key_str; + fill_key_str(&key_str, false); + + if (key_str.length() > 0) + push_string(&item_list, &key_str); + else + item_list.push_back(item_null); + + /* `key_len` */ + StringBuffer<64> key_len_str; + fill_key_len_str(&key_len_str); + if (key_len_str.length() > 0) push_string(&item_list, &key_len_str); else item_list.push_back(item_null); /* `ref` */ - if (ref_set) - push_string(&item_list, &ref); + StringBuffer<64> ref_list_buf; + if (ref_list.is_empty()) + { + if (type == JT_FT) + { + /* Traditionally, EXPLAIN lines with type=fulltext have ref='' */ + push_str(&item_list, ""); + } + else + item_list.push_back(item_null); + } else - item_list.push_back(item_null); + push_string_list(&item_list, ref_list, &ref_list_buf); /* `rows` */ if (rows_set) @@ -629,6 +1040,276 @@ int Explain_table_access::print_explain(select_result_sink *output, uint8 explai } +bool String_list::append_str(MEM_ROOT *mem_root, const char *str) +{ + size_t len= strlen(str); + char *cp; + if (!(cp = (char*)alloc_root(mem_root, len+1))) + return 1; + memcpy(cp, str, len+1); + push_back(cp); + return 0; +} + + +static void write_item(Json_writer *writer, Item *item) +{ + THD *thd= current_thd; + char item_buf[256]; + String str(item_buf, sizeof(item_buf), &my_charset_bin); + str.length(0); + + ulonglong save_option_bits= thd->variables.option_bits; + thd->variables.option_bits &= ~OPTION_QUOTE_SHOW_CREATE; + + item->print(&str, QT_EXPLAIN); + + thd->variables.option_bits= save_option_bits; + writer->add_str(str.c_ptr_safe()); +} + + +void Explain_table_access::tag_to_json(Json_writer *writer, enum explain_extra_tag tag) +{ + switch (tag) + { + case ET_OPEN_FULL_TABLE: + writer->add_member("open_full_table").add_bool(true); + break; + case ET_SCANNED_0_DATABASES: + writer->add_member("scanned_databases").add_ll(0); + break; + case ET_SCANNED_1_DATABASE: + writer->add_member("scanned_databases").add_ll(1); + break; + case ET_SCANNED_ALL_DATABASES: + writer->add_member("scanned_databases").add_str("all"); + break; + case ET_SKIP_OPEN_TABLE: + writer->add_member("skip_open_table").add_bool(true); + break; + case ET_OPEN_FRM_ONLY: + writer->add_member("open_frm_only").add_bool(true); + break; + case ET_USING_INDEX_CONDITION: + writer->add_member("index_condition"); + write_item(writer, pushed_index_cond); + break; + case ET_USING_WHERE: + { + /* + We are printing the condition that is checked when scanning this + table. + - when join buffer is used, it is cache_cond. + - in other cases, it is where_cond. + */ + Item *item= bka_type.is_using_jbuf()? cache_cond: where_cond; + if (item) + { + writer->add_member("attached_condition"); + write_item(writer, item); + } + } + break; + case ET_USING_INDEX: + writer->add_member("using_index").add_bool(true); + break; + case ET_USING: + // index merge: case ET_USING + break; + case ET_USING_JOIN_BUFFER: + /* Do nothing. Join buffer is handled differently */ + case ET_START_TEMPORARY: + case ET_END_TEMPORARY: + /* Handled as "duplicates_removal: { ... } */ + break; + case ET_FIRST_MATCH: + writer->add_member("first_match").add_str(firstmatch_table_name.c_ptr()); + break; + case ET_LOOSESCAN: + writer->add_member("loose_scan").add_bool(true); + break; + default: + DBUG_ASSERT(0); + } +} + + +void Explain_table_access::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + if (bka_type.is_using_jbuf()) + { + writer->add_member("block-nl-join").start_object(); + } + + writer->add_member("table").start_object(); + + writer->add_member("table_name").add_str(table_name); + // partitions + writer->add_member("access_type").add_str(join_type_str[type]); + if (!possible_keys.is_empty()) + { + List_iterator_fast<char> it(possible_keys); + const char *name; + writer->add_member("possible_keys").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + /* `key` */ + /* For non-basic quick select, 'key' will not be present */ + if (!quick_info || quick_info->is_basic()) + { + StringBuffer<64> key_str; + fill_key_str(&key_str, true); + if (key_str.length()) + writer->add_member("key").add_str(key_str); + } + + /* `key_length` */ + StringBuffer<64> key_len_str; + fill_key_len_str(&key_len_str); + if (key_len_str.length()) + writer->add_member("key_length").add_str(key_len_str); + + /* `used_key_parts` */ + String_list *parts_list= NULL; + if (quick_info && quick_info->is_basic()) + parts_list= &quick_info->range.key_parts_list; + else + parts_list= &key.key_parts_list; + + if (parts_list && !parts_list->is_empty()) + { + List_iterator_fast<char> it(*parts_list); + const char *name; + writer->add_member("used_key_parts").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + if (quick_info && !quick_info->is_basic()) + { + writer->add_member("index_merge").start_object(); + quick_info->print_json(writer); + writer->end_object(); + } + + /* `ref` */ + if (!ref_list.is_empty()) + { + List_iterator_fast<char> it(ref_list); + const char *str; + writer->add_member("ref").start_array(); + while ((str= it++)) + writer->add_str(str); + writer->end_array(); + } + + /* r_loops (not present in tabular output) */ + if (is_analyze) + { + writer->add_member("r_loops").add_ll(tracker.get_loops()); + } + + /* `rows` */ + if (rows_set) + writer->add_member("rows").add_ll(rows); + + /* `r_rows` */ + if (is_analyze) + { + writer->add_member("r_rows"); + if (tracker.has_scans()) + { + ha_rows avg_rows= tracker.get_avg_rows(); + writer->add_ll(avg_rows); + } + else + writer->add_null(); + } + + /* `filtered` */ + if (filtered_set) + writer->add_member("filtered").add_double(filtered); + + /* `r_filtered` */ + if (is_analyze) + { + writer->add_member("r_filtered"); + if (tracker.has_scans()) + writer->add_double(tracker.get_filtered_after_where()*100.0); + else + writer->add_null(); + } + + for (int i=0; i < (int)extra_tags.elements(); i++) + { + tag_to_json(writer, extra_tags.at(i)); + } + + if (bka_type.is_using_jbuf()) + { + writer->end_object(); + writer->add_member("buffer_type").add_str(bka_type.incremental? + "incremental":"flat"); + writer->add_member("join_type").add_str(bka_type.join_alg); + if (bka_type.mrr_type.length()) + writer->add_member("mrr_type").add_str(bka_type.mrr_type); + if (where_cond) + { + writer->add_member("attached_condition"); + write_item(writer, where_cond); + } + + if (is_analyze) + { + //writer->add_member("r_loops").add_ll(jbuf_tracker.get_loops()); + writer->add_member("r_filtered"); + if (jbuf_tracker.has_scans()) + writer->add_double(jbuf_tracker.get_filtered_after_where()*100.0); + else + writer->add_null(); + } + } + + if (derived_select_number) + { + /* This is a derived table. Print its contents here */ + writer->add_member("materialized").start_object(); + Explain_node *node= query->get_node(derived_select_number); + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + if (non_merged_sjm_number) + { + /* This is a non-merged semi-join table. Print its contents here */ + writer->add_member("materialized").start_object(); + writer->add_member("unique").add_ll(1); + Explain_node *node= query->get_node(non_merged_sjm_number); + node->connection_type= Explain_node::EXPLAIN_NODE_NON_MERGED_SJ; + node->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + if (sjm_nest) + { + /* This is a non-merged semi-join table. Print its contents here */ + writer->add_member("materialized").start_object(); + writer->add_member("unique").add_ll(1); + sjm_nest->print_explain_json(query, writer, is_analyze); + writer->end_object(); + } + + writer->end_object(); +} + + /* Elements in this array match members of enum Extra_tag, defined in sql_explain.h @@ -758,6 +1439,35 @@ void Explain_quick_select::print_extra(String *str) print_extra_recursive(str); } +void Explain_quick_select::print_json(Json_writer *writer) +{ + if (is_basic()) + { + writer->add_member("range").start_object(); + + writer->add_member("key").add_str(range.get_key_name()); + + List_iterator_fast<char> it(range.key_parts_list); + const char *name; + writer->add_member("used_key_parts").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + + writer->end_object(); + } + else + { + writer->add_member(get_name_by_type()).start_object(); + + List_iterator_fast<Explain_quick_select> it (children); + Explain_quick_select* child; + while ((child = it++)) + child->print_json(writer); + + writer->end_object(); + } +} void Explain_quick_select::print_extra_recursive(String *str) { @@ -867,7 +1577,7 @@ int Explain_delete::print_explain(Explain_query *query, { if (deleting_all_rows) { - const char *msg= "Deleting all rows"; + const char *msg= STR_DELETING_ALL_ROWS; int res= print_explain_message_line(output, explain_flags, is_analyze, 1 /*select number*/, select_type, &rows, msg); @@ -882,6 +1592,27 @@ int Explain_delete::print_explain(Explain_query *query, } +void Explain_delete::print_explain_json(Explain_query *query, + Json_writer *writer, + bool is_analyze) +{ + Json_writer_nesting_guard guard(writer); + + if (deleting_all_rows) + { + writer->add_member("query_block").start_object(); + writer->add_member("select_id").add_ll(1); + writer->add_member("table").start_object(); + // just like mysql-5.6, we don't print table name. Is this ok? + writer->add_member("message").add_str(STR_DELETING_ALL_ROWS); + writer->end_object(); // table + writer->end_object(); // query_block + return; + } + Explain_update::print_explain_json(query, writer, is_analyze); +} + + int Explain_update::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, @@ -893,8 +1624,8 @@ int Explain_update::print_explain(Explain_query *query, if (impossible_where || no_partitions) { const char *msg= impossible_where ? - "Impossible WHERE" : - "No matching rows after partition pruning"; + STR_IMPOSSIBLE_WHERE : + STR_NO_ROWS_AFTER_PRUNING; int res= print_explain_message_line(output, explain_flags, is_analyze, 1 /*select number*/, select_type, @@ -903,7 +1634,6 @@ int Explain_update::print_explain(Explain_query *query, return res; } - if (quick_info) { quick_info->print_key(&key_buf); @@ -917,10 +1647,13 @@ int Explain_update::print_explain(Explain_query *query, extra_str.append(quick_buf); } } - else + else if (key.get_key_name()) { - key_buf.copy(key_str); - key_len_buf.copy(key_len_str); + const char *name= key.get_key_name(); + key_buf.set(name, strlen(name), &my_charset_bin); + char buf[64]; + size_t length= longlong10_to_str(key.get_key_len(), buf, 10) - buf; + key_len_buf.copy(buf, length, &my_charset_bin); } if (using_where) @@ -964,7 +1697,7 @@ int Explain_update::print_explain(Explain_query *query, table_name.c_ptr(), used_partitions_set? used_partitions.c_ptr() : NULL, jtype, - possible_keys_line.length()? possible_keys_line.c_ptr(): NULL, + &possible_keys, key_buf.length()? key_buf.c_ptr() : NULL, key_len_buf.length() ? key_len_buf.c_ptr() : NULL, NULL, /* 'ref' is always NULL in single-table EXPLAIN DELETE */ @@ -977,6 +1710,140 @@ int Explain_update::print_explain(Explain_query *query, } +void Explain_update::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 (impossible_where || no_partitions) + { + const char *msg= impossible_where ? STR_IMPOSSIBLE_WHERE : + STR_NO_ROWS_AFTER_PRUNING; + writer->add_member("table").start_object(); + writer->add_member("message").add_str(msg); + writer->end_object(); // table + writer->end_object(); // query_block + return; + } + + writer->add_member("table").start_object(); + + if (get_type() == EXPLAIN_UPDATE) + writer->add_member("update").add_ll(1); + else + writer->add_member("delete").add_ll(1); + + writer->add_member("table_name").add_str(table_name); + writer->add_member("access_type").add_str(join_type_str[jtype]); + + if (!possible_keys.is_empty()) + { + List_iterator_fast<char> it(possible_keys); + const char *name; + writer->add_member("possible_keys").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + /* `key`, `key_length` */ + if (quick_info && quick_info->is_basic()) + { + StringBuffer<64> key_buf; + StringBuffer<64> key_len_buf; + quick_info->print_extra_recursive(&key_buf); + quick_info->print_key_len(&key_len_buf); + + writer->add_member("key").add_str(key_buf); + writer->add_member("key_length").add_str(key_len_buf); + } + else if (key.get_key_name()) + { + writer->add_member("key").add_str(key.get_key_name()); + writer->add_member("key_length").add_str(key.get_key_len()); + } + + /* `used_key_parts` */ + String_list *parts_list= NULL; + if (quick_info && quick_info->is_basic()) + parts_list= &quick_info->range.key_parts_list; + else + parts_list= &key.key_parts_list; + + if (parts_list && !parts_list->is_empty()) + { + List_iterator_fast<char> it(*parts_list); + const char *name; + writer->add_member("used_key_parts").start_array(); + while ((name= it++)) + writer->add_str(name); + writer->end_array(); + } + + if (quick_info && !quick_info->is_basic()) + { + writer->add_member("index_merge").start_object(); + quick_info->print_json(writer); + writer->end_object(); + } + +#if 0 + /* `ref` */ + if (!ref_list.is_empty()) + { + List_iterator_fast<char> it(ref_list); + const char *str; + writer->add_member("ref").start_array(); + while ((str= it++)) + writer->add_str(str); + writer->end_array(); + } +#endif + + /* `rows` */ + writer->add_member("rows").add_ll(rows); + + /* `r_rows` */ + if (is_analyze && tracker.has_scans()) + { + ha_rows avg_rows= tracker.get_avg_rows(); + writer->add_member("r_rows").add_ll(avg_rows); + } + + /* UPDATE/DELETE do not produce `filtered` estimate */ + + /* `r_filtered` */ + if (is_analyze) + { + double r_filtered= tracker.get_filtered_after_where(); + writer->add_member("r_filtered").add_double(r_filtered); + } + + if (mrr_type.length() != 0) + writer->add_member("mrr_type").add_str(mrr_type.ptr()); + + if (using_filesort) + writer->add_member("using_filesort").add_ll(1); + + if (using_io_buffer) + writer->add_member("using_io_buffer").add_ll(1); + + if (where_cond) + { + writer->add_member("attached_condition"); + write_item(writer, where_cond); + } + + writer->end_object(); // table + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); // query_block +} + + int Explain_insert::print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, @@ -1001,6 +1868,20 @@ int Explain_insert::print_explain(Explain_query *query, return print_explain_for_children(query, output, explain_flags, is_analyze); } +void Explain_insert::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); + writer->add_member("table").start_object(); + writer->add_member("table_name").add_str(table_name.c_ptr()); + writer->end_object(); // table + print_explain_json_for_children(query, writer, is_analyze); + writer->end_object(); // query_block +} + void delete_explain_query(LEX *lex) { diff --git a/sql/sql_explain.h b/sql/sql_explain.h index c4de08f6a4c..68ef59c732d 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -14,7 +14,21 @@ along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -/* Data structures for ANALYZE */ + +class String_list: public List<char> +{ +public: + bool append_str(MEM_ROOT *mem_root, const char *str); +}; + + +/* + A class for collecting read statistics. + + The idea is that we run several scans. Each scans gets rows, and then filters + some of them out. We count scans, rows, and rows left after filtering. +*/ + class Table_access_tracker { public: @@ -29,6 +43,7 @@ public: ha_rows r_rows_after_where; /* Rows after applying attached part of WHERE */ bool has_scans() { return (r_scans != 0); } + ha_rows get_loops() { return r_scans; } ha_rows get_avg_rows() { return r_scans ? (ha_rows)rint((double) r_rows / r_scans): 0; @@ -67,24 +82,43 @@ const int FAKE_SELECT_LEX_ID= (int)UINT_MAX; class Explain_query; +class Json_writer; + /* A node can be either a SELECT, or a UNION. */ class Explain_node : public Sql_alloc { public: + /* A type specifying what kind of node this is */ enum explain_node_type { EXPLAIN_UNION, - EXPLAIN_SELECT, + EXPLAIN_SELECT, + EXPLAIN_BASIC_JOIN, EXPLAIN_UPDATE, EXPLAIN_DELETE, EXPLAIN_INSERT }; + + /* How this node is connected */ + enum explain_connection_type { + EXPLAIN_NODE_OTHER, + EXPLAIN_NODE_DERIVED, /* Materialized derived table */ + EXPLAIN_NODE_NON_MERGED_SJ /* aka JTBM semi-join */ + }; + + Explain_node() : connection_type(EXPLAIN_NODE_OTHER) {} virtual enum explain_node_type get_type()= 0; virtual int get_select_id()= 0; + /* + How this node is connected to its parent. + (NOTE: EXPLAIN_NODE_NON_MERGED_SJ is set very late currently) + */ + enum explain_connection_type connection_type; + /* A node may have children nodes. When a node's explain structure is created, children nodes may not yet have QPFs. This is why we store ids. @@ -97,9 +131,13 @@ public: virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze)=0; - + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze)= 0; + int print_explain_for_children(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json_for_children(Explain_query *query, + Json_writer *writer, bool is_analyze); virtual ~Explain_node(){} }; @@ -107,6 +145,49 @@ public: class Explain_table_access; +/* + A basic join. This is only used for SJ-Materialization nests. + + Basic join doesn't have ORDER/GROUP/DISTINCT operations. It also cannot be + degenerate. + + It has its own select_id. +*/ +class Explain_basic_join : public Explain_node +{ +public: + enum explain_node_type get_type() { return EXPLAIN_BASIC_JOIN; } + + Explain_basic_join() : join_tabs(NULL) {} + ~Explain_basic_join(); + + bool add_table(Explain_table_access *tab) + { + if (!join_tabs) + { + join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) * + MAX_TABLES, MYF(0)); + n_join_tabs= 0; + } + join_tabs[n_join_tabs++]= tab; + return false; + } + + int get_select_id() { return select_id; } + + int select_id; + + int print_explain(Explain_query *query, select_result_sink *output, + uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); + + /* A flat array of Explain structs for tables. */ + Explain_table_access** join_tabs; + uint n_join_tabs; +}; + + /* EXPLAIN structure for a SELECT. @@ -122,30 +203,16 @@ class Explain_table_access; a way get node's children. */ -class Explain_select : public Explain_node +class Explain_select : public Explain_basic_join { public: enum explain_node_type get_type() { return EXPLAIN_SELECT; } Explain_select() : - message(NULL), join_tabs(NULL), + message(NULL), using_temporary(false), using_filesort(false) {} - - ~Explain_select(); - bool add_table(Explain_table_access *tab) - { - if (!join_tabs) - { - join_tabs= (Explain_table_access**) my_malloc(sizeof(Explain_table_access*) * - MAX_TABLES, MYF(0)); - n_join_tabs= 0; - } - join_tabs[n_join_tabs++]= tab; - return false; - } - /* This is used to save the results of "late" test_if_skip_sort_order() calls that are made from JOIN::exec @@ -153,30 +220,22 @@ public: void replace_table(uint idx, Explain_table_access *new_tab); public: - int select_id; const char *select_type; - int get_select_id() { return select_id; } - /* If message != NULL, this is a degenerate join plan, and all subsequent members have no info */ const char *message; - /* - A flat array of Explain structs for tables. The order is "just like EXPLAIN - would print them". - */ - Explain_table_access** join_tabs; - uint n_join_tabs; - /* Global join attributes. In tabular form, they are printed on the first row */ bool using_temporary; bool using_filesort; int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); Table_access_tracker *get_using_temporary_read_tracker() { @@ -222,6 +281,8 @@ public: } int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); const char *fake_select_type; bool using_filesort; @@ -236,6 +297,8 @@ public: return &tmptable_read_tracker; } private: + uint make_union_table_name(char *buf); + Table_access_tracker fake_select_lex_tracker; /* This one is for reading after ORDER BY */ Table_access_tracker tmptable_read_tracker; @@ -311,6 +374,8 @@ public: /* Return tabular EXPLAIN output as a text string */ bool print_explain_str(THD *thd, String *out_str, bool is_analyze); + void print_explain_json(select_result_sink *output, bool is_analyze); + /* If true, at least part of EXPLAIN can be printed */ bool have_query_plan() { return insert_plan || upd_del_plan|| get_node(1) != NULL; } @@ -407,24 +472,24 @@ class Explain_index_use : public Sql_alloc { char *key_name; uint key_len; - /* will add #keyparts here if we implement EXPLAIN FORMAT=JSON */ public: + String_list key_parts_list; + + Explain_index_use() + { + clear(); + } - void set(MEM_ROOT *root, const char *key_name_arg, uint key_len_arg) + void clear() { - if (key_name_arg) - { - size_t name_len= strlen(key_name_arg); - if ((key_name= (char*)alloc_root(root, name_len+1))) - memcpy(key_name, key_name_arg, name_len+1); - } - else - key_name= NULL; - key_len= key_len_arg; + key_name= NULL; + key_len= (uint)-1; } + void set(MEM_ROOT *root, KEY *key_name, uint key_len_arg); + void set_pseudo_key(MEM_ROOT *root, const char *key_name); - inline const char *get_key_name() { return key_name; } - inline uint get_key_len() { return key_len; } + inline const char *get_key_name() const { return key_name; } + inline uint get_key_len() const { return key_len; } }; @@ -438,6 +503,13 @@ public: {} const int quick_type; + + bool is_basic() + { + return (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE || + quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC || + quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX); + } /* This is used when quick_type == QUICK_SELECT_I::QS_TYPE_RANGE */ Explain_index_use range; @@ -448,8 +520,11 @@ public: void print_extra(String *str); void print_key(String *str); void print_key_len(String *str); -private: + + void print_json(Json_writer *writer); + void print_extra_recursive(String *str); +private: const char *get_name_by_type(); }; @@ -461,26 +536,40 @@ private: class Explain_table_access : public Sql_alloc { public: + Explain_table_access() : + derived_select_number(0), + non_merged_sjm_number(0), + start_dups_weedout(false), + end_dups_weedout(false), + where_cond(NULL), + cache_cond(NULL), + pushed_index_cond(NULL), + sjm_nest(NULL) + {} + ~Explain_table_access() { delete sjm_nest; } + void push_extra(enum explain_extra_tag extra_tag); /* Internals */ public: - /* - 0 means this tab is not inside SJM nest and should use Explain_select's id - other value means the tab is inside an SJM nest. - */ - int sjm_nest_select_id; - /* id and 'select_type' are cared-of by the parent Explain_select */ StringBuffer<32> table_name; + /* + Non-zero number means this is a derived table. The number can be used to + find the query plan for the derived table + */ + int derived_select_number; + /* TODO: join with the previous member. */ + int non_merged_sjm_number; + enum join_type type; StringBuffer<32> used_partitions; bool used_partitions_set; - /* Empty string means "NULL" will be printed */ - StringBuffer<32> possible_keys_str; + /* Empty means "NULL" will be printed */ + String_list possible_keys; /* Index use: key name and length. @@ -498,8 +587,7 @@ public: */ Explain_index_use hash_next_key; - bool ref_set; /* not set means 'NULL' should be printed */ - StringBuffer<32> ref; + String_list ref_list; bool rows_set; /* not set means 'NULL' should be printed */ ha_rows rows; @@ -530,17 +618,40 @@ public: StringBuffer<32> firstmatch_table_name; + bool start_dups_weedout; + bool end_dups_weedout; + + /* + Note: lifespan of WHERE condition is less than lifespan of this object. + The below two are valid if tags include "ET_USING_WHERE". + (TODO: indexsubquery may put ET_USING_WHERE without setting where_cond?) + */ + Item *where_cond; + Item *cache_cond; + + Item *pushed_index_cond; + + Explain_basic_join *sjm_nest; + int print_explain(select_result_sink *output, uint8 explain_flags, bool is_analyze, uint select_id, const char *select_type, bool using_temporary, bool using_filesort); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); + + /* ANALYZE members */ - /* ANALYZE members*/ + /* Tracker for reading the table */ Table_access_tracker tracker; Table_access_tracker jbuf_tracker; private: void append_tag_name(String *str, enum explain_extra_tag tag); + void fill_key_str(String *key_str, bool is_json) const; + void fill_key_len_str(String *key_len_str) const; + double get_r_filtered(); + void tag_to_json(Json_writer *writer, enum explain_extra_tag tag); }; @@ -567,14 +678,22 @@ public: StringBuffer<64> table_name; enum join_type jtype; - StringBuffer<128> possible_keys_line; - StringBuffer<128> key_str; - StringBuffer<128> key_len_str; + String_list possible_keys; + + /* Used key when doing a full index scan (possibly with limit) */ + Explain_index_use key; + + /* + MRR that's used with quick select. This should probably belong to the + quick select + */ StringBuffer<64> mrr_type; Explain_quick_select *quick_info; bool using_where; + Item *where_cond; + ha_rows rows; bool using_filesort; @@ -585,6 +704,8 @@ public: virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); }; @@ -605,6 +726,8 @@ public: int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); }; @@ -626,6 +749,8 @@ public: virtual int print_explain(Explain_query *query, select_result_sink *output, uint8 explain_flags, bool is_analyze); + virtual void print_explain_json(Explain_query *query, Json_writer *writer, + bool is_analyze); }; diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 9ad9c15b3ff..3f0e5673736 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -485,6 +485,7 @@ void lex_start(THD *thd) lex->select_lex.group_list_ptrs->clear(); lex->describe= 0; lex->analyze_stmt= 0; + lex->explain_json= false; lex->subqueries= FALSE; lex->context_analysis_only= 0; lex->derived_tables= 0; @@ -4269,6 +4270,13 @@ int st_select_lex_unit::save_union_explain(Explain_query *output) SELECT_LEX *first= first_select(); Explain_union *eu= new (output->mem_root) Explain_union; + if (derived) + eu->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + /* + Note: Non-merged semi-joins cannot be made out of UNIONs currently, so we + dont ever set EXPLAIN_NODE_NON_MERGED_SJ. + */ + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) eu->add_select(sl->select_number); diff --git a/sql/sql_lex.h b/sql/sql_lex.h index dbb5940611c..dec46c61ef4 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -2312,8 +2312,9 @@ public: void set_impossible_where() { impossible_where= true; } void set_no_partitions() { no_partitions= true; } - void save_explain_data(Explain_query *query); - void save_explain_data_intern(Explain_query *query, Explain_update *eu); + void save_explain_data(MEM_ROOT *mem_root, Explain_query *query); + void save_explain_data_intern(MEM_ROOT *mem_root, Explain_query *query, + Explain_update *eu); virtual ~Update_plan() {} @@ -2344,7 +2345,7 @@ public: scanned_rows= rows_arg; } - void save_explain_data(Explain_query *query); + void save_explain_data(MEM_ROOT *mem_root, Explain_query *query); }; @@ -2509,6 +2510,7 @@ struct LEX: public Query_tables_list uint table_count; uint8 describe; bool analyze_stmt; /* TRUE<=> this is "ANALYZE $stmt" */ + bool explain_json; /* A flag that indicates what kinds of derived tables are present in the query (0 if no derived tables, otherwise a combination of flags diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 38cf5964c68..436f69aafd9 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -97,6 +97,8 @@ #include "log_slow.h" #include "sql_bootstrap.h" +#include "my_json_writer.h" + #define FLAGSTR(V,F) ((V)&(F)?#F" ":"") #ifdef WITH_ARIA_STORAGE_ENGINE @@ -5722,19 +5724,27 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) top-level LIMIT */ result->reset_offset_limit(); - lex->explain->print_explain(result, lex->describe, lex->analyze_stmt); - if (lex->describe & DESCRIBE_EXTENDED) + if (lex->explain_json) { - char buff[1024]; - String str(buff,(uint32) sizeof(buff), system_charset_info); - str.length(0); - /* - The warnings system requires input in utf8, @see - mysqld_show_warnings(). - */ - lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); - push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, - ER_YES, str.c_ptr_safe()); + lex->explain->print_explain_json(result, lex->analyze_stmt); + } + else + { + lex->explain->print_explain(result, thd->lex->describe, + thd->lex->analyze_stmt); + if (lex->describe & DESCRIBE_EXTENDED) + { + char buff[1024]; + String str(buff,(uint32) sizeof(buff), system_charset_info); + str.length(0); + /* + The warnings system requires input in utf8, @see + mysqld_show_warnings(). + */ + lex->unit.print(&str, QT_TO_SYSTEM_CHARSET); + push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, + ER_YES, str.c_ptr_safe()); + } } } @@ -7749,6 +7759,9 @@ bool st_select_lex_unit::add_fake_select_lex(THD *thd_arg) fake_select_lex->context.resolve_in_select_list= TRUE; fake_select_lex->context.select_lex= fake_select_lex; + fake_select_lex->nest_level_base= first_select()->nest_level_base; + fake_select_lex->nest_level=first_select()->nest_level; + if (!is_union()) { /* diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 97692463310..531f968fd8c 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -8232,6 +8232,41 @@ JOIN_TAB *next_breadth_first_tab(JOIN *join, enum enum_exec_or_opt tabs_kind, } +/* + Enumerate JOIN_TABs in "EXPLAIN order". This order + - const tabs are included + - we enumerate "optimization tabs". + - +*/ + +JOIN_TAB *first_explain_order_tab(JOIN* join) +{ + JOIN_TAB* tab; + tab= join->table_access_tabs; + return (tab->bush_children) ? tab->bush_children->start : tab; +} + + +JOIN_TAB *next_explain_order_tab(JOIN* join, JOIN_TAB* tab) +{ + /* If we're inside SJM nest and have reached its end, get out */ + if (tab->last_leaf_in_bush) + return tab->bush_root_tab; + + /* Move to next tab in the array we're traversing */ + tab++; + + if (tab == join->table_access_tabs + join->top_join_tab_count) + return NULL; /* Outside SJM nest and reached EOF */ + + if (tab->bush_children) + return tab->bush_children->start; + + return tab; +} + + + JOIN_TAB *first_top_level_tab(JOIN *join, enum enum_with_const_tables const_tbls) { JOIN_TAB *tab= join->join_tab; @@ -16426,6 +16461,7 @@ create_tmp_table(THD *thd, TMP_TABLE_PARAM *param, List<Item> &fields, ((field_count-param->hidden_field_count)+ (share->uniques ? MY_TEST(null_pack_length) : 0)); keyinfo->ext_key_parts= keyinfo->user_defined_key_parts; + keyinfo->usable_key_parts= keyinfo->user_defined_key_parts; table->distinct= 1; share->keys= 1; if (!(key_part_info= (KEY_PART_INFO*) @@ -20861,6 +20897,24 @@ static void free_blobs(Field **ptr) } +/* + @brief + Remove duplicates from a temporary table. + + @detail + Remove duplicate rows from a temporary table. This is used for e.g. queries + like + + select distinct count(*) as CNT from tbl group by col + + Here, we get a group table with count(*) values. It is not possible to + prevent duplicates from appearing in the table (as we don't know the values + before we've done the grouping). Because of that, we have this function to + scan the temptable (maybe, multiple times) and remove the duplicate rows + + Rows that do not satisfy 'having' condition are also removed. +*/ + static int remove_duplicates(JOIN *join, TABLE *table, List<Item> &fields, Item *having) { @@ -23121,7 +23175,6 @@ void JOIN::clear() /* Print an EXPLAIN line with all NULLs and given message in the 'Extra' column - TODO: is_analyze */ int print_explain_message_line(select_result_sink *result, @@ -23181,208 +23234,6 @@ 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, bool is_analyze, - uint select_number, - const char *select_type, - const char *table_name, - const char *partitions, - enum join_type jtype, - const char *possible_keys, - const char *index, - const char *key_len, - const char *ref, - ha_rows *rows, - ha_rows *r_rows, - double r_filtered, - const char *extra) -{ - Item *item_null= new Item_null(); - List<Item> item_list; - Item *item; - - item_list.push_back(new Item_int((int32) select_number)); - item_list.push_back(new Item_string_sys(select_type)); - item_list.push_back(new Item_string_sys(table_name)); - if (options & DESCRIBE_PARTITIONS) - { - if (partitions) - { - item_list.push_back(new Item_string_sys(partitions)); - } - else - item_list.push_back(item_null); - } - - const char *jtype_str= join_type_str[jtype]; - item_list.push_back(new Item_string_sys(jtype_str)); - - item= possible_keys? new Item_string_sys(possible_keys) : item_null; - item_list.push_back(item); - - /* 'index */ - item= index ? new Item_string_sys(index) : item_null; - item_list.push_back(item); - - /* 'key_len */ - item= key_len ? new Item_string_sys(key_len) : item_null; - item_list.push_back(item); - - /* 'ref' */ - item= ref ? new Item_string_sys(ref) : item_null; - item_list.push_back(item); - - /* 'rows' */ - if (rows) - { - item_list.push_back(new Item_int(*rows, - MY_INT64_NUM_DECIMAL_DIGITS)); - } - else - item_list.push_back(item_null); - - /* 'r_rows' */ - if (is_analyze) - { - if (r_rows) - { - item_list.push_back(new Item_int(*r_rows, - MY_INT64_NUM_DECIMAL_DIGITS)); - } - else - item_list.push_back(item_null); - } - - /* 'filtered' */ - const double filtered=100.0; - if (options & DESCRIBE_EXTENDED || is_analyze) - item_list.push_back(new Item_float(filtered, 2)); - - /* 'r_filtered' */ - if (is_analyze) - item_list.push_back(new Item_float(r_filtered, 2)); - - /* 'Extra' */ - if (extra) - item_list.push_back(new Item_string_sys(extra)); - else - item_list.push_back(item_null); - - 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) -{ - Item *item_null= new Item_null(); - List<Item> item_list; - if (on_the_fly) - select_lex->set_explain_type(on_the_fly); - /* - here we assume that the query will return at least two rows, so we - show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong - and no filesort will be actually done, but executing all selects in - the UNION to provide precise EXPLAIN information will hardly be - appreciated :) - */ - char table_name_buffer[SAFE_NAME_LEN]; - item_list.empty(); - /* id */ - item_list.push_back(new Item_null); - /* select_type */ - item_list.push_back(new Item_string_sys(select_lex->type)); - /* table */ - { - SELECT_LEX *sl= select_lex->master_unit()->first_select(); - uint len= 6, lastop= 0; - memcpy(table_name_buffer, STRING_WITH_LEN("<union")); - for (; sl && len + lastop + 5 < NAME_LEN; sl= sl->next_select()) - { - len+= lastop; - lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, - "%u,", sl->select_number); - } - if (sl || len + lastop >= NAME_LEN) - { - memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1); - len+= 4; - } - else - { - len+= lastop; - table_name_buffer[len - 1]= '>'; // change ',' to '>' - } - item_list.push_back(new Item_string_sys(table_name_buffer, len)); - } - /* partitions */ - if (explain_flags & DESCRIBE_PARTITIONS) - item_list.push_back(item_null); - /* type */ - item_list.push_back(new Item_string_sys(join_type_str[JT_ALL])); - - /* possible_keys */ - item_list.push_back(item_null); - /* key*/ - item_list.push_back(item_null); - /* key_len */ - item_list.push_back(item_null); - /* ref */ - item_list.push_back(item_null); - /* in_rows */ - if (explain_flags & DESCRIBE_EXTENDED) - item_list.push_back(item_null); - /* rows */ - item_list.push_back(item_null); - /* extra */ - if (select_lex->master_unit()->global_parameters()->order_list.first) - item_list.push_back(new Item_string_sys("Using filesort", 14)); - else - item_list.push_back(new Item_string_sys("", 0)); - - if (result->send_data(item_list)) - return 1; - return 0; -} - - -/* Append MRR information from quick select to the given string */ @@ -23403,21 +23254,16 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) /////////////////////////////////////////////////////////////////////////////// -// TODO: join with make_possible_keys_line ? -void append_possible_keys(String *str, TABLE *table, key_map possible_keys) +int append_possible_keys(MEM_ROOT *alloc, String_list &list, TABLE *table, + key_map possible_keys) { uint j; for (j=0 ; j < table->s->keys ; j++) { if (possible_keys.is_set(j)) - { - if (str->length()) - str->append(','); - str->append(table->key_info[j].name, - strlen(table->key_info[j].name), - system_charset_info); - } + list.append_str(alloc, table->key_info[j].name); } + return 0; } // TODO: this function is only applicable for the first non-const optimization @@ -23450,32 +23296,20 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; - char buff4[512]; my_bool key_read; char table_name_buffer[SAFE_NAME_LEN]; - String tmp4(buff4,sizeof(buff4),cs); KEY *key_info= 0; uint key_len= 0; - tmp4.length(0); quick_type= -1; QUICK_SELECT_I *quick= NULL; - eta->key.set(thd->mem_root, NULL, (uint)-1); + eta->key.clear(); eta->quick_info= NULL; tab->tracker= &eta->tracker; tab->jbuf_tracker= &eta->jbuf_tracker; - /* id */ - if (tab->bush_root_tab) - { - JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; - eta->sjm_nest_select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); - } - else - eta->sjm_nest_select_id= 0; - - /* select_type is kept in Explain_select */ + /* id and select_type are kept in Explain_select */ /* table */ if (table->derived_select_number) @@ -23558,7 +23392,11 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab eta->type= tab_type; /* Build "possible_keys" value */ - append_possible_keys(&eta->possible_keys_str, table, tab->keys); + // psergey-todo: why does this use thd MEM_ROOT??? Doesn't this + // break ANALYZE ? thd->mem_root will be freed, and after that we will + // attempt to print the query plan? + append_possible_keys(thd->mem_root, eta->possible_keys, table, tab->keys); + // psergey-todo: ^ check for error return code /* Build "key", "key_len", and "ref" */ if (tab_type == JT_NEXT) @@ -23583,21 +23421,18 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (key_info) /* 'index' or 'ref' access */ { - eta->key.set(thd->mem_root, key_info->name, key_len); + eta->key.set(thd->mem_root, key_info, key_len); if (tab->ref.key_parts && tab_type != JT_FT) { store_key **ref=tab->ref.key_copy; for (uint kp= 0; kp < tab->ref.key_parts; kp++) { - if (tmp4.length()) - tmp4.append(','); - if ((key_part_map(1) << kp) & tab->ref.const_ref_part_map) - tmp4.append("const"); + eta->ref_list.append_str(thd->mem_root, "const"); else { - tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); + eta->ref_list.append_str(thd->mem_root, (*ref)->name()); ref++; } } @@ -23607,21 +23442,13 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (tab_type == JT_HASH_NEXT) /* full index scan + hash join */ { eta->hash_next_key.set(thd->mem_root, - table->key_info[tab->index].name, + & table->key_info[tab->index], table->key_info[tab->index].key_length); + // psergey-todo: ^ is the above correct? are we necessarily joining on all + // columns? } - if (key_info) - { - if (key_info && tab_type != JT_NEXT) - { - eta->ref.copy(tmp4); - eta->ref_set= true; - } - else - eta->ref_set= false; - } - else + if (!key_info) { if (table_list && /* SJM bushes don't have table_list */ table_list->schema_table && @@ -23652,9 +23479,8 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab } if (key_name_buf.length()) - eta->key.set(thd->mem_root, key_name_buf.c_ptr_safe(), -1); + eta->key.set_pseudo_key(thd->mem_root, key_name_buf.c_ptr_safe()); } - eta->ref_set= false; } /* "rows" */ @@ -23719,7 +23545,10 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && table->file->pushed_idx_cond) + { eta->push_extra(ET_USING_INDEX_CONDITION); + eta->pushed_index_cond= table->file->pushed_idx_cond; + } else if (tab->cache_idx_cond) eta->push_extra(ET_USING_INDEX_CONDITION_BKA); @@ -23749,7 +23578,11 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab eta->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); } else + { + eta->where_cond= tab->select->cond; + eta->cache_cond= tab->cache_select? tab->cache_select->cond : NULL; eta->push_extra(ET_USING_WHERE); + } } } if (table_list /* SJM bushes don't have table_list */ && @@ -23804,9 +23637,16 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab } if (tab->first_weedout_table) + { + eta->start_dups_weedout= true; eta->push_extra(ET_START_TEMPORARY); + } if (tab->check_weed_out_table) + { eta->push_extra(ET_END_TEMPORARY); + eta->end_dups_weedout= true; + } + else if (tab->do_firstmatch) { if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) @@ -23844,8 +23684,18 @@ void JOIN_TAB::save_explain_data(Explain_table_access *eta, table_map prefix_tab tab->cache->save_explain_data(&eta->bka_type); } } + + /* + In case this is a derived table, here we remember the number of + subselect that used to produce it. + */ + eta->derived_select_number= table->derived_select_number; + + /* The same for non-merged semi-joins */ + eta->non_merged_sjm_number = get_non_merged_semijoin_select(); } + /* Save Query Plan Footprint @@ -23876,6 +23726,8 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, xpl_sel->select_id= join->select_lex->select_number; xpl_sel->select_type= join->select_lex->type; xpl_sel->message= message; + if (select_lex->master_unit()->derived) + xpl_sel->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; /* Setting xpl_sel->message means that all other members are invalid */ output->add_node(xpl_sel); } @@ -23893,13 +23745,23 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, join->select_lex->set_explain_type(true); xpl_sel->select_id= join->select_lex->select_number; xpl_sel->select_type= join->select_lex->type; + if (select_lex->master_unit()->derived) + xpl_sel->connection_type= Explain_node::EXPLAIN_NODE_DERIVED; + + if (need_tmp_table) + xpl_sel->using_temporary= true; + + if (need_order) + xpl_sel->using_filesort= true; JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); + JOIN_TAB* prev_bush_root_tab= NULL; - for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; - tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) + Explain_basic_join *cur_parent= xpl_sel; + + for (JOIN_TAB *tab= first_explain_order_tab(join); tab; + tab= next_explain_order_tab(join, tab)) { - JOIN_TAB *saved_join_tab= NULL; TABLE *table=tab->table; @@ -23910,6 +23772,7 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, continue; } + if (join->table_access_tabs == join->join_tab && tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) { @@ -23918,20 +23781,35 @@ int JOIN::save_explain_data_intern(Explain_query *output, bool need_tmp_table, } Explain_table_access *eta= new (output->mem_root) Explain_table_access; - xpl_sel->add_table(eta); - tab->save_explain_data(eta, used_tables, distinct, first_top_tab); - - if (need_tmp_table) + if (tab->bush_root_tab != prev_bush_root_tab) { - need_tmp_table=0; - xpl_sel->using_temporary= true; - } - if (need_order) - { - need_order=0; - xpl_sel->using_filesort= true; + if (tab->bush_root_tab) + { + /* + We've entered an SJ-Materialization nest. Create an object for it. + */ + cur_parent= new Explain_basic_join; + + JOIN_TAB *first_child= tab->bush_root_tab->bush_children->start; + cur_parent->select_id= + first_child->emb_sj_nest->sj_subq_pred->get_identifier(); + } + else + { + /* + We've just left an SJ-Materialization nest. We are at the join tab + that 'embeds the nest' + */ + DBUG_ASSERT(tab->bush_children); + eta->sjm_nest= cur_parent; + cur_parent= xpl_sel; + } } + prev_bush_root_tab= tab->bush_root_tab; + + cur_parent->add_table(eta); + tab->save_explain_data(eta, used_tables, distinct, first_top_tab); if (saved_join_tab) tab= saved_join_tab; diff --git a/sql/sql_select.h b/sql/sql_select.h index ca2bf59d871..42b2e6b31c2 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -527,6 +527,21 @@ typedef struct st_join_table { bool preread_init(); bool is_sjm_nest() { return MY_TEST(bush_children); } + + /* + If this join_tab reads a non-merged semi-join (also called jtbm), return + the select's number. Otherwise, return 0. + */ + int get_non_merged_semijoin_select() const + { + Item_in_subselect *subq; + if (table->pos_in_table_list && + (subq= table->pos_in_table_list->jtbm_subselect)) + { + return subq->unit->first_select()->select_number; + } + return 0; /* Not a merged semi-join */ + } bool access_from_tables_is_allowed(table_map used_tables, table_map sjm_lookup_tables) @@ -982,7 +997,13 @@ public: */ uint top_join_tab_count; uint send_group_parts; - bool group; /**< If query contains GROUP BY clause */ + /* + True if the query has GROUP BY. + (that is, if group_by != NULL. when DISTINCT is converted into GROUP BY, it + will set this, too. It is not clear why we need a separate var from + group_list) + */ + bool group; bool need_distinct; /** @@ -1826,8 +1847,10 @@ inline bool optimizer_flag(THD *thd, uint flag) return (thd->variables.optimizer_switch & flag); } +/* int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly, SELECT_LEX *select_lex, uint8 select_options); +*/ uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select, ha_rows limit, ha_rows *scanned_limit, @@ -1855,22 +1878,8 @@ int print_explain_message_line(select_result_sink *result, ha_rows *rows, const char *message); void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res); -int print_explain_row(select_result_sink *result, - uint8 options, bool is_analyze, - uint select_number, - const char *select_type, - const char *table_name, - const char *partitions, - enum join_type jtype, - const char *possible_keys, - const char *index, - const char *key_len, - const char *ref, - ha_rows *rows, - ha_rows *r_rows, - double r_filtered, - const char *extra); -void make_possible_keys_line(TABLE *table, key_map possible_keys, String *line); +int append_possible_keys(MEM_ROOT *alloc, String_list &list, TABLE *table, + key_map possible_keys); /**************************************************************************** Temporary table support for SQL Runtime @@ -1914,4 +1923,5 @@ ulong check_selectivity(THD *thd, TABLE *table, List<COND_STATISTIC> *conds); + #endif /* SQL_SELECT_INCLUDED */ diff --git a/sql/sql_update.cc b/sql/sql_update.cc index b81caea00bc..a3e2b1e23a5 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -517,7 +517,7 @@ int mysql_update(THD *thd, */ if (thd->lex->describe) goto produce_explain_and_leave; - query_plan.save_explain_data(thd->lex->explain); + query_plan.save_explain_data(thd->mem_root, thd->lex->explain); DBUG_EXECUTE_IF("show_explain_probe_update_exec_start", dbug_serve_apcs(thd, 1);); @@ -1042,7 +1042,7 @@ produce_explain_and_leave: We come here for various "degenerate" query plans: impossible WHERE, no-partitions-used, impossible-range, etc. */ - query_plan.save_explain_data(thd->lex->explain); + query_plan.save_explain_data(thd->mem_root, thd->lex->explain); emit_explain_and_leave: int err2= thd->lex->explain->send_explain(thd); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index ce4034d62a5..c1c2e23364d 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1152,6 +1152,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); %token FORCE_SYM %token FOREIGN /* SQL-2003-R */ %token FOR_SYM /* SQL-2003-R */ +%token FORMAT_SYM %token FOUND_SYM /* SQL-2003-R */ %token FROM %token FULL /* SQL-2003-R */ @@ -1827,6 +1828,7 @@ bool my_yyoverflow(short **a, YYSTYPE **b, ulong *yystacksize); subselect_end select_var_list select_var_list_init help field_length opt_field_length opt_extended_describe shutdown + opt_format_json prepare prepare_src execute deallocate statement sp_suid sp_c_chistics sp_a_chistics sp_chistic sp_c_chistic xa @@ -9743,6 +9745,18 @@ function_call_conflict: if ($$ == NULL) MYSQL_YYABORT; } + | FORMAT_SYM '(' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format($3, $5); + if ($$ == NULL) + MYSQL_YYABORT; + } + | FORMAT_SYM '(' expr ',' expr ',' expr ')' + { + $$= new (thd->mem_root) Item_func_format($3, $5, $7); + if ($$ == NULL) + MYSQL_YYABORT; + } | LAST_VALUE '(' expr_list ')' { $$= new (thd->mem_root) Item_func_last_value(* $3); @@ -9763,17 +9777,15 @@ function_call_conflict: } | OLD_PASSWORD '(' expr ')' { - $$= new (thd->mem_root) Item_func_old_password($3); + $$= new (thd->mem_root) + Item_func_password($3, Item_func_password::OLD); if ($$ == NULL) MYSQL_YYABORT; } | PASSWORD '(' expr ')' { Item* i1; - if (thd->variables.old_passwords) - i1= new (thd->mem_root) Item_func_old_password($3); - else - i1= new (thd->mem_root) Item_func_password($3); + i1= new (thd->mem_root) Item_func_password($3); if (i1 == NULL) MYSQL_YYABORT; $$= i1; @@ -9818,11 +9830,14 @@ function_call_conflict: } | WEEK_SYM '(' expr ')' { - Item *i1= new (thd->mem_root) Item_int((char*) "0", - thd->variables.default_week_format, - 1); - if (i1 == NULL) + Item *i1; + LEX_STRING name= {STRING_WITH_LEN("default_week_format")}; + if (!(i1= get_system_var(thd, OPT_SESSION, + name, null_lex_str))) MYSQL_YYABORT; + i1->set_name((const char *) + STRING_WITH_LEN("@@default_week_format"), + system_charset_info); $$= new (thd->mem_root) Item_func_week($3, i1); if ($$ == NULL) MYSQL_YYABORT; @@ -12771,16 +12786,34 @@ describe_command: ; analyze_stmt_command: - ANALYZE_SYM explainable_command + ANALYZE_SYM opt_format_json explainable_command { Lex->analyze_stmt= true; } ; opt_extended_describe: - /* empty */ {} - | EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } + EXTENDED_SYM { Lex->describe|= DESCRIBE_EXTENDED; } | PARTITIONS_SYM { Lex->describe|= DESCRIBE_PARTITIONS; } + | opt_format_json {} + ; + +opt_format_json: + /* empty */ {} + | FORMAT_SYM EQ ident_or_text + { + if (!my_strcasecmp(system_charset_info, $3.str, "JSON")) + Lex->explain_json= true; + else if (!my_strcasecmp(system_charset_info, $3.str, "TRADITIONAL")) + { + DBUG_ASSERT(Lex->explain_json==false); + } + else + { + my_error(ER_UNKNOWN_EXPLAIN_FORMAT, MYF(0), $3.str); + MYSQL_YYABORT; + } + } ; opt_describe_column: @@ -14025,6 +14058,7 @@ keyword: | EXAMINED_SYM {} | EXECUTE_SYM {} | FLUSH_SYM {} + | FORMAT_SYM {} | GET_SYM {} | HANDLER_SYM {} | HELP_SYM {} @@ -14868,17 +14902,19 @@ text_or_password: TEXT_STRING { $$=$1.str;} | PASSWORD '(' TEXT_STRING ')' { - $$= $3.length ? thd->variables.old_passwords ? - Item_func_old_password::alloc(thd, $3.str, $3.length) : - Item_func_password::alloc(thd, $3.str, $3.length) : + $$= $3.length ? + Item_func_password::alloc(thd, $3.str, $3.length, + thd->variables.old_passwords ? + Item_func_password::OLD : + Item_func_password::NEW) : $3.str; if ($$ == NULL) MYSQL_YYABORT; } | OLD_PASSWORD '(' TEXT_STRING ')' { - $$= $3.length ? Item_func_old_password:: - alloc(thd, $3.str, $3.length) : + $$= $3.length ? Item_func_password:: + alloc(thd, $3.str, $3.length, Item_func_password::OLD) : $3.str; if ($$ == NULL) MYSQL_YYABORT; diff --git a/sql/sys_vars.cc b/sql/sys_vars.cc index 04e1d94d194..43a8c7269f2 100644 --- a/sql/sys_vars.cc +++ b/sql/sys_vars.cc @@ -2130,7 +2130,7 @@ static bool check_old_passwords(sys_var *self, THD *thd, set_var *var) static Sys_var_mybool Sys_old_passwords( "old_passwords", "Use old password encryption method (needed for 4.0 and older clients)", - NO_SET_STMT SESSION_VAR(old_passwords), CMD_LINE(OPT_ARG), + SESSION_VAR(old_passwords), CMD_LINE(OPT_ARG), DEFAULT(FALSE), NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(check_old_passwords)); export sys_var *Sys_old_passwords_ptr= &Sys_old_passwords; // for sql_acl.cc @@ -3742,7 +3742,7 @@ static Sys_var_session_special Sys_warning_count( static Sys_var_ulong Sys_default_week_format( "default_week_format", "The default week format used by WEEK() functions", - NO_SET_STMT SESSION_VAR(default_week_format), CMD_LINE(REQUIRED_ARG), + SESSION_VAR(default_week_format), CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 7), DEFAULT(0), BLOCK_SIZE(1)); static Sys_var_ulonglong Sys_group_concat_max_len( diff --git a/sql/table.h b/sql/table.h index ec50a433498..75c118f7374 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1072,6 +1072,12 @@ public: TABLE_LIST *pos_in_table_list;/* Element referring to this table */ /* Position in thd->locked_table_list under LOCK TABLES */ TABLE_LIST *pos_in_locked_tables; + + /* + Not-null for temporary tables only. Non-null values means this table is + used to compute GROUP BY, it has a unique of GROUP BY columns. + (set by create_tmp_table) + */ ORDER *group; String alias; /* alias or table name */ uchar *null_flags; |