MWL#182: Explain running statements: address review feedback
- switch SHOW EXPLAIN to using an INFORMATION_SCHEMA table.
This commit is contained in:
parent
a931467e17
commit
725d76e1e8
@ -798,7 +798,7 @@ pk data
|
||||
20 data1
|
||||
set autocommit=0;
|
||||
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;
|
||||
ERROR 70100: Query execution was interrupted
|
||||
rollback;
|
||||
@ -816,3 +816,8 @@ pk data
|
||||
20 data1
|
||||
drop table t1;
|
||||
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;
|
||||
let $wait_condition= select State='Sending data' from information_schema.processlist where id=$thr2;
|
||||
let $thr_default=`select connection_id()`;
|
||||
--source include/wait_condition.inc
|
||||
--echo # do: send_eval show explain for $thr2;
|
||||
--disable_query_log
|
||||
send_eval show explain for $thr2;
|
||||
--enable_query_log
|
||||
|
||||
# kill the SHOW EXPLAIN command
|
||||
connection con3;
|
||||
@ -844,3 +847,9 @@ disconnect con2;
|
||||
## thread and served together.
|
||||
|
||||
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
|
||||
SCH_COLUMN_PRIVILEGES,
|
||||
SCH_ENGINES,
|
||||
SCH_EVENTS,
|
||||
SCH_EXPLAIN,
|
||||
SCH_FILES,
|
||||
SCH_GLOBAL_STATUS,
|
||||
SCH_GLOBAL_VARIABLES,
|
||||
|
@ -24,7 +24,35 @@
|
||||
|
||||
/* 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.
|
||||
|
||||
|
@ -78,20 +78,6 @@ public:
|
||||
virtual bool send_result_set_metadata(List<Item> *list, uint flags);
|
||||
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(const char *from, CHARSET_INFO *cs);
|
||||
String *storage_packet() { return packet; }
|
||||
|
@ -2309,90 +2309,16 @@ int select_send::send_data(List<Item> &items)
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
Save the data being sent in our internal buffer.
|
||||
*/
|
||||
|
||||
int select_result_explain_buffer::send_data(List<Item> &items)
|
||||
{
|
||||
List_iterator_fast<Item> li(items);
|
||||
char buff[MAX_FIELD_WIDTH];
|
||||
String buffer(buff, sizeof(buff), &my_charset_bin);
|
||||
DBUG_ENTER("select_send::send_data");
|
||||
|
||||
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);
|
||||
fill_record(thd, dst_table->field, items, TRUE, FALSE);
|
||||
if ((dst_table->file->ha_write_tmp_row(dst_table->record[0])))
|
||||
return 1;
|
||||
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()
|
||||
{
|
||||
/*
|
||||
|
@ -3319,32 +3319,27 @@ public:
|
||||
|
||||
|
||||
/*
|
||||
A select result sink that collects the sent data and then can flush it to
|
||||
network when requested.
|
||||
|
||||
This class is targeted at collecting EXPLAIN output:
|
||||
- Unoptimized data storage (can't handle big datasets)
|
||||
This is a select_result_sink which simply writes all data into a (temporary)
|
||||
table. Creation/deletion of the table is outside of the scope of the class
|
||||
|
||||
It is aimed at capturing SHOW EXPLAIN output, so:
|
||||
- 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
|
||||
unit)
|
||||
- We don't try to convert the target table to MyISAM
|
||||
*/
|
||||
|
||||
class select_result_explain_buffer : public select_result_sink
|
||||
{
|
||||
public:
|
||||
select_result_explain_buffer(THD *thd_arg, TABLE *table_arg) :
|
||||
thd(thd_arg), dst_table(table_arg) {};
|
||||
|
||||
THD *thd;
|
||||
Protocol *protocol;
|
||||
select_result_explain_buffer(){};
|
||||
TABLE *dst_table; /* table to write into */
|
||||
|
||||
/* The following is called in the child thread: */
|
||||
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
|
||||
char *backup_dir; /* For RESTORE/BACKUP */
|
||||
char* to_log; /* For PURGE MASTER LOGS TO */
|
||||
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;
|
||||
select_result *result;
|
||||
Item *default_value, *on_update_value;
|
||||
|
@ -2144,6 +2144,32 @@ mysql_execute_command(THD *thd)
|
||||
execute_show_status(thd, all_tables);
|
||||
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_TABLES:
|
||||
case SQLCOM_SHOW_TRIGGERS:
|
||||
@ -3128,35 +3154,6 @@ end_with_restore_list:
|
||||
thd->security_ctx->priv_user),
|
||||
lex->verbose);
|
||||
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:
|
||||
res= mysqld_show_authors(thd);
|
||||
break;
|
||||
|
@ -1999,32 +1999,24 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose)
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
const char *target_not_explainable_cmd="Target is not running EXPLAINable command";
|
||||
|
||||
/*
|
||||
SHOW EXPLAIN FOR command handler
|
||||
|
||||
@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
|
||||
running EXPLAINable command".
|
||||
Store the SHOW EXPLAIN output in the temporary table.
|
||||
*/
|
||||
|
||||
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;
|
||||
Protocol *protocol= thd->protocol;
|
||||
List<Item> field_list;
|
||||
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;
|
||||
|
||||
my_thread_id thread_id;
|
||||
DBUG_ENTER("fill_show_explain");
|
||||
|
||||
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
|
||||
kill_one_thread()
|
||||
@ -2042,7 +2034,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
|
||||
}
|
||||
}
|
||||
mysql_mutex_unlock(&LOCK_thread_count);
|
||||
|
||||
|
||||
if (tmp)
|
||||
{
|
||||
Security_context *tmp_sctx= tmp->security_ctx;
|
||||
@ -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");
|
||||
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;
|
||||
@ -2071,9 +2071,7 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
|
||||
Show_explain_request explain_req;
|
||||
select_result_explain_buffer *explain_buf;
|
||||
|
||||
explain_buf= new select_result_explain_buffer;
|
||||
explain_buf->thd=thd;
|
||||
explain_buf->protocol= thd->protocol;
|
||||
explain_buf= new select_result_explain_buffer(thd, table->table);
|
||||
|
||||
explain_req.explain_buf= explain_buf;
|
||||
explain_req.target_thd= tmp;
|
||||
@ -2099,29 +2097,22 @@ void mysqld_show_explain(THD *thd, const char *calling_user, ulong thread_id)
|
||||
else
|
||||
{
|
||||
my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
|
||||
"SHOW EXPLAIN",
|
||||
"Target is not running EXPLAINable command");
|
||||
"SHOW EXPLAIN", target_not_explainable_cmd);
|
||||
}
|
||||
bres= TRUE;
|
||||
explain_buf->discard_data();
|
||||
}
|
||||
else
|
||||
{
|
||||
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
|
||||
ER_YES, explain_req.query_str.c_ptr_safe());
|
||||
}
|
||||
if (!bres)
|
||||
{
|
||||
explain_buf->flush_data();
|
||||
my_eof(thd);
|
||||
}
|
||||
DBUG_RETURN(bres);
|
||||
}
|
||||
else
|
||||
{
|
||||
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[]=
|
||||
};
|
||||
|
||||
|
||||
extern ST_FIELD_INFO show_explain_fields_info[];
|
||||
/*
|
||||
Description of ST_FIELD_INFO in table.h
|
||||
|
||||
@ -8467,6 +8459,8 @@ ST_SCHEMA_TABLE schema_tables[]=
|
||||
{"EVENTS", events_fields_info, create_schema_table,
|
||||
0, make_old_format, 0, -1, -1, 0, 0},
|
||||
#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,
|
||||
hton_fill_schema_table, 0, 0, -1, -1, 0, 0},
|
||||
{"GLOBAL_STATUS", variables_fields_info, create_schema_table,
|
||||
|
@ -11617,8 +11617,9 @@ show_param:
|
||||
| describe_command FOR_SYM expr
|
||||
{
|
||||
Lex->sql_command= SQLCOM_SHOW_EXPLAIN;
|
||||
Lex->value_list.empty();
|
||||
Lex->value_list.push_front($3);
|
||||
if (prepare_schema_table(YYTHD, Lex, 0, SCH_EXPLAIN))
|
||||
MYSQL_YYABORT;
|
||||
Lex->show_explain_for_thread= $3;
|
||||
}
|
||||
;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user