summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Petrunya <psergey@askmonty.org>2012-01-03 23:16:16 +0100
committerSergey Petrunya <psergey@askmonty.org>2012-01-03 23:16:16 +0100
commitb01348879d066c3207ac70aa630ed021a2552920 (patch)
tree44b64f886288ac8b8b5c49ae9a94fe574ddc7c5f
parent7714496dc1c72d01fd214cb7737ca4216a982e0f (diff)
parentca020dfa9e8668ce52eaff92c157097bba671ec1 (diff)
downloadmariadb-git-b01348879d066c3207ac70aa630ed021a2552920.tar.gz
MWL#182: Explain running statements: merge with 5.3-main (needs fixing)
-rw-r--r--client/mysqltest.cc25
-rw-r--r--libmysqld/CMakeLists.txt1
-rw-r--r--libmysqld/Makefile.am2
-rw-r--r--mysql-test/r/show_explain.result128
-rw-r--r--mysql-test/t/show_explain.test198
-rw-r--r--sql/CMakeLists.txt1
-rw-r--r--sql/Makefile.am7
-rw-r--r--sql/item_func.cc2
-rw-r--r--sql/my_apc.cc365
-rw-r--r--sql/my_apc.h102
-rw-r--r--sql/mysql_priv.h6
-rw-r--r--sql/mysqld.cc1
-rw-r--r--sql/opt_subselect.cc5
-rw-r--r--sql/protocol.h19
-rw-r--r--sql/sp_head.cc1
-rw-r--r--sql/sql_class.cc126
-rw-r--r--sql/sql_class.h96
-rw-r--r--sql/sql_lex.cc84
-rw-r--r--sql/sql_lex.h15
-rw-r--r--sql/sql_parse.cc27
-rw-r--r--sql/sql_prepare.cc1
-rw-r--r--sql/sql_select.cc324
-rw-r--r--sql/sql_select.h11
-rw-r--r--sql/sql_show.cc91
-rw-r--r--sql/sql_yacc.yy6
25 files changed, 1517 insertions, 127 deletions
diff --git a/client/mysqltest.cc b/client/mysqltest.cc
index 6d54fa8ad7b..600a7d02fa2 100644
--- a/client/mysqltest.cc
+++ b/client/mysqltest.cc
@@ -77,6 +77,8 @@
#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
@@ -291,7 +293,8 @@ enum enum_commands {
Q_ERROR,
Q_SEND, Q_REAP,
Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN,
- Q_PING, Q_EVAL,
+ Q_PING, Q_EVAL,
+ Q_EVALP,
Q_RPL_PROBE, Q_ENABLE_RPL_PARSE,
Q_DISABLE_RPL_PARSE, Q_EVAL_RESULT,
Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG,
@@ -356,6 +359,7 @@ const char *command_names[]=
"replace_column",
"ping",
"eval",
+ "evalp",
"rpl_probe",
"enable_rpl_parse",
"disable_rpl_parse",
@@ -7575,7 +7579,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);
@@ -7607,10 +7612,20 @@ 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
@@ -8471,6 +8486,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)
@@ -8498,6 +8514,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);
diff --git a/libmysqld/CMakeLists.txt b/libmysqld/CMakeLists.txt
index 12b33e6092b..1c1ed2cabd8 100644
--- a/libmysqld/CMakeLists.txt
+++ b/libmysqld/CMakeLists.txt
@@ -146,6 +146,7 @@ SET(LIBMYSQLD_SOURCES libmysqld.c emb_qcache.cc 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}
${LIB_SOURCES})
diff --git a/libmysqld/Makefile.am b/libmysqld/Makefile.am
index cffde1c7db1..412fc1e2e9f 100644
--- a/libmysqld/Makefile.am
+++ b/libmysqld/Makefile.am
@@ -83,7 +83,7 @@ sqlsources = derror.cc field.cc field_conv.cc strfunc.cc filesort.cc \
rpl_injector.cc my_user.c partition_info.cc \
sql_servers.cc event_parse_data.cc opt_table_elimination.cc \
multi_range_read.cc opt_index_cond_pushdown.cc \
- sql_expression_cache.cc
+ sql_expression_cache.cc my_apc.cc
# automake misses these
sql_yacc.cc sql_yacc.h: $(top_srcdir)/sql/sql_yacc.yy
diff --git a/mysql-test/r/show_explain.result b/mysql-test/r/show_explain.result
new file mode 100644
index 00000000000..b8b732f9607
--- /dev/null
+++ b/mysql-test/r/show_explain.result
@@ -0,0 +1,128 @@
+drop table if exists t0, t1;
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (a int);
+insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C;
+alter table t1 add b int, add c int, add filler char(32);
+update t1 set b=a, c=a, filler='fooo';
+alter table t1 add key(a), add key(b);
+show explain for 2*1000*1000*1000;
+ERROR HY000: Unknown thread id: 2000000000
+show explain for (select max(a) from t0);
+ERROR 42000: This version of MySQL doesn't yet support 'Usage of subqueries or stored function calls as part of this statement'
+show explain for $thr2;
+ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command
+show explain for $thr1;
+ERROR HY000: Error when executing command SHOW EXPLAIN: Target is not running EXPLAINable command
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+select count(*) from t1 where a < 100000;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 index a a 5 NULL 1000 Using where; Using index
+count(*)
+1000
+select max(c) from t1 where a < 10;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range a a 5 NULL 10 Using where
+max(c)
+9
+# We can catch EXPLAIN, too.
+set @show_expl_tmp= @@optimizer_switch;
+set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on';
+explain select max(c) from t1 where a < 10;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan
+id select_type table type possible_keys key key_len ref rows Extra
+1 SIMPLE t1 range a a 5 NULL 10 Using index condition; Rowid-ordered scan
+set optimizer_switch= @show_expl_tmp;
+# UNION, first branch
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+explain select a from t0 A union select a+1 from t0 B;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10
+2 UNION B ALL NULL NULL NULL NULL 10
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10
+2 UNION B ALL NULL NULL NULL NULL 10
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+# UNION, second branch
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+explain select a from t0 A union select a+1 from t0 B;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10
+2 UNION B ALL NULL NULL NULL NULL 10
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10
+2 UNION B ALL NULL NULL NULL NULL 10
+NULL UNION RESULT <union1,2> ALL NULL NULL NULL NULL NULL
+# Uncorrelated subquery, select
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+select a, (select max(a) from t0 B) from t0 A where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10 Using where
+2 SUBQUERY B ALL NULL NULL NULL NULL 10
+a (select max(a) from t0 B)
+0 9
+# Uncorrelated subquery, explain
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+explain select a, (select max(a) from t0 B) from t0 A where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10 Using where
+2 SUBQUERY B ALL NULL NULL NULL NULL 10
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY A ALL NULL NULL NULL NULL 10 Using where
+2 SUBQUERY B ALL NULL NULL NULL NULL 10
+# correlated subquery, select
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY a ALL NULL NULL NULL NULL 10 Using where
+2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where
+a (select max(a) from t0 b where b.a+a.a<10)
+0 9
+# correlated subquery, explain
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY a ALL NULL NULL NULL NULL 10 Using where
+2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where
+a (select max(a) from t0 b where b.a+a.a<10)
+0 9
+# correlated subquery, select, while inside the subquery
+set @show_explain_probe_select_id=2;
+set debug='d,show_explain_probe_1';
+select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY a ALL NULL NULL NULL NULL 10 Using where
+2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where
+a (select max(a) from t0 b where b.a+a.a<10)
+0 9
+# correlated subquery, explain, while inside the subquery
+set @show_explain_probe_select_id=2;
+set debug='d,show_explain_probe_1';
+select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+show explain for $thr2;
+id select_type table type possible_keys key key_len ref rows Extra
+1 PRIMARY a ALL NULL NULL NULL NULL 10 Using where
+2 DEPENDENT SUBQUERY b ALL NULL NULL NULL NULL 10 Using where
+a (select max(a) from t0 b where b.a+a.a<10)
+0 9
+drop table t0,t1;
diff --git a/mysql-test/t/show_explain.test b/mysql-test/t/show_explain.test
new file mode 100644
index 00000000000..5ed78be1ea4
--- /dev/null
+++ b/mysql-test/t/show_explain.test
@@ -0,0 +1,198 @@
+#
+# Tests for SHOW EXPLAIN FOR functionality
+#
+--source include/have_debug.inc
+
+--disable_warnings
+drop table if exists t0, t1;
+--enable_warnings
+
+#
+# Testcases in this file do not work with embedded server. The reason for this
+# is that we use the following commands for synchronization:
+#
+# set @show_explain_probe_select_id=1;
+# set debug='d,show_explain_probe_1';
+# send select count(*) from t1 where a < 100000;
+#
+# When ran with mysqltest_embedded, this translates into:
+#
+# Thread1> DBUG_PUSH("d,show_explain_probe_1");
+# Thread1> create another thread for doing "send ... reap"
+# Thread2> mysql_parse("select count(*) from t1 where a < 100000");
+#
+# That is, "select count(*) ..." is ran in a thread for which DBUG_PUSH(...)
+# has not been called. As a result, show_explain_probe_1 does not fire, and
+# "select count(*) ..." does not wait till its SHOW EXPLAIN command, and the
+# test fails.
+#
+-- source include/not_embedded.inc
+
+
+create table t0 (a int);
+insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
+create table t1 (a int);
+insert into t1 select A.a + 10*B.a + 100*C.a from t0 A, t0 B, t0 C;
+alter table t1 add b int, add c int, add filler char(32);
+update t1 set b=a, c=a, filler='fooo';
+alter table t1 add key(a), add key(b);
+
+#
+# Try killing a non-existent thread
+#
+--error ER_NO_SUCH_THREAD
+show explain for 2*1000*1000*1000;
+
+--error ER_NOT_SUPPORTED_YET
+show explain for (select max(a) from t0);
+
+#
+# Setup two threads and their ids
+#
+let $thr1=`select connection_id()`;
+connect (con1, localhost, root,,);
+connection con1;
+let $thr2=`select connection_id()`;
+connection default;
+
+# SHOW EXPLAIN FOR <idle thread>
+--error ER_ERROR_WHEN_EXECUTING_COMMAND
+evalp show explain for $thr2;
+
+# SHOW EXPLAIN FOR <ourselves>
+--error ER_ERROR_WHEN_EXECUTING_COMMAND
+evalp show explain for $thr1;
+
+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='d,show_explain_probe_1';
+send select count(*) from t1 where a < 100000;
+
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+send select max(c) from t1 where a < 10;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+--echo # We can catch EXPLAIN, too.
+set @show_expl_tmp= @@optimizer_switch;
+set optimizer_switch='index_condition_pushdown=on,mrr=on,mrr_sort_keys=on';
+send explain select max(c) from t1 where a < 10;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+set optimizer_switch= @show_expl_tmp;
+
+
+--echo # UNION, first branch
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send explain select a from t0 A union select a+1 from t0 B;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+--echo # UNION, second branch
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send explain select a from t0 A union select a+1 from t0 B;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+--echo # Uncorrelated subquery, select
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send select a, (select max(a) from t0 B) from t0 A where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+--echo # Uncorrelated subquery, explain
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send explain select a, (select max(a) from t0 B) from t0 A where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+--echo # correlated subquery, select
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+--echo # correlated subquery, explain
+set @show_explain_probe_select_id=1;
+set debug='d,show_explain_probe_1';
+send select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+--echo # correlated subquery, select, while inside the subquery
+set @show_explain_probe_select_id=2; # <---
+set debug='d,show_explain_probe_1';
+send select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+--echo # correlated subquery, explain, while inside the subquery
+set @show_explain_probe_select_id=2;
+set debug='d,show_explain_probe_1';
+send select a, (select max(a) from t0 b where b.a+a.a<10) from t0 a where a<1;
+connection default;
+--source include/wait_condition.inc
+evalp show explain for $thr2;
+connection con1;
+reap;
+
+
+# TODO: explain in the parent subuqery when the un-correlated child has been
+# run (and have done irreversible cleanups)
+
+# TODO: hit JOIN::optimize for non-select commands: UPDATE/DELETE, SET.
+
+
+## TODO: Test this: multiple SHOW EXPLAIN calls in course of running of one select
+##
+## TODO: Test this: have several SHOW EXPLAIN requests be queued up for a
+## thread and served together.
+
+drop table t0,t1;
diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt
index 4354bfc60fb..a8f58731dc5 100644
--- a/sql/CMakeLists.txt
+++ b/sql/CMakeLists.txt
@@ -86,6 +86,7 @@ SET (SQL_SOURCE
opt_index_cond_pushdown.cc
create_options.cc
sql_expression_cache.cc
+ my_apc.cc my_apc.h
${CMAKE_BINARY_DIR}/sql/sql_yacc.cc
${CMAKE_BINARY_DIR}/sql/sql_yacc.h
${CMAKE_BINARY_DIR}/include/mysqld_error.h
diff --git a/sql/Makefile.am b/sql/Makefile.am
index 291f662328b..ae827eaffc1 100644
--- a/sql/Makefile.am
+++ b/sql/Makefile.am
@@ -86,7 +86,8 @@ noinst_HEADERS = item.h item_func.h item_sum.h item_cmpfunc.h \
sql_join_cache.h \
create_options.h \
sql_expression_cache.h \
- gcalc_slicescan.h gcalc_tools.h plistsort.c
+ gcalc_slicescan.h gcalc_tools.h plistsort.c \
+ my_apc.h
mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
item.cc item_sum.cc item_buff.cc item_func.cc \
@@ -137,7 +138,9 @@ mysqld_SOURCES = sql_lex.cc sql_handler.cc sql_partition.cc \
sql_servers.cc event_parse_data.cc \
opt_table_elimination.cc create_options.cc \
multi_range_read.cc \
- opt_index_cond_pushdown.cc sql_expression_cache.cc
+ opt_index_cond_pushdown.cc sql_expression_cache.cc \
+ my_apc.cc
+
nodist_mysqld_SOURCES = mini_client_errors.c pack.c client.c my_time.c my_user.c client_plugin.c
diff --git a/sql/item_func.cc b/sql/item_func.cc
index e5225de62f9..868f37e14f5 100644
--- a/sql/item_func.cc
+++ b/sql/item_func.cc
@@ -3871,7 +3871,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;
diff --git a/sql/my_apc.cc b/sql/my_apc.cc
new file mode 100644
index 00000000000..91559483b1f
--- /dev/null
+++ b/sql/my_apc.cc
@@ -0,0 +1,365 @@
+/*
+ TODO: MP AB Copyright
+*/
+
+
+#ifdef MY_APC_STANDALONE
+
+#include <my_global.h>
+#include <my_pthread.h>
+#include <my_sys.h>
+
+#else
+
+#include "mysql_priv.h"
+
+#endif
+
+//#include "my_apc.h"
+
+/*
+ Standalone testing:
+ g++ -c -DMY_APC_STANDALONE -g -I.. -I../include -o my_apc.o my_apc.cc
+ g++ -L../mysys -L../dbug -L../strings my_apc.o -lmysys -ldbug -lmystrings -lpthread -lrt
+*/
+
+
+void Apc_target::init()
+{
+ // todo: should use my_pthread_... functions instead?
+ DBUG_ASSERT(!enabled);
+ (void)pthread_mutex_init(&LOCK_apc_queue, MY_MUTEX_INIT_SLOW);
+
+#ifndef DBUG_OFF
+ n_calls_processed= 0;
+#endif
+}
+
+
+void Apc_target::destroy()
+{
+ DBUG_ASSERT(!enabled);
+ pthread_mutex_destroy(&LOCK_apc_queue);
+}
+
+
+void Apc_target::enable()
+{
+ pthread_mutex_lock(&LOCK_apc_queue);
+ enabled++;
+ pthread_mutex_unlock(&LOCK_apc_queue);
+}
+
+
+void Apc_target::disable()
+{
+ bool process= FALSE;
+ pthread_mutex_lock(&LOCK_apc_queue);
+ if (!(--enabled))
+ process= TRUE;
+ pthread_mutex_unlock(&LOCK_apc_queue);
+ if (process)
+ process_apc_requests();
+}
+
+void Apc_target::enqueue_request(Call_request *qe)
+{
+ //call_queue_size++;
+ 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;
+ }
+}
+
+void Apc_target::dequeue_request(Call_request *qe)
+{
+ //call_queue_size--;
+ if (apc_calls == qe)
+ {
+ if ((apc_calls= apc_calls->next) == qe)
+ {
+ //DBUG_ASSERT(!call_queue_size);
+ apc_calls= NULL;
+ }
+ }
+
+ qe->prev->next= qe->next;
+ qe->next->prev= qe->prev;
+}
+
+
+/*
+ Make an apc call in another thread. The caller is responsible so
+ that we're not calling to ourselves.
+
+ psergey-todo: Should waits here be KILLable? (it seems one needs
+ to use thd->enter_cond() calls to be killable)
+*/
+
+bool Apc_target::make_apc_call(apc_func_t func, void *func_arg,
+ int timeout_sec, bool *timed_out)
+{
+ bool res= TRUE;
+ *timed_out= FALSE;
+
+ pthread_mutex_lock(&LOCK_apc_queue);
+ if (enabled)
+ {
+ /* Create and post the request */
+ Call_request apc_request;
+ apc_request.func= func;
+ apc_request.func_arg= func_arg;
+ apc_request.done= FALSE;
+ (void)pthread_cond_init(&apc_request.COND_request, NULL);
+ (void)pthread_mutex_init(&apc_request.LOCK_request, MY_MUTEX_INIT_SLOW);
+ pthread_mutex_lock(&apc_request.LOCK_request);
+ enqueue_request(&apc_request);
+ apc_request.what="enqueued by make_apc_call";
+ pthread_mutex_unlock(&LOCK_apc_queue);
+
+ struct timespec abstime;
+ const int timeout= timeout_sec;
+ set_timespec(abstime, timeout);
+
+ int wait_res= 0;
+ /* todo: how about processing other errors here? */
+ while (!apc_request.done && (wait_res != ETIMEDOUT))
+ {
+ wait_res= pthread_cond_timedwait(&apc_request.COND_request,
+ &apc_request.LOCK_request, &abstime);
+ }
+
+ if (!apc_request.done)
+ {
+ /* We timed out */
+ apc_request.done= TRUE;
+ *timed_out= TRUE;
+ pthread_mutex_unlock(&apc_request.LOCK_request);
+
+ pthread_mutex_lock(&LOCK_apc_queue);
+ dequeue_request(&apc_request);
+ pthread_mutex_unlock(&LOCK_apc_queue);
+ res= TRUE;
+ }
+ else
+ {
+ /* Request was successfully executed and dequeued by the target thread */
+ pthread_mutex_unlock(&apc_request.LOCK_request);
+ res= FALSE;
+ }
+
+ /* Destroy all APC request data */
+ pthread_mutex_destroy(&apc_request.LOCK_request);
+ pthread_cond_destroy(&apc_request.COND_request);
+ }
+ else
+ {
+ pthread_mutex_unlock(&LOCK_apc_queue);
+ }
+ return res;
+}
+
+
+/*
+ Process all APC requests
+*/
+
+void Apc_target::process_apc_requests()
+{
+ while (1)
+ {
+ Call_request *request;
+
+ pthread_mutex_lock(&LOCK_apc_queue);
+ if (!(request= get_first_in_queue()))
+ {
+ pthread_mutex_unlock(&LOCK_apc_queue);
+ break;
+ }
+
+ request->what="seen by process_apc_requests";
+ pthread_mutex_lock(&request->LOCK_request);
+
+ if (request->done)
+ {
+ /*
+ We can get here when
+ - the requestor thread has been waiting for this request
+ - the wait has timed out
+ - it has set request->done=TRUE
+ - it has released LOCK_request, because its next action
+ will be to remove the request from the queue, however,
+ it could not attempt to lock the queue while holding the lock on
+ request, because that would deadlock with this function
+ (we here first lock the queue and then lock the request)
+ */
+ pthread_mutex_unlock(&request->LOCK_request);
+ pthread_mutex_unlock(&LOCK_apc_queue);
+ fprintf(stderr, "Whoa rare event #1!\n");
+ continue;
+ }
+ /*
+ Remove the request from the queue (we're holding its 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->done= TRUE;
+
+ pthread_mutex_unlock(&LOCK_apc_queue);
+
+ request->func(request->func_arg);
+ request->what="func called by process_apc_requests";
+
+#ifndef DBUG_OFF
+ n_calls_processed++;
+#endif
+
+ pthread_cond_signal(&request->COND_request);
+
+ pthread_mutex_unlock(&request->LOCK_request);
+ }
+}
+
+/*****************************************************************************
+ * Testing
+ *****************************************************************************/
+#ifdef MY_APC_STANDALONE
+
+volatile bool started= FALSE;
+volatile bool service_should_exit= FALSE;
+volatile bool requestors_should_exit=FALSE;
+
+volatile int apcs_served= 0;
+volatile int apcs_missed=0;
+volatile int apcs_timed_out=0;
+
+Apc_target apc_target;
+
+int int_rand(int size)
+{
+ return round (((double)rand() / RAND_MAX) * size);
+}
+
+/* An APC-serving thread */
+void *test_apc_service_thread(void *ptr)
+{
+ my_thread_init();
+ apc_target.init();
+ apc_target.enable();
+ started= TRUE;
+ fprintf(stderr, "# test_apc_service_thread started\n");
+ while (!service_should_exit)
+ {
+ //apc_target.disable();
+ usleep(10000);
+ //apc_target.enable();
+ for (int i = 0; i < 10 && !service_should_exit; i++)
+ {
+ apc_target.process_apc_requests();
+ usleep(int_rand(30));
+ }
+ }
+ apc_target.disable();
+ apc_target.destroy();
+ my_thread_end();
+ pthread_exit(0);
+}
+
+class Apc_order
+{
+public:
+ int value; // The value
+ int *where_to; // Where to write it
+ Apc_order(int a, int *b) : value(a), where_to(b) {}
+};
+
+void test_apc_func(void *arg)
+{
+ Apc_order *order=(Apc_order*)arg;
+ usleep(int_rand(1000));
+ *(order->where_to) = order->value;
+ __sync_fetch_and_add(&apcs_served, 1);
+}
+
+void *test_apc_requestor_thread(void *ptr)
+{
+ my_thread_init();
+ fprintf(stderr, "# test_apc_requestor_thread started\n");
+ while (!requestors_should_exit)
+ {
+ int dst_value= 0;
+ int src_value= int_rand(4*1000*100);
+ /* Create APC to do dst_value= src_value */
+ Apc_order apc_order(src_value, &dst_value);
+ bool timed_out;
+
+ bool res= apc_target.make_apc_call(test_apc_func, (void*)&apc_order, 60, &timed_out);
+ if (res)
+ {
+ if (timed_out)
+ __sync_fetch_and_add(&apcs_timed_out, 1);
+ else
+ __sync_fetch_and_add(&apcs_missed, 1);
+
+ if (dst_value != 0)
+ fprintf(stderr, "APC was done even though return value says it wasnt!\n");
+ }
+ else
+ {
+ if (dst_value != src_value)
+ fprintf(stderr, "APC was not done even though return value says it was!\n");
+ }
+ //usleep(300);
+ }
+ fprintf(stderr, "# test_apc_requestor_thread exiting\n");
+ my_thread_end();
+}
+
+const int N_THREADS=23;
+int main(int args, char **argv)
+{
+ pthread_t service_thr;
+ pthread_t request_thr[N_THREADS];
+ int i, j;
+ my_thread_global_init();
+
+ pthread_create(&service_thr, NULL, test_apc_service_thread, (void*)NULL);
+ while (!started)
+ usleep(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++)
+ {
+ usleep(500*1000);
+ fprintf(stderr, "# %d APCs served %d missed\n", apcs_served, apcs_missed);
+ }
+ fprintf(stderr, "# Shutting down requestors\n");
+ requestors_should_exit= TRUE;
+ for (i = 0; i < N_THREADS; i++)
+ pthread_join(request_thr[i], NULL);
+
+ fprintf(stderr, "# Shutting down service\n");
+ service_should_exit= TRUE;
+ pthread_join(service_thr, NULL);
+ fprintf(stderr, "# Done.\n");
+ my_thread_end();
+ my_thread_global_end();
+ return 0;
+}
+
+#endif // MY_APC_STANDALONE
+
+
+
diff --git a/sql/my_apc.h b/sql/my_apc.h
new file mode 100644
index 00000000000..3906aa24408
--- /dev/null
+++ b/sql/my_apc.h
@@ -0,0 +1,102 @@
+/*
+ TODO: MP AB Copyright
+*/
+
+/*
+ Design
+ - Mutex-guarded request queue (it belongs to the target), which can be enabled/
+ disabled (when empty).
+
+ - 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.
+*/
+
+/*
+ Target for asynchronous calls.
+*/
+class Apc_target
+{
+public:
+ Apc_target() : enabled(0), apc_calls(NULL) /*, call_queue_size(0)*/ {}
+ ~Apc_target() { DBUG_ASSERT(!enabled && !apc_calls);}
+
+ /*
+ Initialize the target. This must be called before anything else. Right
+ after initialization, the target is disabled.
+ */
+ void init();
+
+ /*
+ Destroy the target. The target must be disabled when this call is made.
+ */
+ void destroy();
+
+ /*
+ Enter into state where this target will be serving APC requests
+ */
+ void enable();
+
+ /*
+ Leave the state where we could serve APC requests (will serve all already
+ enqueued requests)
+ */
+ void disable();
+
+ /*
+ This should be called periodically to serve observation requests.
+ */
+ void process_apc_requests();
+
+ typedef void (*apc_func_t)(void *arg);
+
+ /*
+ Make an APC call: schedule it for execution and wait until the target
+ thread has executed it. This function must not be called from a thread
+ that's different from the target thread.
+
+ @retval FALSE - Ok, the call has been made
+ @retval TRUE - Call wasnt made (either the target is in disabled state or
+ timeout occured)
+ */
+ bool make_apc_call(apc_func_t func, void *func_arg,
+ int timeout_sec, bool *timed_out);
+
+#ifndef DBUG_OFF
+ int n_calls_processed;
+ //int call_queue_size;
+#endif
+private:
+ class Call_request;
+ int enabled;
+
+ Call_request *apc_calls;
+ pthread_mutex_t LOCK_apc_queue;
+
+
+ class Call_request
+ {
+ public:
+ apc_func_t func;
+ void *func_arg;
+ bool done;
+
+ pthread_mutex_t LOCK_request;
+ pthread_cond_t COND_request;
+
+ Call_request *next;
+ Call_request *prev;
+
+ const char *what;
+ };
+
+ void enqueue_request(Call_request *qe);
+ void dequeue_request(Call_request *qe);
+ Call_request *get_first_in_queue()
+ {
+ return apc_calls;
+ }
+};
+
+///////////////////////////////////////////////////////////////////////
+
diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h
index fbd0d8549f7..4e6b7739fc2 100644
--- a/sql/mysql_priv.h
+++ b/sql/mysql_priv.h
@@ -812,6 +812,7 @@ typedef my_bool (*qc_engine_callback)(THD *thd, char *table_key,
ulonglong *engine_data);
#include "sql_string.h"
#include "my_decimal.h"
+#include "my_apc.h"
/*
to unify the code that differs only in the argument passed to the
@@ -1561,6 +1562,7 @@ bool mysqld_show_create(THD *thd, TABLE_LIST *table_list);
bool mysqld_show_create_db(THD *thd, char *dbname, HA_CREATE_INFO *create);
void mysqld_list_processes(THD *thd,const char *user,bool verbose);
+void mysqld_show_explain(THD *thd, ulong thread_id);
int mysqld_show_status(THD *thd);
int mysqld_show_variables(THD *thd,const char *wild);
bool mysqld_show_storage_engines(THD *thd);
@@ -2420,6 +2422,10 @@ int rea_create_table(THD *thd, const char *path,
int format_number(uint inputflag,uint max_length,char * pos,uint length,
char * *errpos);
+#ifndef DBUG_OFF
+void dbug_serve_apcs(THD *thd, int n_calls);
+#endif
+
/* table.cc */
TABLE_SHARE *alloc_table_share(TABLE_LIST *table_list, char *key,
uint key_length);
diff --git a/sql/mysqld.cc b/sql/mysqld.cc
index 1a60d996fed..283224140e5 100644
--- a/sql/mysqld.cc
+++ b/sql/mysqld.cc
@@ -3449,6 +3449,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},
diff --git a/sql/opt_subselect.cc b/sql/opt_subselect.cc
index d06480391be..eeca5179029 100644
--- a/sql/opt_subselect.cc
+++ b/sql/opt_subselect.cc
@@ -1387,7 +1387,8 @@ static bool convert_subq_to_sj(JOIN *parent_join, Item_in_subselect *subq_pred)
while ((ifm= li++))
parent_lex->ftfunc_list->push_front(ifm);
}
-
+
+ parent_lex->have_merged_subqueries= TRUE;
DBUG_RETURN(FALSE);
}
@@ -1498,6 +1499,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)
diff --git a/sql/protocol.h b/sql/protocol.h
index bd7462a1280..c74846755b0 100644
--- a/sql/protocol.h
+++ b/sql/protocol.h
@@ -31,6 +31,7 @@ class Protocol
protected:
THD *thd;
String *packet;
+ /* Used by net_store_data() for charset conversions */
String *convert;
uint field_pos;
#ifndef DBUG_OFF
@@ -45,6 +46,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,
@@ -58,6 +63,20 @@ public:
enum { SEND_NUM_ROWS= 1, SEND_DEFAULTS= 2, SEND_EOF= 4 };
virtual bool send_fields(List<Item> *list, uint flags);
+ 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; }
diff --git a/sql/sp_head.cc b/sql/sp_head.cc
index b30347a5ca7..6f2ef96f7a1 100644
--- a/sql/sp_head.cc
+++ b/sql/sp_head.cc
@@ -206,6 +206,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:
diff --git a/sql/sql_class.cc b/sql/sql_class.cc
index a0ed1cdc83f..b3e1ce77ccc 100644
--- a/sql/sql_class.cc
+++ b/sql/sql_class.cc
@@ -965,6 +965,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();
}
@@ -1128,7 +1129,8 @@ void THD::cleanup(void)
pthread_mutex_unlock(&LOCK_user_locks);
ull= NULL;
}
-
+
+ apc_target.destroy();
cleanup_done=1;
DBUG_VOID_RETURN;
}
@@ -1732,6 +1734,14 @@ 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_fields(field_list,
+ Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF));
+}
+
+
+void THD::make_explain_field_list(List<Item> &field_list)
+{
Item *item;
CHARSET_INFO *cs= system_charset_info;
field_list.push_back(new Item_return_int("id",3, MYSQL_TYPE_LONGLONG));
@@ -1769,10 +1779,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_fields(field_list,
- Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF));
}
+
#ifdef SIGNAL_WITH_VIO_CLOSE
void THD::close_active_vio()
{
@@ -1876,6 +1885,21 @@ void THD::rollback_item_tree_changes()
}
+/*
+ Check if the thread has been killed, and also process "APC requests"
+
+ @retval true The thread is killed, execution should be interrupted
+ @retval false Not killed, continue execution
+*/
+
+bool THD::check_killed()
+{
+ if (killed)
+ return TRUE;
+ apc_target.process_apc_requests();
+ return FALSE;
+}
+
/*****************************************************************************
** Functions to provide a interface to select results
*****************************************************************************/
@@ -2016,6 +2040,68 @@ int select_send::send_data(List<Item> &items)
DBUG_RETURN(0);
}
+
+//////////////////////////////////////////////////////////////////////////////
+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);
+ }
+ //TODO: do we need the following:
+ if (thd->is_error())
+ {
+ protocol->remove_last_row();
+ DBUG_RETURN(1);
+ }
+ /* psergey-TODO: instead of protocol->write(), steal the packet here */
+ 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);
+}
+
+
+void select_result_explain_buffer::flush_data()
+{
+ List_iterator<String> it(data_rows);
+ String *str;
+ while ((str= it++))
+ {
+ /* TODO: write out the lines. */
+ protocol->set_packet(str->ptr(), str->length());
+ protocol->write();
+ delete str;
+ }
+ data_rows.empty();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+
bool select_send::send_eof()
{
/*
@@ -2889,6 +2975,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");
@@ -2903,6 +2993,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");
@@ -2915,6 +3011,30 @@ void THD::restore_active_arena(Query_arena *set, Query_arena *backup)
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::get_explain_data(void *arg)
+{
+ Show_explain_request *req= (Show_explain_request*)arg;
+ //TODO: change mem_root to point to request_thd->mem_root.
+ // Actually, change the ARENA, because we're going to allocate items!
+ Query_arena backup_arena;
+ req->target_thd->set_n_backup_active_arena((Query_arena*)req->request_thd,
+ &backup_arena);
+
+ req->target_thd->lex->unit.print_explain(req->explain_buf);
+
+ req->target_thd->restore_active_arena((Query_arena*)req->request_thd,
+ &backup_arena);
+}
+
+
Statement::~Statement()
{
}
diff --git a/sql/sql_class.h b/sql/sql_class.h
index da82ed5beed..40a941df59d 100644
--- a/sql/sql_class.h
+++ b/sql/sql_class.h
@@ -1454,6 +1454,19 @@ struct Ha_data
};
+class select_result_explain_buffer;
+
+class Show_explain_request
+{
+public:
+ THD *target_thd;
+ THD *request_thd;
+
+ select_result_explain_buffer *explain_buf;
+
+ static void get_explain_data(void *arg);
+};
+
/**
@class THD
For each client connection we create a separate thread with THD serving as
@@ -2002,6 +2015,8 @@ public:
killed_state volatile killed;
+ bool check_killed();
+
/* scramble - random string sent to client on handshake */
char scramble[SCRAMBLE_LENGTH+1];
@@ -2184,6 +2199,16 @@ public:
void close_active_vio();
#endif
void awake(killed_state state_to_set);
+
+
+ /*
+ This is what allows this thread to serve as a target for others to
+ schedule Async Procedure Calls on.
+
+ It's possible to schedule arbitrary C function call but currently this
+ facility is used only by SHOW EXPLAIN code (See Show_explain_request)
+ */
+ Apc_target apc_target;
#ifndef MYSQL_CLIENT
enum enum_binlog_query_type {
@@ -2315,6 +2340,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);
#ifndef EMBEDDED_LIBRARY
/**
Clear the current error, if any.
@@ -2758,10 +2784,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: */
int nest_level;
public:
select_result();
@@ -2780,11 +2838,6 @@ public:
virtual uint field_count(List<Item> &fields) const
{ return fields.elements; }
virtual bool send_fields(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;
@@ -2818,6 +2871,35 @@ 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)
+ - 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)
+*/
+
+class select_result_explain_buffer : public select_result_sink
+{
+public:
+ THD *thd;
+ Protocol *protocol;
+ select_result_explain_buffer(){};
+
+ /* 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();
+
+ List<String> data_rows;
+};
+
+
+
+/*
Base class for select_result descendands which intercept and
transform result set rows. As the rows are not sent to the client,
sending of result set metadata should be suppressed as well.
@@ -3402,6 +3484,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).
diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc
index f29f51325a9..a1560953cd5 100644
--- a/sql/sql_lex.cc
+++ b/sql/sql_lex.cc
@@ -1644,7 +1644,8 @@ void st_select_lex::init_query()
link_next= 0;
lock_option= TL_READ_DEFAULT;
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;
@@ -3129,7 +3130,7 @@ bool st_select_lex::optimize_unflattened_subqueries()
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;
}
@@ -3511,7 +3512,7 @@ void SELECT_LEX::update_used_tables()
Set the EXPLAIN type for this subquery.
*/
-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())
@@ -3533,6 +3534,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);
@@ -3551,11 +3555,16 @@ void st_select_lex::set_explain_type()
using_materialization= TRUE;
}
+ if (this == master_unit()->fake_select_lex)
+ type= "UNION RESULT";
+
if (&master_unit()->thd->lex->select_lex == this)
{
type= is_primary ? "PRIMARY" : "SIMPLE";
}
- else
+
+ if (!on_the_fly)
+ // else
{
if (this == first)
{
@@ -3588,7 +3597,7 @@ void st_select_lex::set_explain_type()
}
}
}
- options|= SELECT_DESCRIBE;
+ options|= SELECT_DESCRIBE;
}
@@ -3735,6 +3744,71 @@ bool st_select_lex::is_merged_child_of(st_select_lex *ancestor)
}
+int st_select_lex::print_explain(select_result_sink *output)
+{
+ int res;
+ if (join && join->optimized == 2)
+ {
+ res= join->print_explain(output, TRUE,
+ FALSE, // need_tmp_table,
+ FALSE, // bool need_order,
+ FALSE, // bool distinct,
+ NULL); //const char *message
+ if (res)
+ goto err;
+
+ for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
+ unit;
+ unit= unit->next_unit())
+ {
+ /*
+ Display subqueries only if they are not parts of eliminated WHERE/ON
+ clauses.
+ */
+ if (!(unit->item && unit->item->eliminated))
+ {
+ unit->print_explain(output);
+ }
+ }
+ }
+ else
+ {
+ /* Produce "not yet optimized" line */
+ const char *msg="Not yet optimized";
+ res= join->print_explain(output, TRUE,
+ FALSE, // need_tmp_table,
+ FALSE, // bool need_order,
+ FALSE, // bool distinct,
+ msg); //const char *message
+ }
+err:
+ return 0;
+}
+
+
+int st_select_lex_unit::print_explain(select_result_sink *output)
+{
+ int res= 0;
+ SELECT_LEX *first= first_select();
+
+ for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
+ {
+ if ((res= sl->print_explain(output)))
+ break;
+ }
+
+ /*
+ Note: it could be that fake_select_lex->join == NULL still at this point
+ */
+ if (fake_select_lex && !fake_select_lex->join)
+ {
+ res= print_fake_select_lex_join(output, TRUE /* on the fly */,
+ fake_select_lex, 0 /* 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.
diff --git a/sql/sql_lex.h b/sql/sql_lex.h
index cefa092a874..6f72c9e3f7b 100644
--- a/sql/sql_lex.h
+++ b/sql/sql_lex.h
@@ -122,6 +122,7 @@ enum enum_sql_command {
SQLCOM_SHOW_PROFILE, SQLCOM_SHOW_PROFILES,
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
@@ -255,6 +256,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
{
@@ -592,6 +595,7 @@ public:
friend int subselect_union_engine::exec();
List<Item> *get_unit_column_types();
+ int print_explain(select_result_sink *output);
};
typedef class st_select_lex_unit SELECT_LEX_UNIT;
@@ -643,6 +647,12 @@ public:
those converted to jtbm nests. The list is emptied when conversion is done.
*/
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;
@@ -883,7 +893,7 @@ public:
*/
bool optimize_unflattened_subqueries();
/* Set the EXPLAIN type for this subquery. */
- void set_explain_type();
+ void set_explain_type(bool on_the_fly);
bool handle_derived(struct st_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);
@@ -906,8 +916,9 @@ 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);
/*
For MODE_ONLY_FULL_GROUP_BY we need to maintain two flags:
- Non-aggregated fields are used in this select.
diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc
index 1676d4a09f4..2fc01827675 100644
--- a/sql/sql_parse.cc
+++ b/sql/sql_parse.cc
@@ -332,6 +332,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;
@@ -3458,6 +3459,32 @@ end_with_restore_list:
thd->security_ctx->priv_user),
lex->verbose);
break;
+ case SQLCOM_SHOW_EXPLAIN:
+ {
+ /* 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;
+ }
+
+ mysqld_show_explain(thd, (ulong)it->val_int());
+ break;
+ }
case SQLCOM_SHOW_AUTHORS:
res= mysqld_show_authors(thd);
break;
diff --git a/sql/sql_prepare.cc b/sql/sql_prepare.cc
index 611c8ddf944..364b0a61d9e 100644
--- a/sql/sql_prepare.cc
+++ b/sql/sql_prepare.cc
@@ -2048,6 +2048,7 @@ static bool check_prepared_statement(Prepared_statement *stmt)
case SQLCOM_SHOW_ENGINE_LOGS:
case SQLCOM_SHOW_ENGINE_STATUS:
case SQLCOM_SHOW_ENGINE_MUTEX:
+ case SQLCOM_SHOW_EXPLAIN:
case SQLCOM_SHOW_CREATE_DB:
case SQLCOM_SHOW_GRANTS:
case SQLCOM_SHOW_BINLOG_EVENTS:
diff --git a/sql/sql_select.cc b/sql/sql_select.cc
index cb620495897..6bed898ce5e 100644
--- a/sql/sql_select.cc
+++ b/sql/sql_select.cc
@@ -245,6 +245,53 @@ Item_equal *find_item_equal(COND_EQUAL *cond_equal, Field *field,
JOIN_TAB *first_depth_first_tab(JOIN* join);
JOIN_TAB *next_depth_first_tab(JOIN* join, JOIN_TAB* tab);
+#ifndef DBUG_OFF
+// psergey:
+void dbug_serve_apcs(THD *thd, int n_calls)
+{
+ // TODO how do we signal that we're SHOW-EXPLAIN-READY?
+ const char *save_proc_info= thd->proc_info;
+ thd_proc_info(thd, "show_explain_trap");
+
+ int n_apcs= thd->apc_target.n_calls_processed + n_calls;
+ while (thd->apc_target.n_calls_processed < n_apcs)
+ {
+ my_sleep(300);
+ if (thd->check_killed())
+ break;
+ }
+ thd_proc_info(thd, save_proc_info);
+}
+
+
+/*
+ Debugging: check if @name=value, comparing as integer
+
+ Intended usage:
+
+ DBUG_EXECUTE_IF("show_explain_probe_2",
+ if (dbug_user_var_equals_int(thd, "select_id", select_id))
+ dbug_serve_apcs(thd, 1);
+ );
+
+*/
+
+bool dbug_user_var_equals_int(THD *thd, const char *name, int value)
+{
+ user_var_entry *var;
+ LEX_STRING varname= {(char*)name, strlen(name)};
+ if ((var= get_variable(&thd->user_vars, varname, FALSE)))
+ {
+ bool null_value;
+ longlong var_value= var->val_int(&null_value);
+ if (!null_value && var_value == value)
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+
+
/**
This handles SELECT with and without UNION.
*/
@@ -881,6 +928,12 @@ err:
}
+int JOIN::optimize()
+{
+ int res= optimize_inner();
+ optimized= 2;
+ return res;
+}
/**
global select optimisation.
@@ -894,7 +947,7 @@ err:
*/
int
-JOIN::optimize()
+JOIN::optimize_inner()
{
ulonglong select_opts_for_readinfo;
uint no_jbuf_after;
@@ -2045,6 +2098,17 @@ JOIN::save_join_tab()
}
+void JOIN::exec()
+{
+ /*
+ Enable SHOW EXPLAIN only if we're in the top-level query.
+ */
+ thd->apc_target.enable();
+ exec_inner();
+ thd->apc_target.disable();
+}
+
+
/**
Exec select.
@@ -2056,12 +2120,19 @@ JOIN::save_join_tab()
@todo
When can we have here thd->net.report_error not zero?
*/
-void
-JOIN::exec()
+
+void JOIN::exec_inner()
{
List<Item> *columns_list= &fields_list;
int tmp_error;
DBUG_ENTER("JOIN::exec");
+
+ DBUG_EXECUTE_IF("show_explain_probe_1",
+ if (dbug_user_var_equals_int(thd,
+ "show_explain_probe_select_id",
+ select_lex->select_number))
+ dbug_serve_apcs(thd, 1);
+ );
thd_proc_info(thd, "executing");
error= 0;
@@ -3586,7 +3657,7 @@ make_join_statistics(JOIN *join, List<TABLE_LIST> &tables_list,
goto error;
/* Generate an execution plan from the found optimal join order. */
- DBUG_RETURN(join->thd->killed || get_best_combination(join));
+ DBUG_RETURN(join->thd->check_killed() || get_best_combination(join));
error:
/*
@@ -6353,7 +6424,7 @@ best_extension_by_limited_search(JOIN *join,
DBUG_ENTER("best_extension_by_limited_search");
THD *thd= join->thd;
- if (thd->killed) // Abort
+ if (thd->check_killed()) // Abort
DBUG_RETURN(TRUE);
DBUG_EXECUTE("opt", print_plan(join, idx, read_time, record_count, idx,
@@ -6510,7 +6581,7 @@ find_best(JOIN *join,table_map rest_tables,uint idx,double record_count,
{
DBUG_ENTER("find_best");
THD *thd= join->thd;
- if (thd->killed)
+ if (thd->check_killed())
DBUG_RETURN(TRUE);
if (!rest_tables)
{
@@ -14707,7 +14778,7 @@ create_internal_tmp_table_from_heap2(THD *thd, TABLE *table,
DBUG_EXECUTE_IF("raise_error", write_err= HA_ERR_FOUND_DUPP_KEY ;);
if (write_err)
goto err;
- if (thd->killed)
+ if (thd->check_killed())
{
thd->send_kill_message();
goto err_killed;
@@ -15085,7 +15156,7 @@ sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool end_of_records)
rc= sub_select(join, join_tab, end_of_records);
DBUG_RETURN(rc);
}
- if (join->thd->killed)
+ if (join->thd->check_killed())
{
/* The user has aborted the execution of the query */
join->thd->send_kill_message();
@@ -15386,7 +15457,7 @@ evaluate_join_record(JOIN *join, JOIN_TAB *join_tab,
DBUG_RETURN(NESTED_LOOP_ERROR);
if (error < 0)
DBUG_RETURN(NESTED_LOOP_NO_MORE_ROWS);
- if (join->thd->killed) // Aborted by user
+ if (join->thd->check_killed()) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
@@ -16524,7 +16595,7 @@ end_write(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
TABLE *table=join->tmp_table;
DBUG_ENTER("end_write");
- if (join->thd->killed) // Aborted by user
+ if (join->thd->check_killed()) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
@@ -16595,7 +16666,7 @@ end_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (end_of_records)
DBUG_RETURN(NESTED_LOOP_OK);
- if (join->thd->killed) // Aborted by user
+ if (join->thd->check_killed()) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
@@ -16676,7 +16747,7 @@ end_unique_update(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
if (end_of_records)
DBUG_RETURN(NESTED_LOOP_OK);
- if (join->thd->killed) // Aborted by user
+ if (join->thd->check_killed()) // Aborted by user
{
join->thd->send_kill_message();
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
@@ -16723,7 +16794,7 @@ end_write_group(JOIN *join, JOIN_TAB *join_tab __attribute__((unused)),
int idx= -1;
DBUG_ENTER("end_write_group");
- if (join->thd->killed)
+ if (join->thd->check_killed())
{ // Aborted by user
join->thd->send_kill_message();
DBUG_RETURN(NESTED_LOOP_KILLED); /* purecov: inspected */
@@ -18496,7 +18567,7 @@ static int remove_dup_with_compare(THD *thd, TABLE *table, Field **first_field,
error= file->ha_rnd_next(record);
for (;;)
{
- if (thd->killed)
+ if (thd->check_killed())
{
thd->send_kill_message();
error=0;
@@ -18628,7 +18699,7 @@ static int remove_dup_with_hash_index(THD *thd, TABLE *table,
for (;;)
{
uchar *org_key_pos;
- if (thd->killed)
+ if (thd->check_killed())
{
thd->send_kill_message();
error=0;
@@ -20634,29 +20705,119 @@ void JOIN::clear()
}
}
+
+int print_fake_select_lex_join(select_result_sink *result, bool on_the_fly,
+ SELECT_LEX *select_lex, uint8 select_options)
+{
+ const CHARSET_INFO *cs= system_charset_info;
+ Item *item_null= new Item_null();
+ List<Item> item_list;
+ if (on_the_fly)
+ select_lex->set_explain_type(on_the_fly); //psergey
+ /*
+ here we assume that the query will return at least two rows, so we
+ show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
+ and no filesort will be actually done, but executing all selects in
+ the UNION to provide precise EXPLAIN information will hardly be
+ appreciated :)
+ */
+ char table_name_buffer[SAFE_NAME_LEN];
+ item_list.empty();
+ /* id */
+ item_list.push_back(new Item_null);
+ /* select_type */
+ item_list.push_back(new Item_string(select_lex->type,
+ strlen(select_lex->type),
+ cs));
+ /* table */
+ {
+ SELECT_LEX *sl= select_lex->master_unit()->first_select();
+ uint len= 6, lastop= 0;
+ memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
+ for (; sl && len + lastop + 5 < NAME_LEN; sl= sl->next_select())
+ {
+ len+= lastop;
+ lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
+ "%u,", sl->select_number);
+ }
+ if (sl || len + lastop >= NAME_LEN)
+ {
+ memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1);
+ len+= 4;
+ }
+ else
+ {
+ len+= lastop;
+ table_name_buffer[len - 1]= '>'; // change ',' to '>'
+ }
+ item_list.push_back(new Item_string(table_name_buffer, len, cs));
+ }
+ /* partitions */
+ if (/*join->thd->lex->describe*/ select_options & DESCRIBE_PARTITIONS)
+ item_list.push_back(item_null);
+ /* type */
+ item_list.push_back(new Item_string(join_type_str[JT_ALL],
+ strlen(join_type_str[JT_ALL]),
+ cs));
+ /* possible_keys */
+ item_list.push_back(item_null);
+ /* key*/
+ item_list.push_back(item_null);
+ /* key_len */
+ item_list.push_back(item_null);
+ /* ref */
+ item_list.push_back(item_null);
+ /* in_rows */
+ if (select_options & DESCRIBE_EXTENDED)
+ item_list.push_back(item_null);
+ /* rows */
+ item_list.push_back(item_null);
+ /* extra */
+ if (select_lex->master_unit()->global_parameters->order_list.first)
+ item_list.push_back(new Item_string("Using filesort",
+ 14, cs));
+ else
+ item_list.push_back(new Item_string("", 0, cs));
+
+ if (result->send_data(item_list))
+ return 1;
+ return 0;
+}
+
+
/**
EXPLAIN handling.
- Send a description about what how the select will be done to stdout.
+ Produce lines explaining execution of *this* select (not including children
+ selects)
+ @param on_the_fly TRUE <=> we're being executed on-the-fly, so don't make
+ modifications to any select's data structures
*/
-static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
- bool distinct,const char *message)
+int JOIN::print_explain(select_result_sink *result, bool on_the_fly,
+ bool need_tmp_table, bool need_order,
+ bool distinct, const char *message)
{
List<Item> field_list;
List<Item> item_list;
+ JOIN *join= this; /* Legacy: this code used to be a non-member function */
THD *thd=join->thd;
- select_result *result=join->result;
Item *item_null= new Item_null();
CHARSET_INFO *cs= system_charset_info;
int quick_type;
- DBUG_ENTER("select_describe");
+ int error= 0;
+ DBUG_ENTER("JOIN::print_explain");
DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
(ulong)join->select_lex, join->select_lex->type,
message ? message : "NULL"));
+ DBUG_ASSERT(this->optimized == 2);
/* Don't log this into the slow query log */
- thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED);
- join->unit->offset_limit_cnt= 0;
+
+ if (!on_the_fly)
+ {
+ thd->server_status&= ~(SERVER_QUERY_NO_INDEX_USED | SERVER_QUERY_NO_GOOD_INDEX_USED);
+ join->unit->offset_limit_cnt= 0;
+ }
/*
NOTE: the number/types of items pushed into item_list must be in sync with
@@ -20677,82 +20838,22 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
item_list.push_back(new Item_string(message,strlen(message),cs));
if (result->send_data(item_list))
- join->error= 1;
+ error= 1;
}
else if (join->select_lex == join->unit->fake_select_lex)
{
- /*
- here we assume that the query will return at least two rows, so we
- show "filesort" in EXPLAIN. Of course, sometimes we'll be wrong
- and no filesort will be actually done, but executing all selects in
- the UNION to provide precise EXPLAIN information will hardly be
- appreciated :)
- */
- char table_name_buffer[SAFE_NAME_LEN];
- item_list.empty();
- /* id */
- item_list.push_back(new Item_null);
- /* select_type */
- item_list.push_back(new Item_string(join->select_lex->type,
- strlen(join->select_lex->type),
- cs));
- /* table */
- {
- SELECT_LEX *sl= join->unit->first_select();
- uint len= 6, lastop= 0;
- memcpy(table_name_buffer, STRING_WITH_LEN("<union"));
- for (; sl && len + lastop + 5 < NAME_LEN; sl= sl->next_select())
- {
- len+= lastop;
- lastop= my_snprintf(table_name_buffer + len, NAME_LEN - len,
- "%u,", sl->select_number);
- }
- if (sl || len + lastop >= NAME_LEN)
- {
- memcpy(table_name_buffer + len, STRING_WITH_LEN("...>") + 1);
- len+= 4;
- }
- else
- {
- len+= lastop;
- table_name_buffer[len - 1]= '>'; // change ',' to '>'
- }
- item_list.push_back(new Item_string(table_name_buffer, len, cs));
- }
- /* partitions */
- if (join->thd->lex->describe & DESCRIBE_PARTITIONS)
- item_list.push_back(item_null);
- /* type */
- item_list.push_back(new Item_string(join_type_str[JT_ALL],
- strlen(join_type_str[JT_ALL]),
- cs));
- /* possible_keys */
- item_list.push_back(item_null);
- /* key*/
- item_list.push_back(item_null);
- /* key_len */
- item_list.push_back(item_null);
- /* ref */
- item_list.push_back(item_null);
- /* in_rows */
- if (join->thd->lex->describe & DESCRIBE_EXTENDED)
- item_list.push_back(item_null);
- /* rows */
- item_list.push_back(item_null);
- /* extra */
- if (join->unit->global_parameters->order_list.first)
- item_list.push_back(new Item_string("Using filesort",
- 14, cs));
- else
- item_list.push_back(new Item_string("", 0, cs));
-
- if (result->send_data(item_list))
- join->error= 1;
+ if (print_fake_select_lex_join(result, on_the_fly,
+ join->select_lex,
+ join->thd->lex->describe))
+ error= 1;
}
else if (!join->select_lex->master_unit()->derived ||
join->select_lex->master_unit()->derived->is_materialized_derived())
{
table_map used_tables=0;
+ //if (!join->select_lex->type)
+ if (on_the_fly)
+ join->select_lex->set_explain_type(on_the_fly); //psergey-todo: this adds SELECT_DESCRIBE to options! bad for on-the-fly
bool printing_materialize_nest= FALSE;
uint select_id= join->select_lex->select_number;
@@ -20806,6 +20907,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
join->select_lex->type;
item_list.push_back(new Item_string(stype, strlen(stype), cs));
+ enum join_type tab_type= tab->type;
if ((tab->type == JT_ALL || tab->type == JT_HASH) &&
tab->select && tab->select->quick)
{
@@ -20814,9 +20916,9 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
(quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_INTERSECT) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
- tab->type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE;
+ tab_type= tab->type == JT_ALL ? JT_INDEX_MERGE : JT_HASH_INDEX_MERGE;
else
- tab->type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE;
+ tab_type= tab->type == JT_ALL ? JT_RANGE : JT_HASH_RANGE;
}
/* table */
@@ -20865,8 +20967,8 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
#endif
}
/* "type" column */
- item_list.push_back(new Item_string(join_type_str[tab->type],
- strlen(join_type_str[tab->type]),
+ item_list.push_back(new Item_string(join_type_str[tab_type],
+ strlen(join_type_str[tab_type]),
cs));
/* Build "possible_keys" value and add it to item_list */
if (!tab->keys.is_clear_all())
@@ -20890,7 +20992,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
item_list.push_back(item_null);
/* Build "key", "key_len", and "ref" values and add them to item_list */
- if (tab->type == JT_NEXT)
+ if (tab_type == JT_NEXT)
{
key_info= table->key_info+tab->index;
key_len= key_info->key_length;
@@ -20919,12 +21021,12 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
}
}
}
- if (is_hj && tab->type != JT_HASH)
+ if (is_hj && tab_type != JT_HASH)
{
tmp2.append(':');
tmp3.append(':');
}
- if (tab->type == JT_HASH_NEXT)
+ if (tab_type == JT_HASH_NEXT)
{
register uint length;
key_info= table->key_info+tab->index;
@@ -20946,7 +21048,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs));
else
item_list.push_back(item_null);
- if (key_info && tab->type != JT_NEXT)
+ if (key_info && tab_type != JT_NEXT)
item_list.push_back(new Item_string(tmp4.ptr(),tmp4.length(),cs));
else
item_list.push_back(item_null);
@@ -20999,7 +21101,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
ha_rows examined_rows;
if (tab->select && tab->select->quick)
examined_rows= tab->select->quick->records;
- else if (tab->type == JT_NEXT || tab->type == JT_ALL || is_hj)
+ else if (tab_type == JT_NEXT || tab_type == JT_ALL || is_hj)
{
if (tab->limit)
examined_rows= tab->limit;
@@ -21038,7 +21140,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
/* Build "Extra" field and add it to item_list. */
key_read=table->key_read;
- if ((tab->type == JT_NEXT || tab->type == JT_CONST) &&
+ if ((tab_type == JT_NEXT || tab_type == JT_CONST) &&
table->covering_keys.is_set(tab->index))
key_read=1;
if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
@@ -21244,9 +21346,27 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
// For next iteration
used_tables|=table->map;
if (result->send_data(item_list))
- join->error= 1;
+ error= 1;
}
}
+ DBUG_RETURN(error);
+}
+
+
+/*
+ See st_select_lex::print_explain() for the SHOW EXPLAIN counterpart
+*/
+
+static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
+ bool distinct,const char *message)
+{
+ THD *thd=join->thd;
+ select_result *result=join->result;
+ DBUG_ENTER("select_describe");
+ join->error= join->print_explain(result, FALSE, /* Not on-the-fly */
+ need_tmp_table, need_order, distinct,
+ message);
+
for (SELECT_LEX_UNIT *unit= join->select_lex->first_inner_unit();
unit;
unit= unit->next_unit())
@@ -21289,7 +21409,7 @@ bool mysql_explain_union(THD *thd, SELECT_LEX_UNIT *unit, select_result *result)
for (SELECT_LEX *sl= first; sl; sl= sl->next_select())
{
- sl->set_explain_type();
+ sl->set_explain_type(FALSE); //psergey-todo: maybe remove this from here?
sl->options|= SELECT_DESCRIBE;
}
diff --git a/sql/sql_select.h b/sql/sql_select.h
index 8b3da18c976..ed5a551b1d8 100644
--- a/sql/sql_select.h
+++ b/sql/sql_select.h
@@ -1137,7 +1137,7 @@ public:
const char *zero_result_cause; ///< not 0 if exec must return zero result
bool union_part; ///< this subselect is part of union
- bool optimized; ///< flag to avoid double optimization in EXPLAIN
+ int optimized; ///< flag to avoid double optimization in EXPLAIN
bool initialized; ///< flag to avoid double init_execution calls
/*
@@ -1248,9 +1248,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();
@@ -1360,6 +1362,10 @@ public:
{
return (unit->item && unit->item->is_in_predicate());
}
+
+ int print_explain(select_result_sink *result, 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
@@ -1652,6 +1658,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);
+
/* Table elimination entry point function */
void eliminate_tables(JOIN *join);
diff --git a/sql/sql_show.cc b/sql/sql_show.cc
index 0e8b919db53..b0acb87a3f6 100644
--- a/sql/sql_show.cc
+++ b/sql/sql_show.cc
@@ -2046,6 +2046,97 @@ void mysqld_list_processes(THD *thd,const char *user, bool verbose)
DBUG_VOID_RETURN;
}
+
+/*
+ SHOW EXPLAIN FOR command handler
+
+ @param thd Current thread's thd
+ @param thread_id Thread whose explain we need
+
+ @notes
+ - Attempt to do "SHOW EXPLAIN FOR <myself>" will properly produce "target not
+ running EXPLAINable command".
+ - todo: check how all this can/will work when using thread pools
+*/
+
+void mysqld_show_explain(THD *thd, ulong thread_id)
+{
+ 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_fields(&field_list, Protocol::SEND_NUM_ROWS |
+ Protocol::SEND_EOF))
+ DBUG_VOID_RETURN;
+
+ /*
+ Find the thread we need EXPLAIN for. Thread search code was copied from
+ kill_one_thread()
+ */
+ VOID(pthread_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 == thread_id)
+ {
+ pthread_mutex_lock(&tmp->LOCK_thd_data); // Lock from delete
+ break;
+ }
+ }
+ VOID(pthread_mutex_unlock(&LOCK_thread_count));
+
+ if (tmp)
+ {
+ 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 an EXPLAIN request.
+ todo: where to get timeout from?
+ */
+ 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;
+ explain_buf->thd=thd;
+ explain_buf->protocol= thd->protocol;
+
+ explain_req.explain_buf= explain_buf;
+ explain_req.target_thd= tmp;
+ explain_req.request_thd= thd;
+
+ bres= tmp->apc_target.make_apc_call(Show_explain_request::get_explain_data,
+ (void*)&explain_req,
+ timeout_sec, &timed_out);
+ if (bres)
+ {
+ /* TODO not enabled or time out */
+ my_error(ER_ERROR_WHEN_EXECUTING_COMMAND, MYF(0),
+ "SHOW EXPLAIN",
+ "Target is not running EXPLAINable command");
+ }
+ pthread_mutex_unlock(&tmp->LOCK_thd_data);
+ if (!bres)
+ {
+ explain_buf->flush_data();
+ my_eof(thd);
+ }
+ }
+ else
+ {
+ my_error(ER_NO_SUCH_THREAD, MYF(0), thread_id);
+ }
+
+ DBUG_VOID_RETURN;
+}
+
+
int fill_schema_processlist(THD* thd, TABLE_LIST* tables, COND* cond)
{
TABLE *table= tables->table;
diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy
index 084314ff23f..e314eeb8cb4 100644
--- a/sql/sql_yacc.yy
+++ b/sql/sql_yacc.yy
@@ -10880,6 +10880,12 @@ show_param:
Lex->spname= $3;
Lex->sql_command = SQLCOM_SHOW_CREATE_EVENT;
}
+ | describe_command FOR_SYM expr
+ {
+ Lex->sql_command= SQLCOM_SHOW_EXPLAIN;
+ Lex->value_list.empty();
+ Lex->value_list.push_front($3);
+ }
;
show_engine_param: