Commit c08de062 authored by Sergei Petrunia's avatar Sergei Petrunia

MDEV-406: ANALYZE $stmt: get ANALYZE work for subqueries

- "ANALYZE $stmt" should discard select's output, but it should still
  evaluate the output columns (otherwise, subqueries in select list
  are not executed)
- SHOW EXPLAIN's code practice of calling JOIN::save_explain_data()
  after JOIN::exec() is disastrous for ANALYZE, because it resets
  all counters after the first execution. It is stopped
  = "Late" test_if_skip_sort_order() calls explicitly update their part
    of the query plan.
  = Also, I had to rewrite I_S optimization to actually have optimization
    and execution stages.
parent 581b8897
......@@ -61,3 +61,17 @@ id select_type table type possible_keys key key_len ref rows r_rows filtered r_f
NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL 5 NULL NULL
drop table t1;
drop table t0;
#
# Try a subquery.
#
create table t0 (a int, b int);
insert into t0 values
(0,0),(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9);
create table t1 (a int, b int);
insert into t1 values (1,1),(2,2),(3,3);
# See .test file for the right values of r_rows and r_filtered.
analyze select a, a in (select t0.b from t0 where t0.b+1=t1.b+1) from t1;
id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 3 3 100.00 100.00
2 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 10 3 100.00 33.33 Using where
drop table t0,t1;
......@@ -641,7 +641,7 @@ set debug_dbug='+d,show_explain_probe_join_exec_start';
SHOW INDEX FROM t1;
show explain for $thr2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE STATISTICS ALL NULL NULL NULL NULL NULL Skip_open_table; Scanned all databases
1 SIMPLE STATISTICS ALL NULL TABLE_SCHEMA,TABLE_NAME NULL NULL NULL Open_full_table; Scanned 0 databases
Warnings:
Note 1003 SHOW INDEX FROM t1
Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment
......
......@@ -34,5 +34,29 @@ insert into t1 select a,a from t0;
analyze (select * from t1 A where a<5) union (select * from t1 B where a in (5,6));
analyze (select * from t1 A where a<5) union (select * from t1 B where a in (1,2));
drop table t1;
drop table t0;
--echo #
--echo # Try a subquery.
--echo #
create table t0 (a int, b int);
insert into t0 values
(0,0),(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9);
create table t1 (a int, b int);
insert into t1 values (1,1),(2,2),(3,3);
#
# t1 t0
# a=1 (0,1) 2 rows
# a=2 (0,1,2) 3 rows
# a=3 (0,1,2,3) 4 rows
#
# TOTAL TOTAL= 9 rows. 3 executions, avg=3 rows.
# WHERE is satisfied for 1 row per query, which gives filtered=33.3
--echo # See .test file for the right values of r_rows and r_filtered.
analyze select a, a in (select t0.b from t0 where t0.b+1=t1.b+1) from t1;
drop table t0,t1;
......@@ -210,6 +210,42 @@ public:
virtual enum enum_protocol_type type() { return PROTOCOL_BINARY; };
};
/*
A helper for "ANALYZE $stmt" which looks a real network procotol but doesn't
write results to the network.
At first glance, class select_send looks like a more appropriate place to
implement the "write nothing" hook. This is not true, because
- we need to evaluate the value of every item, and do it the way
select_send does it (i.e. call item->val_int() or val_real() or...)
- select_send::send_data() has some other code, like telling the storage
engine that the row can be unlocked. We want to keep that also.
as a result, "ANALYZE $stmt" uses a select_send_analyze which still uses
select_send::send_data() & co., and also uses Protocol_discard object.
*/
class Protocol_discard : public Protocol_text
{
public:
Protocol_discard(THD *thd_arg) : Protocol_text(thd_arg) {}
/* The real writing is done only in write() */
virtual bool write() { return 0; }
virtual bool send_result_set_metadata(List<Item> *list, uint flags)
{
// Don't pas Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF flags
return Protocol_text::send_result_set_metadata(list, 0);
}
// send_error is intentionally not overloaded.
virtual bool send_eof(uint server_status, uint statement_warn_count)
{
return 0;
}
};
void send_warning(THD *thd, uint sql_errno, const char *err=0);
bool net_send_error(THD *thd, uint sql_errno, const char *err,
const char* sqlstate);
......
......@@ -3959,19 +3959,21 @@ public:
virtual void cleanup();
};
/*
We need this class, because select_send::send_eof() will call ::my_eof.
See also class Protocol_discard.
*/
class select_send_analyze : public select_send
{
bool discard_data;
bool send_result_set_metadata(List<Item> &list, uint flags) { return 0; }
/*
ANALYZE-todo: we should call val_int() (or val_str() or whatever) to
compute the columns. If we don't, it's not full execution.
*/
int send_data(List<Item> &items) { return 0; }
bool send_eof() { return 0; }
void abort_result_set() {}
};
class select_to_file :public select_result_interceptor {
protected:
sql_exchange *exchange;
......
......@@ -345,6 +345,13 @@ int Explain_node::print_explain_for_children(Explain_query *query,
}
void Explain_select::replace_table(uint idx, Explain_table_access *new_tab)
{
delete join_tabs[idx];
join_tabs[idx]= new_tab;
}
Explain_select::~Explain_select()
{
if (join_tabs)
......
......@@ -130,6 +130,12 @@ public:
return false;
}
/*
This is used to save the results of "late" test_if_skip_sort_order() calls
that are made from JOIN::exec
*/
void replace_table(uint idx, Explain_table_access *new_tab);
public:
int select_id;
const char *select_type;
......
......@@ -5260,10 +5260,13 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
{
//psergey-todo: ANALYZE should hook in here...
select_result *save_result;
Protocol *save_protocol;
if (lex->analyze_stmt)
{
save_result= result;
result= new select_send_analyze();
save_protocol= thd->protocol;
thd->protocol= new Protocol_discard(thd);
}
else
{
......@@ -5280,6 +5283,7 @@ static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
result= save_result;
if (!result && !(result= new select_send()))
return 1;
thd->protocol= save_protocol;
thd->lex->explain->send_explain(thd);
if (result != lex->result)
......
This diff is collapsed.
......@@ -537,6 +537,11 @@ typedef struct st_join_table {
}
void remove_redundant_bnl_scan_conds();
void save_explain_data(Explain_table_access *eta, table_map prefix_tables,
bool distinct, struct st_join_table *first_top_tab);
void update_explain_data(uint idx);
} JOIN_TAB;
......
......@@ -121,12 +121,6 @@ append_algorithm(TABLE_LIST *table, String *buff);
static COND * make_cond_for_info_schema(COND *cond, TABLE_LIST *table);
typedef struct st_lookup_field_values
{
LEX_STRING db_value, table_value;
bool wild_db_value, wild_table_value;
} LOOKUP_FIELD_VALUES;
bool get_lookup_field_values(THD *, COND *, TABLE_LIST *, LOOKUP_FIELD_VALUES *);
/***************************************************************************
......@@ -4628,6 +4622,10 @@ public:
from frm files and storage engine are filled by the function
get_all_tables().
@note This function assumes optimize_for_get_all_tables() has been
run for the table and produced a "read plan" in
tables->is_table_read_plan.
@param[in] thd thread handler
@param[in] tables I_S table
@param[in] cond 'WHERE' condition
......@@ -4644,16 +4642,16 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
TABLE_LIST table_acl_check;
SELECT_LEX *lsel= tables->schema_select_lex;
ST_SCHEMA_TABLE *schema_table= tables->schema_table;
LOOKUP_FIELD_VALUES lookup_field_vals;
IS_table_read_plan *plan= tables->is_table_read_plan;
enum enum_schema_tables schema_table_idx;
Dynamic_array<LEX_STRING*> db_names;
COND *partial_cond= 0;
Item *partial_cond= plan->partial_cond;
int error= 1;
Open_tables_backup open_tables_state_backup;
#ifndef NO_EMBEDDED_ACCESS_CHECKS
Security_context *sctx= thd->security_ctx;
#endif
uint table_open_method;
uint table_open_method= tables->table_open_method;
bool can_deadlock;
DBUG_ENTER("get_all_tables");
......@@ -4677,9 +4675,6 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
thd->reset_n_backup_open_tables_state(&open_tables_state_backup);
schema_table_idx= get_schema_table_idx(schema_table);
tables->table_open_method= table_open_method=
get_table_open_method(tables, schema_table, schema_table_idx);
DBUG_PRINT("open_method", ("%d", tables->table_open_method));
/*
this branch processes SHOW FIELDS, SHOW INDEXES commands.
see sql_parse.cc, prepare_schema_table() function where
......@@ -4703,44 +4698,12 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
goto err;
}
if (get_lookup_field_values(thd, cond, tables, &lookup_field_vals))
if (plan->no_rows)
{
error= 0;
goto err;
}
DBUG_PRINT("info",("db_name='%s', table_name='%s'",
lookup_field_vals.db_value.str,
lookup_field_vals.table_value.str));
if (!lookup_field_vals.wild_db_value && !lookup_field_vals.wild_table_value)
{
/*
if lookup value is empty string then
it's impossible table name or db name
*/
if ((lookup_field_vals.db_value.str &&
!lookup_field_vals.db_value.str[0]) ||
(lookup_field_vals.table_value.str &&
!lookup_field_vals.table_value.str[0]))
{
error= 0;
goto err;
}
}
if (lookup_field_vals.db_value.length &&
!lookup_field_vals.wild_db_value)
tables->has_db_lookup_value= TRUE;
if (lookup_field_vals.table_value.length &&
!lookup_field_vals.wild_table_value)
tables->has_table_lookup_value= TRUE;
if (tables->has_db_lookup_value && tables->has_table_lookup_value)
partial_cond= 0;
else
partial_cond= make_cond_for_info_schema(cond, tables);
if (lex->describe)
{
/* EXPLAIN SELECT */
......@@ -4750,7 +4713,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
bzero((char*) &table_acl_check, sizeof(table_acl_check));
if (make_db_list(thd, &db_names, &lookup_field_vals))
if (make_db_list(thd, &db_names, &plan->lookup_field_vals))
goto err;
for (size_t i=0; i < db_names.elements(); i++)
{
......@@ -4765,7 +4728,7 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
{
Dynamic_array<LEX_STRING*> table_names;
int res= make_table_name_list(thd, &table_names, lex,
&lookup_field_vals, db_name);
&plan->lookup_field_vals, db_name);
if (res == 2) /* Not fatal error, continue */
continue;
if (res)
......@@ -4802,8 +4765,8 @@ int get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
already created by make_table_name_list() function).
*/
if (!table_open_method && schema_table_idx == SCH_TABLES &&
(!lookup_field_vals.table_value.length ||
lookup_field_vals.wild_table_value))
(!plan->lookup_field_vals.table_value.length ||
plan->lookup_field_vals.wild_table_value))
{
table->field[0]->store(STRING_WITH_LEN("def"), system_charset_info);
if (schema_table_store_record(thd, table))
......@@ -7979,6 +7942,137 @@ int make_schema_select(THD *thd, SELECT_LEX *sel,
}
/*
Optimize reading from an I_S table.
@detail
This function prepares a plan for populating an I_S table with
get_all_tables().
The plan is in IS_table_read_plan structure, it is saved in
tables->is_table_read_plan.
@return
false - Ok
true - Out Of Memory
*/
static bool optimize_for_get_all_tables(THD *thd, TABLE_LIST *tables, COND *cond)
{
SELECT_LEX *lsel= tables->schema_select_lex;
ST_SCHEMA_TABLE *schema_table= tables->schema_table;
enum enum_schema_tables schema_table_idx;
IS_table_read_plan *plan;
DBUG_ENTER("get_all_tables");
if (!(plan= new IS_table_read_plan()))
DBUG_RETURN(1);
tables->is_table_read_plan= plan;
schema_table_idx= get_schema_table_idx(schema_table);
tables->table_open_method= get_table_open_method(tables, schema_table,
schema_table_idx);
DBUG_PRINT("open_method", ("%d", tables->table_open_method));
/*
this branch processes SHOW FIELDS, SHOW INDEXES commands.
see sql_parse.cc, prepare_schema_table() function where
this values are initialized
*/
if (lsel && lsel->table_list.first)
{
/* These do not need to have a query plan */
goto end;
}
if (get_lookup_field_values(thd, cond, tables, &plan->lookup_field_vals))
{
plan->no_rows= true;
goto end;
}
DBUG_PRINT("info",("db_name='%s', table_name='%s'",
plan->lookup_field_vals.db_value.str,
plan->lookup_field_vals.table_value.str));
if (!plan->lookup_field_vals.wild_db_value &&
!plan->lookup_field_vals.wild_table_value)
{
/*
if lookup value is empty string then
it's impossible table name or db name
*/
if ((plan->lookup_field_vals.db_value.str &&
!plan->lookup_field_vals.db_value.str[0]) ||
(plan->lookup_field_vals.table_value.str &&
!plan->lookup_field_vals.table_value.str[0]))
{
plan->no_rows= true;
goto end;
}
}
if (plan->has_db_lookup_value() && plan->has_table_lookup_value())
plan->partial_cond= 0;
else
plan->partial_cond= make_cond_for_info_schema(cond, tables);
end:
DBUG_RETURN(0);
}
/*
This is the optimizer part of get_schema_tables_result().
*/
bool optimize_schema_tables_reads(JOIN *join)
{
THD *thd= join->thd;
bool result= 0;
DBUG_ENTER("optimize_schema_tables_reads");
for (JOIN_TAB *tab= first_linear_tab(join, WITH_CONST_TABLES);
tab;
tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
{
if (!tab->table || !tab->table->pos_in_table_list)
continue;
TABLE_LIST *table_list= tab->table->pos_in_table_list;
if (table_list->schema_table && thd->fill_information_schema_tables())
{
/* A value of 0 indicates a dummy implementation */
if (table_list->schema_table->fill_table == 0)
continue;
/* skip I_S optimizations specific to get_all_tables */
if (table_list->schema_table->fill_table != get_all_tables)
continue;
Item *cond= tab->select_cond;
if (tab->cache_select && tab->cache_select->cond)
{
/*
If join buffering is used, we should use the condition that is
attached to the join cache. Cache condition has a part of WHERE that
can be checked when we're populating this table.
join_tab->select_cond is of no interest, because it only has
conditions that depend on both this table and previous tables in the
join order.
*/
cond= tab->cache_select->cond;
}
optimize_for_get_all_tables(thd, table_list, cond);
}
}
DBUG_RETURN(result);
}
/*
Fill temporary schema tables before SELECT
......@@ -7987,6 +8081,10 @@ int make_schema_select(THD *thd, SELECT_LEX *sel,
join join which use schema tables
executed_place place where I_S table processed
SEE ALSO
The optimization part is done by get_schema_tables_result(). This function
is run on query execution.
RETURN
FALSE success
TRUE error
......@@ -8007,7 +8105,7 @@ bool get_schema_tables_result(JOIN *join,
for (JOIN_TAB *tab= first_linear_tab(join, WITH_CONST_TABLES);
tab;
tab= next_linear_tab(join, tab, WITHOUT_BUSH_ROOTS))
tab= next_linear_tab(join, tab, WITH_BUSH_ROOTS))
{
if (!tab->table || !tab->table->pos_in_table_list)
break;
......
......@@ -150,6 +150,41 @@ public:
void call_in_target_thread();
};
typedef struct st_lookup_field_values
{
LEX_STRING db_value, table_value;
bool wild_db_value, wild_table_value;
} LOOKUP_FIELD_VALUES;
/*
INFORMATION_SCHEMA: Execution plan for get_all_tables() call
*/
class IS_table_read_plan : public Sql_alloc
{
public:
IS_table_read_plan() : no_rows(false) {}
bool no_rows;
LOOKUP_FIELD_VALUES lookup_field_vals;
Item *partial_cond;
bool has_db_lookup_value()
{
return (lookup_field_vals.db_value.length &&
!lookup_field_vals.wild_db_value);
}
bool has_table_lookup_value()
{
return (lookup_field_vals.table_value.length &&
!lookup_field_vals.wild_table_value);
}
};
bool optimize_schema_tables_reads(JOIN *join);
/* Handle the ignored database directories list for SHOW/I_S. */
bool ignore_db_dirs_init();
void ignore_db_dirs_free();
......
......@@ -1499,6 +1499,7 @@ typedef struct st_schema_table
uint i_s_requested_object; /* the object we need to open(TABLE | VIEW) */
} ST_SCHEMA_TABLE;
class IS_table_read_plan;
/*
Types of derived tables. The ending part is a bitmap of phases that are
......@@ -2044,12 +2045,23 @@ struct TABLE_LIST
/* TRUE <=> this table is a const one and was optimized away. */
bool optimized_away;
/* I_S: Flags to open_table (e.g. OPEN_TABLE_ONLY or OPEN_VIEW_ONLY) */
uint i_s_requested_object;
bool has_db_lookup_value;
bool has_table_lookup_value;
/*
I_S: how to read the tables (SKIP_OPEN_TABLE/OPEN_FRM_ONLY/OPEN_FULL_TABLE)
*/
uint table_open_method;
/*
I_S: where the schema table was filled
(this is a hack. The code should be able to figure out whether reading
from I_S should be done by create_sort_index() or by JOIN::exec.)
*/
enum enum_schema_table_state schema_table_state;
/* Something like a "query plan" for reading INFORMATION_SCHEMA table */
IS_table_read_plan *is_table_read_plan;
MDL_request mdl_request;
#ifdef WITH_PARTITION_STORAGE_ENGINE
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment