diff options
-rw-r--r-- | libmysqld/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/CMakeLists.txt | 1 | ||||
-rw-r--r-- | sql/handler.h | 3 | ||||
-rw-r--r-- | sql/opt_qpf.cc | 417 | ||||
-rw-r--r-- | sql/opt_qpf.h | 274 | ||||
-rw-r--r-- | sql/sql_delete.cc | 5 | ||||
-rw-r--r-- | sql/sql_lex.cc | 135 | ||||
-rw-r--r-- | sql/sql_lex.h | 12 | ||||
-rw-r--r-- | sql/sql_parse.cc | 8 | ||||
-rw-r--r-- | sql/sql_select.cc | 608 | ||||
-rw-r--r-- | sql/sql_select.h | 15 | ||||
-rwxr-xr-x | support-files/build-tags | 2 |
12 files changed, 1466 insertions, 15 deletions
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt index 07a0551b4ab..856063afd95 100644 --- a/libmysqld/CMakeLists.txt +++ b/libmysqld/CMakeLists.txt @@ -99,6 +99,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc ../sql/sql_expression_cache.cc ../sql/my_apc.cc ../sql/my_apc.h ../sql/rpl_gtid.cc + ../sql/opt_qpf.cc ../sql/opt_qpf.h ${GEN_SOURCES} ${MYSYS_LIBWRAP_SOURCE} ) diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index 63e2df46bed..638c6cbca05 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -80,6 +80,7 @@ SET (SQL_SOURCE sql_reload.cc # added in MariaDB: + opt_qpf.h opt_qpf.cc sql_lifo_buffer.h sql_join_cache.h sql_join_cache.cc create_options.cc multi_range_read.cc opt_index_cond_pushdown.cc opt_subselect.cc diff --git a/sql/handler.h b/sql/handler.h index 18639b6a8a4..785b354deee 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -286,7 +286,8 @@ (yes, the sum is deliberately inaccurate) TODO remove the limit, use dynarrays */ -#define MAX_HA 15 +/*#define MAX_HA 15*/ +#define MAX_HA 32 /* Use this instead of 0 as the initial value for the slot number of diff --git a/sql/opt_qpf.cc b/sql/opt_qpf.cc new file mode 100644 index 00000000000..12aef844328 --- /dev/null +++ b/sql/opt_qpf.cc @@ -0,0 +1,417 @@ +/* + TODO MP AB copyright +*/ + +#ifdef USE_PRAGMA_IMPLEMENTATION +#pragma implementation // gcc: Class implementation +#endif + +#include "sql_priv.h" +#include "sql_select.h" + + +QPF_query::QPF_query() +{ + memset(&unions, 0, sizeof(unions)); + memset(&selects, 0, sizeof(selects)); +} + + +QPF_node *QPF_query::get_node(uint select_id) +{ + if (unions[select_id]) + return unions[select_id]; + else + return selects[select_id]; +} + + +QPF_select *QPF_query::get_select(uint select_id) +{ + return selects[select_id]; +} + + +void QPF_query::add_node(QPF_node *node) +{ + if (node->get_type() == QPF_node::QPF_UNION) + { + QPF_union *u= (QPF_union*)node; + unions[u->get_select_id()]= u; + } + else + { + QPF_select *sel= (QPF_select*)node; + if (sel->select_id == (int)UINT_MAX) + { + //TODO this is a "fake select" from a UNION. + DBUG_ASSERT(0); + } + else + selects[sel->select_id] = sel; + } +} + + +/* + The main entry point to print EXPLAIN of the entire query +*/ + +int QPF_query::print_explain(select_result_sink *output, + uint8 explain_flags) +{ + // Start with id=1 + QPF_node *node= get_node(1); + return node->print_explain(this, output, explain_flags); +} + + +void QPF_union::push_table_name(List<Item> *item_list) +{ +} + + +static void push_str(List<Item> *item_list, const char *str) +{ + item_list->push_back(new Item_string(str, + strlen(str), system_charset_info)); +} + + +static void push_string(List<Item> *item_list, String *str) +{ + item_list->push_back(new Item_string(str->ptr(), str->length(), + system_charset_info)); +} + + +int QPF_union::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + // print all children, in order + for (int i= 0; i < (int) children.elements(); i++) + { + QPF_select *sel= query->get_select(children.at(i)); + sel->print_explain(query, output, explain_flags); + } + + /* Print a line with "UNION RESULT" */ + List<Item> item_list; + Item *item_null= new Item_null(); + + /* `id` column */ + item_list.push_back(item_null); + + /* `select_type` column */ + push_str(&item_list, fake_select_type); + + /* `table` column: something like "<union1,2>" */ + // + { + char table_name_buffer[SAFE_NAME_LEN]; + uint childno= 0; + uint len= 6, lastop= 0; + memcpy(table_name_buffer, STRING_WITH_LEN("<union")); + + for (; childno < children.elements() && len + lastop + 5 < NAME_LEN; + childno++) + { + len+= lastop; + lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len, + "%u,", children.at(childno)); + } + + if (childno < children.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 '>' + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(table_name_buffer, len, cs)); + } + // + push_table_name(&item_list); + + /* `partitions` column */ + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + + /* `type` column */ + push_str(&item_list, join_type_str[JT_ALL]); + + /* `possible_keys` column */ + 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); + + /* `rows` */ + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + /* `Extra` */ + StringBuffer<256> extra_buf; + if (using_filesort) + { + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + if (output->send_data(item_list)) + return 1; + return 0; +} + + +int QPF_select::print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags) +{ + if (message) + { + List<Item> item_list; + const CHARSET_INFO *cs= system_charset_info; + Item *item_null= new Item_null(); + + item_list.push_back(new Item_int((int32) select_id)); + item_list.push_back(new Item_string(select_type, + strlen(select_type), cs)); + for (uint i=0 ; i < 7; i++) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_PARTITIONS) + item_list.push_back(item_null); + if (explain_flags & DESCRIBE_EXTENDED) + item_list.push_back(item_null); + + item_list.push_back(new Item_string(message,strlen(message),cs)); + + if (output->send_data(item_list)) + return 1; + return 0; + } + else + { + bool using_tmp= using_temporary; + bool using_fs= using_filesort; + for (uint i=0; i< n_join_tabs; i++) + { + join_tabs[i]->print_explain(output, explain_flags, select_id, + select_type, using_tmp, using_fs); + if (i == 0) + { + /* + "Using temporary; Using filesort" should only be shown near the 1st + table + */ + using_tmp= false; + using_fs= false; + } + } + } + return 0; +} + + +int QPF_table_access::print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort) +{ + List<Item> item_list; + Item *item_null= new Item_null(); + //const CHARSET_INFO *cs= system_charset_info; + + /* `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 */ + //push_str(item_list, "TODO"); + item_list.push_back(item_null); + + /* `key` */ + if (key_set) + push_string(&item_list, &key); + else + item_list.push_back(item_null); + + /* `key_len` */ + if (key_len_set) + push_string(&item_list, &key_len); + else + item_list.push_back(item_null); + + /* `ref` */ + if (ref_set) + push_string(&item_list, &ref); + else + item_list.push_back(item_null); + + /* `rows` */ + if (rows_set) + { + item_list.push_back(new Item_int((longlong) (ulonglong) rows, + MY_INT64_NUM_DECIMAL_DIGITS)); + } + else + item_list.push_back(item_null); + + /* `filtered` */ + if (explain_flags & DESCRIBE_EXTENDED) + { + if (filtered_set) + { + item_list.push_back(new Item_float(filtered, 2)); + } + else + item_list.push_back(item_null); + } + + /* `Extra` */ + StringBuffer<256> extra_buf; + bool first= true; + for (int i=0; i < (int)extra_tags.elements(); i++) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + append_tag_name(&extra_buf, extra_tags.at(i)); + } + + if (using_temporary) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using temporary")); + } + + if (using_filesort) + { + if (first) + first= false; + else + extra_buf.append(STRING_WITH_LEN("; ")); + extra_buf.append(STRING_WITH_LEN("Using filesort")); + } + + const CHARSET_INFO *cs= system_charset_info; + item_list.push_back(new Item_string(extra_buf.ptr(), extra_buf.length(), cs)); + + if (output->send_data(item_list)) + return 1; + + return 0; +} + + +const char * extra_tag_text[]= +{ + "ET_none", + "Using index condition", + "Using index condition(BKA)", + "Using ", //special + "Range checked for each record (index map: 0x", //special + "Using where with pushed condition", + "Using where", + "Not exists", + + "Using index", + "Full scan on NULL key", + "Skip_open_table", + "Open_frm_only", + "Open_full_table", + + "Scanned 0 databases", + "Scanned 1 database", + "Scanned all databases", + + "Using index for group-by", // Special? + + "USING MRR: DONT PRINT ME", // Special! + + "Distinct", + "LooseScan", + "Start temporary", + "End temporary", + "FirstMatch", //TODO: also handle special variant! + + "Using join buffer", // Special!, + + "const row not found", + "unique row not found", + "Impossible ON condition" +}; + + +void QPF_table_access::append_tag_name(String *str, enum Extra_tag tag) +{ + switch (tag) { + case ET_USING: + { + // quick select + str->append(STRING_WITH_LEN("Using ")); + str->append(quick_info); + break; + } + case ET_RANGE_CHECKED_FOR_EACH_RECORD: + { + /* 4 bits per 1 hex digit + terminating '\0' */ + char buf[MAX_KEY / 4 + 1]; + str->append(STRING_WITH_LEN("Range checked for each " + "record (index map: 0x")); + str->append(range_checked_map.print(buf)); + str->append(')'); + break; + } + case ET_USING_MRR: + { + str->append(mrr_type); + break; + } + case ET_USING_JOIN_BUFFER: + { + str->append(extra_tag_text[tag]); + str->append(join_buffer_type); + break; + } + default: + str->append(extra_tag_text[tag]); + } +} + + diff --git a/sql/opt_qpf.h b/sql/opt_qpf.h new file mode 100644 index 00000000000..86655f3ed3d --- /dev/null +++ b/sql/opt_qpf.h @@ -0,0 +1,274 @@ +/************************************************************************************** + + Query Plan Footprint (QPF) structures + + These structures + - Can be produced in-expensively from query plan. + - Store sufficient information to produce either a tabular or a json EXPLAIN + output + - Have methods that produce a tabular output. + +*************************************************************************************/ + +class QPF_query; + +/* + A node can be either a SELECT, or a UNION. +*/ +class QPF_node : public Sql_alloc +{ +public: + enum qpf_node_type {QPF_UNION, QPF_SELECT}; + + virtual enum qpf_node_type get_type()= 0; + virtual int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags)=0; + virtual ~QPF_node(){} +}; + + +/* + Nesting. + QPF_select may have children QPF_select-s. + (these can be FROM-subqueries, or subqueries from other clauses) + + As for unions, the standard approach is: + - UNION node can be where the select node can be; + - the union has a select that retrieves results from temptable (a special + kind of child) + - and it has regular children selects that are merged into the union. + +*/ + +class QPF_table_access; + +class QPF_select : public QPF_node +{ + /*Construction interface */ +public: + enum qpf_node_type get_type() { return QPF_SELECT; } + +#if 0 + /* Constructs a finished degenerate join plan */ + QPF_select(int select_id_arg, const char *select_type_arg, const char* msg) : + select_id(select_id_arg), + select_type(select_type_arg), + message(msg), + join_tabs(NULL), n_join_tabs(0) + {} + + /* Constructs an un-finished, non degenerate join plan. */ + QPF_select(int select_id_arg, const char *select_type_arg) : + select_id(select_id_arg), + select_type(select_type_arg), + message(NULL), + join_tabs(NULL), n_join_tabs(0) + {} +#endif + QPF_select() : + message(NULL), join_tabs(NULL), + using_temporary(false), using_filesort(false) + {} + + bool add_table(QPF_table_access *tab) + { + if (!join_tabs) + { + join_tabs= (QPF_table_access**) malloc(sizeof(QPF_table_access*) * MAX_TABLES); + n_join_tabs= 0; + } + join_tabs[n_join_tabs++]= tab; + return false; + } + +public: + int select_id; /* -1 means NULL. */ + const char *select_type; + + /* + If message != NULL, this is a degenerate join plan, and all subsequent + members have no info + */ + const char *message; + + /* + According to the discussion: this should be an array of "table + descriptors". + + As for SJ-Materialization. Start_materialize/end_materialize markers? + */ + QPF_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; + + void print_tabular(select_result_sink *output, uint8 explain_flags//, + //bool *printed_anything + ); + + int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); +}; + + +class QPF_union : public QPF_node +{ +public: + enum qpf_node_type get_type() { return QPF_UNION; } + + int get_select_id() + { + DBUG_ASSERT(children.elements() > 0); + return children.at(0); + } + // This has QPF_select children + Dynamic_array<int> children; + + void add_select(int select_no) + { + children.append(select_no); + } + void push_table_name(List<Item> *item_list); + int print_explain(QPF_query *query, select_result_sink *output, + uint8 explain_flags); + + const char *fake_select_type; + bool using_filesort; +}; + + +/* + This is the whole query. +*/ + +class QPF_query +{ +public: + QPF_query(); + void add_node(QPF_node *node); + int print_explain(select_result_sink *output, uint8 explain_flags); + + /* This will return a select, or a union */ + QPF_node *get_node(uint select_id); + + /* This will return a select (even if there is a union with this id) */ + QPF_select *get_select(uint select_id); + +private: + QPF_union *unions[MAX_TABLES]; + QPF_select *selects[MAX_TABLES]; +}; + + +enum Extra_tag +{ + ET_none= 0, /* not-a-tag */ + ET_USING_INDEX_CONDITION, + ET_USING_INDEX_CONDITION_BKA, + ET_USING, /* For quick selects of various kinds */ + ET_RANGE_CHECKED_FOR_EACH_RECORD, + ET_USING_WHERE_WITH_PUSHED_CONDITION, + ET_USING_WHERE, + ET_NOT_EXISTS, + + ET_USING_INDEX, + ET_FULL_SCAN_ON_NULL_KEY, + ET_SKIP_OPEN_TABLE, + ET_OPEN_FRM_ONLY, + ET_OPEN_FULL_TABLE, + + ET_SCANNED_0_DATABASES, + ET_SCANNED_1_DATABASE, + ET_SCANNED_ALL_DATABASES, + + ET_USING_INDEX_FOR_GROUP_BY, + + ET_USING_MRR, // does not print "Using mrr". + + ET_DISTINCT, + ET_LOOSESCAN, + ET_START_TEMPORARY, + ET_END_TEMPORARY, + ET_FIRST_MATCH, + + ET_USING_JOIN_BUFFER, + + ET_CONST_ROW_NOT_FOUND, + ET_UNIQUE_ROW_NOT_FOUND, + ET_IMPOSSIBLE_ON_CONDITION, + + ET_total +}; + + +class QPF_table_access +{ +public: + void push_extra(enum Extra_tag extra_tag); + + /* Internals */ +public: + /* id and 'select_type' are cared-of by the parent QPF_select */ + TABLE *table; + StringBuffer<256> table_name; + + enum join_type type; + + StringBuffer<256> used_partitions; + bool used_partitions_set; + + key_map possible_keys; + + uint key_no; + uint key_length; + + Dynamic_array<enum Extra_tag> extra_tags; + + //temporary: + StringBuffer<256> key; + StringBuffer<256> key_len; + StringBuffer<256> ref; + bool key_set; + bool key_len_set; + bool ref_set; + + bool rows_set; + ha_rows rows; + + double filtered; + bool filtered_set; + + /* Various stuff for 'Extra' column*/ + uint join_cache_level; + + // Valid if ET_USING tag is present + StringBuffer<256> quick_info; + + // Valid if ET_USING_INDEX_FOR_GROUP_BY is present + StringBuffer<256> loose_scan_type; + + // valid with ET_RANGE_CHECKED_FOR_EACH_RECORD + key_map range_checked_map; + + // valid with ET_USING_MRR + StringBuffer <256> mrr_type; + + // valid with ET_USING_JOIN_BUFFER + StringBuffer <256> join_buffer_type; + + TABLE *firstmatch_table; + + int print_explain(select_result_sink *output, uint8 explain_flags, + uint select_id, const char *select_type, + bool using_temporary, bool using_filesort); +private: + void append_tag_name(String *str, enum Extra_tag tag); +}; + +// Update_plan and Delete_plan belong to this kind of structures, too. + +// TODO: should Update_plan inherit from QPF_table_access? + + diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index df659871a64..390d9e3945a 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -169,7 +169,9 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, extra_str.c_ptr()); *printed_anything= true; - + /* + psergey-todo: handle all this through saving QPF. + for (SELECT_LEX_UNIT *unit= select_lex->first_inner_unit(); unit; unit= unit->next_unit()) @@ -177,6 +179,7 @@ int Update_plan::print_explain(select_result_sink *output, uint8 explain_flags, if (unit->print_explain(output, explain_flags, printed_anything)) return 1; } + */ return 0; } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index 1d2490666ea..b2d4ca13823 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -4172,6 +4172,7 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor) return all_merged; } + int LEX::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { @@ -4180,11 +4181,87 @@ int LEX::print_explain(select_result_sink *output, uint8 explain_flags, upd_del_plan->print_explain(output, explain_flags, printed_anything); return 0; } - int res= unit.print_explain(output, explain_flags, printed_anything); - return res; + //int res= unit.print_explain(output, explain_flags, printed_anything); + + //psergey-todo: here, we should make Query Plan Footprint, and then produce + // an EXPLAIN output from it. + /* + The new, QueryPlanFootprint way: + */ + QPF_query qpf; + unit.save_qpf(&qpf); + //return res; + return 0; } +void st_select_lex::save_qpf(QPF_query *output) +{ + int res; + if (join && join->have_query_plan == JOIN::QEP_AVAILABLE) + { + /* + There is a number of reasons join can be marked as degenerate, so all + three conditions below can happen simultaneously, or individually: + */ + if (!join->table_count || !join->tables_list || join->zero_result_cause) + { + /* It's a degenerate join */ + const char *cause= join->zero_result_cause ? join-> zero_result_cause : + "No tables used"; + res= join->save_qpf(output, FALSE, FALSE, FALSE, cause); + } + else + { + join->save_qpf(output, join->need_tmp, // need_tmp_table + !join->skip_sort_order && !join->no_order && + (join->order || join->group_list), // bool need_order + join->select_distinct, // bool distinct + NULL); //const char *message + } + if (res) + goto err; + + for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); + unit; + unit= unit->next_unit()) + { + /* + Display subqueries only if they are not parts of eliminated WHERE/ON + clauses. + */ + if (!(unit->item && unit->item->eliminated)) + { + unit->save_qpf(output); + } + } + } + else + { + const char *msg; + if (!join) + DBUG_ASSERT(0); /* Seems not to be possible */ + + /* Not printing anything useful, don't touch *printed_anything here */ + if (join->have_query_plan == JOIN::QEP_NOT_PRESENT_YET) + msg= "Not yet optimized"; + else + { + DBUG_ASSERT(join->have_query_plan == JOIN::QEP_DELETED); + msg= "Query plan already deleted"; + } + set_explain_type(TRUE/* on_the_fly */); + QPF_select *qp_sel= new QPF_select; + qp_sel->select_id= select_number; + qp_sel->select_type= type; + qp_sel->message= msg; + output->add_node(qp_sel); + } +err: + return ;//res; +} + +#if 0 int st_select_lex::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) @@ -4253,8 +4330,60 @@ int st_select_lex::print_explain(select_result_sink *output, err: return res; } +#endif + + +int st_select_lex_unit::save_qpf(QPF_query *output) +{ + //int res= 0; + SELECT_LEX *first= first_select(); + + QPF_union *qpfu= new QPF_union; + /* + TODO: The following code should be eliminated. If we have a capability to + save Query Plan Footprints, we should just save them, and never need to + print "query plan already deleted". + */ + if (first && !first->next_select() && !first->join) + { + /* + If there is only one child, 'first', and it has join==NULL, emit "not in + EXPLAIN state" error. + */ + const char *msg="Query plan already deleted"; + first->set_explain_type(TRUE/* on_the_fly */); + + QPF_select *qp_sel= new QPF_select; + qp_sel->select_id= first->select_number; + qp_sel->select_type= first->type; + qp_sel->message= msg; + output->add_node(qp_sel); + qpfu->add_select(qp_sel->select_id); + return 0; + } + + for (SELECT_LEX *sl= first; sl; sl= sl->next_select()) + { + sl->save_qpf(output); + qpfu->add_select(sl->select_number); + } + // Save the UNION node + output->add_node(qpfu); +#if 0 + /* Note: fake_select_lex->join may be NULL or non-NULL at this point */ + if (fake_select_lex) + { + res= print_fake_select_lex_join(output, TRUE /* on the fly */, + fake_select_lex, explain_flags); + } + return res; +#endif + return 0; +} + +#if 0 int st_select_lex_unit::print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything) { @@ -4288,7 +4417,7 @@ int st_select_lex_unit::print_explain(select_result_sink *output, } return res; } - +#endif /** A routine used by the parser to decide whether we are specifying a full diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 2b075b77000..d2a8b59a593 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -617,7 +617,7 @@ class select_result; class JOIN; class select_union; class Procedure; - +class QPF_query; class st_select_lex_unit: public st_select_lex_node { protected: @@ -728,8 +728,11 @@ public: friend int subselect_union_engine::exec(); List<Item> *get_unit_column_types(); +#if 0 int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); +#endif + int save_qpf(QPF_query *output); }; typedef class st_select_lex_unit SELECT_LEX_UNIT; @@ -1048,8 +1051,11 @@ public: bool save_prep_leaf_tables(THD *thd); bool is_merged_child_of(st_select_lex *ancestor); +#if 0 int print_explain(select_result_sink *output, uint8 explain_flags, bool *printed_anything); +#endif + void save_qpf(QPF_query *output); /* For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags: - Non-aggregated fields are used in this select. @@ -2360,6 +2366,8 @@ class SQL_SELECT; /* Query plan of a single-table UPDATE. (This is actually a plan for single-table DELETE also) + + TODO: this should be a query plan footprint, not a query plan. */ class Update_plan { @@ -2411,6 +2419,7 @@ public: }; +class QPF_query; /* The state of the lex parsing. This is saved in the THD struct */ struct LEX: public Query_tables_list @@ -2424,6 +2433,7 @@ struct LEX: public Query_tables_list /* For single-table DELETE: its query plan */ Update_plan *upd_del_plan; + QPF_query *query_plan_footprint; char *length,*dec,*change; LEX_STRING name; diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 74981eb907b..aab26e72a65 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4811,7 +4811,15 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables) if (!(result= new select_send())) return 1; /* purecov: inspected */ thd->send_explain_fields(result); + thd->lex->query_plan_footprint= new QPF_query; res= mysql_explain_union(thd, &thd->lex->unit, result); + + thd->lex->query_plan_footprint->print_explain(result, thd->lex->describe); + + //psergey-todo: here, produce the EXPLAIN output. + // mysql_explain_union() itself is only responsible for calling + // optimize() for all parts of the query. + /* The code which prints the extended description is not robust against malformed queries, so skip it if we have an error. diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 74df58db214..45090a2cec5 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -3856,7 +3856,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list, if (*s->on_expr_ref) { /* Generate empty row */ - s->info= "Impossible ON condition"; + s->info= ET_IMPOSSIBLE_ON_CONDITION; found_const_table_map|= s->table->map; s->type= JT_CONST; mark_as_null_row(s->table); // All fields are NULL @@ -17219,7 +17219,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) { if ((error=join_read_system(tab))) { // Info for DESCRIBE - tab->info="const row not found"; + tab->info= ET_CONST_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -17245,7 +17245,7 @@ join_read_const_table(JOIN_TAB *tab, POSITION *pos) table->disable_keyread(); if (error) { - tab->info="unique row not found"; + tab->info= ET_UNIQUE_ROW_NOT_FOUND; /* Mark for EXPLAIN that the row was not found */ pos->records_read=0.0; pos->ref_depend_map= 0; @@ -22321,8 +22321,11 @@ void explain_append_mrr_info(QUICK_RANGE_SELECT *quick, String *res) selects) @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make modifications to any select's data structures -*/ + psergey-todo: should this produce a data structure with a query plan? Or, the + data structure with the query plan should be produced in any way? +*/ +#if 0 int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, bool on_the_fly, bool need_tmp_table, bool need_order, @@ -22381,6 +22384,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, bool printing_materialize_nest= FALSE; uint select_id= join->select_lex->select_number; + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; @@ -22392,7 +22396,7 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); printing_materialize_nest= TRUE; } - + TABLE *table=tab->table; TABLE_LIST *table_list= tab->table->pos_in_table_list; char buff[512]; @@ -22672,7 +22676,23 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, key_read=1; if (tab->info) - item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs)); + { + const char *reason; + switch (tab->info) { + case ET_CONST_ROW_NOT_FOUND: + reason= "const row not found"; + break; + case ET_UNIQUE_ROW_NOT_FOUND: + reason= "unique row not found"; + break; + case ET_IMPOSSIBLE_ON_CONDITION: + reason= "Impossible ON condition"; + break; + default: + DBUG_ASSERT(0); + } + item_list.push_back(new Item_string(reason,strlen(reason),cs)); + } else if (tab->packed_info & TAB_INFO_HAVE_VALUE) { if (tab->packed_info & TAB_INFO_USING_INDEX) @@ -22876,6 +22896,577 @@ int JOIN::print_explain(select_result_sink *result, uint8 explain_flags, } DBUG_RETURN(error); } +#endif +///////////////////////////////////////////////////////////////////////////////////////////////// +void QPF_table_access::push_extra(enum Extra_tag extra_tag) +{ + extra_tags.append(extra_tag); +} + + +/* + Save Query Plan Footprint + push_extra + + P +*/ + +int JOIN::save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, + bool distinct, const char *message) +{ + QPF_select *qp_sel; + const bool on_the_fly= true; + + JOIN *join= this; /* Legacy: this code used to be a non-member function */ + THD *thd=join->thd; + const CHARSET_INFO *cs= system_charset_info; + int quick_type; + int error= 0; + DBUG_ENTER("JOIN::print_explain"); + DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", + (ulong)join->select_lex, join->select_lex->type, + message ? message : "NULL")); + DBUG_ASSERT(have_query_plan == QEP_AVAILABLE); + /* Don't log this into the slow query log */ + + qp_sel= new QPF_select; + + /* + NOTE: the number/types of items pushed into item_list must be in sync with + EXPLAIN column types as they're "defined" in THD::send_explain_fields() + */ + if (message) + { + join->select_lex->set_explain_type(on_the_fly); + + qp_sel->select_id= join->select_lex->select_number; + qp_sel->select_type= join->select_lex->type; + qp_sel->message= message; + /* Setting qp_sel->message means that all other members are invalid */ + output->add_node(qp_sel); + } + else if (join->select_lex == join->unit->fake_select_lex) + { + select_lex->set_explain_type(on_the_fly); + QPF_union *qp_union= new QPF_union; + + SELECT_LEX *child; + for (child= select_lex->master_unit()->first_select(); child; + child=child->next_select()) + { + qp_union->add_select(child->select_number); + } + + qp_union->fake_select_type= select_lex->type; + qp_union->using_filesort= + test(select_lex->master_unit()->global_parameters->order_list.first); + + output->add_node(qp_union); + } + else if (!join->select_lex->master_unit()->derived || + join->select_lex->master_unit()->derived->is_materialized_derived()) + { + table_map used_tables=0; + + if (on_the_fly) + join->select_lex->set_explain_type(on_the_fly); + + bool printing_materialize_nest= FALSE; + uint select_id= join->select_lex->select_number; + + qp_sel->select_id= join->select_lex->select_number; + qp_sel->select_type= join->select_lex->type; + + JOIN_TAB* const first_top_tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); + + for (JOIN_TAB *tab= first_breadth_first_tab(join, WALK_OPTIMIZATION_TABS); tab; + tab= next_breadth_first_tab(join, WALK_OPTIMIZATION_TABS, tab)) + { + QPF_table_access *qpt= new QPF_table_access; + qp_sel->add_table(qpt); + + if (tab->bush_root_tab) + { + JOIN_TAB *first_sibling= tab->bush_root_tab->bush_children->start; + select_id= first_sibling->emb_sj_nest->sj_subq_pred->get_identifier(); + printing_materialize_nest= TRUE; + } + + TABLE *table=tab->table; + TABLE_LIST *table_list= tab->table->pos_in_table_list; + char buff2[512], buff3[512], buff4[512]; + char keylen_str_buf[64]; + my_bool key_read; + char table_name_buffer[SAFE_NAME_LEN]; + String tmp2(buff2,sizeof(buff2),cs); + String tmp3(buff3,sizeof(buff3),cs); + String tmp4(buff4,sizeof(buff4),cs); + char hash_key_prefix[]= "#hash#"; + KEY *key_info= 0; + uint key_len= 0; + bool is_hj= tab->type == JT_HASH || tab->type ==JT_HASH_NEXT; + + tmp2.length(0); + tmp3.length(0); + tmp4.length(0); + quick_type= -1; + QUICK_SELECT_I *quick= NULL; + JOIN_TAB *saved_join_tab= NULL; + + /* Don't show eliminated tables */ + if (table->map & join->eliminated_tables) + { + used_tables|=table->map; + continue; + } + + + if (join->table_access_tabs == join->join_tab && + tab == (first_top_tab + join->const_tables) && pre_sort_join_tab) + { + saved_join_tab= tab; + tab= pre_sort_join_tab; + } + + /* id */ + qp_sel->select_id= select_id; + + /* select_type */ + //const char* stype= printing_materialize_nest? "MATERIALIZED" : + // join->select_lex->type; + //item_list.push_back(new Item_string(stype, strlen(stype), cs)); + qp_sel->select_type= join->select_lex->type; + + /* table */ + if (table->derived_select_number) + { + /* Derived table name generation */ + int len= my_snprintf(table_name_buffer, sizeof(table_name_buffer)-1, + "<derived%u>", + table->derived_select_number); + qpt->table_name.set(table_name_buffer, len, cs); + } + else if (tab->bush_children) + { + JOIN_TAB *ctab= tab->bush_children->start; + /* table */ + int len= my_snprintf(table_name_buffer, + sizeof(table_name_buffer)-1, + "<subquery%d>", + ctab->emb_sj_nest->sj_subq_pred->get_identifier()); + qpt->table_name.set(table_name_buffer, len, cs); + } + else + { + TABLE_LIST *real_table= table->pos_in_table_list; + qpt->table_name.set(real_table->alias, strlen(real_table->alias), cs); + } + + /* "partitions" column */ + { +#ifdef WITH_PARTITION_STORAGE_ENGINE + partition_info *part_info; + if (!table->derived_select_number && + (part_info= table->part_info)) + { + make_used_partitions_str(part_info, &qpt->used_partitions); + qpt->used_partitions_set= true; + } + else + qpt->used_partitions_set= false; +#else + /* just produce empty column if partitioning is not compiled in */ + qpt->used_partitions_set= false; +#endif + } + + /* "type" column */ + enum join_type tab_type= tab->type; + if ((tab->type == JT_ALL || tab->type == JT_HASH) && + tab->select && tab->select->quick && tab->use_quick != 2) + { + quick= tab->select->quick; + quick_type= tab->select->quick->get_type(); + if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) || + (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) || + (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)) + tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE; + else + tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE; + } + qpt->type= tab_type; + + /* Build "possible_keys" value */ + qpt->possible_keys= tab->keys; + + /* Build "key", "key_len", and "ref" */ + + // tmp2 holds key_name + // tmp3 holds key_length + // tmp4 holds ref? + if (tab_type == JT_NEXT) + { + key_info= table->key_info+tab->index; + key_len= key_info->key_length; + } + else if (tab->ref.key_parts) + { + key_info= tab->get_keyinfo_by_key_no(tab->ref.key); + key_len= tab->ref.key_length; + } + + if (key_info) + { + register uint length; + if (is_hj) + tmp2.append(hash_key_prefix, strlen(hash_key_prefix), cs); + tmp2.append(key_info->name, strlen(key_info->name), cs); + length= (longlong10_to_str(key_len, keylen_str_buf, 10) - + keylen_str_buf); + tmp3.append(keylen_str_buf, length, cs); + 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"); + else + { + tmp4.append((*ref)->name(), strlen((*ref)->name()), cs); + ref++; + } + } + } + } + + if (is_hj && tab_type != JT_HASH) + { + tmp2.append(':'); + tmp3.append(':'); + } + + if (tab_type == JT_HASH_NEXT) + { + register uint length; + key_info= table->key_info+tab->index; + key_len= key_info->key_length; + tmp2.append(key_info->name, strlen(key_info->name), cs); + length= (longlong10_to_str(key_len, keylen_str_buf, 10) - + keylen_str_buf); + tmp3.append(keylen_str_buf, length, cs); + } + + if (tab->type != JT_CONST && tab->select && quick) + tab->select->quick->add_keys_and_lengths(&tmp2, &tmp3); + + if (key_info || (tab->select && quick)) + { + if (tmp2.length()) + { + qpt->key.copy(tmp2); + qpt->key_set= true; + } + else + qpt->key_set= false; + + if (tmp3.length()) + { + qpt->key_len.copy(tmp3); + qpt->key_len_set= true; + } + else + qpt->key_len_set= false; + + if (key_info && tab_type != JT_NEXT) + { + qpt->ref.copy(tmp4); + qpt->ref_set= true; + } + else + qpt->ref_set= false; + } + else + { + if (table_list && /* SJM bushes don't have table_list */ + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + const char *tmp_buff; + int f_idx; + if (table_list->has_db_lookup_value) + { + f_idx= table_list->schema_table->idx_field1; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + tmp2.append(tmp_buff, strlen(tmp_buff), cs); + } + if (table_list->has_table_lookup_value) + { + if (table_list->has_db_lookup_value) + tmp2.append(','); + f_idx= table_list->schema_table->idx_field2; + tmp_buff= table_list->schema_table->fields_info[f_idx].field_name; + tmp2.append(tmp_buff, strlen(tmp_buff), cs); + } + if (tmp2.length()) + { + qpt->key.copy(tmp2); + qpt->key_set= true; + } + else + qpt->key_set= false; + } + else + qpt->key_set= false; + + qpt->key_len_set= false; + qpt->ref_set= false; + } + + /* "rows" */ + + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table) + { + /* I_S tables have rows=extra=NULL */ + qpt->rows_set= false; + qpt->filtered_set= false; + } + else + { + ha_rows examined_rows= tab->get_examined_rows(); + + qpt->rows_set= true; + qpt->rows= examined_rows; + + /* "filtered" */ + float f= 0.0; + if (examined_rows) + { + double pushdown_cond_selectivity= tab->cond_selectivity; + if (pushdown_cond_selectivity == 1.0) + f= (float) (100.0 * tab->records_read / examined_rows); + else + f= (float) (100.0 * pushdown_cond_selectivity); + } + set_if_smaller(f, 100.0); + qpt->filtered_set= true; + qpt->filtered= f; + } + + /* Build "Extra" field and save it */ + key_read=table->key_read; + if ((tab_type == JT_NEXT || tab_type == JT_CONST) && + table->covering_keys.is_set(tab->index)) + key_read=1; + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT && + !((QUICK_ROR_INTERSECT_SELECT*)quick)->need_to_fetch_row) + key_read=1; + + if (tab->info) + { + qpt->push_extra(tab->info); + } + else if (tab->packed_info & TAB_INFO_HAVE_VALUE) + { + if (tab->packed_info & TAB_INFO_USING_INDEX) + qpt->push_extra(ET_USING_INDEX); + if (tab->packed_info & TAB_INFO_USING_WHERE) + qpt->push_extra(ET_USING_WHERE); + if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL) + qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + } + else + { + uint keyno= MAX_KEY; + if (tab->ref.key_parts) + keyno= tab->ref.key; + else if (tab->select && quick) + keyno = quick->index; + + if (keyno != MAX_KEY && keyno == table->file->pushed_idx_cond_keyno && + table->file->pushed_idx_cond) + qpt->push_extra(ET_USING_INDEX_CONDITION); + else if (tab->cache_idx_cond) + qpt->push_extra(ET_USING_INDEX_CONDITION_BKA); + + if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || + quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT || + quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) + { + qpt->push_extra(ET_USING); + tab->select->quick->add_info_string(&qpt->quick_info); + } + if (tab->select) + { + if (tab->use_quick == 2) + { + qpt->push_extra(ET_RANGE_CHECKED_FOR_EACH_RECORD); + qpt->range_checked_map= tab->keys; + } + else if (tab->select->cond) + { + const COND *pushed_cond= tab->table->file->pushed_cond; + + if (((thd->variables.optimizer_switch & + OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN) || + (tab->table->file->ha_table_flags() & + HA_MUST_USE_TABLE_CONDITION_PUSHDOWN)) && + pushed_cond) + { + qpt->push_extra(ET_USING_WHERE_WITH_PUSHED_CONDITION); + /* + psergey-todo: what to do? This was useful with NDB only. + + if (explain_flags & DESCRIBE_EXTENDED) + { + extra.append(STRING_WITH_LEN(": ")); + ((COND *)pushed_cond)->print(&extra, QT_ORDINARY); + } + */ + } + else + qpt->push_extra(ET_USING_WHERE); + } + } + if (table_list /* SJM bushes don't have table_list */ && + table_list->schema_table && + table_list->schema_table->i_s_requested_object & OPTIMIZE_I_S_TABLE) + { + if (!table_list->table_open_method) + qpt->push_extra(ET_SKIP_OPEN_TABLE); + else if (table_list->table_open_method == OPEN_FRM_ONLY) + qpt->push_extra(ET_OPEN_FRM_ONLY); + else + qpt->push_extra(ET_OPEN_FULL_TABLE); + /* psergey-note: the following has a bug.*/ + if (table_list->has_db_lookup_value && + table_list->has_table_lookup_value) + qpt->push_extra(ET_SCANNED_0_DATABASES); + else if (table_list->has_db_lookup_value || + table_list->has_table_lookup_value) + qpt->push_extra(ET_SCANNED_1_DATABASE); + else + qpt->push_extra(ET_SCANNED_ALL_DATABASES); + } + if (key_read) + { + if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) + { + QUICK_GROUP_MIN_MAX_SELECT *qgs= + (QUICK_GROUP_MIN_MAX_SELECT *) tab->select->quick; + qpt->push_extra(ET_USING_INDEX_FOR_GROUP_BY); + qgs->append_loose_scan_type(&qpt->loose_scan_type); + } + else + qpt->push_extra(ET_USING_INDEX); + } + if (table->reginfo.not_exists_optimize) + qpt->push_extra(ET_NOT_EXISTS); + + /* + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE && + !(((QUICK_RANGE_SELECT*)(tab->select->quick))->mrr_flags & + HA_MRR_USE_DEFAULT_IMPL)) + { + extra.append(STRING_WITH_LEN("; Using MRR")); + } + */ + if (quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) + { + qpt->push_extra(ET_USING_MRR); + explain_append_mrr_info((QUICK_RANGE_SELECT*)(tab->select->quick), + &qpt->mrr_type); + } + + if (need_tmp_table) + { + need_tmp_table=0; + qp_sel->using_temporary= true; + ///extra.append(STRING_WITH_LEN("; Using temporary")); + } + if (need_order) + { + need_order=0; + qp_sel->using_filesort= true; + ///extra.append(STRING_WITH_LEN("; Using filesort")); + } + if (distinct & test_all_bits(used_tables, + join->select_list_used_tables)) + qpt->push_extra(ET_DISTINCT); + if (tab->loosescan_match_tab) + { + qpt->push_extra(ET_LOOSESCAN); + } + + if (tab->first_weedout_table) + qpt->push_extra(ET_START_TEMPORARY); + if (tab->check_weed_out_table) + qpt->push_extra(ET_END_TEMPORARY); + else if (tab->do_firstmatch) + { + if (tab->do_firstmatch == /*join->join_tab*/ first_top_tab - 1) + qpt->push_extra(ET_FIRST_MATCH); + else + { + qpt->push_extra(ET_FIRST_MATCH); + //TABLE *prev_table=tab->do_firstmatch->table; + /* + TODO: qpt->firstmatch_table... + This must be a reference to another QPF element. Or, its index. + */ +#if 0 + extra.append(STRING_WITH_LEN("; FirstMatch(")); + if (prev_table->derived_select_number) + { + char namebuf[NAME_LEN]; + /* Derived table name generation */ + int len= my_snprintf(namebuf, sizeof(namebuf)-1, + "<derived%u>", + prev_table->derived_select_number); + extra.append(namebuf, len); + } + else + extra.append(prev_table->pos_in_table_list->alias); + extra.append(STRING_WITH_LEN(")")); +#endif + } + } + + for (uint part= 0; part < tab->ref.key_parts; part++) + { + if (tab->ref.cond_guards[part]) + { + qpt->push_extra(ET_FULL_SCAN_ON_NULL_KEY); + break; + } + } + + if (tab->cache) + { + qpt->push_extra(ET_USING_JOIN_BUFFER); + tab->cache->print_explain_comment(&qpt->join_buffer_type); + } + + } + + if (saved_join_tab) + tab= saved_join_tab; + + // For next iteration + used_tables|=table->map; + } + output->add_node(qp_sel); + } + DBUG_RETURN(error); +} + +////////////////////////////////////////////////////////////////////////////////////////////// /* @@ -22888,10 +23479,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, THD *thd=join->thd; select_result *result=join->result; DBUG_ENTER("select_describe"); +#if 0 join->error= join->print_explain(result, thd->lex->describe, FALSE, /* Not on-the-fly */ need_tmp_table, need_order, distinct, message); +#endif + //psergey-todo: save QPF here, too. + join->save_qpf(thd->lex->query_plan_footprint, need_tmp_table, need_order, + distinct, message); for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit(); unit; diff --git a/sql/sql_select.h b/sql/sql_select.h index 5a3dbccb97f..5ec09fcfcb8 100644 --- a/sql/sql_select.h +++ b/sql/sql_select.h @@ -198,6 +198,12 @@ int rr_sequential(READ_RECORD *info); int rr_sequential_and_unpack(READ_RECORD *info); +#include "opt_qpf.h" + +/************************************************************************************** + * New EXPLAIN structures END + *************************************************************************************/ + class JOIN_CACHE; class SJ_TMP_TABLE; class JOIN_TAB_RANGE; @@ -252,7 +258,9 @@ typedef struct st_join_table { JOIN_TAB_RANGE *bush_children; /* Special content for EXPLAIN 'Extra' column or NULL if none */ - const char *info; + enum Extra_tag info; + //const char *info; + /* Bitmap of TAB_INFO_* bits that encodes special line for EXPLAIN 'Extra' column, or 0 if there is no info. @@ -1454,11 +1462,14 @@ public: { return (unit->item && unit->item->is_in_predicate()); } - +/* int print_explain(select_result_sink *result, uint8 explain_flags, bool on_the_fly, bool need_tmp_table, bool need_order, bool distinct,const char *message); +*/ + int save_qpf(QPF_query *output, bool need_tmp_table, bool need_order, + bool distinct, const char *message); private: /** TRUE if the query contains an aggregate function but has no GROUP diff --git a/support-files/build-tags b/support-files/build-tags index b5386dc79c3..31e67a4ebfe 100755 --- a/support-files/build-tags +++ b/support-files/build-tags @@ -6,7 +6,7 @@ filter='\.cc$\|\.c$\|\.h$\|\.yy$' list="find . -type f" bzr root >/dev/null 2>/dev/null && list="bzr ls --from-root -R --kind=file --versioned" -$list |grep $filter |while read f; +$list |grep $filter | grep -v gen-cpp |while read f; do etags -o TAGS --append $f done |