[Commits] Rev 3376: MWL#182: Explain running statements: merge with 5.3-main (needs fixing) in file:///home/psergey/dev2/5.3-show-explain-r10/

Sergey Petrunya psergey at askmonty.org
Wed Jan 4 00:17:26 EET 2012


At file:///home/psergey/dev2/5.3-show-explain-r10/

------------------------------------------------------------
revno: 3376 [merge]
revision-id: psergey at askmonty.org-20120103221616-ix6yvribpixbtn6x
parent: psergey at askmonty.org-20111230211905-he458ysn3sse6wlm
parent: psergey at askmonty.org-20111027223002-kgpvunfaiyrma2ma
committer: Sergey Petrunya <psergey at askmonty.org>
branch nick: 5.3-show-explain-r10
timestamp: Tue 2012-01-03 23:16:16 +0100
message:
  MWL#182: Explain running statements: merge with 5.3-main (needs fixing)
added:
  mysql-test/r/show_explain.result show_explain.result-20110824104040-a8uwts1ie2z0zdo2-1
  mysql-test/t/show_explain.test show_explain.test-20110824104035-fmqsdgk20kffuefn-1
  sql/my_apc.cc                  my_apc.cc-20110821164223-1znkpd9m6pgz49jj-1
  sql/my_apc.h                   my_apc.h-20110821164219-b4ajyi1lfv1hfzmr-1
modified:
  client/mysqltest.cc            sp1f-mysqltest.c-20001010065317-ix4zw26srlev7yugcz455ux22zwyynyf
  libmysqld/CMakeLists.txt       sp1f-cmakelists.txt-20060403082523-x3vxka3k56u2wpzwcrlpykznlz2akpxd
  libmysqld/Makefile.am          sp1f-makefile.am-20010411110351-26htpk3ynkyh7pkfvnshztqrxx3few4g
  sql/CMakeLists.txt             sp1f-cmakelists.txt-20060831175237-esoeu5kpdtwjvehkghwy6fzbleniq2wy
  sql/Makefile.am                sp1f-makefile.am-19700101030959-xsjdiakci3nqcdd4xl4yomwdl5eo2f3q
  sql/item_func.cc               sp1f-item_func.cc-19700101030959-3wmsx76yvc25sroqpfrx2n77kqdxxn3y
  sql/mysql_priv.h               sp1f-mysql_priv.h-19700101030959-4fl65tqpop5zfgxaxkqotu2fa2ree5ci
  sql/mysqld.cc                  sp1f-mysqld.cc-19700101030959-zpswdvekpvixxzxf7gdtofzel7nywtfj
  sql/opt_subselect.cc           opt_subselect.cc-20100215190428-nekkl8wisp0k6nlk-1
  sql/protocol.h                 sp1f-protocol.h-20021211071747-atezjtpiyby4so64hlg3lnnrbpmdi73x
  sql/sp_head.cc                 sp1f-sp_head.cc-20021208185920-jtgc5wvyqdnu2gvcdus3gazrfhxbofxd
  sql/sql_class.cc               sp1f-sql_class.cc-19700101030959-rpotnweaff2pikkozh3butrf7mv3oero
  sql/sql_class.h                sp1f-sql_class.h-19700101030959-jnqnbrjyqsvgncsibnumsmg3lyi7pa5s
  sql/sql_lex.cc                 sp1f-sql_lex.cc-19700101030959-4pizwlu5rqkti27gcwsvxkawq6bc2kph
  sql/sql_lex.h                  sp1f-sql_lex.h-19700101030959-sgldb2sooc7twtw5q7pgjx7qzqiaa3sn
  sql/sql_parse.cc               sp1f-sql_parse.cc-19700101030959-ehcre3rwhv5l3mlxqhaxg36ujenxnrcd
  sql/sql_prepare.cc             sp1f-sql_prepare.cc-20020612210720-gtqjjiu7vpmfxb5xct2qke7urmqcabli
  sql/sql_select.cc              sp1f-sql_select.cc-19700101030959-egb7whpkh76zzvikycs5nsnuviu4fdlb
  sql/sql_select.h               sp1f-sql_select.h-19700101030959-oqegfxr76xlgmrzd6qlevonoibfnwzoz
  sql/sql_show.cc                sp1f-sql_show.cc-19700101030959-umlljfnpplg452h7reeyqr4xnbmlkvfj
  sql/sql_yacc.yy                sp1f-sql_yacc.yy-19700101030959-wvn4qyy2drpmge7kaq3dysprbhlrv27j
=== modified file 'client/mysqltest.cc'
--- a/client/mysqltest.cc	2011-12-12 12:00:33 +0000
+++ b/client/mysqltest.cc	2012-01-03 22:16:16 +0000
@@ -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 @@
   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 @@
   "replace_column",
   "ping",
   "eval",
+  "evalp",
   "rpl_probe",
   "enable_rpl_parse",
   "disable_rpl_parse",
@@ -7575,7 +7579,8 @@
   /*
     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 @@
   */
   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 @@
       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 @@
           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);
 

=== modified file 'libmysqld/CMakeLists.txt'
--- a/libmysqld/CMakeLists.txt	2011-12-12 12:00:33 +0000
+++ b/libmysqld/CMakeLists.txt	2012-01-03 22:16:16 +0000
@@ -146,6 +146,7 @@
            ../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})
 

=== modified file 'libmysqld/Makefile.am'
--- a/libmysqld/Makefile.am	2011-12-11 09:34:44 +0000
+++ b/libmysqld/Makefile.am	2012-01-03 22:16:16 +0000
@@ -83,7 +83,7 @@
 	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

=== added file 'mysql-test/r/show_explain.result'
--- a/mysql-test/r/show_explain.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/show_explain.result	2011-10-27 22:30:02 +0000
@@ -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;

=== added file 'mysql-test/t/show_explain.test'
--- a/mysql-test/t/show_explain.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/show_explain.test	2011-10-27 22:30:02 +0000
@@ -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;

=== modified file 'sql/CMakeLists.txt'
--- a/sql/CMakeLists.txt	2011-12-12 12:00:33 +0000
+++ b/sql/CMakeLists.txt	2012-01-03 22:16:16 +0000
@@ -86,6 +86,7 @@
                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

=== modified file 'sql/Makefile.am'
--- a/sql/Makefile.am	2011-12-11 09:34:44 +0000
+++ b/sql/Makefile.am	2012-01-03 22:16:16 +0000
@@ -86,7 +86,8 @@
                         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 @@
 			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
 

=== modified file 'sql/item_func.cc'
--- a/sql/item_func.cc	2011-12-11 09:34:44 +0000
+++ b/sql/item_func.cc	2012-01-03 22:16:16 +0000
@@ -3871,7 +3871,7 @@
 
 #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;

=== added file 'sql/my_apc.cc'
--- a/sql/my_apc.cc	1970-01-01 00:00:00 +0000
+++ b/sql/my_apc.cc	2011-09-24 17:56:42 +0000
@@ -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
+
+
+

=== added file 'sql/my_apc.h'
--- a/sql/my_apc.h	1970-01-01 00:00:00 +0000
+++ b/sql/my_apc.h	2011-09-24 17:56:42 +0000
@@ -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;
+  }
+};
+
+///////////////////////////////////////////////////////////////////////
+

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2011-12-15 08:21:15 +0000
+++ b/sql/mysql_priv.h	2012-01-03 22:16:16 +0000
@@ -812,6 +812,7 @@
                                       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_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 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);

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2011-12-15 22:26:59 +0000
+++ b/sql/mysqld.cc	2012-01-03 22:16:16 +0000
@@ -3449,6 +3449,7 @@
   {"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},

=== modified file 'sql/opt_subselect.cc'
--- a/sql/opt_subselect.cc	2011-12-24 16:55:10 +0000
+++ b/sql/opt_subselect.cc	2012-01-03 22:16:16 +0000
@@ -1387,7 +1387,8 @@
     while ((ifm= li++))
       parent_lex->ftfunc_list->push_front(ifm);
   }
-
+  
+  parent_lex->have_merged_subqueries= TRUE;
   DBUG_RETURN(FALSE);
 }
 
@@ -1498,6 +1499,8 @@
   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)

=== modified file 'sql/protocol.h'
--- a/sql/protocol.h	2011-12-11 09:34:44 +0000
+++ b/sql/protocol.h	2012-01-03 22:16:16 +0000
@@ -31,6 +31,7 @@
 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 @@
   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 @@
   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; }

=== modified file 'sql/sp_head.cc'
--- a/sql/sp_head.cc	2011-12-13 12:00:20 +0000
+++ b/sql/sp_head.cc	2012-01-03 22:16:16 +0000
@@ -206,6 +206,7 @@
   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:

=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc	2011-12-11 16:39:33 +0000
+++ b/sql/sql_class.cc	2012-01-03 22:16:16 +0000
@@ -965,6 +965,7 @@
   /* 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 @@
     pthread_mutex_unlock(&LOCK_user_locks);
     ull= NULL;
   }
-
+  
+  apc_target.destroy();
   cleanup_done=1;
   DBUG_VOID_RETURN;
 }
@@ -1732,6 +1734,14 @@
 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 @@
   }
   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 @@
 }
 
 
+/*
+  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 @@
   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 @@
 }
 
 
+/*
+  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 @@
 }
 
 
+/*
+  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 @@
   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()
 {
 }

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2011-12-11 09:34:44 +0000
+++ b/sql/sql_class.h	2012-01-03 22:16:16 +0000
@@ -1454,6 +1454,19 @@
 };
 
 
+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 @@
 
   killed_state volatile killed;
 
+  bool check_killed();
+
   /* scramble - random string sent to client on handshake */
   char	     scramble[SCRAMBLE_LENGTH+1];
 
@@ -2184,6 +2199,16 @@
   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 @@
   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 @@
 
 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 @@
   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 @@
 
 
 /*
+  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 @@
   DTCollation collation;
 };
 
+user_var_entry *get_variable(HASH *hash, LEX_STRING &name,
+				    bool create_if_not_exists);
 
 /*
    Unique -- class for unique (removing of duplicates).

=== modified file 'sql/sql_lex.cc'
--- a/sql/sql_lex.cc	2011-12-11 17:28:05 +0000
+++ b/sql/sql_lex.cc	2012-01-03 22:16:16 +0000
@@ -1644,7 +1644,8 @@
   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 @@
         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 @@
   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 @@
     }
   }
 
+  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 @@
       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 @@
       }
     }
   }
-  options|= SELECT_DESCRIBE;
+    options|= SELECT_DESCRIBE;
 }
 
 
@@ -3735,6 +3744,71 @@
 }
 
 
+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.

=== modified file 'sql/sql_lex.h'
--- a/sql/sql_lex.h	2011-12-11 16:39:33 +0000
+++ b/sql/sql_lex.h	2012-01-03 22:16:16 +0000
@@ -122,6 +122,7 @@
   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 @@
 #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 @@
   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 @@
     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 @@
   */
   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 @@
 
   bool save_leaf_tables(THD *thd);
   bool save_prep_leaf_tables(THD *thd);
+
   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.

=== modified file 'sql/sql_parse.cc'
--- a/sql/sql_parse.cc	2011-12-13 12:00:20 +0000
+++ b/sql/sql_parse.cc	2012-01-03 22:16:16 +0000
@@ -332,6 +332,7 @@
   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 @@
                            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;

=== modified file 'sql/sql_prepare.cc'
--- a/sql/sql_prepare.cc	2011-12-11 16:39:33 +0000
+++ b/sql/sql_prepare.cc	2012-01-03 22:16:16 +0000
@@ -2048,6 +2048,7 @@
   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:

=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc	2011-12-24 16:55:10 +0000
+++ b/sql/sql_select.cc	2012-01-03 22:16:16 +0000
@@ -245,6 +245,53 @@
 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 @@
 }
 
 
+int JOIN::optimize()
+{
+  int res= optimize_inner();
+  optimized= 2;
+  return res;
+}
 /**
   global select optimisation.
 
@@ -894,7 +947,7 @@
 */
 
 int
-JOIN::optimize()
+JOIN::optimize_inner()
 {
   ulonglong select_opts_for_readinfo;
   uint no_jbuf_after;
@@ -2045,6 +2098,17 @@
 }
 
 
+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 @@
   @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 @@
     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 @@
   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 @@
 {
   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 @@
     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 @@
       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 @@
     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 @@
   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 @@
 
   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 @@
 
   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 @@
   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 @@
   error= file->ha_rnd_next(record);
   for (;;)
   {
-    if (thd->killed)
+    if (thd->check_killed())
     {
       thd->send_kill_message();
       error=0;
@@ -18628,7 +18699,7 @@
   for (;;)
   {
     uchar *org_key_pos;
-    if (thd->killed)
+    if (thd->check_killed())
     {
       thd->send_kill_message();
       error=0;
@@ -20634,29 +20705,119 @@
   }
 }
 
+
+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 @@
   
     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 @@
                                                     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 @@
             (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 @@
 #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 @@
 	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 @@
           }
         }
       }
-      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 @@
           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 @@
         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 @@
 
       /* 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 @@
       // 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 @@
 
   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;
   }
 

=== modified file 'sql/sql_select.h'
--- a/sql/sql_select.h	2011-12-19 14:07:19 +0000
+++ b/sql/sql_select.h	2012-01-03 22:16:16 +0000
@@ -1137,7 +1137,7 @@
   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 @@
 	      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 @@
   {
     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 @@
   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);
 

=== modified file 'sql/sql_show.cc'
--- a/sql/sql_show.cc	2011-12-14 18:36:51 +0000
+++ b/sql/sql_show.cc	2012-01-03 22:16:16 +0000
@@ -2046,6 +2046,97 @@
   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;

=== modified file 'sql/sql_yacc.yy'
--- a/sql/sql_yacc.yy	2011-12-11 09:34:44 +0000
+++ b/sql/sql_yacc.yy	2012-01-03 22:16:16 +0000
@@ -10880,6 +10880,12 @@
             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:



More information about the commits mailing list