Commit 725d76e1 authored by Sergey Petrunya's avatar Sergey Petrunya

MWL#182: Explain running statements: address review feedback

- switch SHOW EXPLAIN to using an INFORMATION_SCHEMA table.
parent a931467e
...@@ -798,7 +798,7 @@ pk data ...@@ -798,7 +798,7 @@ pk data
20 data1 20 data1
set autocommit=0; set autocommit=0;
select * from t1 where pk between 10 and 20 for update; select * from t1 where pk between 10 and 20 for update;
show explain for 3; # do: send_eval show explain for 3;
kill query $thr_default; kill query $thr_default;
ERROR 70100: Query execution was interrupted ERROR 70100: Query execution was interrupted
rollback; rollback;
...@@ -816,3 +816,8 @@ pk data ...@@ -816,3 +816,8 @@ pk data
20 data1 20 data1
drop table t1; drop table t1;
drop table t0; drop table t0;
#
# Check that the I_S table is invisible
#
select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%';
table_name
...@@ -818,7 +818,10 @@ connection default; ...@@ -818,7 +818,10 @@ connection default;
let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2; let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2;
let $thr_default=`select connection_id()`; let $thr_default=`select connection_id()`;
--source include/wait_condition.inc --source include/wait_condition.inc
--echo # do: send_eval show explain for $thr2;
--disable_query_log
send_eval show explain for $thr2; send_eval show explain for $thr2;
--enable_query_log
# kill the SHOW EXPLAIN command # kill the SHOW EXPLAIN command
connection con3; connection con3;
...@@ -844,3 +847,9 @@ disconnect con2; ...@@ -844,3 +847,9 @@ disconnect con2;
## thread and served together. ## thread and served together.
drop table t0; drop table t0;
--echo #
--echo # Check that the I_S table is invisible
--echo #
select table_name from information_schema.tables where table_schema='information_schema' and table_name like '%explain%';
...@@ -600,6 +600,7 @@ enum enum_schema_tables ...@@ -600,6 +600,7 @@ enum enum_schema_tables
SCH_COLUMN_PRIVILEGES, SCH_COLUMN_PRIVILEGES,
SCH_ENGINES, SCH_ENGINES,
SCH_EVENTS, SCH_EVENTS,
SCH_EXPLAIN,
SCH_FILES, SCH_FILES,
SCH_GLOBAL_STATUS, SCH_GLOBAL_STATUS,
SCH_GLOBAL_VARIABLES, SCH_GLOBAL_VARIABLES,
......
...@@ -24,7 +24,35 @@ ...@@ -24,7 +24,35 @@
/* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */ /* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */
#ifndef MY_APC_STANDALONE
ST_FIELD_INFO show_explain_fields_info[]=
{
/* field_name, length, type, value, field_flags, old_name*/
{"id", 3, MYSQL_TYPE_LONGLONG, 0 /*value*/, MY_I_S_MAYBE_NULL, "id",
SKIP_OPEN_TABLE},
{"select_type", 19, MYSQL_TYPE_STRING, 0 /*value*/, 0, "select_type",
SKIP_OPEN_TABLE},
{"table", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0 /*value*/, MY_I_S_MAYBE_NULL,
"table", SKIP_OPEN_TABLE},
{"type", 10, MYSQL_TYPE_STRING, 0, MY_I_S_MAYBE_NULL, "type", SKIP_OPEN_TABLE},
{"possible_keys", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/,
MY_I_S_MAYBE_NULL, "possible_keys", SKIP_OPEN_TABLE},
{"key", NAME_CHAR_LEN, MYSQL_TYPE_STRING, 0/*value*/, MY_I_S_MAYBE_NULL,
"key", SKIP_OPEN_TABLE},
{"key_len", NAME_CHAR_LEN*MAX_KEY, MYSQL_TYPE_STRING, 0/*value*/,
MY_I_S_MAYBE_NULL, "key_len", SKIP_OPEN_TABLE},
{"ref", NAME_CHAR_LEN*MAX_REF_PARTS, MYSQL_TYPE_STRING, 0/*value*/,
MY_I_S_MAYBE_NULL, "ref", SKIP_OPEN_TABLE},
{"rows", 10, MYSQL_TYPE_LONGLONG, 0/*value*/, MY_I_S_MAYBE_NULL, "rows",
SKIP_OPEN_TABLE},
{"Extra", 255, MYSQL_TYPE_STRING, 0/*value*/, 0 /*flags*/, "Extra",
SKIP_OPEN_TABLE},
{0, 0, MYSQL_TYPE_STRING, 0, 0, 0, SKIP_OPEN_TABLE}
};
#endif
/* /*
Initialize the target. Initialize the target.
......
...@@ -78,20 +78,6 @@ public: ...@@ -78,20 +78,6 @@ public:
virtual bool send_result_set_metadata(List<Item> *list, uint flags); virtual bool send_result_set_metadata(List<Item> *list, uint flags);
bool send_result_set_row(List<Item> *row_items); bool send_result_set_row(List<Item> *row_items);
void get_packet(const char **start, size_t *length)
{
*start= packet->ptr();
*length= packet->length();
}
void set_packet(const char *start, size_t len)
{
packet->length(0);
packet->append(start, len);
#ifndef DBUG_OFF
field_pos= field_count - 1;
#endif
}
bool store(I_List<i_string> *str_list); bool store(I_List<i_string> *str_list);
bool store(const char *from, CHARSET_INFO *cs); bool store(const char *from, CHARSET_INFO *cs);
String *storage_packet() { return packet; } String *storage_packet() { return packet; }
......
...@@ -2309,89 +2309,15 @@ int select_send::send_data(List<Item> &items) ...@@ -2309,89 +2309,15 @@ int select_send::send_data(List<Item> &items)
DBUG_RETURN(0); DBUG_RETURN(0);
} }
//////////////////////////////////////////////////////////////////////////////
/*
Save the data being sent in our internal buffer.
*/
int select_result_explain_buffer::send_data(List<Item> &items) int select_result_explain_buffer::send_data(List<Item> &items)
{ {
List_iterator_fast<Item> li(items); fill_record(thd, dst_table->field, items, TRUE, FALSE);
char buff[MAX_FIELD_WIDTH]; if ((dst_table->file->ha_write_tmp_row(dst_table->record[0])))
String buffer(buff, sizeof(buff), &my_charset_bin); return 1;
DBUG_ENTER("select_send::send_data"); return 0;
protocol->prepare_for_resend();
Item *item;
while ((item=li++))
{
if (item->send(protocol, &buffer))
{
protocol->free(); // Free used buffer
my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
break;
}
/*
Reset buffer to its original state, as it may have been altered in
Item::send().
*/
buffer.set(buff, sizeof(buff), &my_charset_bin);
}
if (thd->is_error())
{
protocol->remove_last_row();
DBUG_RETURN(1);
}
/*
Instead of calling protocol->write(), steal the packed and put it to our
buffer
*/
const char *packet_data;
size_t len;
protocol->get_packet(&packet_data, &len);
String *s= new (thd->mem_root) String;
s->append(packet_data, len);
data_rows.push_back(s);
protocol->remove_last_row(); // <-- this does nothing. Do we need it?
// prepare_for_resend() will wipe out the packet
DBUG_RETURN(0);
}
/* Write the saved resultset to the client (via this->protocol) and free it. */
void select_result_explain_buffer::flush_data()
{
List_iterator<String> it(data_rows);
String *str;
while ((str= it++))
{
protocol->set_packet(str->ptr(), str->length());
protocol->write();
delete str;
}
data_rows.empty();
}
/* Free the accumulated resultset */
void select_result_explain_buffer::discard_data()
{
List_iterator<String> it(data_rows);
String *str;
while ((str= it++))
{
delete str;
}
data_rows.empty();
} }
//////////////////////////////////////////////////////////////////////////////
bool select_send::send_eof() bool select_send::send_eof()
{ {
......
...@@ -3319,32 +3319,27 @@ public: ...@@ -3319,32 +3319,27 @@ public:
/* /*
A select result sink that collects the sent data and then can flush it to This is a select_result_sink which simply writes all data into a (temporary)
network when requested. table. Creation/deletion of the table is outside of the scope of the class
This class is targeted at collecting EXPLAIN output: It is aimed at capturing SHOW EXPLAIN output, so:
- Unoptimized data storage (can't handle big datasets)
- Unlike select_result class, we don't assume that the sent data is an - Unlike select_result class, we don't assume that the sent data is an
output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the output of a SELECT_LEX_UNIT (and so we dont apply "LIMIT x,y" from the
unit) unit)
- We don't try to convert the target table to MyISAM
*/ */
class select_result_explain_buffer : public select_result_sink class select_result_explain_buffer : public select_result_sink
{ {
public: public:
select_result_explain_buffer(THD *thd_arg, TABLE *table_arg) :
thd(thd_arg), dst_table(table_arg) {};
THD *thd; THD *thd;
Protocol *protocol; TABLE *dst_table; /* table to write into */
select_result_explain_buffer(){};
/* The following is called in the child thread: */ /* The following is called in the child thread: */
int send_data(List<Item> &items); int send_data(List<Item> &items);
/* this will be called in the parent thread: */
void flush_data();
void discard_data();
List<String> data_rows;
}; };
......
...@@ -2357,7 +2357,8 @@ struct LEX: public Query_tables_list ...@@ -2357,7 +2357,8 @@ struct LEX: public Query_tables_list
char *backup_dir; /* For RESTORE/BACKUP */ char *backup_dir; /* For RESTORE/BACKUP */
char* to_log; /* For PURGE MASTER LOGS TO */ char* to_log; /* For PURGE MASTER LOGS TO */
char* x509_subject,*x509_issuer,*ssl_cipher; char* x509_subject,*x509_issuer,*ssl_cipher;
String *wild; String *wild; /* Wildcard in SHOW {something} LIKE 'wild'*/
Item *show_explain_for_thread; /* id in SHOW EXPLAIN FOR id */
sql_exchange *exchange; sql_exchange *exchange;
select_result *result; select_result *result;
Item *default_value, *on_update_value; Item *default_value, *on_update_value;
......
...@@ -2144,6 +2144,32 @@ mysql_execute_command(THD *thd) ...@@ -2144,6 +2144,32 @@ mysql_execute_command(THD *thd)
execute_show_status(thd, all_tables); execute_show_status(thd, all_tables);
break; break;
} }
case SQLCOM_SHOW_EXPLAIN:
{
if (!thd->security_ctx->priv_user[0] &&
check_global_access(thd,PROCESS_ACL))
break;
/*
The select should use only one table, it's the SHOW EXPLAIN pseudo-table
*/
if (lex->sroutines.records || lex->query_tables->next_global)
{
my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored "
"function calls as part of this statement");
break;
}
Item **it= &(lex->show_explain_for_thread);
if ((!(*it)->fixed && (*it)->fix_fields(lex->thd, it)) ||
(*it)->check_cols(1))
{
my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
MYF(0));
goto error;
}
/* no break; fall through */
}
case SQLCOM_SHOW_DATABASES: case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_TABLES: case SQLCOM_SHOW_TABLES:
case SQLCOM_SHOW_TRIGGERS: case SQLCOM_SHOW_TRIGGERS:
...@@ -3128,35 +3154,6 @@ end_with_restore_list: ...@@ -3128,35 +3154,6 @@ end_with_restore_list:
thd->security_ctx->priv_user), thd->security_ctx->priv_user),
lex->verbose); lex->verbose);
break; break;
case SQLCOM_SHOW_EXPLAIN:
{
const char *effective_user;
/* Same security as SHOW PROCESSLIST (TODO check this) */
if (!thd->security_ctx->priv_user[0] &&
check_global_access(thd,PROCESS_ACL))
break;
Item *it= (Item *)lex->value_list.head();
if (lex->table_or_sp_used())
{
my_error(ER_NOT_SUPPORTED_YET, MYF(0), "Usage of subqueries or stored "
"function calls as part of this statement");
break;
}
if ((!it->fixed && it->fix_fields(lex->thd, &it)) || it->check_cols(1))
{
my_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
MYF(0));
goto error;
}
effective_user=(thd->security_ctx->master_access & PROCESS_ACL ? NullS :
thd->security_ctx->priv_user);
mysqld_show_explain(thd, effective_user, (ulong)it->val_int());
break;
}
case SQLCOM_SHOW_AUTHORS: case SQLCOM_SHOW_AUTHORS:
res= mysqld_show_authors(thd); res= mysqld_show_authors(thd);
break; break;
......
...@@ -1999,32 +1999,24 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose) ...@@ -1999,32 +1999,24 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose)
} }
/* static
SHOW EXPLAIN FOR command handler const char *target_not_explainable_cmd="Target is not running EXPLAINable command";
@param thd Current thread's thd
@param calling_user User that invoked SHOW EXPLAIN, or NULL if the user
has SUPER or PROCESS privileges, and so is allowed
to run SHOW EXPLAIN on anybody.
@param thread_id Thread whose explain we need
@notes /*
- Attempt to do "SHOW EXPLAIN FOR <myself>" will properly produce "target not Store the SHOW EXPLAIN output in the temporary table.
running EXPLAINable command".
*/ */
void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond)
{ {
const char *calling_user;
THD *tmp; THD *tmp;
Protocol *protocol= thd->protocol; my_thread_id thread_id;
List<Item> field_list; DBUG_ENTER("fill_show_explain");
DBUG_ENTER("mysqld_show_explain");
thd->make_explain_field_list(field_list);
if (protocol->send_result_set_metadata(&field_list, Protocol::SEND_NUM_ROWS |
Protocol::SEND_EOF))
DBUG_VOID_RETURN;
DBUG_ASSERT(cond==NULL);
thread_id= thd->lex->show_explain_for_thread->val_int();
calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS :
thd->security_ctx->priv_user;
/* /*
Find the thread we need EXPLAIN for. Thread search code was copied from Find the thread we need EXPLAIN for. Thread search code was copied from
kill_one_thread() kill_one_thread()
...@@ -2058,7 +2050,15 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) ...@@ -2058,7 +2050,15 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
{ {
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESSLIST"); my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESSLIST");
mysql_mutex_unlock(&tmp->LOCK_thd_data); mysql_mutex_unlock(&tmp->LOCK_thd_data);
DBUG_VOID_RETURN; DBUG_RETURN(1);
}
if (tmp == thd)
{
mysql_mutex_unlock(&tmp->LOCK_thd_data);
my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), "SHOW EXPLAIN",
target_not_explainable_cmd);
DBUG_RETURN(1);
} }
bool bres; bool bres;
...@@ -2071,9 +2071,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) ...@@ -2071,9 +2071,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
Show_explain_request explain_req; Show_explain_request explain_req;
select_result_explain_buffer *explain_buf; select_result_explain_buffer *explain_buf;
explain_buf= new select_result_explain_buffer; explain_buf= new select_result_explain_buffer(thd, table->table);
explain_buf->thd=thd;
explain_buf->protocol= thd->protocol;
explain_req.explain_buf= explain_buf; explain_req.explain_buf= explain_buf;
explain_req.target_thd= tmp; explain_req.target_thd= tmp;
...@@ -2099,29 +2097,22 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id) ...@@ -2099,29 +2097,22 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
else else
{ {
my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0), my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
"SHOW EXPLAIN", "SHOW EXPLAIN", target_not_explainable_cmd);
"Target is not running EXPLAINable command");
} }
bres= TRUE; bres= TRUE;
explain_buf->discard_data();
} }
else else
{ {
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE, push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_YES, explain_req.query_str.c_ptr_safe()); ER_YES, explain_req.query_str.c_ptr_safe());
} }
if (!bres) DBUG_RETURN(bres);
{
explain_buf->flush_data();
my_eof(thd);
}
} }
else else
{ {
my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id); my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id);
DBUG_RETURN(1);
} }
DBUG_VOID_RETURN;
} }
...@@ -8436,6 +8427,7 @@ ST_FIELD_INFO keycache_fields_info[]= ...@@ -8436,6 +8427,7 @@ ST_FIELD_INFO keycache_fields_info[]=
}; };
extern ST_FIELD_INFO show_explain_fields_info[];
/* /*
Description of ST_FIELD_INFO in table.h Description of ST_FIELD_INFO in table.h
...@@ -8467,6 +8459,8 @@ ST_SCHEMA_TABLE schema_tables[]= ...@@ -8467,6 +8459,8 @@ ST_SCHEMA_TABLE schema_tables[]=
{"EVENTS", events_fields_info, create_schema_table, {"EVENTS", events_fields_info, create_schema_table,
0, make_old_format, 0, -1, -1, 0, 0}, 0, make_old_format, 0, -1, -1, 0, 0},
#endif #endif
{"EXPLAIN", show_explain_fields_info, create_schema_table, fill_show_explain,
make_old_format, 0, -1, -1, TRUE /*hidden*/ , 0},
{"FILES", files_fields_info, create_schema_table, {"FILES", files_fields_info, create_schema_table,
hton_fill_schema_table, 0, 0, -1, -1, 0, 0}, hton_fill_schema_table, 0, 0, -1, -1, 0, 0},
{"GLOBAL_STATUS", variables_fields_info, create_schema_table, {"GLOBAL_STATUS", variables_fields_info, create_schema_table,
......
...@@ -11617,8 +11617,9 @@ show_param: ...@@ -11617,8 +11617,9 @@ show_param:
| describe_command FOR_SYM expr | describe_command FOR_SYM expr
{ {
Lex->sql_command= SQLCOM_SHOW_EXPLAIN; Lex->sql_command= SQLCOM_SHOW_EXPLAIN;
Lex->value_list.empty(); if (prepare_schema_table(YYTHD, Lex, 0, SCH_EXPLAIN))
Lex->value_list.push_front($3); MYSQL_YYABORT;
Lex->show_explain_for_thread= $3;
} }
; ;
......
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