List:Commits« Previous MessageNext Message »
From:Martin Hansson Date:January 1 1970 12:00am
Subject:bzr commit into mysql-6.0 branch (mhansson:2652) WL#3724
View as plain text  
#At file:///data0/martin/bzr/6.0o-wl3724/

 2652 Martin Hansson	2008-06-27
      WL#3724: Short-Cutting Join Execution: Speeding up star queries
added:
  mysql-test/r/short-cut.result
  mysql-test/t/short-cut.test
modified:
  sql/item.h
  sql/item_func.cc
  sql/item_func.h
  sql/mysqld.cc
  sql/set_var.cc
  sql/sql_class.h
  sql/sql_select.cc
  sql/sql_select.h
  sql/structs.h
  support-files/build-tags

per-file messages:
  mysql-test/r/short-cut.result
    WL#3724: Test result.
  mysql-test/t/short-cut.test
    WL#3724: Test case.
  sql/item.h
    WL#3724: Commenting only
  sql/item_func.cc
    WL#3724: Initialization of used_tables_cache.
  sql/item_func.h
    WL#3724: Initialization of used_tables_cache.
  sql/mysqld.cc
    To do: remove
  sql/set_var.cc
    To do: remove
  sql/sql_class.h
    To do: remove
  sql/sql_select.cc
    WL#3724: to do: remove reference to system variable.
    - Added function initialize_shortcuts
    - Added short-cut optimization to select_cond, by means
      of helper functions/methods.
    - Added helper function to append information about 
      optimization to EXPLAIN.
  sql/sql_select.h
    WL#3724:
    - added fields to JOIN_TAB.
      - join_shortcut
      - has_partial_tuple
    - added method do_read_first_record to JOIN_TAB.
    - added field last_join_tab_accessed to JOIN
    - added method get_join_tab_by_map to JOIN.
  sql/structs.h
    WL#3724: Commenting only.
=== added file 'mysql-test/r/short-cut.result'
--- a/mysql-test/r/short-cut.result	1970-01-01 00:00:00 +0000
+++ b/mysql-test/r/short-cut.result	2008-06-27 09:17:42 +0000
@@ -0,0 +1,336 @@
+SET explain_shortcuts = 1;
+CREATE TABLE t1 (
+a INT,
+b INT
+);
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+CREATE TABLE t2 (
+a INT,
+KEY( a )
+);
+INSERT INTO t2 VALUES (1), (1), (1), (1), (1);
+CREATE TABLE t3 (
+b INT,
+KEY( b )
+);
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1), (1);
+EXPLAIN SELECT * FROM t1 JOIN t2 USING( a ) JOIN t3 USING( b );
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+SELECT * FROM t1 JOIN t2 USING( a ) JOIN t3 USING( b );
+b	a
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( t2, t3 ) USING( b );
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+1	SIMPLE	t2	index	NULL	a	5	NULL	5	Using index
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( t2 JOIN t3 ON a = b ) USING( b );
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+1	SIMPLE	t2	ref	a	a	5	test.t3.b	2	Using index
+EXPLAIN SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+EXPLAIN SELECT * FROM t1, t3, t2 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+EXPLAIN SELECT * FROM t2, t1, t3 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+EXPLAIN SELECT * FROM t2, t3, t1 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+EXPLAIN SELECT * FROM t3, t1, t2 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+EXPLAIN SELECT * FROM t3, t2, t1 WHERE t1.a = t2.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	b	b	5	test.t1.b	2	Using index
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 ( a INT, b INT );
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+CREATE TABLE t2 ( a INT, b INT, KEY( a ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+INSERT INTO t2 SELECT * FROM t2;
+CREATE TABLE t3 ( b INT, KEY( b ) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t3 SELECT * FROM t3;
+CREATE TABLE t4 ( b INT, KEY( b ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+FLUSH STATUS;
+EXPLAIN
+SELECT * FROM t1 JOIN t2 USING(a) JOIN t3 ON t2.a = t3.b JOIN t4 ON t1.b = t4.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	
+1	SIMPLE	t3	ref	b	b	5	test.t2.a	2	Using where; Using index; Shortcut(t1)
+1	SIMPLE	t4	ref	b	b	5	test.t1.b	4	Using index
+SELECT * FROM t1 JOIN t2 USING(a) JOIN t3 ON t2.a = t3.b JOIN t4 ON t1.b = t4.b;
+a	b	b	b	b
+SHOW STATUS LIKE 'handler_read_%';
+Variable_name	Value
+Handler_read_first	0
+Handler_read_key	4
+Handler_read_next	0
+Handler_read_prev	0
+Handler_read_rnd	0
+Handler_read_rnd_next	3
+DROP TABLE t1, t2, t3, t4;
+CREATE TABLE t1 ( a INT );
+INSERT INTO t1 VALUES (1), (2), (3), (4), (5);
+CREATE TABLE t2 ( a INT, KEY (a) );
+INSERT INTO t2 VALUES (1), (2), (2), (2), (2), (2), (3), (4), (4), (4), (5);
+CREATE TABLE t3 ( a INT, KEY (a) );
+INSERT INTO t3 VALUES (1), (3), (5), (-1), (-1), (-1), (-1), (-1), (-1);
+FLUSH STATUS;
+EXPLAIN
+SELECT STRAIGHT_JOIN * FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.a = t3.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	5	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	a	a	5	test.t1.a	2	Using index
+SELECT STRAIGHT_JOIN * FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.a = t3.a;
+a	a	a
+1	1	1
+3	3	3
+5	5	5
+SHOW STATUS LIKE 'handler_read_%';
+Variable_name	Value
+Handler_read_first	0
+Handler_read_key	10
+Handler_read_next	6
+Handler_read_prev	0
+Handler_read_rnd	0
+Handler_read_rnd_next	6
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 ( a INT, b INT );
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+CREATE TABLE t2 ( a INT, b INT, KEY( a ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+INSERT INTO t2 SELECT * FROM t2;
+CREATE TABLE t3 ( b INT, KEY( b ) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t3 SELECT * FROM t3;
+CREATE TABLE t4 ( b INT, KEY( b ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+CREATE TABLE t5 ( b INT, KEY( b ) );
+INSERT INTO t5 VALUES (1), (1), (1), (1), (1), (1), (1), (1);
+INSERT INTO t5 SELECT * FROM t5;
+EXPLAIN
+SELECT *
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING ( a )
+LEFT JOIN t4 ON t1.a = t4.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	
+1	SIMPLE	t3	ref	b	b	5	test.t2.a	2	Using index
+1	SIMPLE	t4	ref	b	b	5	test.t1.a	4	Using index
+EXPLAIN
+SELECT * 
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING(a)
+LEFT JOIN (t4 JOIN t5 USING( b )) ON t1.a = t4.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	
+1	SIMPLE	t3	ref	b	b	5	test.t2.a	2	Using index
+1	SIMPLE	t4	ref	b	b	5	test.t1.a	4	Using index
+1	SIMPLE	t5	ref	b	b	5	test.t4.b	2	Using index
+EXPLAIN 
+SELECT * 
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING(a), t4
+WHERE t1.a = t4.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	
+1	SIMPLE	t3	ref	b	b	5	test.t1.a	2	Using index
+1	SIMPLE	t4	ref	b	b	5	test.t1.a	4	Using index
+DROP TABLE t1, t2, t3, t4, t5;
+CREATE TABLE t1( 
+a INT,
+b INT,
+KEY ( b )
+);
+INSERT INTO t1 VALUES (2, 1),  (2, 2);
+CREATE TABLE t2 (
+c INT,
+KEY ( c )
+);
+INSERT INTO t2 VALUES (1);
+CREATE TABLE t3(
+d INT,
+KEY ( d )
+);
+INSERT INTO t3 VALUES (1), (2), (3);
+EXPLAIN 
+SELECT * FROM t1 LEFT JOIN t2 ON a = c JOIN t3 ON b = d;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	b	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ref	c	c	5	test.t1.a	2	Using index
+1	SIMPLE	t3	index	d	d	5	NULL	3	Using where; Using index; Using join buffer
+SELECT * FROM t1 LEFT JOIN t2 ON a = c JOIN t3 ON b = d;
+a	b	c	d
+2	1	NULL	1
+2	2	NULL	2
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 (
+a INT,
+KEY ( A )
+);
+INSERT INTO t1 VALUES (1), (2), (3), (13);
+CREATE TABLE t2 (
+a INT,
+KEY ( A )
+);
+INSERT INTO t2 VALUES (1), (2);
+CREATE TABLE t3 (
+a INT,
+KEY ( A )
+);
+INSERT INTO t3 VALUES (1), (2), (3);
+CREATE TABLE t4 (
+a INT,
+KEY ( A )
+);
+INSERT INTO t4 VALUES (1), (2), (3);
+EXPLAIN SELECT STRAIGHT_JOIN t1.a, t2.a, t3.a, t4.a 
+FROM t1 LEFT JOIN (t2 JOIN t3 USING(a)) ON t1.a=t3.a, t4 
+WHERE t4.a=t1.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	index	a	a	5	NULL	4	Using index
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	Using index
+1	SIMPLE	t3	ref	a	a	5	test.t1.a	2	Using index
+1	SIMPLE	t4	index	a	a	5	NULL	3	Using where; Using index; Using join buffer
+DROP TABLE t1, t2, t3, t4;
+CREATE TABLE t1 ( a INT, b INT );
+CREATE TABLE t2 ( a INT, b INT, PRIMARY KEY (a,b) );
+CREATE TABLE t3 ( a INT, b INT, PRIMARY KEY (a,b) );
+INSERT INTO t1 VALUES ( 1, 1 ), ( 2, 1 ), ( 1, 3 );
+INSERT INTO t2 VALUES ( 1, 1 ), ( 2, 2 ), ( 3, 3 );
+INSERT INTO t3 VALUES ( 1, 1 ), ( 2, 1 ), ( 1, 3 );
+EXPLAIN 
+SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.b = t3.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	3	
+1	SIMPLE	t2	ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index
+1	SIMPLE	t3	eq_ref	PRIMARY	PRIMARY	8	test.t2.b,test.t1.b	1	Using index
+SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.b = t3.b;
+a	b	a	b	a	b
+1	1	1	1	1	1
+2	1	2	2	2	1
+1	3	1	1	1	3
+EXPLAIN 
+SELECT * 
+FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.b = t3.a 
+WHERE t3.b + 1 = t2.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	3	
+1	SIMPLE	t2	ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index
+1	SIMPLE	t3	ref	PRIMARY	PRIMARY	4	test.t1.b	1	Using where; Using index
+EXPLAIN 
+SELECT * 
+FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.b = t3.a 
+WHERE t3.a + 1 = t2.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	3	Using where
+1	SIMPLE	t2	ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	PRIMARY	PRIMARY	4	test.t1.b	1	Using index
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 ( a INT );
+INSERT INTO t1 VALUES (1), (1), (1), (1);
+CREATE TABLE t2 ( a INT, KEY (a), b INT, c INT );
+INSERT INTO t2 VALUES (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1);
+INSERT INTO t2 SELECT * FROM t2;
+INSERT INTO t2 SELECT * FROM t2;
+CREATE TABLE t3 ( a INT, KEY (a) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (3);
+CREATE TABLE t4 ( a INT, KEY (a), b INT );
+INSERT INTO t4 VALUES (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1);
+INSERT INTO t4 SELECT * FROM t4;
+EXPLAIN
+SELECT STRAIGHT_JOIN * 
+FROM t1 JOIN t2 ON t1.a = t2.a 
+JOIN t3 ON t2.b = t3.a
+JOIN t4 ON t1.a = t4.a AND t2.c = t4.b;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	4	
+1	SIMPLE	t2	ref	a	a	5	test.t1.a	2	
+1	SIMPLE	t3	ref	a	a	5	test.t2.b	2	Using index; Shortcut(t2)
+1	SIMPLE	t4	ref	a	a	5	test.t1.a	2	Using where
+DROP TABLE t1, t2, t3, t4;
+CREATE TABLE t1( a INT );
+CREATE TABLE t2( a INT PRIMARY KEY );
+CREATE TABLE t3( a INT, INDEX( a ) );
+INSERT INTO t1( a ) VALUES ( 2 );
+INSERT INTO t1( a ) VALUES ( 2 );
+INSERT INTO t2( a ) VALUES ( 1 );
+INSERT INTO t2( a ) VALUES ( 2 );
+INSERT INTO t3( a ) VALUES ( 6 );
+INSERT INTO t3( a ) VALUES ( 5 );
+INSERT INTO t3( a ) VALUES ( 2 );
+EXPLAIN 
+SELECT t2.* FROM t2, t1, t3 WHERE t2.a = t1.a AND t1.a = t3.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	eq_ref	PRIMARY	PRIMARY	4	test.t1.a	1	Using index; Shortcut(t1)
+1	SIMPLE	t3	ref	a	a	5	test.t2.a	2	Using where; Using index
+SELECT t2.* FROM t2, t1, t3 WHERE t2.a = t1.a AND t1.a = t3.a;
+a
+2
+2
+DROP TABLE t1, t2, t3;
+CREATE TABLE t1 ( a INT, b INT, c INT );
+INSERT INTO t1 VALUES (1, 1, 2), (-1, -1, -1);
+CREATE TABLE t2 ( a INT, b INT, KEY( a ), KEY( b ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+CREATE TABLE t3 ( a INT, KEY( a ) );
+INSERT INTO t3 VALUES (2), (2), (2), (2), (2);
+INSERT INTO t3 SELECT * FROM t3;
+INSERT INTO t3 SELECT * FROM t3;
+INSERT INTO t3 SELECT * FROM t3;
+CREATE TABLE t4 ( a INT, KEY( a ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+FLUSH STATUS;
+EXPLAIN 
+SELECT * FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.c = t4.a;
+id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
+1	SIMPLE	t1	ALL	NULL	NULL	NULL	NULL	2	
+1	SIMPLE	t2	ALL	a,b	NULL	NULL	NULL	5	Using where; Using join buffer
+1	SIMPLE	t3	ref	a	a	5	test.t2.b	4	Using index; Shortcut(t1)
+1	SIMPLE	t4	ref	a	a	5	test.t1.c	9	Using index
+SELECT * FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.c = t4.a;
+a	b	c	a	b	a	a
+SHOW STATUS LIKE 'handler_read_%';
+Variable_name	Value
+Handler_read_first	0
+Handler_read_key	2
+Handler_read_next	0
+Handler_read_prev	0
+Handler_read_rnd	0
+Handler_read_rnd_next	4
+DROP TABLE t1, t2, t3, t4;
+SET explain_shortcuts = 0;

=== added file 'mysql-test/t/short-cut.test'
--- a/mysql-test/t/short-cut.test	1970-01-01 00:00:00 +0000
+++ b/mysql-test/t/short-cut.test	2008-06-27 09:17:42 +0000
@@ -0,0 +1,344 @@
+#
+# wl3724: Short-Cutting Join Execution: Speeding up star queries
+#
+
+# This flag will be removed
+SET explain_shortcuts = 1;
+
+#
+# Test 1
+# Tests of EXPLAIN.
+#
+CREATE TABLE t1 (
+  a INT,
+  b INT
+);
+
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+
+CREATE TABLE t2 (
+  a INT,
+  KEY( a )
+);
+INSERT INTO t2 VALUES (1), (1), (1), (1), (1);
+
+CREATE TABLE t3 (
+  b INT,
+  KEY( b )
+);
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1), (1);
+
+EXPLAIN SELECT * FROM t1 JOIN t2 USING( a ) JOIN t3 USING( b );
+
+SELECT * FROM t1 JOIN t2 USING( a ) JOIN t3 USING( b );
+
+# Test that the shortcut does not cross an outer join boundary
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( t2, t3 ) USING( b );
+EXPLAIN SELECT * FROM t1 LEFT JOIN ( t2 JOIN t3 ON a = b ) USING( b );
+
+# Test that the optimization is robust wrt reordering of tables
+EXPLAIN SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t1.b = t3.b;
+EXPLAIN SELECT * FROM t1, t3, t2 WHERE t1.a = t2.a AND t1.b = t3.b;
+
+EXPLAIN SELECT * FROM t2, t1, t3 WHERE t1.a = t2.a AND t1.b = t3.b;
+EXPLAIN SELECT * FROM t2, t3, t1 WHERE t1.a = t2.a AND t1.b = t3.b;
+
+EXPLAIN SELECT * FROM t3, t1, t2 WHERE t1.a = t2.a AND t1.b = t3.b;
+EXPLAIN SELECT * FROM t3, t2, t1 WHERE t1.a = t2.a AND t1.b = t3.b;
+
+DROP TABLE t1, t2, t3;
+
+
+# Test 2 
+# That the optimization actually works, i.e. that unneccesary reads are
+# indeed shortcut.
+
+# Test 2a: We have two tables between giving and receiving end of short-cut:
+# t2 and t3.
+CREATE TABLE t1 ( a INT, b INT );
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+
+CREATE TABLE t2 ( a INT, b INT, KEY( a ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+INSERT INTO t2 SELECT * FROM t2;
+
+CREATE TABLE t3 ( b INT, KEY( b ) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t3 SELECT * FROM t3;
+
+CREATE TABLE t4 ( b INT, KEY( b ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+
+FLUSH STATUS;
+
+EXPLAIN
+SELECT * FROM t1 JOIN t2 USING(a) JOIN t3 ON t2.a = t3.b JOIN t4 ON t1.b = t4.b;
+
+SELECT * FROM t1 JOIN t2 USING(a) JOIN t3 ON t2.a = t3.b JOIN t4 ON t1.b = t4.b;
+
+SHOW STATUS LIKE 'handler_read_%';
+
+DROP TABLE t1, t2, t3, t4;
+
+# Test 2b
+# Short-cuts followed by successful keys. This tests that we perform the
+# optimal number of reads after the shortcut is taken without missing tuples.
+CREATE TABLE t1 ( a INT );
+INSERT INTO t1 VALUES (1), (2), (3), (4), (5);
+
+CREATE TABLE t2 ( a INT, KEY (a) );
+INSERT INTO t2 VALUES (1), (2), (2), (2), (2), (2), (3), (4), (4), (4), (5);
+
+CREATE TABLE t3 ( a INT, KEY (a) );
+INSERT INTO t3 VALUES (1), (3), (5), (-1), (-1), (-1), (-1), (-1), (-1);
+
+FLUSH STATUS;
+
+EXPLAIN
+SELECT STRAIGHT_JOIN * FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.a = t3.a;
+SELECT STRAIGHT_JOIN * FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.a = t3.a;
+
+SHOW STATUS LIKE 'handler_read_%';
+
+DROP TABLE t1, t2, t3;
+
+# Test 3.
+# Test for outer joins. We may not take shortcuts across outer join
+# boundaries. An outer join boundary goes before the first inner table for an
+# outer join.
+CREATE TABLE t1 ( a INT, b INT );
+INSERT INTO t1 VALUES (1, 2), (-1, -1);
+
+CREATE TABLE t2 ( a INT, b INT, KEY( a ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+INSERT INTO t2 SELECT * FROM t2;
+
+CREATE TABLE t3 ( b INT, KEY( b ) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t3 SELECT * FROM t3;
+
+CREATE TABLE t4 ( b INT, KEY( b ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+
+CREATE TABLE t5 ( b INT, KEY( b ) );
+INSERT INTO t5 VALUES (1), (1), (1), (1), (1), (1), (1), (1);
+INSERT INTO t5 SELECT * FROM t5;
+
+# Test of two outer join boundaries in one join.
+EXPLAIN
+SELECT *
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING ( a )
+        LEFT JOIN t4 ON t1.a = t4.b;
+
+EXPLAIN
+SELECT * 
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING(a)
+        LEFT JOIN (t4 JOIN t5 USING( b )) ON t1.a = t4.b;
+
+
+# Test 3: That outer join boundaries are not crossed even with more than one
+# inner table in the outer join.
+EXPLAIN 
+SELECT * 
+FROM t1 LEFT JOIN (t2 JOIN t3 ON t2.a = t3.b) USING(a), t4
+WHERE t1.a = t4.b;
+
+DROP TABLE t1, t2, t3, t4, t5;
+
+# Test 3: Test for outer joins. We may not take shortcuts across outer join
+# boundaries. An outer join boundary goes before the first inner table for an
+# outer join.
+
+CREATE TABLE t1( 
+  a INT,
+  b INT,
+  KEY ( b )
+);
+INSERT INTO t1 VALUES (2, 1),  (2, 2);
+
+CREATE TABLE t2 (
+  c INT,
+  KEY ( c )
+);
+INSERT INTO t2 VALUES (1);
+
+CREATE TABLE t3(
+  d INT,
+  KEY ( d )
+);
+INSERT INTO t3 VALUES (1), (2), (3);
+
+# This tests the case where we have a left outer join, followed by a table
+# that is inner joined with the first table.
+#
+# In this query, the join predicate t1.b = t3.d is transferred to the WHERE 
+# clause, since the join operation to which it is attached is not an
+# outer join.
+# Normally, we wouldn''t have a problem with outer joins since join conditions
+# are not transferred to the WHERE clause in this case.
+#
+# t2 is the first (and only) inner table of the outer join.
+EXPLAIN 
+SELECT * FROM t1 LEFT JOIN t2 ON a = c JOIN t3 ON b = d;
+SELECT * FROM t1 LEFT JOIN t2 ON a = c JOIN t3 ON b = d;
+
+DROP TABLE t1, t2, t3;
+
+# Test with more than one inner table
+
+CREATE TABLE t1 (
+  a INT,
+  KEY ( A )
+);
+INSERT INTO t1 VALUES (1), (2), (3), (13);
+
+CREATE TABLE t2 (
+  a INT,
+  KEY ( A )
+);
+INSERT INTO t2 VALUES (1), (2);
+
+CREATE TABLE t3 (
+  a INT,
+  KEY ( A )
+);
+INSERT INTO t3 VALUES (1), (2), (3);
+
+CREATE TABLE t4 (
+  a INT,
+  KEY ( A )
+);
+INSERT INTO t4 VALUES (1), (2), (3);
+
+EXPLAIN SELECT STRAIGHT_JOIN t1.a, t2.a, t3.a, t4.a 
+FROM t1 LEFT JOIN (t2 JOIN t3 USING(a)) ON t1.a=t3.a, t4 
+WHERE t4.a=t1.a;
+
+DROP TABLE t1, t2, t3, t4;
+
+# Test 3. Tests that we do not miss any data due to shortcutting.
+
+CREATE TABLE t1 ( a INT, b INT );
+CREATE TABLE t2 ( a INT, b INT, PRIMARY KEY (a,b) );
+CREATE TABLE t3 ( a INT, b INT, PRIMARY KEY (a,b) );
+
+INSERT INTO t1 VALUES ( 1, 1 ), ( 2, 1 ), ( 1, 3 );
+INSERT INTO t2 VALUES ( 1, 1 ), ( 2, 2 ), ( 3, 3 );
+INSERT INTO t3 VALUES ( 1, 1 ), ( 2, 1 ), ( 1, 3 );
+
+# Test of the short-cut detection algorithm. When traversing the push-down
+# conditions the algorithm will first find that the prospective short-cut
+# t3->t1, but this must be invalidated due to the later found dependency t3->t2.
+EXPLAIN 
+SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.b = t3.b;
+SELECT * FROM t1, t2, t3 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.b = t3.b;
+
+# Test of internal representation of pushdown conditions.
+# The join conditions will be encoded in ref access, while the WHERE
+# expression remains in the pushdown condition.
+EXPLAIN 
+SELECT * 
+FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.b = t3.a 
+WHERE t3.b + 1 = t2.b;
+
+# Short-cut is actually applicable in this case, thanks to equality propagation.
+# The WHERE condition t3.a + 1 = t2.a is rewritten into t1.b + 1 = t1.a, and
+# hence we have an optimizable star query.
+EXPLAIN 
+SELECT * 
+FROM t1 JOIN t2 ON t1.a = t2.a JOIN t3 ON t1.b = t3.a 
+WHERE t3.a + 1 = t2.a;
+
+DROP TABLE t1, t2, t3;
+
+# Test 3b
+CREATE TABLE t1 ( a INT );
+INSERT INTO t1 VALUES (1), (1), (1), (1);
+
+CREATE TABLE t2 ( a INT, KEY (a), b INT, c INT );
+INSERT INTO t2 VALUES (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1), (1, 1, 1);
+INSERT INTO t2 SELECT * FROM t2;
+INSERT INTO t2 SELECT * FROM t2;
+
+CREATE TABLE t3 ( a INT, KEY (a) );
+INSERT INTO t3 VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1), (3);
+
+CREATE TABLE t4 ( a INT, KEY (a), b INT );
+INSERT INTO t4 VALUES (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1);
+INSERT INTO t4 SELECT * FROM t4;
+
+#
+# Test that the shortcut t4->t1 is rejected in favor of t4->t2.
+#
+EXPLAIN
+SELECT STRAIGHT_JOIN * 
+FROM t1 JOIN t2 ON t1.a = t2.a 
+        JOIN t3 ON t2.b = t3.a
+        JOIN t4 ON t1.a = t4.a AND t2.c = t4.b;
+
+DROP TABLE t1, t2, t3, t4;
+
+# Test 4. Test of execution step.
+# In this test we are testing that a shortcut is properly separated from
+# the case of a normal 'end of records' state on the last table in the plan.
+# This particular case gets hit only when
+# - The optimizer reorders the tables
+# - We have a SELECT <table>.*
+CREATE TABLE t1( a INT );
+CREATE TABLE t2( a INT PRIMARY KEY );
+CREATE TABLE t3( a INT, INDEX( a ) );
+
+INSERT INTO t1( a ) VALUES ( 2 );
+INSERT INTO t1( a ) VALUES ( 2 );
+
+INSERT INTO t2( a ) VALUES ( 1 );
+INSERT INTO t2( a ) VALUES ( 2 );
+
+INSERT INTO t3( a ) VALUES ( 6 );
+INSERT INTO t3( a ) VALUES ( 5 );
+INSERT INTO t3( a ) VALUES ( 2 );
+
+EXPLAIN 
+SELECT t2.* FROM t2, t1, t3 WHERE t2.a = t1.a AND t1.a = t3.a;
+SELECT t2.* FROM t2, t1, t3 WHERE t2.a = t1.a AND t1.a = t3.a;
+
+DROP TABLE t1, t2, t3;
+
+# Test 5.
+# Test that a short-cut may still cross a plan node using join buffering.
+CREATE TABLE t1 ( a INT, b INT, c INT );
+INSERT INTO t1 VALUES (1, 1, 2), (-1, -1, -1);
+
+CREATE TABLE t2 ( a INT, b INT, KEY( a ), KEY( b ) );
+INSERT INTO t2 VALUES (1, 2),(1, 2),(1, 2),(1, 2),(1, 2);
+
+CREATE TABLE t3 ( a INT, KEY( a ) );
+INSERT INTO t3 VALUES (2), (2), (2), (2), (2);
+INSERT INTO t3 SELECT * FROM t3;
+INSERT INTO t3 SELECT * FROM t3;
+INSERT INTO t3 SELECT * FROM t3;
+
+CREATE TABLE t4 ( a INT, KEY( a ) );
+INSERT INTO t4 VALUES (1), (1), (1), (1), (1);
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+INSERT INTO t4 SELECT * FROM t4;
+
+FLUSH STATUS;
+
+EXPLAIN 
+SELECT * FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.c = t4.a;
+SELECT * FROM t1, t2, t3, t4 WHERE t1.a = t2.a AND t2.b = t3.a AND t1.c = t4.a;
+
+SHOW STATUS LIKE 'handler_read_%';
+
+DROP TABLE t1, t2, t3, t4;
+
+SET explain_shortcuts = 0;

=== modified file 'sql/item.h'
--- a/sql/item.h	2008-05-22 18:40:15 +0000
+++ b/sql/item.h	2008-06-27 09:17:42 +0000
@@ -741,7 +741,18 @@ public:
   { return val_decimal(val); }
   virtual bool val_bool_result() { return val_bool(); }
 
-  /* bit map of tables used by item */
+  /**
+     @brief Bitmap of tables used by item. 
+
+     Conceptually, this is the set of tables directly referenced by an Item.
+     
+     For an Item_field, the set contains simply the table to which the field
+     belongs.
+
+     For pushdown conditions, used_tables is the set tables referenced by the
+     pushdown condition recursively, *including* the table to which the
+     condition is pushed.
+  */
   virtual table_map used_tables() const { return (table_map) 0L; }
   /*
     Return table map of tables that can't be NULL tables (tables that are

=== modified file 'sql/item_func.cc'
--- a/sql/item_func.cc	2008-04-28 23:00:37 +0000
+++ b/sql/item_func.cc	2008-06-27 09:17:42 +0000
@@ -85,7 +85,7 @@ void Item_func::set_arguments(List<Item>
 }
 
 Item_func::Item_func(List<Item> &list)
-  :allowed_arg_cols(1)
+  :allowed_arg_cols(1), used_tables_cache(0)
 {
   set_arguments(list);
 }

=== modified file 'sql/item_func.h'
--- a/sql/item_func.h	2008-03-27 19:02:15 +0000
+++ b/sql/item_func.h	2008-06-27 09:17:42 +0000
@@ -61,26 +61,26 @@ public:
   enum Type type() const { return FUNC_ITEM; }
   virtual enum Functype functype() const   { return UNKNOWN_FUNC; }
   Item_func(void):
-    allowed_arg_cols(1), arg_count(0)
+    allowed_arg_cols(1), arg_count(0), used_tables_cache(0)
   {
     with_sum_func= 0;
   }
   Item_func(Item *a):
-    allowed_arg_cols(1), arg_count(1)
+    allowed_arg_cols(1), arg_count(1), used_tables_cache(0)
   {
     args= tmp_arg;
     args[0]= a;
     with_sum_func= a->with_sum_func;
   }
   Item_func(Item *a,Item *b):
-    allowed_arg_cols(1), arg_count(2)
+    allowed_arg_cols(1), arg_count(2), used_tables_cache(0)
   {
     args= tmp_arg;
     args[0]= a; args[1]= b;
     with_sum_func= a->with_sum_func || b->with_sum_func;
   }
   Item_func(Item *a,Item *b,Item *c):
-    allowed_arg_cols(1)
+    allowed_arg_cols(1), used_tables_cache(0)
   {
     arg_count= 0;
     if ((args= (Item**) sql_alloc(sizeof(Item*)*3)))
@@ -91,7 +91,7 @@ public:
     }
   }
   Item_func(Item *a,Item *b,Item *c,Item *d):
-    allowed_arg_cols(1)
+    allowed_arg_cols(1), used_tables_cache(0)
   {
     arg_count= 0;
     if ((args= (Item**) sql_alloc(sizeof(Item*)*4)))
@@ -103,7 +103,7 @@ public:
     }
   }
   Item_func(Item *a,Item *b,Item *c,Item *d,Item* e):
-    allowed_arg_cols(1)
+    allowed_arg_cols(1), used_tables_cache(0)
   {
     arg_count= 5;
     if ((args= (Item**) sql_alloc(sizeof(Item*)*5)))

=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc	2008-06-11 23:16:53 +0000
+++ b/sql/mysqld.cc	2008-06-27 09:17:42 +0000
@@ -7701,6 +7701,7 @@ static void mysql_init_variables(void)
   max_system_variables.ndb_index_stat_cache_entries=~0L;
   global_system_variables.ndb_index_stat_update_freq=20;
   max_system_variables.ndb_index_stat_update_freq=~0L;
+  max_system_variables.explain_shortcuts=1;
 #ifdef HAVE_OPENSSL
   have_ssl=SHOW_OPTION_YES;
 #else

=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc	2008-05-21 10:17:29 +0000
+++ b/sql/set_var.cc	2008-06-27 09:17:42 +0000
@@ -247,6 +247,7 @@ static sys_var_event_scheduler sys_event
 
 static sys_var_long_ptr	sys_expire_logs_days(&vars, "expire_logs_days",
 					     &expire_logs_days);
+static sys_var_thd_ulong sys_explain_shortcuts(&vars, "explain_shortcuts", &SV::explain_shortcuts);
 static sys_var_bool_ptr	sys_flush(&vars, "flush", &myisam_flush);
 static sys_var_long_ptr	sys_flush_time(&vars, "flush_time", &flush_time);
 static sys_var_str             sys_ft_boolean_syntax(&vars, "ft_boolean_syntax",

=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h	2008-05-22 18:40:15 +0000
+++ b/sql/sql_class.h	2008-06-27 09:17:42 +0000
@@ -441,7 +441,7 @@ struct system_variables
   DATE_TIME_FORMAT *datetime_format;
   DATE_TIME_FORMAT *time_format;
   my_bool sysdate_is_now;
-
+  ulong explain_shortcuts;
 };
 
 
@@ -515,7 +515,7 @@ typedef struct system_status_var
     sense to add to the /global/ status variable counter.
   */
   double last_query_cost;
-
+  ulong explain_shortcuts;
 
 } STATUS_VAR;
 

=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc	2008-05-31 07:14:57 +0000
+++ b/sql/sql_select.cc	2008-06-27 09:17:42 +0000
@@ -234,6 +234,7 @@ static bool test_if_ref(Item_field *left
 static bool replace_where_subcondition(JOIN *join, TABLE_LIST *emb_nest, 
                                        Item *old_cond, Item *new_cond,
                                        bool do_fix_fields);
+static void initialize_shortcuts(JOIN *join);
 
 /*
   This is used to mark equalities that were made from i-th IN-equality.
@@ -2985,6 +2986,7 @@ mysql_select(THD *thd, Item ***rref_poin
   if (thd->is_error())
     goto err;
 
+  initialize_shortcuts(join);
   join->exec();
 
   if (thd->cursor && thd->cursor->is_open())
@@ -6855,6 +6857,16 @@ cache_record_length(JOIN *join,uint idx)
   return length;
 }
 
+/**
+   @brief Reads the first record from the table belonging to this JOIN_TAB.
+ */  
+int JOIN_TAB::do_read_first_record() {
+  join->last_join_tab_accessed= this;
+  int error= (*read_first_record)(this);
+  has_partial_tuple= !error;
+  return error;
+}
+
 
 /*
   Get the number of different row combinations for subset of partial join
@@ -10913,6 +10925,85 @@ optimize_cond(JOIN *join, COND *conds, L
   DBUG_RETURN(conds);
 }
 
+/**
+   @brief Pre-computes short-cuts for the join short-cut optimization.
+
+   @details This function initialized the field JOIN_TAB::join_shortcut
+   whenever short-cut optimization is applicable. The algorithm is implemented
+   as a nested loop, where the outer loop walks the JOIN_TAB's in the plan,
+   and the inner loop iterates through all tables that this table depends
+   upon. Valid short-cuts have the following property.
+
+   - The short-cut is of minimum length of 2. If a table is dependent on the
+     table directly preceding it, the short-cut would be of length 1, and it
+     would indicate that there are no valid short-cuts from this table.
+
+   - The short-cut does not cross an outer join boundary. In this case the
+     optimization is not applicable, because a valid result would be missed.
+
+   - If there are several applicable shortcuts by the above requirements, the
+     shortest one is chosen.
+
+   @see JOIN_TAB::join_shortcut.
+ */
+static void initialize_shortcuts(JOIN *join) {
+
+  if(!join->join_tab)
+    return;
+  /* 
+     We start searching for short-cuts on the third table, since the minimum
+     length of a short-cut must be 2 steps.
+  */
+  for (uint i= 0; i < join->tables && i < 2; i++)
+    join->join_tab[i].join_shortcut= NULL;
+
+  for (uint i= 2; i < join->tables; i++)
+  {
+    JOIN_TAB *join_tab= &join->join_tab[i];
+    join_tab->join_shortcut= NULL;
+
+    const table_map select_cond_tables= join_tab->select_cond ? 
+      join_tab->select_cond->used_tables() : (table_map)0L;
+
+    const table_map all_depenencies= 
+      (join_tab->ref.depend_map | select_cond_tables) & ~join_tab->table->map;
+
+    if (all_depenencies == 0)
+      continue;
+
+    /* We iterate through the set by sliding a mask over the bitmap */
+    uint i;
+    table_map mask;
+    for (mask= 1, i= 0; mask <= all_depenencies; mask <<=1, ++i)
+    {
+      if (!(mask & all_depenencies))
+        continue;
+
+      JOIN_TAB *dependency= join->map2table[i];
+      
+      if (join_tab > dependency) {
+        bool crosses_outer_join= FALSE;
+        for (JOIN_TAB *jt= dependency; jt < join_tab; ++jt)
+          if (jt->last_inner)
+          {
+            crosses_outer_join= TRUE;
+            break;
+          }
+
+        if (!crosses_outer_join &&
+            (!join_tab->join_shortcut || 
+             join_tab->join_shortcut < dependency) &&
+            join_tab - dependency > 1)
+        join_tab->join_shortcut= dependency;
+      }
+
+      if (dependency == join_tab - 1)
+        join_tab->join_shortcut= NULL;
+
+    }
+  }
+}
+
 
 /**
   Remove const and eq items.
@@ -13442,6 +13533,25 @@ sub_select_cache(JOIN *join,JOIN_TAB *jo
 */
 int do_sj_reset(SJ_TMP_TABLE *sj_tbl);
 
+/**
+   @brief Helper function for join the short-cut optimization.
+
+   @details The Nested-loops join implementation is implemented as a number of
+   functions, recursing together. Hence in order to pass control backwards in
+   the plan, the @c return statement is used. But at each recursion level the
+   algorithm has to know whether it is in the middle of short-cut or not.
+
+   @see sub_select, sub_select_cache, flush_cached_records
+ */
+static bool execution_is_within_shortcut(JOIN *join, JOIN_TAB *join_tab)
+{
+  return 
+    (uint)(join_tab - join->join_tab) < join->tables - 1 &&
+    join->last_join_tab_accessed->join_shortcut &&
+    !join->last_join_tab_accessed->has_partial_tuple &&
+    join->last_join_tab_accessed->join_shortcut != join_tab;
+}
+
 enum_nested_loop_state
 sub_select(JOIN *join,JOIN_TAB *join_tab,bool end_of_records)
 {
@@ -13486,8 +13596,12 @@ sub_select(JOIN *join,JOIN_TAB *join_tab
     }
     join->thd->row_count= 0;
 
-    error= (*join_tab->read_first_record)(join_tab);
+    join->last_join_tab_accessed= join_tab;
+    error= join_tab->do_read_first_record();
     rc= evaluate_join_record(join, join_tab, error);
+
+    if (execution_is_within_shortcut(join, join_tab))
+      return NESTED_LOOP_OK;
   }
   
   /* 
@@ -13514,6 +13628,10 @@ sub_select(JOIN *join,JOIN_TAB *join_tab
     }
 
     rc= evaluate_join_record(join, join_tab, error);
+
+    if (execution_is_within_shortcut(join, join_tab))
+      return NESTED_LOOP_OK;
+
   }
 
   if (rc == NESTED_LOOP_NO_MORE_ROWS &&
@@ -13886,6 +14004,8 @@ flush_cached_records(JOIN *join,JOIN_TAB
               !(res= do_sj_dups_weedout(join->thd, join_tab->check_weed_out_table)))
           {
             rc= (join_tab->next_select)(join,join_tab+1,0);
+            if (execution_is_within_shortcut(join, join_tab))
+              return NESTED_LOOP_OK;
             if (rc != NESTED_LOOP_OK && rc != NESTED_LOOP_NO_MORE_ROWS)
             {
               reset_cache_write(&join_tab->cache);
@@ -18545,6 +18665,31 @@ void JOIN::clear()
 }
 
 /**
+   @brief Append information about the short-cut optimization to EXPLAIN
+   output, if it is used for the current plan node.
+
+   @param join     The execution plan.
+   @param join_tab The plan node to be annotated.
+   @param extra    The current content of the 'extra' column in EXPLAIN.
+*/
+static void append_shortcut_to_explain(JOIN* join, JOIN_TAB *join_tab, 
+                                       String *extra)
+{
+  JOIN_TAB *next_tab= join_tab + 1;
+  if (next_tab >= (join->join_tab + join->tables)) 
+    // i.e. there is no next join_tab
+    return;
+
+  if (next_tab->join_shortcut && join->thd->variables.explain_shortcuts)
+  {
+    extra->append(STRING_WITH_LEN("; Shortcut("));
+    extra->append(next_tab->join_shortcut->table->alias);
+    extra->append(STRING_WITH_LEN(")")); 
+  }
+}
+
+
+/**
   EXPLAIN handling.
 
   Send a description about what how the select will be done to stdout.
@@ -18885,6 +19030,7 @@ void select_describe(JOIN *join, bool ne
           extra.append(STRING_WITH_LEN("; Using where"));
         if (tab->packed_info & TAB_INFO_FULL_SCAN_ON_NULL)
           extra.append(STRING_WITH_LEN("; Full scan on NULL key"));
+        append_shortcut_to_explain(join, tab, &extra);
         /* Skip initial "; "*/
         const char *str= extra.ptr();
         uint32 len= extra.length();
@@ -19029,6 +19175,7 @@ void select_describe(JOIN *join, bool ne
 
         if (i > 0 && tab[-1].next_select == sub_select_cache)
           extra.append(STRING_WITH_LEN("; Using join buffer"));
+        append_shortcut_to_explain(join, tab, &extra);
 
         /* Skip initial "; "*/
         const char *str= extra.ptr();

=== modified file 'sql/sql_select.h'
--- a/sql/sql_select.h	2008-05-31 07:14:57 +0000
+++ b/sql/sql_select.h	2008-06-27 09:17:42 +0000
@@ -184,6 +184,17 @@ typedef struct st_join_table {
   TABLE		*table;
   KEYUSE	*keyuse;			/**< pointer to first used key */
   SQL_SELECT	*select;
+  /**
+     @brief This field corresponds somewhat to a pushdown condition. It may
+     contain more than just those conditions pushed to the actual table,
+     however.
+     
+     @note For the first table in the plan there is usually a select_cond
+     attached but it is not initialized.
+     
+     @note Not all pushdown conditions are found in this field. @c ref access
+     conditions are removed from here and are encoded in JOIN_TAB::ref.
+   */
   COND		*select_cond;
   QUICK_SELECT_I *quick;
   /* 
@@ -202,7 +213,31 @@ typedef struct st_join_table {
   st_join_table *last_inner;    /**< last table table for embedding outer join */
   st_join_table *first_upper;  /**< first inner table for embedding outer join */
   st_join_table *first_unmatched; /**< used for optimization purposes only     */
-  
+
+  /**
+     @brief Used for join short-cut optimization.
+     
+     @details This field is updated in initialize_shortcuts(), right before
+     execution. The requirement for a join short-cut from a table @i t is that:
+     
+     - There is no predicate expression (in either @c WHERE clause or join
+       conditions) connecting the table with the table immediately preceding
+       it in the query execution plan.
+
+     - The goal of the short-cut is the last table in the plan to which
+       @i t is connected.
+  */
+  st_join_table *join_shortcut;
+
+  /**
+     @brief True if the last read operation succeeded for this JOIN_TAB.
+
+     @details This field is updated during nested-loops join execution as soon
+     as a read operation is performed. It is set to @c true if the operation
+     succeeded.
+   */
+  bool has_partial_tuple;
+
   /* Special content for EXPLAIN 'Extra' column or NULL if none */
   const char	*info;
   /* 
@@ -314,6 +349,9 @@ typedef struct st_join_table {
             (select->quick->get_type() ==
              QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX));
   }
+  
+  int do_read_first_record();
+
 } JOIN_TAB;
 
 enum_nested_loop_state sub_select_cache(JOIN *join, JOIN_TAB *join_tab, bool
@@ -427,6 +465,15 @@ public:
   JOIN_TAB *join_tab,**best_ref;
   JOIN_TAB **map2table;    ///< mapping between table indexes and JOIN_TABs
   JOIN_TAB *join_tab_save; ///< saved join_tab for subquery reexecution
+  /**
+     @brief During execution this field contains the JOIN_TAB of the table
+     that was last read from.
+
+     @details This field is kept updated during nested-loops join execution
+     and is set right before reading from the table occurs. Hence it is
+     updated even if the reading caused an error.
+   */
+  JOIN_TAB *last_join_tab_accessed;
   TABLE    **table,**all_tables;
   /**
     The table which has an index that allows to produce the requried ordering.
@@ -692,6 +739,7 @@ public:
     return (unit == &thd->lex->unit && (unit->fake_select_lex == 0 ||
                                         select_lex == unit->fake_select_lex));
   }
+
 };
 
 

=== modified file 'sql/structs.h'
--- a/sql/structs.h	2008-05-31 07:14:57 +0000
+++ b/sql/structs.h	2008-06-27 09:17:42 +0000
@@ -121,6 +121,38 @@ class THD;
 class handler;
 struct st_join_table;
 
+/**
+   @brief Structure responsible for retrieving data during query execution.
+   
+   The READ_RECORD could be called a "Record Reader", and acts as 
+   
+   - An abstraction layer beneath the query execution engine. The only read
+   operation the execution engine has to know of is reading the next
+   record. Beneath the abstraction layer is the storage engine API, which is
+   invoked differently for table/index scans and key lookups, and the join
+   buffer.
+
+   - An executable program, resulting from the optimizer. It is set up with a
+   reference to a handler and a function pointer to a read function during
+   query optimization.
+
+   The abstraction is not complete. For instace, reading the first record from
+   a handler is done through a function pointer in the JOIN_TAB rather than in
+   its READ_RECORD.
+
+   Sometimes the READ_RECORD is initialized during query execution.
+   
+   - It is reinitialized during buffered nested loops join, before the buffer is
+     flushed.
+
+   - For many table accesses, it is initialized right before the first record is
+     read.
+
+   The READ_RECORD design can be viewed as a strategy pattern, where the
+   abstract strategy is the READ_RECORD struct and the concrete strategy is
+   determined by the values of the function pointer READ_RECORD::read_record.
+   
+*/
 typedef struct st_read_record {			/* Parameter to read_record */
   struct st_table *table;			/* Head-form */
   handler *file;

=== modified file 'support-files/build-tags'
--- a/support-files/build-tags	2007-02-08 11:56:43 +0000
+++ b/support-files/build-tags	2008-06-27 09:17:42 +0000
@@ -2,7 +2,7 @@
 
 rm -f TAGS
 filter='\.cc$\|\.c$\|\.h$\|\.yy\|\.[ch]pp$'
-files=`bk -r sfiles -gU | grep $filter `
+files=`find . | grep $filter `
 for f in $files ;
 do
 	 etags -o TAGS --append $f

Thread
bzr commit into mysql-6.0 branch (mhansson:2652) WL#3724Martin Hansson27 Jun