SHOW EXPLAIN: merge with mariadb 5.5-main

This commit is contained in:
Sergey Petrunya 2012-10-06 11:17:30 +04:00
commit 74816d2963
37 changed files with 4046 additions and 197 deletions

View file

@ -1136,6 +1136,7 @@ plugin/handler_socket/perl-Net-HandlerSocket/Makefile.PL
libmysqld/libmysqld_exports_file.cc
libmysqld/gcalc_slicescan.cc
libmysqld/gcalc_tools.cc
libmysqld/my_apc.cc
sql/share/errmsg.sys
sql/share/mysql
install_manifest.txt

View file

@ -283,6 +283,7 @@ IF(WITH_UNIT_TESTS)
ADD_SUBDIRECTORY(unittest/strings)
ADD_SUBDIRECTORY(unittest/examples)
ADD_SUBDIRECTORY(unittest/mysys)
ADD_SUBDIRECTORY(unittest/sql)
ENDIF()
IF(NOT WITHOUT_SERVER)

View file

@ -84,6 +84,8 @@ static my_bool non_blocking_api_enabled= 0;
#define QUERY_SEND_FLAG 1
#define QUERY_REAP_FLAG 2
#define QUERY_PRINT_ORIGINAL_FLAG 4
#ifndef HAVE_SETENV
static int setenv(const char *name, const char *value, int overwrite);
#endif
@ -343,6 +345,7 @@ enum enum_commands {
Q_SEND, Q_REAP,
Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN,
Q_PING, Q_EVAL,
Q_EVALP,
Q_EVAL_RESULT,
Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG,
Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG,
@ -408,6 +411,7 @@ const char *command_names[]=
"replace_column",
"ping",
"eval",
"evalp",
"eval_result",
/* Enable/disable that the _query_ is logged to result file */
"enable_query_log",
@ -8296,7 +8300,8 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags)
/*
Evaluate query if this is an eval command
*/
if (command->type == Q_EVAL || command->type == Q_SEND_EVAL)
if (command->type == Q_EVAL || command->type == Q_SEND_EVAL ||
command->type == Q_EVALP)
{
init_dynamic_string(&eval_query, "", command->query_len+256, 1024);
do_eval(&eval_query, command->query, command->end, FALSE);
@ -8328,11 +8333,21 @@ void run_query(struct st_connection *cn, struct st_command *command, int flags)
*/
if (!disable_query_log && (flags & QUERY_SEND_FLAG))
{
replace_dynstr_append_mem(ds, query, query_len);
char *print_query= query;
int print_len= query_len;
if (flags & QUERY_PRINT_ORIGINAL_FLAG)
{
print_query= command->query;
print_len= command->end - command->query;
}
replace_dynstr_append_mem(ds, print_query, print_len);
dynstr_append_mem(ds, delimiter, delimiter_length);
dynstr_append_mem(ds, "\n", 1);
}
/* We're done with this flag */
flags &= ~QUERY_PRINT_ORIGINAL_FLAG;
/*
Write the command to the result file before we execute the query
This is needed to be able to analyse the log if something goes
@ -9192,6 +9207,7 @@ int main(int argc, char **argv)
case Q_EVAL_RESULT:
die("'eval_result' command is deprecated");
case Q_EVAL:
case Q_EVALP:
case Q_QUERY_VERTICAL:
case Q_QUERY_HORIZONTAL:
if (command->query == command->query_buf)
@ -9219,6 +9235,9 @@ int main(int argc, char **argv)
flags= QUERY_REAP_FLAG;
}
if (command->type == Q_EVALP)
flags |= QUERY_PRINT_ORIGINAL_FLAG;
/* Check for special property for this query */
display_result_vertically|= (command->type == Q_QUERY_VERTICAL);

View file

@ -95,6 +95,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/create_options.cc ../sql/rpl_utility.cc
../sql/rpl_reporting.cc
../sql/sql_expression_cache.cc
../sql/my_apc.cc ../sql/my_apc.h
${GEN_SOURCES}
${MYSYS_LIBWRAP_SOURCE}
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,27 @@
drop table if exists t0, t1;
select * from performance_schema.setup_instruments where name like '%show_explain%';
NAME ENABLED TIMED
wait/synch/cond/sql/show_explain YES YES
# We've got no instances
select * from performance_schema.cond_instances where name like '%show_explain%';
NAME OBJECT_INSTANCE_BEGIN
# Check out if our cond is hit.
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
set @show_explain_probe_select_id=1;
set debug_dbug='d,show_explain_probe_join_exec_start';
select count(*) from t0 where a < 100000;
show explain for $thr2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t0 ALL NULL NULL NULL NULL 10 Using where
Warnings:
Note 1003 select count(*) from t0 where a < 100000
count(*)
10
set debug_dbug='';
select event_name
from performance_schema.events_waits_history_long
where event_name='wait/synch/cond/sql/show_explain';
event_name
wait/synch/cond/sql/show_explain
drop table t0;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,51 @@
#
# Test how SHOW EXPLAIN is represented in performance schema
#
--source include/have_debug.inc
--source include/have_perfschema.inc
# Like all other perfschema tests, we don't work on embedded server:
--source include/not_embedded.inc
--disable_warnings
drop table if exists t0, t1;
--enable_warnings
select * from performance_schema.setup_instruments where name like '%show_explain%';
--echo # We've got no instances
select * from performance_schema.cond_instances where name like '%show_explain%';
--echo # Check out if our cond is hit.
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
let $thr1=`select connection_id()`;
connect (con1, localhost, root,,);
connection con1;
let $thr2=`select connection_id()`;
connection default;
let $wait_condition= select State='show_explain_trap' from information_schema.processlist where id=$thr2;
#
# Test SHOW EXPLAIN for simple queries
#
connection con1;
set @show_explain_probe_select_id=1;
set debug_dbug='d,show_explain_probe_join_exec_start';
send select count(*) from t0 where a < 100000;
connection default;
--source include/wait_condition.inc
evalp show explain for $thr2;
connection con1;
reap;
set debug_dbug='';
select event_name
from performance_schema.events_waits_history_long
where event_name='wait/synch/cond/sql/show_explain';
drop table t0;

View file

@ -85,6 +85,7 @@ SET (SQL_SOURCE
gcalc_slicescan.cc gcalc_tools.cc
threadpool_common.cc
../sql-common/mysql_async.c
my_apc.cc my_apc.h
${GEN_SOURCES}
${MYSYS_LIBWRAP_SOURCE}
)

View file

@ -505,7 +505,6 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select,
my_off_t record;
TABLE *sort_form;
THD *thd= current_thd;
volatile killed_state *killed= &thd->killed;
handler *file;
MY_BITMAP *save_read_set, *save_write_set, *save_vcol_set;
uchar *next_sort_key= sort_keys_buf;
@ -526,6 +525,11 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select,
if (flag)
ref_pos= &file->ref[0];
next_pos=ref_pos;
DBUG_EXECUTE_IF("show_explain_in_find_all_keys",
dbug_serve_apcs(thd, 1);
);
if (!quick_select)
{
next_pos=(uchar*) 0; /* Find records in sequence */
@ -589,7 +593,7 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select,
break;
}
if (*killed)
if (thd->check_killed())
{
DBUG_PRINT("info",("Sort killed by user"));
if (!quick_select)
@ -1234,18 +1238,13 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
void *first_cmp_arg;
element_count dupl_count= 0;
uchar *src;
killed_state not_killable;
uchar *unique_buff= param->unique_buff;
volatile killed_state *killed= &current_thd->killed;
const bool killable= !param->not_killable;
THD* const thd=current_thd;
DBUG_ENTER("merge_buffers");
status_var_increment(current_thd->status_var.filesort_merge_passes);
current_thd->query_plan_fsort_passes++;
if (param->not_killable)
{
killed= &not_killable;
not_killable= NOT_KILLED;
}
status_var_increment(thd->status_var.filesort_merge_passes);
thd->query_plan_fsort_passes++;
error=0;
rec_length= param->rec_length;
@ -1322,7 +1321,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
while (queue.elements > 1)
{
if (*killed)
if (killable && thd->check_killed())
{
error= 1; goto err; /* purecov: inspected */
}

View file

@ -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,

View file

@ -4299,7 +4299,7 @@ longlong Item_func_sleep::val_int()
#define extra_size sizeof(double)
static user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
bool create_if_not_exists)
{
user_var_entry *entry;

View file

@ -1919,7 +1919,7 @@ bool Item_allany_subselect::is_maxmin_applicable(JOIN *join)
WHERE condition.
*/
return (abort_on_null || (upper_item && upper_item->is_top_level_item())) &&
!join->select_lex->master_unit()->uncacheable && !func->eqne_op();
!(join->select_lex->master_unit()->uncacheable & ~UNCACHEABLE_EXPLAIN) && !func->eqne_op();
}

270
sql/my_apc.cc Normal file
View file

@ -0,0 +1,270 @@
/*
Copyright (c) 2011 - 2012, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#ifndef MY_APC_STANDALONE
#include "sql_priv.h"
#include "sql_class.h"
#endif
/* For standalone testing of APC system, see unittest/sql/my_apc-t.cc */
/*
Initialize the target.
@note
Initialization must be done prior to enabling/disabling the target, or making
any call requests to it.
Initial state after initialization is 'disabled'.
*/
void Apc_target::init(mysql_mutex_t *target_mutex)
{
DBUG_ASSERT(!enabled);
LOCK_thd_data_ptr= target_mutex;
#ifndef DBUG_OFF
n_calls_processed= 0;
#endif
}
/*
Destroy the target. The target must be disabled when this call is made.
*/
void Apc_target::destroy()
{
DBUG_ASSERT(!enabled);
}
/*
Enter ther state where the target is available for serving APC requests
*/
void Apc_target::enable()
{
/* Ok to do without getting/releasing the mutex: */
enabled++;
}
/*
Make the target unavailable for serving APC requests.
@note
This call will serve all requests that were already enqueued
*/
void Apc_target::disable()
{
bool process= FALSE;
mysql_mutex_lock(LOCK_thd_data_ptr);
if (!(--enabled))
process= TRUE;
mysql_mutex_unlock(LOCK_thd_data_ptr);
if (process)
process_apc_requests();
}
/* [internal] Put request qe into the request list */
void Apc_target::enqueue_request(Call_request *qe)
{
mysql_mutex_assert_owner(LOCK_thd_data_ptr);
if (apc_calls)
{
Call_request *after= apc_calls->prev;
qe->next= apc_calls;
apc_calls->prev= qe;
qe->prev= after;
after->next= qe;
}
else
{
apc_calls= qe;
qe->next= qe->prev= qe;
}
}
/*
[internal] Remove request qe from the request queue.
The request is not necessarily first in the queue.
*/
void Apc_target::dequeue_request(Call_request *qe)
{
mysql_mutex_assert_owner(LOCK_thd_data_ptr);
if (apc_calls == qe)
{
if ((apc_calls= apc_calls->next) == qe)
{
apc_calls= NULL;
}
}
qe->prev->next= qe->next;
qe->next->prev= qe->prev;
}
#ifdef HAVE_PSI_INTERFACE
/* One key for all conds */
PSI_cond_key key_show_explain_request_COND;
static PSI_cond_info show_explain_psi_conds[]=
{
{ &key_show_explain_request_COND, "show_explain", 0 /* not using PSI_FLAG_GLOBAL*/ }
};
void init_show_explain_psi_keys(void)
{
if (PSI_server == NULL)
return;
PSI_server->register_cond("sql", show_explain_psi_conds,
array_elements(show_explain_psi_conds));
}
#endif
/*
Make an APC (Async Procedure Call) to another thread.
@detail
Make an APC call: schedule it for execution and wait until the target
thread has executed it.
- The caller is responsible for making sure he's not posting request
to the thread he's calling this function from.
- The caller must have locked target_mutex. The function will release it.
@retval FALSE - Ok, the call has been made
@retval TRUE - Call wasnt made (either the target is in disabled state or
timeout occured)
*/
bool Apc_target::make_apc_call(THD *caller_thd, Apc_call *call,
int timeout_sec, bool *timed_out)
{
bool res= TRUE;
*timed_out= FALSE;
if (enabled)
{
/* Create and post the request */
Call_request apc_request;
apc_request.call= call;
apc_request.processed= FALSE;
mysql_cond_init(key_show_explain_request_COND, &apc_request.COND_request,
NULL);
enqueue_request(&apc_request);
apc_request.what="enqueued by make_apc_call";
struct timespec abstime;
const int timeout= timeout_sec;
set_timespec(abstime, timeout);
int wait_res= 0;
const char *old_msg;
old_msg= caller_thd->enter_cond(&apc_request.COND_request,
LOCK_thd_data_ptr, "show_explain");
/* todo: how about processing other errors here? */
while (!apc_request.processed && (wait_res != ETIMEDOUT))
{
/* We own LOCK_thd_data_ptr */
wait_res= mysql_cond_timedwait(&apc_request.COND_request,
LOCK_thd_data_ptr, &abstime);
// &apc_request.LOCK_request, &abstime);
if (caller_thd->killed)
break;
}
if (!apc_request.processed)
{
/*
The wait has timed out, or this thread was KILLed.
Remove the request from the queue (ok to do because we own
LOCK_thd_data_ptr)
*/
apc_request.processed= TRUE;
dequeue_request(&apc_request);
*timed_out= TRUE;
res= TRUE;
}
else
{
/* Request was successfully executed and dequeued by the target thread */
res= FALSE;
}
/*
exit_cond() will call mysql_mutex_unlock(LOCK_thd_data_ptr) for us:
*/
caller_thd->exit_cond(old_msg);
/* Destroy all APC request data */
mysql_cond_destroy(&apc_request.COND_request);
}
else
{
mysql_mutex_unlock(LOCK_thd_data_ptr);
}
return res;
}
/*
Process all APC requests.
This should be called periodically by the APC target thread.
*/
void Apc_target::process_apc_requests()
{
while (1)
{
Call_request *request;
mysql_mutex_lock(LOCK_thd_data_ptr);
if (!(request= get_first_in_queue()))
{
/* No requests in the queue */
mysql_mutex_unlock(LOCK_thd_data_ptr);
break;
}
/*
Remove the request from the queue (we're holding queue lock so we can be
sure that request owner won't try to remove it)
*/
request->what="dequeued by process_apc_requests";
dequeue_request(request);
request->processed= TRUE;
request->call->call_in_target_thread();
request->what="func called by process_apc_requests";
#ifndef DBUG_OFF
n_calls_processed++;
#endif
mysql_cond_signal(&request->COND_request);
mysql_mutex_unlock(LOCK_thd_data_ptr);
}
}

138
sql/my_apc.h Normal file
View file

@ -0,0 +1,138 @@
#ifndef INCLUDES_MY_APC_H
#define INCLUDES_MY_APC_H
/*
Copyright (c) 2011 - 2012, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
Interface
~~~~~~~~~
(
- This is an APC request queue
- We assume there is a particular owner thread which periodically calls
process_apc_requests() to serve the call requests.
- Other threads can post call requests, and block until they are exectued.
)
Implementation
~~~~~~~~~~~~~~
- The target has a mutex-guarded request queue.
- After the request has been put into queue, the requestor waits for request
to be satisfied. The worker satisifes the request and signals the
requestor.
*/
class THD;
/*
Target for asynchronous procedure calls (APCs).
- A target is running in some particular thread,
- One can make calls to it from other threads.
*/
class Apc_target
{
mysql_mutex_t *LOCK_thd_data_ptr;
public:
Apc_target() : enabled(0), apc_calls(NULL) {}
~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);}
void init(mysql_mutex_t *target_mutex);
void destroy();
void enable();
void disable();
void process_apc_requests();
/*
A lightweight function, intended to be used in frequent checks like this:
if (apc_target.have_requests()) apc_target.process_apc_requests()
*/
inline bool have_apc_requests()
{
return test(apc_calls);
}
/* Functor class for calls you can schedule */
class Apc_call
{
public:
/* This function will be called in the target thread */
virtual void call_in_target_thread()= 0;
virtual ~Apc_call() {}
};
/* Make a call in the target thread (see function definition for details) */
bool make_apc_call(THD *caller_thd, Apc_call *call, int timeout_sec, bool *timed_out);
#ifndef DBUG_OFF
int n_calls_processed; /* Number of calls served by this target */
#endif
private:
class Call_request;
/*
Non-zero value means we're enabled. It's an int, not bool, because one can
call enable() N times (and then needs to call disable() N times before the
target is really disabled)
*/
int enabled;
/*
Circular, double-linked list of all enqueued call requests.
We use this structure, because we
- process requests sequentially: requests are added at the end of the
list and removed from the front. With circular list, we can keep one
pointer, and access both front an back of the list with it.
- a thread that has posted a request may time out (or be KILLed) and
cancel the request, which means we need a fast request-removal
operation.
*/
Call_request *apc_calls;
class Call_request
{
public:
Apc_call *call; /* Functor to be called */
/* The caller will actually wait for "processed==TRUE" */
bool processed;
/* Condition that will be signalled when the request has been served */
mysql_cond_t COND_request;
/* Double linked-list linkage */
Call_request *next;
Call_request *prev;
const char *what; /* (debug) state of the request */
};
void enqueue_request(Call_request *qe);
void dequeue_request(Call_request *qe);
/* return the first call request in queue, or NULL if there are none enqueued */
Call_request *get_first_in_queue()
{
return apc_calls;
}
};
#ifdef HAVE_PSI_INTERFACE
void init_show_explain_psi_keys(void);
#endif
#endif //INCLUDES_MY_APC_H

View file

@ -3345,6 +3345,7 @@ SHOW_VAR com_status_vars[]= {
{"show_engine_status", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ENGINE_STATUS]), SHOW_LONG_STATUS},
{"show_events", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EVENTS]), SHOW_LONG_STATUS},
{"show_errors", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_ERRORS]), SHOW_LONG_STATUS},
{"show_explain", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_EXPLAIN]), SHOW_LONG_STATUS},
{"show_fields", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FIELDS]), SHOW_LONG_STATUS},
#ifndef DBUG_OFF
{"show_function_code", (char*) offsetof(STATUS_VAR, com_stat[(uint) SQLCOM_SHOW_FUNC_CODE]), SHOW_LONG_STATUS},
@ -3924,6 +3925,7 @@ static int init_thread_environment()
#ifdef HAVE_EVENT_SCHEDULER
Events::init_mutexes();
#endif
init_show_explain_psi_keys();
/* Parameter for threads created for connections */
(void) pthread_attr_init(&connection_attrib);
(void) pthread_attr_setdetachstate(&connection_attrib,

View file

@ -1657,6 +1657,7 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
parent_lex->ftfunc_list->push_front(ifm);
}
parent_lex->have_merged_subqueries= TRUE;
DBUG_RETURN(FALSE);
}
@ -1767,6 +1768,8 @@ static bool convert_subq_to_jtbm(JOIN *parent_join,
create_subquery_temptable_name(tbl_alias, hash_sj_engine->materialize_join->
select_lex->select_number);
jtbm->alias= tbl_alias;
parent_lex->have_merged_subqueries= TRUE;
#if 0
/* Inject sj_on_expr into the parent's WHERE or ON */
if (emb_tbl_nest)

View file

@ -35,6 +35,7 @@ class Protocol
protected:
THD *thd;
String *packet;
/* Used by net_store_data() for charset conversions */
String *convert;
uint field_pos;
#ifndef DBUG_OFF
@ -49,6 +50,10 @@ protected:
MYSQL_FIELD *next_mysql_field;
MEM_ROOT *alloc;
#endif
/*
The following two are low-level functions that are invoked from
higher-level store_xxx() funcs. The data is stored into this->packet.
*/
bool net_store_data(const uchar *from, size_t length,
CHARSET_INFO *fromcs, CHARSET_INFO *tocs);
bool store_string_aux(const char *from, size_t length,

View file

@ -4345,13 +4345,13 @@ ER_TOO_MANY_USER_CONNECTIONS 42000
ER_SET_CONSTANTS_ONLY
dan "Du må kun bruge konstantudtryk med SET"
nla "U mag alleen constante expressies gebruiken bij SET"
eng "You may only use constant expressions with SET"
eng "You may only use constant expressions in this statement"
est "Ainult konstantsed suurused on lubatud SET klauslis"
fre "Seules les expressions constantes sont autorisées avec SET"
ger "Bei SET dürfen nur konstante Ausdrücke verwendet werden"
ger "Bei diesem Befehl dürfen nur konstante Ausdrücke verwendet werden"
ita "Si possono usare solo espressioni costanti con SET"
por "Você pode usar apenas expressões constantes com SET"
rus "Вы можете использовать в SET только константные выражения"
rus "С этой командой вы можете использовать только константные выражения"
serbian "Možete upotrebiti samo konstantan iskaz sa komandom 'SET'"
spa "Tu solo debes usar expresiones constantes con SET"
swe "Man kan endast använda konstantuttryck med SET"
@ -6588,3 +6588,5 @@ ER_QUERY_EXCEEDED_ROWS_EXAMINED_LIMIT
ER_NO_SUCH_TABLE_IN_ENGINE 42S02
eng "Table '%-.192s.%-.192s' doesn't exist in engine"
swe "Det finns ingen tabell som heter '%-.192s.%-.192s' i handlern"
ER_TARGET_NOT_EXPLAINABLE
eng "Target is not running an EXPLAINable command"

View file

@ -218,6 +218,7 @@ sp_get_flags_for_command(LEX *lex)
case SQLCOM_SHOW_CREATE_TRIGGER:
case SQLCOM_SHOW_DATABASES:
case SQLCOM_SHOW_ERRORS:
case SQLCOM_SHOW_EXPLAIN:
case SQLCOM_SHOW_FIELDS:
case SQLCOM_SHOW_FUNC_CODE:
case SQLCOM_SHOW_GRANTS:

View file

@ -1196,6 +1196,7 @@ void THD::init(void)
/* Initialize the Debug Sync Facility. See debug_sync.cc. */
debug_sync_init_thread(this);
#endif /* defined(ENABLED_DEBUG_SYNC) */
apc_target.init(&LOCK_thd_data);
}
@ -1361,6 +1362,7 @@ void THD::cleanup(void)
ull= NULL;
}
apc_target.destroy();
cleanup_done=1;
DBUG_VOID_RETURN;
}
@ -2013,6 +2015,20 @@ CHANGED_TABLE_LIST* THD::changed_table_dup(const char *key, long key_length)
int THD::send_explain_fields(select_result *result)
{
List<Item> field_list;
make_explain_field_list(field_list);
return (result->send_result_set_metadata(field_list,
Protocol::SEND_NUM_ROWS |
Protocol::SEND_EOF));
}
/*
Populate the provided field_list with EXPLAIN output columns.
this->lex->describe has the EXPLAIN flags
*/
void THD::make_explain_field_list(List<Item> &field_list)
{
Item *item;
CHARSET_INFO *cs= system_charset_info;
field_list.push_back(item= new Item_return_int("id",3, MYSQL_TYPE_LONGLONG));
@ -2051,10 +2067,9 @@ int THD::send_explain_fields(select_result *result)
}
item->maybe_null= 1;
field_list.push_back(new Item_empty_string("Extra", 255, cs));
return (result->send_result_set_metadata(field_list,
Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF));
}
#ifdef SIGNAL_WITH_VIO_CLOSE
void THD::close_active_vio()
{
@ -2286,6 +2301,7 @@ int select_send::send_data(List<Item> &items)
DBUG_RETURN(0);
}
bool select_send::send_eof()
{
/*
@ -3179,6 +3195,10 @@ void THD::end_statement()
}
/*
Start using arena specified by @set. Current arena data will be saved to
*backup.
*/
void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup)
{
DBUG_ENTER("THD::set_n_backup_active_arena");
@ -3193,6 +3213,12 @@ void THD::set_n_backup_active_arena(Query_arena *set, Query_arena *backup)
}
/*
Stop using the temporary arena, and start again using the arena that is
specified in *backup.
The temporary arena is returned back into *set.
*/
void THD::restore_active_arena(Query_arena *set, Query_arena *backup)
{
DBUG_ENTER("THD::restore_active_arena");
@ -3767,6 +3793,7 @@ void THD::restore_backup_open_tables_state(Open_tables_backup *backup)
@retval 1 the user thread has been killed
This is used to signal a storage engine if it should be killed.
See also THD::check_killed().
*/
extern "C" int thd_killed(const MYSQL_THD thd)
@ -3774,6 +3801,10 @@ extern "C" int thd_killed(const MYSQL_THD thd)
if (!thd)
thd= current_thd;
Apc_target *apc_target= (Apc_target*)&thd->apc_target;
if (apc_target->have_apc_requests())
apc_target->process_apc_requests();
if (!(thd->killed & KILL_HARD_BIT))
return 0;
return thd->killed;

View file

@ -43,7 +43,7 @@
#include "violite.h" /* vio_is_connected */
#include "thr_lock.h" /* thr_lock_type, THR_LOCK_DATA,
THR_LOCK_INFO */
#include "my_apc.h"
class Reprepare_observer;
class Relay_log_info;
@ -1523,6 +1523,11 @@ private:
extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
class THD;
#ifndef DBUG_OFF
void dbug_serve_apcs(THD *thd, int n_calls);
#endif
/**
@class THD
For each client connection we create a separate thread with THD serving as
@ -2192,6 +2197,16 @@ public:
*/
killed_state volatile killed;
/* See also thd_killed() */
inline bool check_killed()
{
if (killed)
return TRUE;
if (apc_target.have_apc_requests())
apc_target.process_apc_requests();
return FALSE;
}
/* scramble - random string sent to client on handshake */
char scramble[SCRAMBLE_LENGTH+1];
@ -2394,6 +2409,17 @@ public:
/** Disconnect the associated communication endpoint. */
void disconnect();
/*
Allows this thread to serve as a target for others to schedule Async
Procedure Calls on.
It's possible to schedule any code to be executed this way, by
inheriting from the Apc_call object. Currently, only
Show_explain_request uses this.
*/
Apc_target apc_target;
#ifndef MYSQL_CLIENT
enum enum_binlog_query_type {
/* The query can be logged in row format or in statement format. */
@ -2587,7 +2613,7 @@ public:
void add_changed_table(const char *key, long key_length);
CHANGED_TABLE_LIST * changed_table_dup(const char *key, long key_length);
int send_explain_fields(select_result *result);
void make_explain_field_list(List<Item> &field_list);
/**
Clear the current error, if any.
We do not clear is_fatal_error or is_fatal_sub_stmt_error since we
@ -3197,10 +3223,42 @@ public:
class JOIN;
class select_result :public Sql_alloc {
/* Pure interface for sending tabular data */
class select_result_sink: public Sql_alloc
{
public:
/*
send_data returns 0 on ok, 1 on error and -1 if data was ignored, for
example for a duplicate row entry written to a temp table.
*/
virtual int send_data(List<Item> &items)=0;
virtual ~select_result_sink() {};
};
/*
Interface for sending tabular data, together with some other stuff:
- Primary purpose seems to be seding typed tabular data:
= the DDL is sent with send_fields()
= the rows are sent with send_data()
Besides that,
- there seems to be an assumption that the sent data is a result of
SELECT_LEX_UNIT *unit,
- nest_level is used by SQL parser
*/
class select_result :public select_result_sink
{
protected:
THD *thd;
/*
All descendant classes have their send_data() skip the first
unit->offset_limit_cnt rows sent. Select_materialize
also uses unit->get_unit_column_types().
*/
SELECT_LEX_UNIT *unit;
/* Something used only by the parser: */
public:
select_result();
virtual ~select_result() {};
@ -3218,11 +3276,6 @@ public:
virtual uint field_count(List<Item> &fields) const
{ return fields.elements; }
virtual bool send_result_set_metadata(List<Item> &list, uint flags)=0;
/*
send_data returns 0 on ok, 1 on error and -1 if data was ignored, for
example for a duplicate row entry written to a temp table.
*/
virtual int send_data(List<Item> &items)=0;
virtual bool initialize_tables (JOIN *join=0) { return 0; }
virtual void send_error(uint errcode,const char *err);
virtual bool send_eof()=0;
@ -3249,6 +3302,32 @@ public:
};
/*
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;
TABLE *dst_table; /* table to write into */
/* The following is called in the child thread: */
int send_data(List<Item> &items);
};
/*
Base class for select_result descendands which intercept and
transform result set rows. As the rows are not sent to the client,
@ -3820,6 +3899,8 @@ class user_var_entry
DTCollation collation;
};
user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
bool create_if_not_exists);
/*
Unique -- class for unique (removing of duplicates).

View file

@ -2236,7 +2236,7 @@ enum_nested_loop_state JOIN_CACHE::join_matching_records(bool skip_last)
while (!(error= join_tab_scan->next()))
{
if (join->thd->killed)
if (join->thd->check_killed())
{
/* The user has aborted the execution of the query */
join->thd->send_kill_message();
@ -2506,7 +2506,7 @@ enum_nested_loop_state JOIN_CACHE::join_null_complements(bool skip_last)
for ( ; cnt; cnt--)
{
if (join->thd->killed)
if (join->thd->check_killed())
{
/* The user has aborted the execution of the query */
join->thd->send_kill_message();
@ -3356,7 +3356,7 @@ int JOIN_TAB_SCAN::next()
update_virtual_fields(thd, table);
while (!err && select && (skip_rc= select->skip_record(thd)) <= 0)
{
if (thd->killed || skip_rc < 0)
if (thd->check_killed() || skip_rc < 0)
return 1;
/*
Move to the next record if the last retrieved record does not

View file

@ -1874,6 +1874,7 @@ void st_select_lex::init_query()
nest_level= 0;
link_next= 0;
is_prep_leaf_list_saved= FALSE;
have_merged_subqueries= FALSE;
bzero((char*) expr_cache_may_be_used, sizeof(expr_cache_may_be_used));
m_non_agg_field_used= false;
m_agg_func_used= false;
@ -3465,7 +3466,7 @@ bool st_select_lex::optimize_unflattened_subqueries(bool const_only)
if (options & SELECT_DESCRIBE)
{
/* Optimize the subquery in the context of EXPLAIN. */
sl->set_explain_type();
sl->set_explain_type(FALSE);
sl->options|= SELECT_DESCRIBE;
inner_join->select_options|= SELECT_DESCRIBE;
}
@ -3915,9 +3916,12 @@ void st_select_lex::update_correlated_cache()
/**
Set the EXPLAIN type for this subquery.
@param on_the_fly TRUE<=> We're running a SHOW EXPLAIN command, so we must
not change any variables
*/
void st_select_lex::set_explain_type()
void st_select_lex::set_explain_type(bool on_the_fly)
{
bool is_primary= FALSE;
if (next_select())
@ -3939,6 +3943,9 @@ void st_select_lex::set_explain_type()
}
}
if (on_the_fly && !is_primary && have_merged_subqueries)
is_primary= TRUE;
SELECT_LEX *first= master_unit()->first_select();
/* drop UNCACHEABLE_EXPLAIN, because it is for internal usage only */
uint8 is_uncacheable= (uncacheable & ~UNCACHEABLE_EXPLAIN);
@ -3991,10 +3998,15 @@ void st_select_lex::set_explain_type()
else
{
type= is_uncacheable ? "UNCACHEABLE UNION": "UNION";
if (this == master_unit()->fake_select_lex)
type= "UNION RESULT";
}
}
}
options|= SELECT_DESCRIBE;
if (!on_the_fly)
options|= SELECT_DESCRIBE;
}
@ -4141,6 +4153,116 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor)
}
int print_explain_message_line(select_result_sink *result,
SELECT_LEX *select_lex,
bool on_the_fly,
uint8 options,
const char *message);
int st_select_lex::print_explain(select_result_sink *output,
uint8 explain_flags,
bool *printed_anything)
{
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:
*/
*printed_anything= TRUE;
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->print_explain(output, explain_flags, TRUE, FALSE, FALSE,
FALSE, cause);
}
else
{
res= join->print_explain(output, explain_flags, TRUE,
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))
{
if ((res= unit->print_explain(output, explain_flags, printed_anything)))
goto err;
}
}
}
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";
}
res= print_explain_message_line(output, this, TRUE /* on_the_fly */,
0, msg);
}
err:
return res;
}
int st_select_lex_unit::print_explain(select_result_sink *output,
uint8 explain_flags, bool *printed_anything)
{
int res= 0;
SELECT_LEX *first= first_select();
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";
res= print_explain_message_line(output, first, TRUE /* on_the_fly */,
0, msg);
return 0;
}
for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
{
if ((res= sl->print_explain(output, explain_flags, printed_anything)))
break;
}
/* 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;
}
/**
A routine used by the parser to decide whether we are specifying a full
partitioning or if only partitions to add or to split.

View file

@ -193,6 +193,7 @@ enum enum_sql_command {
SQLCOM_SHOW_RELAYLOG_EVENTS,
SQLCOM_SHOW_USER_STATS, SQLCOM_SHOW_TABLE_STATS, SQLCOM_SHOW_INDEX_STATS,
SQLCOM_SHOW_CLIENT_STATS,
SQLCOM_SHOW_EXPLAIN,
/*
When a command is added here, be sure it's also added in mysqld.cc
@ -355,6 +356,8 @@ typedef uchar index_clause_map;
#define INDEX_HINT_MASK_ALL (INDEX_HINT_MASK_JOIN | INDEX_HINT_MASK_GROUP | \
INDEX_HINT_MASK_ORDER)
class select_result_sink;
/* Single element of an USE/FORCE/IGNORE INDEX list specified as a SQL hint */
class Index_hint : public Sql_alloc
{
@ -715,6 +718,8 @@ public:
friend int subselect_union_engine::exec();
List<Item> *get_unit_column_types();
int print_explain(select_result_sink *output, uint8 explain_flags,
bool *printed_anything);
};
typedef class st_select_lex_unit SELECT_LEX_UNIT;
@ -776,6 +781,12 @@ public:
*/
List<Item_in_subselect> sj_subselects;
/*
Needed to correctly generate 'PRIMARY' or 'SIMPLE' for select_type column
of EXPLAIN
*/
bool have_merged_subqueries;
List<TABLE_LIST> leaf_tables;
List<TABLE_LIST> leaf_tables_exec;
List<TABLE_LIST> leaf_tables_prep;
@ -999,7 +1010,7 @@ public:
bool is_part_of_union() { return master_unit()->is_union(); }
bool optimize_unflattened_subqueries(bool const_only);
/* Set the EXPLAIN type for this subquery. */
void set_explain_type();
void set_explain_type(bool on_the_fly);
bool handle_derived(LEX *lex, uint phases);
void append_table_to_list(TABLE_LIST *TABLE_LIST::*link, TABLE_LIST *table);
bool get_free_table_map(table_map *map, uint *tablenr);
@ -1023,8 +1034,10 @@ public:
bool save_leaf_tables(THD *thd);
bool save_prep_leaf_tables(THD *thd);
bool is_merged_child_of(st_select_lex *ancestor);
bool is_merged_child_of(st_select_lex *ancestor);
int print_explain(select_result_sink *output, uint8 explain_flags,
bool *printed_anything);
/*
For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags:
- Non-aggregated fields are used in this select.
@ -2346,7 +2359,7 @@ 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'*/
sql_exchange *exchange;
select_result *result;
Item *default_value, *on_update_value;

View file

@ -335,6 +335,7 @@ void init_update_queries(void)
sql_command_flags[SQLCOM_SHOW_ENGINE_STATUS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_ENGINE_MUTEX]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_ENGINE_LOGS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_EXPLAIN]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_PROCESSLIST]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_GRANTS]= CF_STATUS_COMMAND;
sql_command_flags[SQLCOM_SHOW_CREATE_DB]= CF_STATUS_COMMAND;
@ -2141,6 +2142,33 @@ 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_message(ER_SET_CONSTANTS_ONLY, ER(ER_SET_CONSTANTS_ONLY),
MYF(0));
goto error;
}
Item **it= lex->value_list.head_ref();
if (!(*it)->basic_const_item() ||
(!(*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:
@ -6552,6 +6580,35 @@ void add_join_natural(TABLE_LIST *a, TABLE_LIST *b, List<String> *using_fields,
}
/**
Find a thread by id and return it, locking it LOCK_thd_data
@param id Identifier of the thread we're looking for
@return NULL - not found
pointer - thread found, and its LOCK_thd_data is locked.
*/
THD *find_thread_by_id(ulong id)
{
THD *tmp;
mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
I_List_iterator<THD> it(threads);
while ((tmp=it++))
{
if (tmp->command == COM_DAEMON)
continue;
if (tmp->thread_id == id)
{
mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
break;
}
}
mysql_mutex_unlock(&LOCK_thread_count);
return tmp;
}
/**
kill on thread.
@ -6570,20 +6627,7 @@ uint kill_one_thread(THD *thd, ulong id, killed_state kill_signal)
DBUG_ENTER("kill_one_thread");
DBUG_PRINT("enter", ("id: %lu signal: %u", id, (uint) kill_signal));
mysql_mutex_lock(&LOCK_thread_count); // For unlink from list
I_List_iterator<THD> it(threads);
while ((tmp=it++))
{
if (tmp->command == COM_DAEMON)
continue;
if (tmp->thread_id == id)
{
mysql_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
break;
}
}
mysql_mutex_unlock(&LOCK_thread_count);
if (tmp)
if ((tmp= find_thread_by_id(id)))
{
/*
If we're SUPER, we can KILL anything, including system-threads.

View file

@ -2134,6 +2134,7 @@ static bool check_prepared_statement(Prepared_statement *stmt)
Note that we don't need to have cases in this list if they are
marked with CF_STATUS_COMMAND in sql_command_flags
*/
case SQLCOM_SHOW_EXPLAIN:
case SQLCOM_DROP_TABLE:
case SQLCOM_RENAME_TABLE:
case SQLCOM_ALTER_TABLE:

View file

@ -331,6 +331,7 @@ enum enum_yes_no_unknown
External variables
*/
/* sql_yacc.cc */
#ifndef DBUG_OFF
extern void turn_parser_debug_on();

File diff suppressed because it is too large Load diff

View file

@ -101,6 +101,13 @@ typedef struct st_table_ref
uchar *key_buff; ///< value to look for with key
uchar *key_buff2; ///< key_buff+key_length
store_key **key_copy; //
/*
Bitmap of key parts which refer to constants. key_copy only has copiers for
non-const key parts.
*/
key_part_map const_ref_part_map;
Item **items; ///< val()'s for each keypart
/*
Array of pointers to trigger variables. Some/all of the pointers may be
@ -897,7 +904,6 @@ protected:
public:
JOIN_TAB *join_tab, **best_ref;
/*
For "Using temporary+Using filesort" queries, JOIN::join_tab can point to
either:
@ -910,6 +916,15 @@ public:
JOIN_TAB *table_access_tabs;
uint top_table_access_tabs_count;
/*
Saved join_tab for pre_sorting. create_sort_index() will save here..
*/
JOIN_TAB *pre_sort_join_tab;
uint pre_sort_index;
Item *pre_sort_idx_pushed_cond;
void clean_pre_sort_join_tab();
JOIN_TAB **map2table; ///< mapping between table indexes and JOIN_TABs
JOIN_TAB *join_tab_save; ///< saved join_tab for subquery reexecution
@ -1175,9 +1190,15 @@ public:
const char *zero_result_cause; ///< not 0 if exec must return zero result
bool union_part; ///< this subselect is part of union
enum join_optimization_state { NOT_OPTIMIZED=0,
OPTIMIZATION_IN_PROGRESS=1,
OPTIMIZATION_DONE=2};
bool optimized; ///< flag to avoid double optimization in EXPLAIN
bool initialized; ///< flag to avoid double init_execution calls
enum { QEP_NOT_PRESENT_YET, QEP_AVAILABLE, QEP_DELETED} have_query_plan;
/*
Additional WHERE and HAVING predicates to be considered for IN=>EXISTS
subquery transformation of a JOIN object.
@ -1259,6 +1280,7 @@ public:
ref_pointer_array_size= 0;
zero_result_cause= 0;
optimized= 0;
have_query_plan= QEP_NOT_PRESENT_YET;
initialized= 0;
cond_equal= 0;
having_equal= 0;
@ -1279,6 +1301,8 @@ public:
outer_ref_cond= pseudo_bits_cond= NULL;
in_to_exists_where= NULL;
in_to_exists_having= NULL;
pre_sort_join_tab= NULL;
}
int prepare(Item ***rref_pointer_array, TABLE_LIST *tables, uint wind_num,
@ -1287,9 +1311,11 @@ public:
SELECT_LEX_UNIT *unit);
bool prepare_stage2();
int optimize();
int optimize_inner();
int reinit();
int init_execution();
void exec();
void exec_inner();
int destroy();
void restore_tmp();
bool alloc_func_list();
@ -1403,6 +1429,11 @@ 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);
private:
/**
TRUE if the query contains an aggregate function but has no GROUP
@ -1754,6 +1785,9 @@ inline bool optimizer_flag(THD *thd, uint flag)
return (thd->variables.optimizer_switch & flag);
}
int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly,
SELECT_LEX *select_lex, uint8 select_options);
uint get_index_for_order(ORDER *order, TABLE *table, SQL_SELECT *select,
ha_rows limit, bool *need_sort, bool *reverse);
ORDER *simple_remove_const(ORDER *order, COND *where);

View file

@ -2003,6 +2003,166 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose)
DBUG_VOID_RETURN;
}
/*
Produce EXPLAIN data.
This function is APC-scheduled to be run in the context of the thread that
we're producing EXPLAIN for.
*/
void Show_explain_request::call_in_target_thread()
{
Query_arena backup_arena;
bool printed_anything= FALSE;
/*
Change the arena because JOIN::print_explain and co. are going to allocate
items. Let them allocate them on our arena.
*/
target_thd->set_n_backup_active_arena((Query_arena*)request_thd,
&backup_arena);
query_str.copy(target_thd->query(),
target_thd->query_length(),
target_thd->query_charset());
if (target_thd->lex->unit.print_explain(explain_buf, 0 /* explain flags*/,
&printed_anything))
{
failed_to_produce= TRUE;
}
if (!printed_anything)
failed_to_produce= TRUE;
target_thd->restore_active_arena((Query_arena*)request_thd, &backup_arena);
}
int select_result_explain_buffer::send_data(List<Item> &items)
{
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;
}
/*
Store the SHOW EXPLAIN output in the temporary table.
*/
int fill_show_explain(THD *thd, TABLE_LIST *table, COND *cond)
{
const char *calling_user;
THD *tmp;
my_thread_id thread_id;
DBUG_ENTER("fill_show_explain");
DBUG_ASSERT(cond==NULL);
thread_id= thd->lex->value_list.head()->val_int();
calling_user= (thd->security_ctx->master_access & PROCESS_ACL) ? NullS :
thd->security_ctx->priv_user;
if ((tmp= find_thread_by_id(thread_id)))
{
Security_context *tmp_sctx= tmp->security_ctx;
/*
If calling_user==NULL, calling thread has SUPER or PROCESS
privilege, and so can do SHOW EXPLAIN on any user.
if calling_user!=NULL, he's only allowed to view SHOW EXPLAIN on
his own threads.
*/
if (calling_user && (!tmp_sctx->user || strcmp(calling_user,
tmp_sctx->user)))
{
my_error(ER_SPECIFIC_ACCESS_DENIED_ERROR, MYF(0), "PROCESS");
mysql_mutex_unlock(&tmp->LOCK_thd_data);
DBUG_RETURN(1);
}
if (tmp == thd)
{
mysql_mutex_unlock(&tmp->LOCK_thd_data);
my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0));
DBUG_RETURN(1);
}
bool bres;
/*
Ok we've found the thread of interest and it won't go away because
we're holding its LOCK_thd data. Post it a SHOW EXPLAIN request.
*/
bool timed_out;
int timeout_sec= 30;
Show_explain_request explain_req;
select_result_explain_buffer *explain_buf;
explain_buf= new select_result_explain_buffer(thd, table->table);
explain_req.explain_buf= explain_buf;
explain_req.target_thd= tmp;
explain_req.request_thd= thd;
explain_req.failed_to_produce= FALSE;
/* Ok, we have a lock on target->LOCK_thd_data, can call: */
bres= tmp->apc_target.make_apc_call(thd, &explain_req, timeout_sec, &timed_out);
if (bres || explain_req.failed_to_produce)
{
if (thd->killed)
thd->send_kill_message();
else if (timed_out)
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
else
my_error(ER_TARGET_NOT_EXPLAINABLE, MYF(0));
bres= TRUE;
}
else
{
/*
Push the query string as a warning. The query may be in a different
charset than the charset that's used for error messages, so, convert it
if needed.
*/
CHARSET_INFO *fromcs= explain_req.query_str.charset();
CHARSET_INFO *tocs= error_message_charset_info;
char *warning_text;
if (!my_charset_same(fromcs, tocs))
{
uint conv_length= 1 + tocs->mbmaxlen * explain_req.query_str.length() /
fromcs->mbminlen;
uint dummy_errors;
char *to, *p;
if (!(to= (char*)thd->alloc(conv_length + 1)))
DBUG_RETURN(1);
p= to;
p+= copy_and_convert(to, conv_length, tocs,
explain_req.query_str.c_ptr(),
explain_req.query_str.length(), fromcs,
&dummy_errors);
*p= 0;
warning_text= to;
}
else
warning_text= explain_req.query_str.c_ptr_safe();
push_warning(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_YES, warning_text);
}
DBUG_RETURN(bres);
}
else
{
my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id);
DBUG_RETURN(1);
}
}
int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond)
{
TABLE *table= tables->table;
@ -8317,6 +8477,32 @@ ST_FIELD_INFO keycache_fields_info[]=
};
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", 15, 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*MAX_KEY, 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}
};
/*
Description of ST_FIELD_INFO in table.h
@ -8348,6 +8534,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,

View file

@ -19,6 +19,7 @@
#include "sql_list.h" /* List */
#include "handler.h" /* enum_schema_tables */
#include "table.h" /* enum_schema_table_state */
#include "my_apc.h"
/* Forward declarations */
class JOIN;
@ -131,5 +132,30 @@ enum enum_schema_tables get_schema_table_idx(ST_SCHEMA_TABLE *schema_table);
/* These functions were under INNODB_COMPATIBILITY_HOOKS */
int get_quote_char_for_identifier(THD *thd, const char *name, uint length);
THD *find_thread_by_id(ulong id);
class select_result_explain_buffer;
/*
SHOW EXPLAIN request object.
*/
class Show_explain_request : public Apc_target::Apc_call
{
public:
THD *target_thd; /* thd that we're running SHOW EXPLAIN for */
THD *request_thd; /* thd that run SHOW EXPLAIN command */
/* If true, there was some error when producing EXPLAIN output. */
bool failed_to_produce;
/* SHOW EXPLAIN will be stored here */
select_result_explain_buffer *explain_buf;
/* Query that we've got SHOW EXPLAIN for */
String query_str;
/* Overloaded virtual function */
void call_in_target_thread();
};
#endif /* SQL_SHOW_H */

View file

@ -722,6 +722,8 @@ bool st_select_lex_unit::exec()
}
}
DBUG_EXECUTE_IF("show_explain_probe_union_read",
dbug_serve_apcs(thd, 1););
/* Send result to 'result' */
saved_error= TRUE;
{

View file

@ -11639,6 +11639,14 @@ show_param:
Lex->spname= $3;
Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
}
| describe_command FOR_SYM expr
{
THD *thd= YYTHD;
Lex->sql_command= SQLCOM_SHOW_EXPLAIN;
if (prepare_schema_table(thd, Lex, 0, SCH_EXPLAIN))
MYSQL_YYABORT;
add_value_to_list(thd, $3);
}
;
show_engine_param:

View file

@ -1111,7 +1111,12 @@ public:
See TABLE_LIST::process_index_hints().
*/
bool force_index_group;
bool distinct,const_table,no_rows, used_for_duplicate_elimination;
/*
TRUE<=> this table was created with create_tmp_table(... distinct=TRUE..)
call
*/
bool distinct;
bool const_table,no_rows, used_for_duplicate_elimination;
/**
If set, the optimizer has found that row retrieval should access index

View file

@ -0,0 +1,3 @@
MY_ADD_TESTS(my_apc LINK_LIBRARIES mysys EXT cc)

227
unittest/sql/my_apc-t.cc Normal file
View file

@ -0,0 +1,227 @@
/*
Copyright (c) 2012, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
/*
This file does standalone APC system tests.
*/
#include <stdio.h>
#include <my_global.h>
#include <my_pthread.h>
#include <my_sys.h>
#include <tap.h>
/*
A fake THD with enter_cond/exit_cond and some other members.
*/
class THD
{
mysql_mutex_t* thd_mutex;
public:
bool killed;
THD() : killed(FALSE) {}
inline const char* enter_cond(mysql_cond_t *cond, mysql_mutex_t* mutex,
const char* msg)
{
mysql_mutex_assert_owner(mutex);
thd_mutex= mutex;
return NULL;
}
inline void exit_cond(const char* old_msg)
{
mysql_mutex_unlock(thd_mutex);
}
};
#include "../sql/my_apc.h"
#define MY_APC_STANDALONE 1
#include "../sql/my_apc.cc"
volatile bool started= FALSE;
volatile bool service_should_exit= FALSE;
volatile bool requestors_should_exit=FALSE;
/* Counters for APC calls */
int apcs_served= 0;
int apcs_missed=0;
int apcs_timed_out=0;
mysql_mutex_t apc_counters_mutex;
inline void increment_counter(int *var)
{
mysql_mutex_lock(&apc_counters_mutex);
*var= *var+1;
mysql_mutex_unlock(&apc_counters_mutex);
}
volatile bool have_errors= false;
Apc_target apc_target;
mysql_mutex_t target_mutex;
int int_rand(int size)
{
return (int) (0.5 + ((double)rand() / RAND_MAX) * size);
}
/*
APC target thread (the one that will serve the APC requests). We will have
one target.
*/
void *test_apc_service_thread(void *ptr)
{
my_thread_init();
mysql_mutex_init(0, &target_mutex, MY_MUTEX_INIT_FAST);
apc_target.init(&target_mutex);
apc_target.enable();
started= TRUE;
diag("test_apc_service_thread started");
while (!service_should_exit)
{
//apc_target.disable();
my_sleep(10000);
//apc_target.enable();
for (int i = 0; i < 10 && !service_should_exit; i++)
{
apc_target.process_apc_requests();
my_sleep(int_rand(30));
}
}
apc_target.disable();
apc_target.destroy();
mysql_mutex_destroy(&target_mutex);
my_thread_end();
pthread_exit(0);
return NULL;
}
/*
One APC request (to write 'value' into *where_to)
*/
class Apc_order : public Apc_target::Apc_call
{
public:
int value; // The value
int *where_to; // Where to write it
Apc_order(int a, int *b) : value(a), where_to(b) {}
void call_in_target_thread()
{
my_sleep(int_rand(1000));
*where_to = value;
increment_counter(&apcs_served);
}
};
/*
APC requestor thread. It makes APC requests, and checks if they were actually
executed.
*/
void *test_apc_requestor_thread(void *ptr)
{
my_thread_init();
diag("test_apc_requestor_thread started");
THD my_thd;
while (!requestors_should_exit)
{
int dst_value= 0;
int src_value= int_rand(4*1000*100);
/* Create an APC to do "dst_value= src_value" assignment */
Apc_order apc_order(src_value, &dst_value);
bool timed_out;
mysql_mutex_lock(&target_mutex);
bool res= apc_target.make_apc_call(&my_thd, &apc_order, 60, &timed_out);
if (res)
{
if (timed_out)
increment_counter(&apcs_timed_out);
else
increment_counter(&apcs_missed);
if (dst_value != 0)
{
diag("APC was done even though return value says it wasnt!");
have_errors= true;
}
}
else
{
if (dst_value != src_value)
{
diag("APC was not done even though return value says it was!");
have_errors= true;
}
}
//my_sleep(300);
}
diag("test_apc_requestor_thread exiting");
my_thread_end();
return NULL;
}
/* Number of APC requestor threads */
const int N_THREADS=23;
int main(int args, char **argv)
{
pthread_t service_thr;
pthread_t request_thr[N_THREADS];
int i;
my_thread_global_init();
mysql_mutex_init(0, &apc_counters_mutex, MY_MUTEX_INIT_FAST);
plan(1);
diag("Testing APC delivery and execution");
pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL);
while (!started)
my_sleep(1000);
for (i = 0; i < N_THREADS; i++)
pthread_create(&request_thr[i], NULL, test_apc_requestor_thread, (void*)NULL);
for (i = 0; i < 15; i++)
{
my_sleep(500*1000);
diag("%d APCs served %d missed", apcs_served, apcs_missed);
}
diag("Shutting down requestors");
requestors_should_exit= TRUE;
for (i = 0; i < N_THREADS; i++)
pthread_join(request_thr[i], NULL);
diag("Shutting down service");
service_should_exit= TRUE;
pthread_join(service_thr, NULL);
mysql_mutex_destroy(&apc_counters_mutex);
diag("Done");
my_thread_end();
my_thread_global_end();
ok1(!have_errors);
return exit_status();
}