List:Commits« Previous MessageNext Message »
From:Sergey Petrunia Date:July 26 2008 9:41pm
Subject:bzr push into mysql-6.0-opt-subqueries branch (sergefp:2678 to 2680) WL#3985
View as plain text  
 2680 Sergey Petrunia	2008-07-26
      Fix tree name
modified:
  .bzr-mysql/default.conf

 2679 Sergey Petrunia	2008-07-26
      WL#3985  Subquery optimization: smart choice between semi-join and materialization,
patch 2
      - Review1 changes: removed join optimization forking, switched to "late subquery
strategy detection"
      - Caught and fixed several bugs
      - Code cleanup
modified:
  mysql-test/r/group_by.result
  mysql-test/r/subselect.result
  mysql-test/r/subselect3.result
  mysql-test/r/subselect_no_mat.result
  mysql-test/r/subselect_no_opts.result
  mysql-test/r/subselect_no_semijoin.result
  mysql-test/r/subselect_sj2.result
  sql/item_subselect.h
  sql/mysql_priv.h
  sql/sql_select.cc
  sql/sql_select.h
  sql/sql_test.cc

 2678 Sergey Petrunia	2008-07-12
      WL#3985: Subquery optimization: smart choice between semi-join and materialization
modified:
  mysql-test/r/subselect.result
  mysql-test/r/subselect_mat.result
  mysql-test/r/subselect_no_mat.result
  mysql-test/r/subselect_no_opts.result
  mysql-test/r/subselect_no_semijoin.result
  mysql-test/r/subselect_sj2.result
  sql/handler.h
  sql/item_cmpfunc.h
  sql/item_subselect.h
  sql/sql_class.h
  sql/sql_select.cc
  sql/sql_select.h
  sql/sql_test.cc
  sql/structs.h
  sql/table.h

=== modified file '.bzr-mysql/default.conf'
--- a/.bzr-mysql/default.conf	2008-07-10 16:02:38 +0000
+++ b/.bzr-mysql/default.conf	2008-07-26 19:40:17 +0000
@@ -2,4 +2,4 @@
 tree_location = bzr+ssh://bk-internal.mysql.com/bzrroot/server/mysql-6.0-opt/
 post_commit_to = "commits@stripped"
 post_push_to = "commits@stripped"
-tree_name = "mysql-6.0-opt"
+tree_name = "mysql-6.0-opt-subqueries"

=== modified file 'mysql-test/r/group_by.result'
--- a/mysql-test/r/group_by.result	2008-04-01 15:13:57 +0000
+++ b/mysql-test/r/group_by.result	2008-07-26 19:37:13 +0000
@@ -1513,8 +1513,8 @@ id	select_type	table	type	possible_keys	
 EXPLAIN SELECT 1 FROM t1 WHERE a IN
 (SELECT a FROM t1 USE INDEX (i2) IGNORE INDEX (i2));
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	144	Start temporary
-1	PRIMARY	t1	eq_ref	PRIMARY,i2	PRIMARY	4	test.t1.a	1	Using index; End temporary
+1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	144	Materialize; Scan
+1	PRIMARY	t1	eq_ref	PRIMARY,i2	PRIMARY	4	test.t1.a	1	Using index
 CREATE TABLE t2 (a INT, b INT, KEY(a));
 INSERT INTO t2 VALUES (1, 1), (2, 2), (3,3), (4,4);
 EXPLAIN SELECT a, SUM(b) FROM t2 GROUP BY a LIMIT 2;
@@ -1526,8 +1526,8 @@ id	select_type	table	type	possible_keys	
 EXPLAIN SELECT 1 FROM t2 WHERE a IN
 (SELECT a FROM t1 USE INDEX (i2) IGNORE INDEX (i2));
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	144	Start temporary
-1	PRIMARY	t2	index	a	a	5	NULL	4	Using where; Using index; End temporary; Using join
buffer
+1	PRIMARY	t2	index	a	a	5	NULL	4	Using index
+1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	144	Using where; FirstMatch(t2)
 SHOW VARIABLES LIKE 'old';
 Variable_name	Value
 old	OFF

=== modified file 'mysql-test/r/subselect.result'
--- a/mysql-test/r/subselect.result	2008-07-12 19:26:05 +0000
+++ b/mysql-test/r/subselect.result	2008-07-26 19:37:13 +0000
@@ -1356,9 +1356,9 @@ a
 3
 explain extended select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index; Start temporary
-1	PRIMARY	t3	index	a	a	5	NULL	3	100.00	Using index; Using join buffer
-1	PRIMARY	t1	ref	a	a	10	test.t2.a,test.t3.a	116	100.00	Using index; End temporary
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index
+1	PRIMARY	t3	index	a	a	5	NULL	3	100.00	Using index
+1	PRIMARY	t1	ref	a	a	10	test.t2.a,test.t3.a	116	100.00	Using index; FirstMatch(t2)
 Warnings:
 Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1` join
`test`.`t3`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t1`.`b` =
`test`.`t3`.`a`))
 insert into t1 values (3,31);
@@ -2819,7 +2819,7 @@ Note	1003	select `test`.`t1`.`one` AS `o
 explain extended SELECT one,two from t1 where ROW(one,two) IN (SELECT one,two FROM t2
WHERE flag = 'N');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	
-1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; Materialize
+1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; FirstMatch(t1)
 Warnings:
 Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1`
semi join (`test`.`t2`) where ((`test`.`t2`.`two` = `test`.`t1`.`two`) and
(`test`.`t2`.`one` = `test`.`t1`.`one`) and (`test`.`t2`.`flag` = 'N'))
 explain extended SELECT one,two,ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = '0'
group by one,two) as 'test' from t1;
@@ -4215,8 +4215,8 @@ CREATE INDEX I1 ON t1 (a);
 CREATE INDEX I2 ON t1 (b);
 EXPLAIN SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 a	b
 CREATE TABLE t2 (a VARCHAR(1), b VARCHAR(10));
@@ -4225,15 +4225,15 @@ CREATE INDEX I1 ON t2 (a);
 CREATE INDEX I2 ON t2 (b);
 EXPLAIN SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; LooseScan
-1	PRIMARY	t2	ref	I2	I2	13	test.t2.a	2	Using index condition
+1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t2	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 a	b
 EXPLAIN
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 a	b
 DROP TABLE t1,t2;

=== modified file 'mysql-test/r/subselect3.result'
--- a/mysql-test/r/subselect3.result	2008-05-22 18:40:15 +0000
+++ b/mysql-test/r/subselect3.result	2008-07-26 19:37:13 +0000
@@ -99,7 +99,7 @@ oref	a
 1	1
 show status like '%Handler_read_rnd_next';
 Variable_name	Value
-Handler_read_rnd_next	11
+Handler_read_rnd_next	24
 delete from t2;
 insert into t2 values (NULL, 0),(NULL, 0), (NULL, 0), (NULL, 0);
 flush status;
@@ -337,8 +337,8 @@ dd	NULL	0
 bb	NULL	NULL
 select oref, a from t2 where a in (select ie from t1 where oref=t2.oref);
 oref	a
-aa	1
 ff	2
+aa	1
 select oref, a from t2 where a not in (select ie from t1 where oref=t2.oref);
 oref	a
 bb	2
@@ -421,8 +421,8 @@ dd	NULL	0
 bb	NULL	NULL
 select oref, a from t2 where a in (select ie from t1 where oref=t2.oref);
 oref	a
-aa	1
 ff	2
+aa	1
 select oref, a from t2 where a not in (select ie from t1 where oref=t2.oref);
 oref	a
 bb	2
@@ -515,8 +515,8 @@ aa	1	1	1
 dd	1	NULL	0
 select oref, a, b from t2 where (a,b) in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
-aa	1	1
 ff	2	2
+aa	1	1
 select oref, a, b from t2 where (a,b) not in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
 bb	2	1
@@ -560,8 +560,8 @@ aa	1	1	1
 dd	1	NULL	0
 select oref, a, b from t2 where (a,b) in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
-aa	1	1
 ff	2	2
+aa	1	1
 select oref, a, b from t2 where (a,b) not in (select ie1,ie2 from t1 where oref=t2.oref);
 oref	a	b
 bb	2	1

=== modified file 'mysql-test/r/subselect_no_mat.result'
--- a/mysql-test/r/subselect_no_mat.result	2008-07-12 19:26:05 +0000
+++ b/mysql-test/r/subselect_no_mat.result	2008-07-26 19:37:13 +0000
@@ -1360,9 +1360,9 @@ a
 3
 explain extended select * from t2 where t2.a in (select t1.a from t1,t3 where t1.b=t3.a);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
-1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index; Start temporary
-1	PRIMARY	t3	index	a	a	5	NULL	3	100.00	Using index; Using join buffer
-1	PRIMARY	t1	ref	a	a	10	test.t2.a,test.t3.a	116	100.00	Using index; End temporary
+1	PRIMARY	t2	index	a	a	5	NULL	4	100.00	Using index
+1	PRIMARY	t3	index	a	a	5	NULL	3	100.00	Using index
+1	PRIMARY	t1	ref	a	a	10	test.t2.a,test.t3.a	116	100.00	Using index; FirstMatch(t2)
 Warnings:
 Note	1003	select `test`.`t2`.`a` AS `a` from `test`.`t2` semi join (`test`.`t1` join
`test`.`t3`) where ((`test`.`t1`.`a` = `test`.`t2`.`a`) and (`test`.`t1`.`b` =
`test`.`t3`.`a`))
 insert into t1 values (3,31);
@@ -2823,7 +2823,7 @@ Note	1003	select `test`.`t1`.`one` AS `o
 explain extended SELECT one,two from t1 where ROW(one,two) IN (SELECT one,two FROM t2
WHERE flag = 'N');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	
-1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; Materialize
+1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; FirstMatch(t1)
 Warnings:
 Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1`
semi join (`test`.`t2`) where ((`test`.`t2`.`two` = `test`.`t1`.`two`) and
(`test`.`t2`.`one` = `test`.`t1`.`one`) and (`test`.`t2`.`flag` = 'N'))
 explain extended SELECT one,two,ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = '0'
group by one,two) as 'test' from t1;
@@ -4219,8 +4219,8 @@ CREATE INDEX I1 ON t1 (a);
 CREATE INDEX I2 ON t1 (b);
 EXPLAIN SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 a	b
 CREATE TABLE t2 (a VARCHAR(1), b VARCHAR(10));
@@ -4229,15 +4229,15 @@ CREATE INDEX I1 ON t2 (a);
 CREATE INDEX I2 ON t2 (b);
 EXPLAIN SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; LooseScan
-1	PRIMARY	t2	ref	I2	I2	13	test.t2.a	2	Using index condition
+1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t2	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 a	b
 EXPLAIN
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 a	b
 DROP TABLE t1,t2;

=== modified file 'mysql-test/r/subselect_no_opts.result'
--- a/mysql-test/r/subselect_no_opts.result	2008-07-12 19:26:05 +0000
+++ b/mysql-test/r/subselect_no_opts.result	2008-07-26 19:37:13 +0000
@@ -2823,7 +2823,7 @@ Note	1003	select `test`.`t1`.`one` AS `o
 explain extended SELECT one,two from t1 where ROW(one,two) IN (SELECT one,two FROM t2
WHERE flag = 'N');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	
-1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; Materialize
+1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; FirstMatch(t1)
 Warnings:
 Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1`
semi join (`test`.`t2`) where ((`test`.`t2`.`two` = `test`.`t1`.`two`) and
(`test`.`t2`.`one` = `test`.`t1`.`one`) and (`test`.`t2`.`flag` = 'N'))
 explain extended SELECT one,two,ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = '0'
group by one,two) as 'test' from t1;
@@ -4219,8 +4219,8 @@ CREATE INDEX I1 ON t1 (a);
 CREATE INDEX I2 ON t1 (b);
 EXPLAIN SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 a	b
 CREATE TABLE t2 (a VARCHAR(1), b VARCHAR(10));
@@ -4229,15 +4229,15 @@ CREATE INDEX I1 ON t2 (a);
 CREATE INDEX I2 ON t2 (b);
 EXPLAIN SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; LooseScan
-1	PRIMARY	t2	ref	I2	I2	13	test.t2.a	2	Using index condition
+1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t2	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 a	b
 EXPLAIN
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 a	b
 DROP TABLE t1,t2;

=== modified file 'mysql-test/r/subselect_no_semijoin.result'
--- a/mysql-test/r/subselect_no_semijoin.result	2008-07-12 19:26:05 +0000
+++ b/mysql-test/r/subselect_no_semijoin.result	2008-07-26 19:37:13 +0000
@@ -2823,7 +2823,7 @@ Note	1003	select `test`.`t1`.`one` AS `o
 explain extended SELECT one,two from t1 where ROW(one,two) IN (SELECT one,two FROM t2
WHERE flag = 'N');
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	filtered	Extra
 1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	8	100.00	
-1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; Materialize
+1	PRIMARY	t2	ALL	NULL	NULL	NULL	NULL	9	100.00	Using where; FirstMatch(t1)
 Warnings:
 Note	1003	select `test`.`t1`.`one` AS `one`,`test`.`t1`.`two` AS `two` from `test`.`t1`
semi join (`test`.`t2`) where ((`test`.`t2`.`two` = `test`.`t1`.`two`) and
(`test`.`t2`.`one` = `test`.`t1`.`one`) and (`test`.`t2`.`flag` = 'N'))
 explain extended SELECT one,two,ROW(one,two) IN (SELECT one,two FROM t2 WHERE flag = '0'
group by one,two) as 'test' from t1;
@@ -4219,8 +4219,8 @@ CREATE INDEX I1 ON t1 (a);
 CREATE INDEX I2 ON t1 (b);
 EXPLAIN SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1);
 a	b
 CREATE TABLE t2 (a VARCHAR(1), b VARCHAR(10));
@@ -4229,15 +4229,15 @@ CREATE INDEX I1 ON t2 (a);
 CREATE INDEX I2 ON t2 (b);
 EXPLAIN SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; LooseScan
-1	PRIMARY	t2	ref	I2	I2	13	test.t2.a	2	Using index condition
+1	PRIMARY	t2	index	I1	I1	4	NULL	2	Using index; Materialize; Scan
+1	PRIMARY	t2	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t2 WHERE b IN (SELECT a FROM t2);
 a	b
 EXPLAIN
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; LooseScan
-1	PRIMARY	t1	ref	I2	I2	13	test.t1.a	2	Using index condition
+1	PRIMARY	t1	index	I1	I1	2	NULL	2	Using where; Using index; Materialize; Scan
+1	PRIMARY	t1	ALL	I2	NULL	NULL	NULL	2	Using where; Using join buffer
 SELECT a,b FROM t1 WHERE b IN (SELECT a FROM t1 WHERE LENGTH(a)<500);
 a	b
 DROP TABLE t1,t2;

=== modified file 'mysql-test/r/subselect_sj2.result'
--- a/mysql-test/r/subselect_sj2.result	2008-07-12 19:26:05 +0000
+++ b/mysql-test/r/subselect_sj2.result	2008-07-26 19:37:13 +0000
@@ -97,8 +97,8 @@ set join_buffer_size= @save_join_buffer_
 set max_heap_table_size= @save_max_heap_table_size;
 explain select * from t1 where a in (select b from t2);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t2	index	b	b	5	NULL	10	Using index; Materialize; Scan
-1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	Using where; Using join buffer
+1	PRIMARY	t1	ALL	NULL	NULL	NULL	NULL	3	
+1	PRIMARY	t2	ref	b	b	5	test.t1.a	2	Using index; FirstMatch(t1)
 select * from t1;
 a	b
 1	1
@@ -265,8 +265,8 @@ explain select * 
 from t0 where a in
 (select t2.a+t3.a from t1 left join (t2 join t3) on t2.a=t1.a and t3.a=t1.a);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t0	ALL	NULL	NULL	NULL	NULL	10	Start temporary
-1	PRIMARY	t1	index	NULL	a	5	NULL	10	Using index; Using join buffer
+1	PRIMARY	t0	ALL	NULL	NULL	NULL	NULL	10	
+1	PRIMARY	t1	index	NULL	a	5	NULL	10	Using index; Start temporary
 1	PRIMARY	t2	ref	a	a	5	test.t1.a	1	Using index
 1	PRIMARY	t3	ref	a	a	5	test.t1.a	1	Using where; Using index; End temporary
 drop table t0, t1,t2,t3;
@@ -342,7 +342,7 @@ WHERE t1.Code IN (
 SELECT t2.CountryCode FROM t2 WHERE Population > 5000000);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
 1	PRIMARY	t1	ALL	PRIMARY	NULL	NULL	NULL	31	
-1	PRIMARY	t2	ALL	CountryCode	NULL	NULL	NULL	545	Using where; Materialize
+1	PRIMARY	t2	ref	CountryCode	CountryCode	3	test.t1.Code	18	Using where; FirstMatch(t1)
 SELECT Name FROM t1 
 WHERE t1.Code IN (
 SELECT t2.CountryCode FROM t2 WHERE Population > 5000000);
@@ -675,8 +675,8 @@ alter table t3 add primary key(id), add 
 The following must use loose index scan over t3, key a:
 explain select count(a) from t2 where a in ( SELECT  a FROM t3);
 id	select_type	table	type	possible_keys	key	key_len	ref	rows	Extra
-1	PRIMARY	t3	index	a	a	5	NULL	30000	Using index; LooseScan
-1	PRIMARY	t2	ref	a	a	5	test.t3.a	1	Using index
+1	PRIMARY	t2	index	a	a	5	NULL	1000	Using index
+1	PRIMARY	t3	ref	a	a	5	test.t2.a	30	Using index; FirstMatch(t2)
 select count(a) from t2 where a in ( SELECT  a FROM t3);
 count(a)
 1000

=== modified file 'sql/item_subselect.h'
--- a/sql/item_subselect.h	2008-07-12 19:26:05 +0000
+++ b/sql/item_subselect.h	2008-07-26 19:37:13 +0000
@@ -303,8 +303,18 @@ public:
      - (TABLE_LIST*)1 if the predicate is in the WHERE.
   */
   TABLE_LIST *expr_join_nest;
+  /*
+    Types of left_expr and subquery's select list allow to perform subquery
+    materialization. Currently, we set this to FALSE when it as well could
+    be TRUE. This is to be properly addressed with fix for BUG#36752.
+  */
   bool types_allow_materialization;
+
+  /* 
+    Same as above, but they also allow to scan the materialized table. 
+  */
   bool sjm_scan_allowed;
+
   /* The method chosen to execute the IN predicate.  */
   enum enum_exec_method {
     NOT_TRANSFORMED, /* No execution method was chosen for this IN. */

=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h	2008-07-10 16:02:38 +0000
+++ b/sql/mysql_priv.h	2008-07-26 19:37:13 +0000
@@ -1837,6 +1837,7 @@ void TEST_filesort(SORT_FIELD *sortorder
 void print_plan(JOIN* join,uint idx, double record_count, double read_time,
                 double current_read_time, const char *info);
 void print_keyuse_array(DYNAMIC_ARRAY *keyuse_array);
+void print_sjm(SJ_MATERIALIZE_INFO *sjm);
 #define EXTRA_DEBUG_DUMP_TABLE_LISTS
 #ifdef EXTRA_DEBUG_DUMP_TABLE_LISTS
 void dump_TABLE_LIST_graph(SELECT_LEX *select_lex, TABLE_LIST* tl);

=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc	2008-07-12 19:26:05 +0000
+++ b/sql/sql_select.cc	2008-07-26 19:37:13 +0000
@@ -64,10 +64,10 @@ static void set_position(JOIN *join,uint
 static bool create_ref_for_key(JOIN *join, JOIN_TAB *j, KEYUSE *org_keyuse,
 			       table_map used_tables);
 static bool choose_plan(JOIN *join, table_map join_tables);
-static void best_access_path(JOIN *join, JOIN_TAB *s, THD *thd,
-                             table_map remaining_tables, uint idx,
-                             bool disable_jbuf,
-                             double record_count, double read_time);
+static void best_access_path(JOIN  *join, JOIN_TAB *s, 
+                             table_map remaining_tables, uint idx, 
+                             bool disable_jbuf, double record_count,
+                             POSITION *pos, POSITION *loose_scan_pos);
 static void optimize_straight_join(JOIN *join, table_map join_tables);
 static bool greedy_search(JOIN *join, table_map remaining_tables,
                              uint depth, uint prune_level);
@@ -88,7 +88,7 @@ static bool find_best(JOIN *join,table_m
 		      double record_count,double read_time);
 static uint cache_record_length(JOIN *join,uint index);
 static double prev_record_reads(JOIN *join, uint idx, table_map found_ref);
-static bool get_best_combination(JOIN *join);
+static bool get_best_combination(JOIN *join, table_map join_tables);
 static store_key *get_store_key(THD *thd,
 				KEYUSE *keyuse, table_map used_tables,
 				KEY_PART_INFO *key_part, uchar *key_buff,
@@ -124,7 +124,8 @@ static uint build_bitmap_for_nested_join
 static 
 void advance_sj_state(JOIN *join, const table_map remaining_tables, 
                       const JOIN_TAB *s, uint idx, 
-                      double *current_record_count, double *current_read_time);
+                      double *current_record_count, double *current_read_time,
+                      POSITION *loose_scan_pos);
 
 static void restore_prev_sj_state(const table_map remaining_tables, 
                                   const JOIN_TAB *tab, uint idx);
@@ -270,10 +271,12 @@ bool subquery_types_allow_materializatio
 const char *subq_sj_cond_name=
   "0123456789ABCDEF0123456789abcdef0123456789ABCDEF0123456789abcdef-sj-cond";
 
+#if 0
 static bool bitmap_covers(const table_map x, const table_map y)
 {
   return !test(y & ~x);
 }
+#endif
 
 /**
   This handles SELECT with and without UNION.
@@ -829,8 +832,27 @@ err:
 /*
   Check if subquery's compared types allow materialization.
 
+  SYNOPSIS
+    subquery_types_allow_materialization()
+      thd               Thread handle
+      in_subs           Subquery predicate
+      scan_allowed  OUT If the return value is TRUE: 
+                          indicates whether it is possible to use subquery
+                          materialization and scan the materialized table
+                        Else
+                          undefined
   DESCRIPTION
     This is a temporary fix for BUG#36752.
+    
+    There are two subquery materialization strategies:
+
+    1. Materialize and do index lookups in the materialized table.
+       See BUG#36752 for description of limitations.
+
+    2. Materialize, then scan materialized table use its column values.
+       Here the problem is that subquery's IN-equalities are attached to the
+       outer tables and/or are used to drive access to those tables. Outer 
+       tables may also use join buffering, so they'll need to copy 
 
   RETURN 
     TRUE   Yes
@@ -842,8 +864,8 @@ bool subquery_types_allow_materializatio
                                           bool *scan_allowed)
 {
   DBUG_ENTER("subquery_types_allow_materialization");
-
-  // psergey-todo: pull out all code like this into a function:
+  
+  /* Fix the left expression if it is not yet fixed */
   if (!in_subs->left_expr->fixed)
   {
     SELECT_LEX *save_lex= thd->lex->current_select;
@@ -862,7 +884,7 @@ bool subquery_types_allow_materializatio
   if (in_subs->left_expr->cols() != elements)
     DBUG_RETURN(FALSE);
   
-  bool all_are_fields;
+  bool all_are_fields= TRUE;
   for (uint i= 0; i < elements; i++)
   {
     Item *outer= in_subs->left_expr->element_index(i);
@@ -1140,7 +1162,7 @@ TABLE *create_duplicate_weedout_tmp_tabl
     FALSE  OK 
     TRUE   Out of memory error
 */
-
+#if 0
 static
 int setup_semijoin_dups_elimination(JOIN *join, ulonglong options, uint no_jbuf_after)
 {
@@ -1203,7 +1225,7 @@ int setup_semijoin_dups_elimination(JOIN
           be that it overlaps with anther semi-join's range. we don't
           support LooseScan for joined ranges)
         */
-        if (join->best_positions[i].insideout_key != MAX_KEY)
+        if (join->best_positions[i].loosescan_key != MAX_KEY)
           emb_insideout_nest= tab->emb_sj_nest;
       }
 
@@ -1321,18 +1343,7 @@ int setup_semijoin_dups_elimination(JOIN
     JOIN_TAB *jump_to;
     if (dups_ranges[j].strategy == SJ_STRATEGY_INSIDEOUT)
     {
-      /* We jump from the last table to the first one */
-      tab->insideout_match_tab= join->join_tab + dups_ranges[j].end_idx - 1;
-      
-      /* Calculate key length */
-      // psergey-todo-todo: why join->positions[] and not best_positions[] ??
-      uint nparts= join->positions[dups_ranges[j].start_idx].insideout_parts;
-      uint keyno= join->positions[dups_ranges[j].start_idx].insideout_key;
-      uint keylen= 0;
-      for (uint kp=0; kp < nparts; kp++)
-        keylen += tab->table->key_info[keyno].key_part[kp].store_length;
-      tab->insideout_key_len= keylen;
-      jump_to= tab++;
+      ...
     }
     else // DuplicateWeedout or FirstMatch
     {
@@ -1409,10 +1420,144 @@ int setup_semijoin_dups_elimination(JOIN
   }
   DBUG_RETURN(FALSE);
 }
+#endif
+
+
+int setup_semijoin_dups_elimination2(JOIN *join, ulonglong options, uint no_jbuf_after)
+{
+  uint i;
+  THD *thd= join->thd;
+  DBUG_ENTER("setup_semijoin_dups_elimination");
+
+  for (i= join->const_tables ; i < join->tables ; i++)
+  {
+    JOIN_TAB *tab=join->join_tab + i;
+    POSITION *pos= join->best_positions + i;
+    //uint jump_over= pos->n_sj_tables;
+    uint keylen, keyno;
+    switch (pos->sj_strategy) {
+      case SJ_OPT_MATERIALIZE:
+      case SJ_OPT_MATERIALIZE_SCAN:
+        /* 
+          Do nothing. (Q: can we move the code from setup_sj_materialization to here?
+        */
+        i += pos->n_sj_tables;
+        break;
+      case SJ_OPT_LOOSE_SCAN:
+      {
+        /* We jump from the last table to the first one */
+        tab->insideout_match_tab= tab + pos->n_sj_tables - 1;
+
+        /* Calculate key length */
+        keylen= 0;
+        keyno= pos->loosescan_key;
+        for (uint kp=0; kp < pos->loosescan_parts; kp++)
+          keylen += tab->table->key_info[keyno].key_part[kp].store_length;
+
+        tab->insideout_key_len= keylen;
+        //jump_to= tab++;
+        //TODO Create the FirstMatch tail
+        if (pos->n_sj_tables > 1) 
+          tab[pos->n_sj_tables - 1].do_firstmatch= tab;
+        i += pos->n_sj_tables;
+        break;
+      }
+      case SJ_OPT_DUPS_WEEDOUT:
+      {
+        // Check for join buffering. If there is one, move the first table
+        // forwards, but do not destroy other duplicate elimination methods.
+        uint first_table= i;
+        for (uint j= i; j < i + pos->n_sj_tables; j++)
+        {
+          if (join->best_positions[j].use_join_buffer && j <=
no_jbuf_after)
+          {
+            first_table= join->const_tables;
+            break;
+          }
+        }
+
+        SJ_TMP_TABLE::TAB sjtabs[MAX_TABLES];
+        SJ_TMP_TABLE::TAB *last_tab= sjtabs;
+        //table_map cur_map= join->const_table_map | PSEUDO_TABLE_BITS;
+        uint jt_rowid_offset= 0; // # tuple bytes are already occupied (w/o NULL bytes)
+        uint jt_null_bits= 0;    // # null bits in tuple bytes
+        uint rowid_keep_flags= JOIN_TAB::CALL_POSITION | JOIN_TAB::KEEP_ROWID;
+        //JOIN_TAB *last_outer_tab= tab - 1;
+        /*
+          Walk through the range and remember
+           - tables that need their rowids to be put into temptable
+           - the last outer table
+        */
+        for (JOIN_TAB *j=join->join_tab + first_table; 
+             j < join->join_tab + i + pos->n_sj_tables; j++)
+        {
+          if (sj_table_is_included(join, j))
+          {
+            last_tab->join_tab= j;
+            last_tab->rowid_offset= jt_rowid_offset;
+            jt_rowid_offset += j->table->file->ref_length;
+            if (j->table->maybe_null)
+            {
+              last_tab->null_byte= jt_null_bits / 8;
+              last_tab->null_bit= jt_null_bits++;
+            }
+            last_tab++;
+            j->table->prepare_for_position();
+            j->rowid_keep_flags= rowid_keep_flags;
+          }
+          //cur_map |= j->table->map;
+        }
+
+        DBUG_ASSERT(jt_rowid_offset);
+        if (jt_rowid_offset) /* Temptable has at least one rowid */
+        {
+          SJ_TMP_TABLE *sjtbl;
+          uint tabs_size= (last_tab - sjtabs) * sizeof(SJ_TMP_TABLE::TAB);
+          if (!(sjtbl= (SJ_TMP_TABLE*)thd->alloc(sizeof(SJ_TMP_TABLE))) ||
+              !(sjtbl->tabs= (SJ_TMP_TABLE::TAB*) thd->alloc(tabs_size)))
+            DBUG_RETURN(TRUE);
+          memcpy(sjtbl->tabs, sjtabs, tabs_size);
+          sjtbl->tabs_end= sjtbl->tabs + (last_tab - sjtabs);
+          sjtbl->rowid_len= jt_rowid_offset;
+          sjtbl->null_bits= jt_null_bits;
+          sjtbl->null_bytes= (jt_null_bits + 7)/8;
+          sjtbl->tmp_table= 
+            create_duplicate_weedout_tmp_table(thd, 
+                                               sjtbl->rowid_len + 
+                                               sjtbl->null_bytes,
+                                               sjtbl);
+          join->sj_tmp_tables.push_back(sjtbl->tmp_table);
+          join->join_tab[first_table].flush_weedout_table= sjtbl;
+          join->join_tab[i + pos->n_sj_tables - 1].check_weed_out_table= sjtbl;
+        }
+        //tab= last_outer_tab + 1;
+        //jump_to= last_outer_tab;
+        i += pos->n_sj_tables;
+        break;
+      }
+      case SJ_OPT_FIRST_MATCH:
+      {
+        JOIN_TAB *j, *jump_to= tab-1;
+        for (j= tab; j != tab + pos->n_sj_tables; j++)
+        {
+          if (!tab->emb_sj_nest)
+            jump_to= tab;
+        }
+        j[-1].do_firstmatch= jump_to;
+        i += pos->n_sj_tables;
+        break;
+      }
+      case SJ_OPT_NONE:
+        break;
+    }
+  }
+
+  DBUG_RETURN(FALSE);
+}
 
 
 /*
-  Destroy all temporary tables created by NL-semijoin runtime.
+  Destroy all temporary tables created by NL-semijoin runtime
 */
 
 static void destroy_sj_tmp_tables(JOIN *join)
@@ -1429,6 +1574,13 @@ static void destroy_sj_tmp_tables(JOIN *
 
 /*
   Remove all records from all temp tables used by NL-semijoin runtime
+
+  SYNOPSIS
+    clear_sj_tmp_tables()
+      join  The join to remove tables for
+
+  DESCRIPTION
+    This function must be called before join re-execution.
 */
 
 static int clear_sj_tmp_tables(JOIN *join)
@@ -1960,7 +2112,7 @@ JOIN::optimize()
     (select_lex->ftfunc_list->elements ?  SELECT_NO_JOIN_CACHE : 0);
 
   if (!select_lex->sj_nests.is_empty())
-    setup_semijoin_dups_elimination(this, select_opts_for_readinfo,
+    setup_semijoin_dups_elimination2(this, select_opts_for_readinfo,
                                     no_jbuf_after);
 
   // No cache for MATCH == 'Don't use join buffering when we use MATCH'.
@@ -3901,27 +4053,6 @@ typedef struct st_sargable_param
 } SARGABLE_PARAM;  
 
 
-#ifndef DBUG_OFF
-static void print_sjm(SJ_MATERIALIZE_INFO *sjm)
-{
-  DBUG_LOCK_FILE;
-  fprintf(DBUG_FILE, "\nsemi-join nest{\n");
-  fprintf(DBUG_FILE, "  tables { \n");
-  for (uint i= 0;i < sjm->n_tables; i++)
-  {
-    fprintf(DBUG_FILE, "    %s%s\n", 
-            sjm->positions[i].table->table->alias,
-            (i == sjm->n_tables -1)? "": ",");
-  }
-  fprintf(DBUG_FILE, "  }\n");
-  fprintf(DBUG_FILE, "  materialize_cost= %g\n",
-          sjm->materialization_cost.total_cost());
-  fprintf(DBUG_FILE, "  rows= %g\n", sjm->rows);
-  fprintf(DBUG_FILE, "}\n");
-  DBUG_UNLOCK_FILE;
-}
-#endif 
-
 
 /*
   Get an approximate length of the temporary table column
@@ -4470,9 +4601,6 @@ make_join_statistics(JOIN *join, TABLE_L
         }
         sjm->lookup_cost.set_double(lookup_cost);
         sj_nest->sj_mat_info= sjm;
-        // psergey-todo: handle the case where the subquery was initially
-        // uncorrelated but became correlated after we have pulled out a
-        // table.
         DBUG_EXECUTE("opt", print_sjm(sjm););
       }
     }
@@ -4482,7 +4610,6 @@ make_join_statistics(JOIN *join, TABLE_L
   /* Find an optimal join order of the non-constant tables. */
   if (join->const_tables != join->tables)
   {
-   // optimize_keyuse(join, keyuse_array);
     if (choose_plan(join, all_table_map & ~join->const_table_map))
       DBUG_RETURN(TRUE);
   }
@@ -4493,7 +4620,9 @@ make_join_statistics(JOIN *join, TABLE_L
     join->best_read=1.0;
   }
   /* Generate an execution plan from the found optimal join order. */
-  DBUG_RETURN(join->thd->killed || get_best_combination(join));
+  DBUG_RETURN(join->thd->killed || 
+              get_best_combination(join, all_table_map & 
+                                         ~join->const_table_map));
 }
 
 
@@ -5579,7 +5708,7 @@ set_position(JOIN *join,uint idx,JOIN_TA
   join->positions[idx].records_read=1.0;	/* This is a const table */
   join->positions[idx].ref_depend_map= 0;
 
-  join->positions[idx].insideout_key= MAX_KEY; /* Not an insideout scan */
+  join->positions[idx].loosescan_key= MAX_KEY; /* Not a LooseScan */
   join->positions[idx].use_sj_mat= FALSE;
   join->positions[idx].use_join_buffer= FALSE;
 
@@ -5637,6 +5766,252 @@ ulonglong get_bound_sj_equalities(TABLE_
 }
 
 
+/*
+  This is a class for considering possible loose index scan optimizations.
+  It's usage pattern is as follows:
+    best_access_path()
+    {
+       Loose_scan_opt opt;
+
+       opt.init()
+       for each index we can do ref access with
+       {
+         opt.next_ref_key();
+         for each keyuse 
+           opt.add_keyuse();
+         opt.check_ref_access();
+       }
+
+       if (some criteria for range scans)
+         opt.check_range_access();
+       
+       opt.get_best_option();
+    }
+*/
+
+class Loose_scan_opt
+{
+public:
+  /* All methods must check this before doing anything else */
+  bool try_loosescan;
+
+  /*
+    If we consider (oe1, .. oeN) IN (SELECT ie1, .. ieN) then ieK=oeK is
+    called sj-equality. If oeK depends only on preceding tables then such
+    equality is called 'bound'.
+  */
+  ulonglong bound_sj_equalities;
+ 
+  /* Accumulated properties of ref access we're now considering: */
+  ulonglong handled_sj_equalities;
+  key_part_map loose_scan_keyparts;
+  uint max_loose_keypart;
+  bool part1_conds_met;
+
+  /*
+    Use of quick select is a special case. Some of its properties:
+  */
+  uint quick_uses_applicable_index;
+  uint quick_max_loose_keypart;
+  
+  /* Best loose scan method so far */
+  uint   best_loose_scan_key;
+  double best_loose_scan_cost;
+  double best_loose_scan_records;
+  KEYUSE *best_loose_scan_start_key;
+
+  uint best_max_loose_keypart;
+
+
+  Loose_scan_opt():
+    try_loosescan(FALSE),
+    bound_sj_equalities(0),
+    quick_uses_applicable_index(FALSE)
+  {
+    LINT_INIT(quick_max_loose_keypart); // Protected by sj_insideout_quick_*
+  }
+  
+  void init(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
+  {
+    /*
+      Discover the bound equalites. We need to do this, if
+        1. The next table is an SJ-inner table, and
+        2. It is the first table from that semijoin, and
+        3. We're not within a semi-join range (i.e. all semi-joins either have
+           all or none of their tables in join_table_map), except
+           s->emb_sj_nest (which we've just entered, see #2).
+        4. All non-IN-equality correlation references from this sj-nest are 
+           bound
+        5. But some of the IN-equalities aren't (so this can't be handled by 
+           FirstMatch strategy)
+    */
+    best_loose_scan_cost= DBL_MAX;
+    if (!join->emb_sjm_nest && s->emb_sj_nest &&                   
    // (1)
+        s->emb_sj_nest->sj_in_exprs < 64 && 
+        ((remaining_tables & s->emb_sj_nest->sj_inner_tables) ==        // (2)
+         s->emb_sj_nest->sj_inner_tables) &&                            //
(2)
+        join->cur_emb_sj_nests == 0 &&                                  // (3)
+        !(remaining_tables & 
+          s->emb_sj_nest->nested_join->sj_corr_tables) &&              
// (4)
+        remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on
&&// (5)
+        !test(join->thd->variables.optimizer_switch &
OPTIMIZER_SWITCH_NO_LOOSE_SCAN))
+    {
+      /* This table is an LooseScan scan candidate */
+      bound_sj_equalities= get_bound_sj_equalities(s->emb_sj_nest, 
+                                                   remaining_tables);
+      try_loosescan= TRUE;
+      DBUG_PRINT("info", ("Will try LooseScan scan, bound_map=%llx",
+                          (longlong)bound_sj_equalities));
+    }
+  }
+
+  void next_ref_key()
+  {
+    handled_sj_equalities=0;
+    loose_scan_keyparts= 0;
+    max_loose_keypart= 0;
+    part1_conds_met= FALSE;
+  }
+  
+  void add_keyuse(table_map remaining_tables, KEYUSE *keyuse)
+  {
+    if (try_loosescan && keyuse->sj_pred_no != UINT_MAX)
+    {
+      if (!(remaining_tables & keyuse->used_tables))
+      {
+        /* 
+          This allows to use equality propagation to infer that some 
+          sj-equalities are bound.
+        */
+        bound_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+      }
+      else
+      {
+        handled_sj_equalities |= 1ULL << keyuse->sj_pred_no;
+        loose_scan_keyparts |= ((key_part_map)1) << keyuse->keypart;
+        set_if_bigger(max_loose_keypart, keyuse->keypart);
+      }
+    }
+  }
+
+  bool have_a_case() { return test(handled_sj_equalities); }
+
+  void check_ref_access_part1(JOIN_TAB *s, uint key, KEYUSE *start_key, 
+                              table_map found_part)
+  {
+    /*
+      Check if we can use LooseScan semi-join strategy. We can if
+      1. This is the right table at right location
+      2. All IN-equalities are either
+         - "bound", ie. the outer_expr part refers to the preceding tables
+         - "handled", ie. covered by the index we're considering
+      3. Index order allows to enumerate subquery's duplicate groups in
+         order. This happens when the index definition matches this
+         pattern:
+
+           (handled_col|bound_col)* (other_col|bound_col)
+
+    */
+    if (try_loosescan &&                                    // (1)
+        (handled_sj_equalities | bound_sj_equalities) ==      // (2)
+        PREV_BITS(ulonglong, s->emb_sj_nest->sj_in_exprs) &&  // (2)
+        (PREV_BITS(key_part_map, max_loose_keypart+1) &           // (3)
+         (found_part | loose_scan_keyparts)) ==                     // (3)
+         (found_part | loose_scan_keyparts) &&                     // (3)
+        !key_uses_partial_cols(s->table, key))
+    {
+      /* Ok, can use the strategy */
+      part1_conds_met= TRUE;
+      if (s->quick && s->quick->index == key && 
+          s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
+      {
+        quick_uses_applicable_index= TRUE;
+        quick_max_loose_keypart= max_loose_keypart;
+      }
+      DBUG_PRINT("info", ("Can use LooseScan scan"));
+
+      /* 
+        Check if this is a confluent where there are no usable bound
+        IN-equalities, e.g. we have
+
+          outer_expr IN (SELECT innertbl.key FROM ...) 
+        
+        and outer_expr cannot be evaluated yet, so it's actually full
+        index scan and not a ref access
+      */
+      if (!(found_part & 1 ) && /* no usable ref access for 1st key part */
+          s->table->covering_keys.is_set(key))
+      {
+        DBUG_PRINT("info", ("Can use full index scan for LooseScan"));
+        
+        /* Calculate the cost of complete loose index scan.  */
+        double records= rows2double(s->table->file->stats.records);
+
+        /* The cost is entire index scan cost (divided by 2) */
+        double read_time= s->table->file->index_only_read_time(key, records);
+
+        /*
+          Now find out how many different keys we will get (for now we
+          ignore the fact that we have "keypart_i=const" restriction for
+          some key components, that may make us think think that loose
+          scan will produce more distinct records than it actually will)
+        */
+        ulong rpc;
+        if ((rpc= s->table->key_info[key].rec_per_key[max_loose_keypart]))
+          records= records / rpc;
+
+        // TODO: previous version also did /2
+        if (read_time < best_loose_scan_cost)
+        {
+          best_loose_scan_key= key;
+          best_loose_scan_cost= read_time;
+          best_loose_scan_records= records;
+          best_max_loose_keypart= max_loose_keypart;
+          best_loose_scan_start_key= start_key;
+        }
+      }
+    }
+  }
+  
+  void check_ref_access_part2(uint key, KEYUSE *start_key, double records, 
+                              double read_time)
+  {
+    if (part1_conds_met && read_time < best_loose_scan_cost)
+    {
+      /* TODO use rec-per-key-based fanout calculations */
+      best_loose_scan_key= key;
+      best_loose_scan_cost= read_time;
+      best_loose_scan_records= records;
+      best_max_loose_keypart= max_loose_keypart;
+      best_loose_scan_start_key= start_key;
+    }
+  }
+
+  void check_range_access(JOIN *join, uint idx, QUICK_SELECT_I *quick)
+  {
+    /* TODO: this the right part restriction: */
+    if (quick_uses_applicable_index && idx == join->const_tables && 
+        quick->read_time < best_loose_scan_cost)
+    {
+      best_loose_scan_key= quick->index;
+      best_loose_scan_cost= quick->read_time;
+      best_loose_scan_records= quick->records; /* ok because idx ==
join->const_tables */
+      best_max_loose_keypart= quick_max_loose_keypart;
+      best_loose_scan_start_key= NULL;
+    }
+  }
+
+  void save_to_position(POSITION *pos)
+  {
+    pos->records_read=    best_loose_scan_records;
+    pos->read_time=       best_loose_scan_cost;
+    pos->key=             best_loose_scan_start_key;
+    pos->loosescan_key=   best_loose_scan_key;
+    pos->loosescan_parts= best_max_loose_keypart + 1;
+  }
+};
+
+
 /**
   Find the best access path for an extension of a partial execution
   plan and add this path to the plan.
@@ -5659,7 +6034,9 @@ ulonglong get_bound_sj_equalities(TABLE_
   @param read_time        the cost of the partial plan
  
  TODO: do we need to add this:
-  @param loose_scan       IN   TRUE <=> can consider loosescan for semi-join
+  @param loose_scan_keys  if non-NULL but points to (key_map) - check if we
+                          can use loose scan
+                          IN   TRUE <=> can consider loosescan for semi-join
                           OUT  TRUE <=> Picked loose scan.
 
   @return
@@ -5669,13 +6046,14 @@ ulonglong get_bound_sj_equalities(TABLE_
 static void
 best_access_path(JOIN      *join,
                  JOIN_TAB  *s,
-                 THD       *thd,
                  table_map remaining_tables,
                  uint      idx,
                  bool      disable_jbuf,
                  double    record_count,
-                 double    read_time)
+                 POSITION *pos,
+                 POSITION *loose_scan_pos)
 {
+  THD *thd= join->thd;
   KEYUSE *best_key=         0;
   uint best_max_key_part=   0;
   my_bool found_constraint= 0;
@@ -5685,54 +6063,19 @@ best_access_path(JOIN      *join,
   table_map best_ref_depends_map= 0;
   double tmp;
   ha_rows rec;
-  uint best_is_sj_inside_out= MAX_KEY;
-  uint best_sj_keyparts;
-  bool try_sj_inside_out= FALSE;
-  uint sj_insideout_quick_select= FALSE;
-  uint sj_insideout_quick_max_sj_keypart;
-  uint sj_inside_out_scan= MAX_KEY;
-  bool best_uses_jbuf;
+  bool best_uses_jbuf= FALSE;
+
+  Loose_scan_opt loose_scan_opt;
   DBUG_ENTER("best_access_path");
   
-  LINT_INIT(best_sj_keyparts); // Protected by sj_inside_out_scan
-  LINT_INIT(sj_insideout_quick_max_sj_keypart); // Protected by sj_insideout_quick_*
+  loose_scan_opt.init(join, s, remaining_tables);
+  
   if (s->keyuse)
   {                                            /* Use key if possible */
     TABLE *table= s->table;
     KEYUSE *keyuse,*start_key=0;
     double best_records= DBL_MAX;
     uint max_key_part=0;
-    ulonglong bound_sj_equalities= 0;
-    /*
-      Discover the bound equalites. We need to do this, if
-        1. The next table is an SJ-inner table, and
-        2. It is the first table from that semijoin, and
-        3. We're not within a semi-join range (i.e. all semi-joins either have
-           all or none of their tables in join_table_map), except
-           s->emb_sj_nest (which we've just entered, see #2).
-        4. All non-IN-equality correlation references from this sj-nest are 
-           bound
-        5. But some of the IN-equalities aren't (so this can't be handled by 
-           FirstMatch strategy)
-    */
-    if (!join->emb_sjm_nest && s->emb_sj_nest &&                   
    // (1)
-        s->emb_sj_nest->sj_in_exprs < 64 && 
-        ((remaining_tables & s->emb_sj_nest->sj_inner_tables) ==        // (2)
-         s->emb_sj_nest->sj_inner_tables) &&                            //
(2)
-//        join->cur_emb_sj_nests == s->emb_sj_nest->sj_inner_tables &&  
 // (3)
-        join->cur_emb_sj_nests == 0 &&    // (3)
-        !(remaining_tables & 
-          s->emb_sj_nest->nested_join->sj_corr_tables) &&              
// (4)
-        remaining_tables & s->emb_sj_nest->nested_join->sj_depends_on
&&// (5)
-        !test(thd->variables.optimizer_switch & OPTIMIZER_SWITCH_NO_LOOSE_SCAN))
-    {
-      /* This table is an LooseScan scan candidate */
-      bound_sj_equalities= get_bound_sj_equalities(s->emb_sj_nest, 
-                                                   remaining_tables);
-      try_sj_inside_out= TRUE;
-      DBUG_PRINT("info", ("Will try LooseScan scan, bound_map=%llx",
-                          (longlong)bound_sj_equalities));
-    }
 
     /* Test how we can use keys */
     rec= s->records/MATCHING_ROWS_IN_OTHER_TABLE;  // Assumed records/key
@@ -5750,9 +6093,8 @@ best_access_path(JOIN      *join,
 
       /* Calculate how many key segments of the current key we can use */
       start_key= keyuse;
-      ulonglong handled_sj_equalities=0;
-      key_part_map sj_insideout_map= 0;
-      uint max_sj_keypart= 0;
+
+      loose_scan_opt.next_ref_key();
       DBUG_PRINT("info", ("Considering ref access on key %s",
                           keyuse->table->key_info[keyuse->key].name));
 
@@ -5792,19 +6134,7 @@ best_access_path(JOIN      *join,
             if (keyuse->optimize & KEY_OPTIMIZE_REF_OR_NULL)
               ref_or_null_part |= keyuse->keypart_map;
           }
-
-          if (try_sj_inside_out && keyuse->sj_pred_no != UINT_MAX)
-          {
-            if (!(remaining_tables & keyuse->used_tables))
-              bound_sj_equalities |= 1ULL << keyuse->sj_pred_no;
-            else
-            {
-              handled_sj_equalities |= 1ULL << keyuse->sj_pred_no;
-              sj_insideout_map |= ((key_part_map)1) << keyuse->keypart;
-              set_if_bigger(max_sj_keypart, keyuse->keypart);
-            }
-          }
-
+          loose_scan_opt.add_keyuse(remaining_tables, keyuse);
           keyuse++;
         } while (keyuse->table == table && keyuse->key == key &&
                  keyuse->keypart == keypart);
@@ -5814,13 +6144,12 @@ best_access_path(JOIN      *join,
       /*
         Assume that that each key matches a proportional part of table.
       */
-      if (!found_part && !ft_key && !handled_sj_equalities)
+      if (!found_part && !ft_key && !loose_scan_opt.have_a_case())
         continue;                               // Nothing usable found
 
       if (rec < MATCHING_ROWS_IN_OTHER_TABLE)
         rec= MATCHING_ROWS_IN_OTHER_TABLE;      // Fix for small tables
 
-      sj_inside_out_scan= MAX_KEY;
       /*
         ft-keys require special treatment
       */
@@ -5836,73 +6165,9 @@ best_access_path(JOIN      *join,
       else
       {
         found_constraint= test(found_part);
-        /*
-          Check if we can use LooseScan semi-join strategy. We can if
-          1. This is the right table at right location
-          2. All IN-equalities are either
-             - "bound", ie. the outer_expr part refers to the preceding tables
-             - "handled", ie. covered by the index we're considering
-          3. Index order allows to enumerate subquery's duplicate groups in
-             order. This happens when the index definition matches this
-             pattern:
+        loose_scan_opt.check_ref_access_part1(s, key, start_key, found_part);
 
-               (handled_col|bound_col)* (other_col|bound_col)
-
-        */
-        if (try_sj_inside_out &&                                    // (1)
-            (handled_sj_equalities | bound_sj_equalities) ==      // (2)
-            PREV_BITS(ulonglong, s->emb_sj_nest->sj_in_exprs) &&  // (2)
-            (PREV_BITS(key_part_map, max_sj_keypart+1) &           // (3)
-             (found_part | sj_insideout_map)) ==                     // (3)
-             (found_part | sj_insideout_map) &&                     // (3)
-            !key_uses_partial_cols(s->table, key))
-        {
-          /* Ok, can use the strategy */
-          sj_inside_out_scan= key;
-          if (s->quick && s->quick->index == key && 
-              s->quick->get_type() == QUICK_SELECT_I::QS_TYPE_RANGE)
-          {
-            sj_insideout_quick_select= TRUE;
-            sj_insideout_quick_max_sj_keypart= max_sj_keypart;
-          }
-          DBUG_PRINT("info", ("Can use LooseScan scan"));
-
-          /* 
-            Check if this is a confluent where there are no usable bound
-            IN-equalities, e.g. we have
-
-              outer_expr IN (SELECT innertbl.key FROM ...) 
-            
-            and outer_expr cannot be evaluated yet, so it's actually full
-            index scan and not a ref access
-          */
-          if (!(found_part & 1 ) && /* no usable ref access for 1st key part
*/
-              table->covering_keys.is_set(key))
-          {
-            DBUG_PRINT("info", ("Can use full index scan for LooseScan"));
-            /* Calculate the cost of complete loose index scan.  */
-            records= rows2double(s->table->file->stats.records);
-
-            /* The cost is entire index scan cost (divided by 2) */
-            best_time= s->table->file->index_only_read_time(key, records);
-
-            /*
-              Now find out how many different keys we will get (for now we
-              ignore the fact that we have "keypart_i=const" restriction for
-              some key components, that may make us think think that loose
-              scan will produce more distinct records than it actually will)
-            */
-            ulong rpc;
-            if ((rpc= keyinfo->rec_per_key[max_sj_keypart]))
-              records= records / rpc;
-            start_key= NULL;
-            /* Fall through */
-          }
-        }
-
-        /*
-          Check if we found full key
-        */
+        /* Check if we found full key */
         if (found_part == PREV_BITS(uint,keyinfo->key_parts) &&
             !ref_or_null_part)
         {                                         /* use eq key */
@@ -6150,13 +6415,7 @@ best_access_path(JOIN      *join,
           else
             tmp= best_time;                    // Do nothing
         }
-
-        if ((sj_inside_out_scan != MAX_KEY) && !start_key)
-        {
-          tmp= tmp/2;
-          if (records)
-            records= records/2;
-        }
+        loose_scan_opt.check_ref_access_part2(key, start_key, records, tmp);
 
       } /* not ft_key */
       if (tmp < best_time - records/(double) TIME_FOR_COMPARE)
@@ -6167,9 +6426,6 @@ best_access_path(JOIN      *join,
         best_key= start_key;
         best_max_key_part= max_key_part;
         best_ref_depends_map= found_ref;
-        best_is_sj_inside_out= sj_inside_out_scan;
-        best_sj_keyparts= max_sj_keypart;
-        best_uses_jbuf= FALSE;
       }
     } /* for each key */
     records= best_records;
@@ -6210,7 +6466,7 @@ best_access_path(JOIN      *join,
         ! s->table->covering_keys.is_clear_all() && best_key &&
!s->quick) &&// (3)
       !(s->table->force_index && best_key && !s->quick))        
        // (4)
   {                                             // Check full join
-    sj_inside_out_scan= MAX_KEY;
+    //part1_conds_met= MAX_KEY;
     ha_rows rnd_records= s->found_records;
     /*
       If there is a filtering condition on the table (i.e. ref analyzer found
@@ -6236,6 +6492,7 @@ best_access_path(JOIN      *join,
       than FULL: so if RANGE is present, it's always preferred to FULL.
       Here we estimate its cost.
     */
+
     if (s->quick)
     {
       /*
@@ -6250,11 +6507,8 @@ best_access_path(JOIN      *join,
       tmp= record_count *
         (s->quick->read_time +
          (s->found_records - rnd_records)/(double) TIME_FOR_COMPARE);
-      
-      if (sj_insideout_quick_select && idx == join->const_tables)
-      {
-        sj_inside_out_scan= s->quick->index;
-      }
+
+      loose_scan_opt.check_range_access(join, idx, s->quick);
     }
     else
     {
@@ -6306,22 +6560,22 @@ best_access_path(JOIN      *join,
       best_key= 0;
       /* range/index_merge/ALL/index access method are "independent", so: */
       best_ref_depends_map= 0;
-      best_is_sj_inside_out= sj_inside_out_scan;
-      best_sj_keyparts= sj_insideout_quick_max_sj_keypart;
-      best_uses_jbuf= test(idx != join->const_tables);
+      best_uses_jbuf= test(!disable_jbuf && !((s->table->map & 
+                                               join->outer_join)));
     }
   }
-
+  
   /* Update the cost information for the current partial plan */
-  join->positions[idx].records_read= records;
-  join->positions[idx].read_time=    best;
-  join->positions[idx].key=          best_key;
-  join->positions[idx].table=        s;
-  join->positions[idx].ref_depend_map= best_ref_depends_map;
-  join->positions[idx].insideout_key= best_is_sj_inside_out;
-  join->positions[idx].insideout_parts= best_sj_keyparts + 1;
-  join->positions[idx].use_sj_mat= 0;
-  join->positions[idx].use_join_buffer= best_uses_jbuf; //psergey-todo: this is wrong!
+  pos->records_read= records;
+  pos->read_time=    best;
+  pos->key=          best_key;
+  pos->table=        s;
+  pos->ref_depend_map= best_ref_depends_map;
+  pos->loosescan_key= MAX_KEY;
+  pos->use_sj_mat= 0;
+  pos->use_join_buffer= best_uses_jbuf;
+   
+  loose_scan_opt.save_to_position(loose_scan_pos);
 
   if (!best_key &&
       idx == join->const_tables &&
@@ -6329,44 +6583,6 @@ best_access_path(JOIN      *join,
       join->unit->select_limit_cnt >= records)
     join->sort_by_table= (TABLE*) 1;  // Must use temporary table
 
-#if 0
-  if (last semi-join table)
-    adjust the fanout;
-  
-  /*
-    Semi-join stategy costs.
-    
-    / *
-      Check if adding this table will cause us to switch to duplicate
-      elimination.
-
-    * /
-    if ((strategy == first_match && best_uses_jbuf) ||
-        (strategy == loose_scan && (this is not a table from that semi-join))
-    {
-      / * walk back and collect rowid sizes * /
-      for (each position starting from first sj table)
-      {
-        if (outer)
-        {
-          add rowid length;
-          add cardinality into fanout;
-        }
-      }
-    }
-
-    if (using dups elimination already)
-    {
-      // add the cost of this table being in dups elimination
-      if (this is an outer table)
-      {
-        ;
-      }
-    }
-
-
-  */
-#endif
   DBUG_VOID_RETURN;
 }
 
@@ -6406,28 +6622,71 @@ choose_plan(JOIN *join, table_map join_t
   DBUG_ENTER("choose_plan");
 
   join->cur_embedding_map= 0;
+  join->cur_unhandled_sj_fanout= 0;
   reset_nj_counters(join->join_list);
-  /*
-    if (SELECT_STRAIGHT_JOIN option is set)
-      reorder tables so dependent tables come after tables they depend 
-      on, otherwise keep tables in the order they were specified in the query 
-    else
-      Apply heuristic: pre-sort all access plans with respect to the number of
-      records accessed.
-  */
   qsort2_cmp jtab_sort_func;
+
   if (join->emb_sjm_nest)
+  {
+    /* We're optimizing semi-join materialization nest, so put the 
+       tables from this semi-join as first
+    */
     jtab_sort_func= join_tab_cmp_embedded_first;
+  }
   else
+  {
+    /*
+      if (SELECT_STRAIGHT_JOIN option is set)
+        reorder tables so dependent tables come after tables they depend 
+        on, otherwise keep tables in the order they were specified in the query 
+      else
+        Apply heuristic: pre-sort all access plans with respect to the number of
+        records accessed.
+    */
     jtab_sort_func= straight_join ? join_tab_cmp_straight : join_tab_cmp;
+  }
   my_qsort2(join->best_ref + join->const_tables,
             join->tables - join->const_tables, sizeof(JOIN_TAB*),
             jtab_sort_func, (void*)join->emb_sjm_nest);
   join->cur_emb_sj_nests= 0;
 
-  //if (sj_nest)
-  //  join_tables= sj_nest->sj_inner_tables;
- 
+  if (!join->emb_sjm_nest && straight_join)
+  {
+    /* Put all sj-inner tables right after their last outer table table.  */
+    uint inner;
+
+    /* Find the first inner table (inner tables follow outer) */
+    for (inner= join->const_tables;
+         inner < join->tables && !join->best_ref[inner]->emb_sj_nest;
+         inner++);
+
+    while (inner < join->tables) /* for each group of inner tables... */
+    {
+      TABLE_LIST *emb_sj_nest= join->best_ref[inner]->emb_sj_nest;
+      uint n_tables= my_count_bits(emb_sj_nest->sj_inner_tables);
+      table_map cur_map= join->const_table_map;
+      table_map needed_map= emb_sj_nest->nested_join->sj_depends_on |
+                            emb_sj_nest->nested_join->sj_corr_tables;
+      /* Locate the last outer table with which this semi-join is correlated */
+      uint last_outer;
+      for (last_outer= join->const_tables; last_outer < inner; last_outer++)
+      {
+        cur_map |= join->best_ref[last_outer]->table->map;
+        if (!(needed_map & ~cur_map))
+          break;
+      }
+      /* Move the inner tables to here */
+      JOIN_TAB *tmp[MAX_TABLES];
+      memcpy(tmp, join->best_ref + inner, n_tables*sizeof(JOIN_TAB));
+      for (uint i= inner - 1; i > last_outer; i--)
+      {
+        join->best_ref[i + n_tables]= join->best_ref[i]; 
+      }
+      memcpy(join->best_ref + last_outer + 1, tmp, n_tables*sizeof(JOIN_TAB));
+      inner += n_tables;
+    }
+  }
+
   if (straight_join)
   {
     optimize_straight_join(join, join_tables);
@@ -6517,6 +6776,20 @@ join_tab_cmp_straight(const void *dummy,
 {
   JOIN_TAB *jt1= *(JOIN_TAB**) ptr1;
   JOIN_TAB *jt2= *(JOIN_TAB**) ptr2;
+ 
+  
+  /* Put SJ-inner tables at the end */
+  if (jt1->emb_sj_nest && !jt2->emb_sj_nest)
+    return -1;
+  if (!jt1->emb_sj_nest && jt2->emb_sj_nest)
+    return 1;
+
+  /* Group SJ-inner tables by their embedding nest */
+  if (jt1->emb_sj_nest && jt2->emb_sj_nest)
+  {
+    ptrdiff_t diff=  jt1->emb_sj_nest - jt2->emb_sj_nest;
+    return diff > 0 ? 1 : (diff < 0 ? -1 : 0);
+  }
 
   if (jt1->dependent & jt2->table->map)
     return 1;
@@ -6640,16 +6913,20 @@ optimize_straight_join(JOIN *join, table
   uint idx= join->const_tables;
   double    record_count= 1.0;
   double    read_time=    0.0;
+  POSITION  loose_scan_pos;
  
   for (JOIN_TAB **pos= join->best_ref + idx ; (s= *pos) ; pos++)
   {
     /* Find the best access method from 's' to the current partial plan */
-    best_access_path(join, s, join->thd, join_tables, idx, FALSE, // psergey-todo
-                     record_count, read_time);
-    advance_sj_state(join, join_tables, s, idx, &record_count, &read_time);
+    best_access_path(join, s, join_tables, idx, FALSE, record_count,
+                     join->positions + idx, &loose_scan_pos);
+
     /* compute the cost of the new plan extended with 's' */
     record_count*= join->positions[idx].records_read;
     read_time+=    join->positions[idx].read_time;
+    advance_sj_state(join, join_tables, s, idx, &record_count, &read_time,
+                     &loose_scan_pos);
+
     join_tables&= ~(s->table->map);
     ++idx;
   }
@@ -6682,7 +6959,7 @@ optimize_straight_join(JOIN *join, table
 */
 
 SJ_MATERIALIZE_INFO *
-at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab, 
+at_sjmat_pos(const JOIN *join, table_map remaining_tables, const JOIN_TAB *tab,
              uint idx, bool *insideout_scan)
 {
   /*
@@ -6697,9 +6974,7 @@ at_sjmat_pos(const JOIN *join, table_map
   {
     /* 
       Walk back and check if all immediately preceding tables are from
-      this semi-join
-      psergey-sjm-todo: also count other option costs here.
-         and jbuf use.
+      this semi-join.
     */
     uint n_tables= my_count_bits(tab->emb_sj_nest->sj_inner_tables);
     for (uint i= 1; i < n_tables ; i++)
@@ -6707,7 +6982,7 @@ at_sjmat_pos(const JOIN *join, table_map
       if (join->positions[idx - i].table->emb_sj_nest != tab->emb_sj_nest)
         return NULL;
     }
-    *insideout_scan= test(remaining_tables & ~tab->table->map &      
+    *insideout_scan= test(remaining_tables & ~tab->table->map &
                              (emb_sj_nest->sj_inner_tables |
                               emb_sj_nest->nested_join->sj_depends_on));
     if (*insideout_scan && !emb_sj_nest->sj_subq_pred->sjm_scan_allowed)
@@ -6839,8 +7114,6 @@ greedy_search(JOIN      *join,
       */
       DBUG_EXECUTE("opt", print_plan(join, n_tables, record_count, read_time,
                                      read_time, "optimal"););
-      // psergey-sjm-todo: is this the most appropriate place:
-      //  A: no. moved.
       DBUG_RETURN(FALSE);
     }
 
@@ -6918,79 +7191,6 @@ void get_partial_join_cost(JOIN *join, u
 }
 
 
-/*
-  Check if we're entering a semi-join duplicate producer range that can be 
-  handled by FirstMatch strategy.
-*/
-
-static 
-bool at_firstmatch_start(JOIN *join, JOIN_TAB *s, table_map remaining_tables)
-{
-  /*
-    This is true if 
-     1. The next join tab belongs to semi-join nest
-     2. We're not in a duplicate producer range yet
-     3. All outer tables that 
-         - the subquery is correlated with, or
-         - referred to from the outer_expr 
-        are in the prefix
-  */
-  return (s->emb_sj_nest &&
-          !join->cur_emb_sj_nests && 
-          !(remaining_tables & 
-            (s->emb_sj_nest->nested_join->sj_corr_tables |
-             s->emb_sj_nest->nested_join->sj_depends_on)));
-}
-
-
-/*
-  Check if current semi-join strategy allows to add the table into join prefix
-  
-  SYNOPSIS
-    check_semi_join_ok()
-      join   The join we're optimizing
-      idx    Number of tables already in the prefix (incl. const tables)
-      s      Table we want to add
-
-  DESCRIPTION
-    Check if the semi-join strategy we're now considering allows to extend
-    the join prefix with specified table.
-
-  RETURN
-    TRUE   Yes can extend
-    FALSE  No
-*/
-
-static bool check_semi_join_ok(JOIN *join, uint idx, JOIN_TAB *s)
-{
-  if (idx != join->const_tables)
-  {
-    POSITION *prev_pos= join->positions + idx - 1; 
-    if (prev_pos->cur_sj_strategy == SJ_OPT_LOOSE_SCAN)
-    {
-      /* 
-        Allow only tables from the same sj nest.
-        TODO we could actually allow interleaving with other join nests.
-      */
-      return test(s->emb_sj_nest && 
-                  s->emb_sj_nest->sj_inner_tables == join->cur_emb_sj_nests);
-    }
-    if (prev_pos->cur_sj_strategy == SJ_OPT_FIRST_MATCH)
-    {
-      /*
-        Don't allow inner tables whose outer correlated tables do not
-        precede the first inner table in this duplicate-generating range.
-      */
-      return test (!s->emb_sj_nest ||
-                   !(prev_pos->first_firstmatch_rtbl & 
-                     (s->emb_sj_nest->nested_join->sj_depends_on | 
-                      s->emb_sj_nest->nested_join->sj_corr_tables)));
-    }
-  }
-  return TRUE;
-}
-
-
 /**
   Find a good, possibly optimal, query execution plan (QEP) by a possibly
   exhaustive search.
@@ -7147,83 +7347,23 @@ best_extension_by_limited_search(JOIN   
     if ((remaining_tables & real_table_bit) && 
         (allowed_tables & real_table_bit) &&
         !(remaining_tables & s->dependent) && 
-        (!idx || !check_interleaving_with_nj(join->positions[idx-1].table, s))
&&
-        check_semi_join_ok(join, idx, s))
+        (!idx || !check_interleaving_with_nj(join->positions[idx-1].table, s)))
     {
       double current_record_count, current_read_time;
-      bool in_fork= FALSE, after_fork=FALSE;
       POSITION *position= join->positions + idx;
-restart:
-      /* Copy semi-join optimization state */
-      if (idx == join->const_tables)
-      {
-        position->cur_sj_strategy= SJ_OPT_NONE ;
-        position->cur_disable_jbuf= FALSE;
-        position->cur_fanout_generators= (table_map)0;
-        position->dupsweedout_tables= 0;
-        position->cur_forks= 0;
-        position->sjm_scan_finish= 0;
-        position->sjm_scan_edge= 0; // not necesarry but to shut up the valgrind
-      }
-      else
-      {
-        position->cur_sj_strategy= position[-1].cur_sj_strategy;
-        position->cur_disable_jbuf= position[-1].cur_disable_jbuf;
-        position->first_firstmatch_table= position[-1].first_firstmatch_table;
-        position->first_firstmatch_rtbl= position[-1].first_firstmatch_rtbl;
-        position->cur_fanout_generators= position[-1].cur_fanout_generators;
-        position->dupsweedout_tables= position[-1].dupsweedout_tables;
-        position->first_dupsweedout_table= position[-1].first_dupsweedout_table;
-        position->cur_forks= position[-1].cur_forks;
-        position->sjm_scan_finish= position[-1].sjm_scan_finish;
-        position->sjm_scan_edge= position[-1].sjm_scan_edge;
-      }
-      
-      /* 
-        If we're entrering a semi-join nest, fork off the FirstMatch search
-        branch.
-      */
-      if (!after_fork && at_firstmatch_start(join, s, remaining_tables))
-      {
-        position->cur_disable_jbuf= TRUE;
-        in_fork= TRUE;
-        position->cur_sj_strategy= SJ_OPT_FIRST_MATCH;
-        position->first_firstmatch_table= idx;
-        position->first_firstmatch_rtbl= remaining_tables;
-        position->cur_forks |= s->emb_sj_nest->sj_inner_tables;
-      }
 
-      /*
-        psergey-insideout-todo: 
-          when best_access_path() detects it could do an LooseScan scan or 
-          some other scan, have it return an insideout scan and a flag that 
-          requests to "fork" this loop iteration. (Q: how does that behave 
-          when the depth is insufficient??)
-      */
       /* Find the best access method from 's' to the current partial plan */
-      //TODO: if (after_fork) disable loose scan;
-      best_access_path(join, s, thd, remaining_tables, idx,
-                       position->cur_disable_jbuf,
-                       record_count, read_time);
-
-      if (!(in_fork || after_fork) && position->insideout_key != MAX_KEY)
-      {
-        in_fork= TRUE;
-        position->cur_disable_jbuf= TRUE;
-        position->cur_sj_strategy= SJ_OPT_LOOSE_SCAN;
-        position->cur_forks |= s->emb_sj_nest->sj_inner_tables;
-      }
-       
+      POSITION loose_scan_pos;
+      best_access_path(join, s, remaining_tables, idx, FALSE, record_count, 
+                       join->positions + idx, &loose_scan_pos);
+
       /* Compute the cost of extending the plan with 's' */
 
       current_record_count= record_count * position->records_read;
       current_read_time=    read_time + position->read_time;
 
       advance_sj_state(join, remaining_tables, s, idx, &current_record_count,
-                       &current_read_time);
-      
-      position->prefix_cost.set_double(current_read_time);
-      position->prefix_record_count= current_record_count;
+                       &current_read_time, &loose_scan_pos);
 
       /* Expand only partial plans with lower cost than the best QEP so far */
       if ((current_read_time +
@@ -7313,13 +7453,6 @@ restart:
       }
       restore_prev_nj_state(s);
       restore_prev_sj_state(remaining_tables, s, idx);
-      if (in_fork)
-      {
-        in_fork= FALSE;
-        position->cur_forks &= ~s->emb_sj_nest->sj_inner_tables;
-        after_fork= TRUE;
-        goto restart;
-      }
     }
   }
   DBUG_RETURN(FALSE);
@@ -7373,10 +7506,9 @@ find_best(JOIN *join,table_map rest_tabl
         (!idx|| !check_interleaving_with_nj(join->positions[idx-1].table, s)))
     {
       double records, best;
-      best_access_path(join, s, thd, rest_tables, idx, 
-                       FALSE, //psergey-todo
-                       record_count, read_time);
-      advance_sj_state(join, rest_tables, s, idx, &record_count, &read_time);
+      POSITION loose_scan_pos;
+      best_access_path(join, s, rest_tables, idx, FALSE, record_count, 
+                       join->positions + idx, &loose_scan_pos);
       records= join->positions[idx].records_read;
       best= join->positions[idx].read_time;
       /*
@@ -7385,6 +7517,9 @@ find_best(JOIN *join,table_map rest_tabl
       */
       double current_record_count=record_count*records;
       double current_read_time=read_time+best;
+      advance_sj_state(join, rest_tables, s, idx, &current_record_count, 
+                       &current_read_time, &loose_scan_pos);
+
       if (best_record_count > current_record_count ||
 	  best_read_time > current_read_time ||
 	  idx == join->const_tables && s->table == join->sort_by_table)
@@ -7562,7 +7697,7 @@ prev_record_reads(JOIN *join, uint idx, 
 */
 
 static bool
-get_best_combination(JOIN *join)
+get_best_combination(JOIN *join, table_map join_tables)
 {
   uint i,tablenr;
   table_map used_tables;
@@ -7583,32 +7718,109 @@ get_best_combination(JOIN *join)
 
   /*
     Prepare semi-join processing info for plan refimenent stage:
-    - Copy materialiation's POSITIONs.
   */
-  for (tablenr= table_count - 1 ; tablenr != 0 ; tablenr--)
+  table_map remaining_tables= join_tables;
+  table_map handled_tabs= 0;
+  for (tablenr= table_count - 1 ; tablenr != join->const_tables - 1; tablenr--)
   {
     POSITION *pos= join->best_positions + tablenr;
+    JOIN_TAB *s= pos->table;
+    uint first;
+
+    if ((handled_tabs & s->table->map) || pos->sj_strategy == SJ_OPT_NONE)
+      continue;
+    
     if (pos->sj_strategy == SJ_OPT_MATERIALIZE)
     {
-      j= pos->table;
-      SJ_MATERIALIZE_INFO *sjm= j->emb_sj_nest->sj_mat_info;
+      SJ_MATERIALIZE_INFO *sjm= s->emb_sj_nest->sj_mat_info;
       sjm->is_used= TRUE;
       sjm->is_sj_scan= FALSE;
       memcpy(pos - sjm->n_tables + 1, sjm->positions, 
              sizeof(POSITION) * sjm->n_tables);
+      first= tablenr - sjm->n_tables + 1;
+      join->best_positions[first].n_sj_tables= sjm->n_tables;
     }
-    if (pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
+    else if (pos->sj_strategy == SJ_OPT_MATERIALIZE_SCAN)
     {
-      j= pos->table;
-      SJ_MATERIALIZE_INFO *sjm=
-     
join->best_positions[pos->sjm_scan_edge].table->emb_sj_nest->sj_mat_info;
+      POSITION *first_inner= join->best_positions + pos->sjm_scan_last_inner;
+      SJ_MATERIALIZE_INFO *sjm= first_inner->table->emb_sj_nest->sj_mat_info;
       sjm->is_used= TRUE;
       sjm->is_sj_scan= TRUE;
-      int first_pos= (pos->sjm_scan_edge - sjm->n_tables + 1);
-      memcpy(join->best_positions + first_pos, 
+      first= pos->sjm_scan_last_inner - sjm->n_tables + 1;
+      memcpy(join->best_positions + first, 
              sjm->positions, sizeof(POSITION) * sjm->n_tables);
-      join->best_positions[first_pos].use_sj_mat |= SJ_MAT_SCAN;
+      join->best_positions[first].use_sj_mat |= SJ_MAT_SCAN;
+      join->best_positions[first].n_sj_tables= sjm->n_tables;
+    }
+ 
+    if (pos->sj_strategy == SJ_OPT_FIRST_MATCH)
+    {
+      first= pos->first_firstmatch_table;
+      join->best_positions[first].sj_strategy= SJ_OPT_FIRST_MATCH;
+      join->best_positions[first].n_sj_tables= tablenr - first + 1;
+      POSITION dummy; // For loose scan paths
+      double record_count= (first== join->const_tables)? 1.0: 
+                           join->best_positions[tablenr - 1].prefix_record_count;
+      /*
+        Re-run best_access_path to produce best access methods that do not use
+        join buffering
+      */ 
+      for (uint idx= first; idx <= tablenr; idx++)
+      {
+        if (join->best_positions[idx].use_join_buffer)
+        {
+           best_access_path(join, join->best_positions[idx].table, 
+                            remaining_tables, idx, TRUE /* no jbuf */,
+                            record_count, join->best_positions + idx, &dummy);
+        }
+        record_count *= join->best_positions[idx].records_read;
+      }
+    }
+
+    if (pos->sj_strategy == SJ_OPT_LOOSE_SCAN) 
+    {
+      first= pos->first_loosescan_table;
+      POSITION *first_pos= join->best_positions + first;
+      first_pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
+      first_pos->n_sj_tables=
my_count_bits(first_pos->table->emb_sj_nest->sj_inner_tables);
+      POSITION loose_scan_pos; // For loose scan paths
+      double record_count= (first== join->const_tables)? 1.0: 
+                           join->best_positions[tablenr - 1].prefix_record_count;
+      
+      /*
+        Re-run best_access_path to produce best access methods that do not use
+        join buffering
+      */ 
+      for (uint idx= first; idx <= tablenr; i++)
+      {
+        if (join->best_positions[idx].use_join_buffer || (idx == first))
+        {
+           best_access_path(join, join->best_positions[idx].table,
+                            remaining_tables, idx, TRUE /* no jbuf */,
+                            record_count, join->best_positions + idx,
+                            &loose_scan_pos);
+           if (idx==first)
+             join->best_positions[idx]= loose_scan_pos;
+        }
+        record_count *= join->best_positions[idx].records_read;
+      }
+    }
+
+    if (pos->sj_strategy == SJ_OPT_DUPS_WEEDOUT)
+    {
+      /* 
+        Duplicate Weedout starting at pos->first_dupsweedout_table, ending at
+        this table.
+      */
+      first= pos->first_dupsweedout_table;
+      join->best_positions[first].sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+      join->best_positions[first].n_sj_tables= tablenr - first + 1;
     }
+    
+    uint i_end=join->best_positions[first].n_sj_tables;
+    for (uint i= first; i < i_end; i++)
+      handled_tabs |= join->best_positions[i].table->table->map;
+
   }
 
   for (j=join_tab, tablenr=0 ; tablenr < table_count ; tablenr++,j++)
@@ -7616,7 +7828,6 @@ get_best_combination(JOIN *join)
     TABLE *form;
     *j= *join->best_positions[tablenr].table;
     form=join->table[tablenr]=j->table;
-    //POSITION *pos= join->best_positions + tablenr;
     used_tables|= form->map;
     form->reginfo.join_tab=j;
     if (!*j->on_expr_ref)
@@ -7638,7 +7849,7 @@ get_best_combination(JOIN *join)
     if (j->keys.is_clear_all() || !(keyuse= join->best_positions[tablenr].key))
     {
       j->type=JT_ALL;
-      j->index= join->best_positions[tablenr].insideout_key;
+      j->index= join->best_positions[tablenr].loosescan_key;
       if (tablenr != join->const_tables)
 	join->full_join=1;
     }
@@ -7934,6 +8145,7 @@ make_simple_join(JOIN *join,TABLE *tmp_t
   join_tab->flush_weedout_table= join_tab->check_weed_out_table= NULL;
   join_tab->do_firstmatch= NULL;
   join_tab->insideout_match_tab= NULL;
+  join_tab->emb_sj_nest= NULL;
   bzero((char*) &join_tab->read_record,sizeof(join_tab->read_record));
   tmp_table->status=0;
   tmp_table->null_row=0;
@@ -9108,8 +9320,6 @@ bool setup_sj_materialization(JOIN_TAB *
   SJ_MATERIALIZE_INFO *sjm= emb_sj_nest->sj_mat_info;
   THD *thd= tab->join->thd;
   /* First the calls come to the materialization function */
-  // already done by caller: tab[-1].next_select= sub_select_sjm;
-  //Item *left_expr= emb_sj_nest->sj_subq_pred->left_expr;
   List<Item> &item_list=
emb_sj_nest->sj_subq_pred->unit->first_select()->item_list;
 
   /* 
@@ -9175,10 +9385,8 @@ bool setup_sj_materialization(JOIN_TAB *
     store_key **ref_key= tab_ref->key_copy;
     uchar *cur_ref_buff= tab_ref->key_buff;
     
-    //it.rewind();
     for (i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
     {
-      //tab_ref->items[i]= it++;
       tab_ref->items[i]=
emb_sj_nest->sj_subq_pred->left_expr->element_index(i);
       int null_count= test(cur_key_part->field->real_maybe_null());
       *ref_key= new store_key_item(thd, cur_key_part->field,
@@ -9219,12 +9427,12 @@ bool setup_sj_materialization(JOIN_TAB *
     it.rewind();
     for (uint i=0; i < sjm->sjm_table_cols.elements; i++)
     {
-      //Item *left_expr= emb_sj_nest->sj_subq_pred->left_expr->element_index(i);
       sjm->copy_field[i].set(((Item_field*)it++)->field,
                               sjm->table->field[i], FALSE);
     }
     /*
-     DONT NEED: 
+      Dont neeed this because we're using different approach here:
+      we copy materialized table columns to 
        Do not create/attach IN-equalities. They are already appropriately
        attached into JOIN_TABs.
     if (!(in_eq= create_subq_in_equalities(thd, sjm, emb_sj_nest->sj_subq_pred)))
@@ -9384,6 +9592,7 @@ make_join_readinfo(JOIN *join, ulonglong
       table->status=STATUS_NO_RECORD;
       using_join_cache= FALSE;
       if (i != join->const_tables && !(options & SELECT_NO_JOIN_CACHE)
&&
+          join->best_positions[i].use_join_buffer && 
           tab->use_quick != 2 && !tab->first_inner && i <=
no_jbuf_after &&
           !tab->insideout_match_tab &&
           !(join->best_positions[i].use_sj_mat & SJ_MAT_FIRST))
@@ -9706,7 +9915,6 @@ void JOIN::cleanup(bool full)
           tab->table->file->ha_index_or_rnd_end();
       }
     }
-    //was: cleanup_sj_tmp_tables(this);//
   }
   /*
     We are not using tables anymore
@@ -11901,6 +12109,63 @@ static void restore_prev_nj_state(JOIN_T
 
 
 /*
+  Change access methods not to use join buffering and adjust costs.
+*/
+
+void optimize_wo_join_buffering(JOIN *join, uint first_tab, uint last_tab, 
+                                table_map last_remaining_tables, 
+                                bool first_alt, uint no_jbuf_before,
+                                double *reopt_rec_count, double *reopt_cost,
+                                double *sj_inner_fanout)
+{
+  double cost, rec_count, inner_fanout= 1.0;
+  table_map reopt_remaining_tables= last_remaining_tables;
+  uint i;
+
+  if (first_tab > join->const_tables)
+  {
+    cost=      join->positions[first_tab - 1].prefix_cost.total_cost();
+    rec_count= join->positions[first_tab - 1].prefix_record_count;
+  }
+  else
+  {
+    cost= 0.0;
+    rec_count= 1;
+  }
+
+  for (i= first_tab; i <= last_tab; i++)
+    reopt_remaining_tables |= join->positions[i].table->table->map;
+
+  for (i= first_tab; i <= last_tab; i++)
+  {
+    JOIN_TAB *rs= join->positions[i].table;
+    POSITION pos, loose_scan_pos;
+    
+    if ((i == first_tab && first_alt) || join->positions[i].use_join_buffer)
+    {
+      /* Find the best access method that would not use join buffering */
+      best_access_path(join, rs, reopt_remaining_tables, i, 
+                       test(i < no_jbuf_before), rec_count,
+                       &pos, &loose_scan_pos);
+    }
+    if ((i == first_tab && first_alt))
+      pos= loose_scan_pos;
+
+    reopt_remaining_tables &= ~rs->table->map;
+    rec_count *= pos.records_read;
+    cost += pos.read_time;
+
+    if (rs->emb_sj_nest)
+      inner_fanout *= pos.records_read;
+  }
+
+  *reopt_rec_count= rec_count;
+  *reopt_cost= cost;
+  *sj_inner_fanout= inner_fanout;
+}
+
+
+/*
   Join optimization: update the semi-join related members after we've added
   a table
   
@@ -11910,114 +12175,191 @@ static void restore_prev_nj_state(JOIN_T
 */
 
 static 
-void advance_sj_state(JOIN *join, const table_map remaining_tables, 
+void advance_sj_state(JOIN *join, table_map remaining_tables, 
                       const JOIN_TAB *s, uint idx, 
-                      double *current_record_count, double *current_read_time)
+                      double *current_record_count, double *current_read_time, 
+                      POSITION *loose_scan_pos)
 {
   TABLE_LIST *emb_sj_nest;
   POSITION *pos= join->positions + idx;
-  bool case_handled= FALSE;
+  remaining_tables &= ~s->table->map;
 
+  pos->prefix_cost.set_double(*current_read_time);
+  pos->prefix_record_count= *current_record_count;
   pos->sj_strategy= SJ_OPT_NONE;
-  /* 
-    1. Update join->cur_emb_sj_nests
-  */
-  if ((emb_sj_nest= s->emb_sj_nest))
+  
+  /* Initialize the state or copy it from prev. tables */
+  if (idx == join->const_tables)
   {
-    join->cur_emb_sj_nests |= emb_sj_nest->sj_inner_tables;
-    /* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
-    if (!(remaining_tables & emb_sj_nest->sj_inner_tables &
~s->table->map))
-      join->cur_emb_sj_nests &= ~emb_sj_nest->sj_inner_tables;
-
-    pos->cur_fanout_generators |= s->table->map;
+    pos->first_firstmatch_table= MAX_TABLES;
+    pos->first_loosescan_table= MAX_TABLES; 
+    pos->dupsweedout_tables= 0;
+    pos->sjm_scan_need_tables= 0;
+    LINT_INIT(pos->sjm_scan_last_inner);
   }
-
-  /*
-    2. Loose Scan's strategy handler.
-       This is executed in its own join optimization 'fork', so we don't
-       need to consider other join methods when we considering this method -
-       we'll consider them in the other forks.
-  */
-  if (!join->cur_emb_sj_nests && pos->cur_sj_strategy == SJ_OPT_LOOSE_SCAN)
+  else
+  {
+    // FirstMatch
+    pos->first_firstmatch_table= (pos[-1].sj_strategy == SJ_OPT_FIRST_MATCH)?
+                                  MAX_TABLES : pos[-1].first_firstmatch_table;
+    pos->first_firstmatch_rtbl= pos[-1].first_firstmatch_rtbl;
+    pos->firstmatch_need_tables= pos[-1].firstmatch_need_tables;
+    // LooseScan
+    pos->first_loosescan_table= (pos[-1].sj_strategy == SJ_OPT_LOOSE_SCAN)? 
+                                  MAX_TABLES: pos[-1].first_loosescan_table;
+    pos->loosescan_need_tables= pos[-1].loosescan_need_tables;
+    // SJ-Materialization Scan
+    pos->sjm_scan_need_tables= (pos[-1].sj_strategy == SJ_OPT_MATERIALIZE_SCAN)? 
+                               0: pos[-1].sjm_scan_need_tables;
+    pos->sjm_scan_last_inner= pos[-1].sjm_scan_last_inner;
+
+    // Duplicate Weedout
+    pos->dupsweedout_tables=      pos[-1].dupsweedout_tables;
+    pos->first_dupsweedout_table= pos[-1].first_dupsweedout_table;
+  }
+  
+  table_map handled_by_fm_or_ls= 0;
+  /* FirstMatch Strategy */
   {
-
     /* 
-      - Use of check_semi_join_ok function guaranteed no interleaves.
-      - There is no added cost in using loose scan.
-      - Need to remove the fanout.
-      - And enable everything back
-    */
-    pos->cur_disable_jbuf= FALSE;
-    pos->cur_sj_strategy= SJ_OPT_NONE;
-
-    pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
-    uint n_tables= my_count_bits(s->emb_sj_nest->sj_inner_tables);
-    pos->n_tables= n_tables;
+      Enter condition:
+       1. The next join tab belongs to semi-join nest
+       2. We're not in a duplicate producer range yet
+       3. All outer tables that
+           - the subquery is correlated with, or
+           - referred to from the outer_expr 
+          are in the prefix
+    */
+    if (s->emb_sj_nest && !join->cur_emb_sj_nests &&   //(1), (2)
+       !(remaining_tables & (s->emb_sj_nest->nested_join->sj_corr_tables |
//(3)
+                             s->emb_sj_nest->nested_join->sj_depends_on))) //(3)
+    {
+      /* Start tracking potential FirstMatch range */
+      pos->first_firstmatch_table= idx;
+      pos->firstmatch_need_tables= s->emb_sj_nest->sj_inner_tables;
+      pos->first_firstmatch_rtbl= remaining_tables;
+    }
+
+    if (pos->first_firstmatch_table != MAX_TABLES && s->emb_sj_nest)
+    {
+      table_map outer_corr_tables= s->emb_sj_nest->nested_join->sj_corr_tables |
+                                   s->emb_sj_nest->nested_join->sj_depends_on;
+      if (outer_corr_tables & pos->first_firstmatch_rtbl)
+      {
+        /*
+          Trying to add an sj-inner table whose sj-nest has an outer correlated 
+          table that was not in the prefix. This means FirstMatch can't be used.
+        */
+        pos->first_firstmatch_table= MAX_TABLES;
+      }
+      else
+      {
+        /* Record that we need all of this semi-join's inner tables, too */
+        pos->firstmatch_need_tables |= s->emb_sj_nest->sj_inner_tables;
+      }
     
-    /* Remove the fanout generated by sj-inner tables */
-    double extra_fanout= 1.0;
-    for (uint i= idx - n_tables; i <= idx ; i++)
-    {
-      if (join->positions[i].records_read)
-        extra_fanout *= join->positions[i].records_read;
+      if (!(pos->firstmatch_need_tables & remaining_tables))
+      {
+        /*
+          Got a complete FirstMatch range.
+            Calculate correct costs and fanout
+        */
+        double reopt_cost, reopt_rec_count, sj_inner_fanout;
+        optimize_wo_join_buffering(join, pos->first_firstmatch_table, idx,
+                                   remaining_tables, FALSE, idx,
+                                   &reopt_rec_count, &reopt_cost, 
+                                   &sj_inner_fanout);
+        /*
+          We don't yet know what are the other strategies, so pick the
+          FirstMatch.
+
+          We ought to save the alternate POSITIONs produced by
+          optimize_wo_join_buffering but the problem is that providing save
+          space uses too much space. Instead, we will re-caclulate the
+          alternate POSITIONs after we've picked the best QEP.
+        */
+        pos->sj_strategy= SJ_OPT_FIRST_MATCH;
+        *current_read_time=    reopt_cost;
+        *current_record_count= reopt_rec_count / sj_inner_fanout;
+        handled_by_fm_or_ls=  pos->firstmatch_need_tables;
+      }
     }
-    pos->cur_fanout_generators= 0;
-    /* Duplicate elimination should not try handling this: */
-    pos->dupsweedout_tables= 0;
-    *current_record_count /= extra_fanout;
-    case_handled= TRUE;
   }
 
-  /*
-    3. First Match strategy handler.
-       This has its own 'fork', too.
-  */
-  if (!join->cur_emb_sj_nests && pos->cur_sj_strategy ==
SJ_OPT_FIRST_MATCH)
+  /* LooseScan Strategy */
   {
-    /* 
-      - There is no added cost in using first match
-      - Need to remove the fanout
-      - And enable everything back
-    */
-    pos->cur_sj_strategy= SJ_OPT_NONE;
-    pos->cur_disable_jbuf= FALSE;
+    POSITION *first=join->positions+pos->first_loosescan_table; 
+    /* Check that there's no interleaving w/ other tables */
+    if ((pos->first_loosescan_table != MAX_TABLES) &&
+        (first->table->emb_sj_nest->sj_inner_tables & remaining_tables)
&&
+        s->emb_sj_nest != first->table->emb_sj_nest)
+    {
+      pos->first_loosescan_table= MAX_TABLES;
+    }
 
-    pos->sj_strategy= SJ_OPT_FIRST_MATCH;
-    pos->n_tables= idx - pos->first_firstmatch_table;
+    /* Start loosescan nest if we're at the first LooseScan table */
+    if (loose_scan_pos->read_time != DBL_MAX)
+    {
+      pos->first_loosescan_table= idx;
+      pos->loosescan_need_tables= s->emb_sj_nest->sj_inner_tables | 
+                                  s->emb_sj_nest->nested_join->sj_depends_on |
+                                  s->emb_sj_nest->nested_join->sj_corr_tables;
+    }
     
-    /* Remove the fanout generated by sj-inner tables */
-    double extra_fanout= 1.0;
-    for (uint i= pos->first_firstmatch_table; i <= idx ; i++)
+    /* 
+      Ok have put all loose scan's inner and outer correlated tables into the
+      prefix.
+    */
+    if ((pos->first_loosescan_table != MAX_TABLES) && 
+        !(remaining_tables & pos->loosescan_need_tables))
     {
+      /* At the last LooseScan table */
+      first=join->positions + pos->first_loosescan_table; 
+      uint n_tables= my_count_bits(first->table->emb_sj_nest->sj_inner_tables);
+      /* Got a complete LooseScan range. Calculate its cost */
+      double reopt_cost, reopt_rec_count, sj_inner_fanout;
+
       /*
-        Crude fanout handling: ignore the jumps between interleaved
-        tables.
+        The same problem as with FirstMatch - we need to save POSITIONs
+        somewhere but reserving space for all cases would require too
+        much space. We will re-caclulate POSITION structures later on. 
+      */
+      optimize_wo_join_buffering(join, pos->first_loosescan_table, idx,
+                                 remaining_tables, 
+                                 TRUE,  //first_alt
+                                 pos->first_loosescan_table + n_tables,
+                                 &reopt_rec_count, 
+                                 &reopt_cost, &sj_inner_fanout);
+      /*
+        We don't yet know what are the other strategies, so pick the
+        LooseScan.
       */
-      if (join->positions[i].records_read && 
-          join->positions[i].table->emb_sj_nest)
-      {
-        extra_fanout *= join->positions[i].records_read;
-      }
+      pos->sj_strategy= SJ_OPT_LOOSE_SCAN;
+      *current_read_time=    reopt_cost;
+      *current_record_count= reopt_rec_count / sj_inner_fanout;
+      handled_by_fm_or_ls= first->table->emb_sj_nest->sj_inner_tables;
     }
-    pos->cur_fanout_generators= 0;
-    /* Duplicate elimination should not try handling this: */
-    pos->dupsweedout_tables= 0;
-    *current_record_count /= extra_fanout;
-    case_handled= TRUE;
   }
-  
+
   /* 
-    4. SJ-Materialization and SJ-Materialization-scan strategy handler
+    Update join->cur_emb_sj_nests (Used by FirstMatch in this function and
+    LooseScan detector in best_access_path)
   */
-  int best_sj_strategy= SJ_OPT_NONE;
-  double best_sj_read_time=  DBL_MAX;
-  double best_rec_cnt=       DBL_MAX;
-  table_map best_removed_fanout;
+  if ((emb_sj_nest= s->emb_sj_nest))
+  {
+    join->cur_emb_sj_nests |= emb_sj_nest->sj_inner_tables;
+    join->cur_unhandled_sj_fanout |= emb_sj_nest->sj_inner_tables;
+
+    /* Remove the sj_nest if all of its SJ-inner tables are in cur_table_map */
+    if (!(remaining_tables & emb_sj_nest->sj_inner_tables &
~s->table->map))
+      join->cur_emb_sj_nests &= ~emb_sj_nest->sj_inner_tables;
+  }
+  join->cur_unhandled_sj_fanout &= ~handled_by_fm_or_ls;
 
+  /* 4. SJ-Materialization and SJ-Materialization-scan strategy handler */
   bool sjm_scan;
   SJ_MATERIALIZE_INFO *mat_info;
-  if (!case_handled && (mat_info= at_sjmat_pos(join, remaining_tables, s, 
-                                               idx, &sjm_scan)))
+  if ((mat_info= at_sjmat_pos(join, remaining_tables, s, idx, &sjm_scan)))
   {
     if (sjm_scan)
     {
@@ -12040,13 +12382,14 @@ void advance_sj_state(JOIN *join, const 
         The simple way to model this is to remove SJM-SCAN(...) fanout once
         we reach the point #2.
       */
-      pos->sjm_scan_finish= s->emb_sj_nest->sj_inner_tables | 
-                            s->emb_sj_nest->nested_join->sj_depends_on |
-                            s->emb_sj_nest->nested_join->sj_corr_tables;
-      pos->sjm_scan_edge= idx;
+      pos->sjm_scan_need_tables= s->emb_sj_nest->sj_inner_tables | 
+                                 s->emb_sj_nest->nested_join->sj_depends_on |
+                                 s->emb_sj_nest->nested_join->sj_corr_tables;
+      pos->sjm_scan_last_inner= idx;
     }
     else
     {
+      /* This is SJ-Materialization with lookups */
       COST_VECT prefix_cost; 
       int first_tab= idx - mat_info->n_tables;
       if (idx == join->const_tables)
@@ -12059,150 +12402,157 @@ void advance_sj_state(JOIN *join, const 
       mat_read_time += mat_info->materialization_cost.total_cost() +
                        prefix_rec_count * mat_info->lookup_cost.total_cost();
 
-      if (mat_read_time < best_sj_read_time)
+      if (mat_read_time < *current_read_time)
       {
         /*
           NOTE: When we pick to use SJM[-Scan] we don't memcpy its POSITION
           elements to join->positions as that makes it hard to return things
-          back when making one step back in join optimization.
-          We just note the use of Materialization.
+          back when making one step back in join optimization. That's done 
+          after the QEP has been chosen.
         */
-        best_sj_read_time= mat_read_time;
-        best_rec_cnt= prefix_rec_count;
-        best_removed_fanout= s->emb_sj_nest->sj_inner_tables;
-        best_sj_strategy= SJ_OPT_MATERIALIZE;
+        pos->sj_strategy= SJ_OPT_MATERIALIZE;
+        *current_read_time=    mat_read_time;
+        *current_record_count= prefix_rec_count;
+        join->cur_unhandled_sj_fanout&= ~s->emb_sj_nest->sj_inner_tables;
       }
     }
   }
   
-  /*
-    4.A SJM-Scan second phase check
-  */
-  if (pos->sjm_scan_finish && !(pos->sjm_scan_finish &
~s->table->map &
-                                remaining_tables))
+  /* 4.A SJM-Scan second phase check */
+  if (pos->sjm_scan_need_tables && /* Have SJM-Scan prefix */
+      !(pos->sjm_scan_need_tables & remaining_tables))
   {
-    TABLE_LIST *mat_nest=
join->positions[pos->sjm_scan_edge].table->emb_sj_nest;
+    TABLE_LIST *mat_nest= 
+      join->positions[pos->sjm_scan_last_inner].table->emb_sj_nest;
     SJ_MATERIALIZE_INFO *mat_info= mat_nest->sj_mat_info;
 
-    int first_tab= pos->sjm_scan_edge - mat_info->n_tables;
-    COST_VECT prefix_cost= join->positions[first_tab].prefix_cost;
-    double prefix_rec_count= join->positions[first_tab].prefix_record_count;
+    COST_VECT prefix_cost;
+    double prefix_rec_count;
+    int first_tab;
+    if ((first_tab= pos->sjm_scan_last_inner - mat_info->n_tables) + 1 != 
+        (int)join->const_tables)
+    {
+      prefix_cost= join->positions[first_tab].prefix_cost;
+      prefix_rec_count= join->positions[first_tab].prefix_record_count;
+    }
+    else
+    {
+      prefix_rec_count= 1.0;
+      prefix_cost.zero();
+    }
 
     double mat_read_time= prefix_cost.total_cost();
     mat_read_time += mat_info->materialization_cost.total_cost() +
                      prefix_rec_count * mat_info->scan_cost.total_cost();
+    //psergey-todo: add costs for tables that are after the inner tables!
     if (mat_read_time < *current_read_time)
     {
-      best_sj_read_time= mat_read_time;
-      best_rec_cnt= prefix_rec_count;
-      best_removed_fanout= mat_nest->sj_inner_tables;
-      best_sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
-    }
-  }
-
-  /*
-    5. Duplicate Weedout strategy handler
-  */
-  /* 
-     Duplicate weedout can be applied after all ON-correlated and 
-     correlated 
-  */
-  TABLE_LIST *nest;
-  if ((nest= s->emb_sj_nest))
-  {
-    if (!pos->dupsweedout_tables)
-      pos->first_dupsweedout_table= idx;
+      pos->sj_strategy= SJ_OPT_MATERIALIZE_SCAN;
+      *current_read_time=    mat_read_time;
+      *current_record_count= prefix_rec_count;
+      join->cur_unhandled_sj_fanout&= ~mat_nest->sj_inner_tables;
 
-    pos->dupsweedout_tables |= nest->sj_inner_tables |
-                               nest->nested_join->sj_depends_on |
-                               nest->nested_join->sj_corr_tables;
+    }
   }
 
-  if (!(pos->cur_forks & pos->dupsweedout_tables) &&
-       pos->dupsweedout_tables && 
-      !((remaining_tables & ~s->table->map) & pos->dupsweedout_tables))
+  /* 5. Duplicate Weedout strategy handler */
   {
-    /*
-      Ok, reached a state where we could put a dups weedout point.
-      Walk back and calculate
-        - the join cost (this is needed as the accumulated cost may assume 
-          some other duplicate elimination method)
-        - extra fanout that will be removed by duplicate elimination
-        - duplicate elimination cost
-      There are two cases:
-        1. We have other strategy/ies to remove all of the duplicates.
-        2. We don't.
-      
-      We need to calculate the cost in case #2 also because we need to make
-      choice between this join order and others.
+    /* 
+       Duplicate weedout can be applied after all ON-correlated and 
+       correlated 
     */
-    uint first_tab= pos->first_dupsweedout_table;
-    double dups_cost;
-    double prefix_rec_count;
-    double sj_inner_fanout= 1.0;
-    double sj_outer_fanout= 1.0;
-    uint temptable_rec_size;
-    if (first_tab == join->const_tables)
+    TABLE_LIST *nest;
+    if ((nest= s->emb_sj_nest))
     {
-      prefix_rec_count= 1.0;
-      temptable_rec_size= 0;
-      dups_cost= 0.0;
-    }
-    else
-    {
-      dups_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
-      prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
-      temptable_rec_size= 8; /* This is not true but we'll make it so */
+      if (!pos->dupsweedout_tables)
+        pos->first_dupsweedout_table= idx;
+
+      pos->dupsweedout_tables |= nest->sj_inner_tables |
+                                 nest->nested_join->sj_depends_on |
+                                 nest->nested_join->sj_corr_tables;
     }
-    
-    table_map dups_removed_fanout= 0;
-    for (uint j= pos->first_dupsweedout_table; j <= idx; j++)
+
+    if (pos->dupsweedout_tables && 
+        !((remaining_tables & ~s->table->map) &
pos->dupsweedout_tables))
     {
-      POSITION *p= join->positions + j;
-      dups_cost += p->read_time;
-      if (p->table->emb_sj_nest)
-      {
-        sj_inner_fanout *= p->records_read;
-        dups_removed_fanout |= p->table->table->map;
+      /*
+        Ok, reached a state where we could put a dups weedout point.
+        Walk back and calculate
+          - the join cost (this is needed as the accumulated cost may assume 
+            some other duplicate elimination method)
+          - extra fanout that will be removed by duplicate elimination
+          - duplicate elimination cost
+        There are two cases:
+          1. We have other strategy/ies to remove all of the duplicates.
+          2. We don't.
+        
+        We need to calculate the cost in case #2 also because we need to make
+        choice between this join order and others.
+      */
+      uint first_tab= pos->first_dupsweedout_table;
+      double dups_cost;
+      double prefix_rec_count;
+      double sj_inner_fanout= 1.0;
+      double sj_outer_fanout= 1.0;
+      uint temptable_rec_size;
+      if (first_tab == join->const_tables)
+      {
+        prefix_rec_count= 1.0;
+        temptable_rec_size= 0;
+        dups_cost= 0.0;
       }
       else
       {
-        sj_outer_fanout *= p->records_read;
-        temptable_rec_size += p->table->table->file->ref_length;
+        dups_cost= join->positions[first_tab - 1].prefix_cost.total_cost();
+        prefix_rec_count= join->positions[first_tab - 1].prefix_record_count;
+        temptable_rec_size= 8; /* This is not true but we'll make it so */
+      }
+      
+      table_map dups_removed_fanout= 0;
+      for (uint j= pos->first_dupsweedout_table; j <= idx; j++)
+      {
+        POSITION *p= join->positions + j;
+        dups_cost += p->read_time;
+        if (p->table->emb_sj_nest)
+        {
+          sj_inner_fanout *= p->records_read;
+          dups_removed_fanout |= p->table->table->map;
+        }
+        else
+        {
+          sj_outer_fanout *= p->records_read;
+          temptable_rec_size += p->table->table->file->ref_length;
+        }
       }
-    }
-
-    /*
-      Add the cost of temptable use. The table will have sj_outer_fanout
-      records, and we will make 
-      - sj_outer_fanout table writes
-      - sj_inner_fanout*sj_outer_fanout  lookups.
-
-    */
-    bool is_disk_table= test(sj_outer_fanout > 
-                             join->thd->variables.max_heap_table_size);
 
-    double write_cost= join->positions[first_tab].prefix_record_count* 
-                       sj_outer_fanout * (is_disk_table? 1.0: 0.05);
-    double lookup_cost= *current_record_count * (is_disk_table? 1.0:0.05);
-    dups_cost += write_cost + lookup_cost;
+      /*
+        Add the cost of temptable use. The table will have sj_outer_fanout
+        records, and we will make 
+        - sj_outer_fanout table writes
+        - sj_inner_fanout*sj_outer_fanout  lookups.
 
-    if (dups_cost < best_sj_read_time)
-    {
-      best_sj_read_time= dups_cost;
-      best_rec_cnt= *current_record_count / sj_inner_fanout;
-      best_removed_fanout= dups_removed_fanout;
-      best_sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+      */
+      bool is_disk_table= test(sj_outer_fanout > 
+                               join->thd->variables.max_heap_table_size);
+
+      double write_cost= join->positions[first_tab].prefix_record_count* 
+                         sj_outer_fanout * (is_disk_table? 1.0: 0.05);
+      double lookup_cost= *current_record_count * (is_disk_table? 1.0:0.05);
+      dups_cost += write_cost + lookup_cost;
+      
+      /*
+        Duplicate Weedout is the catch-all default, so we should pick it if
+        there is unhandled sj-fanout.
+      */
+      if (dups_cost < *current_read_time || join->cur_unhandled_sj_fanout)
+      {
+        pos->sj_strategy= SJ_OPT_DUPS_WEEDOUT;
+        *current_read_time= dups_cost;
+        *current_record_count= *current_record_count / sj_inner_fanout;
+        join->cur_unhandled_sj_fanout &= ~dups_removed_fanout;
+      }
     }
   }
-
-  if (best_sj_strategy != SJ_OPT_NONE)
-  {
-    *current_record_count= best_rec_cnt;
-    *current_read_time=    best_sj_read_time;
-    pos->sj_strategy=      best_sj_strategy;
-    pos->cur_fanout_generators &= ~best_removed_fanout;
-  }
 }
 
 
@@ -12224,8 +12574,6 @@ static void restore_prev_sj_state(const 
       tab->join->cur_emb_sj_nests &= ~emb_sj_nest->sj_inner_tables;
     }
   }
-  //join->cur_sj_strategy= join->positions[idx].save_sj_strategy;
-  //join->disable_join_buffering= join->positions[idx].save_sj_jbuf;
 }
 
 
@@ -14838,6 +15186,19 @@ sub_select_sjm(JOIN *join,JOIN_TAB *join
 {
   int res;
   enum_nested_loop_state rc;
+  if (!join_tab->emb_sj_nest)
+  {
+    /* 
+      We're handling GROUP BY/ORDER BY, this is the first table, and we've
+      actually executed the join already and now we're just reading the
+      result of the join from the temporary table.
+      Bypass to regular join handling.
+      Yes, it would be nicer if sub_select_sjm wasn't called at all in this
+      case but there's no easy way to arrange this.
+    */
+    return sub_select(join, join_tab, end_of_records);
+  }
+
   if (end_of_records)
     return (*join_tab->next_select)(join, join_tab + 1, end_of_records);
 

=== modified file 'sql/sql_select.h'
--- a/sql/sql_select.h	2008-07-12 19:26:05 +0000
+++ b/sql/sql_select.h	2008-07-26 19:37:13 +0000
@@ -362,58 +362,71 @@ typedef struct st_position
 
   /* If ref-based access is used: bitmap of tables this table depends on  */
   table_map ref_depend_map;
+  bool use_join_buffer; 
   
-  /* 
+  
+  /* These form a stack of partial join order costs and output sizes */
+  COST_VECT prefix_cost;
+  double    prefix_record_count;
+
+  /*
+    Current optimization state: Semi-join strategy to be used for this
+    and preceding join tables.
+    
+    Join optimizer sets this for the *last* join_tab in the
+    duplicate-generating range. That is, in order to interpret this field, 
+    one needs to traverse join->[best_]positions array from right to left.
+    When you see a join table with sj_strategy!= SJ_OPT_NONE, some other
+    field (depending on the strategy) tells how many preceding positions 
+    this applies to. The values of covered_preceding_positions->sj_strategy
+    must be ignored.
+  */
+  uint sj_strategy;
+
+  /* Current optimization state: Loose Scan strategy */
+  uint        first_loosescan_table;
+  table_map   loosescan_need_tables;
+  ;
+
+/* LooseScan strategy members */
+  /*
     keyno  - This is an insideout scan on this key. If keyuse is NULL then
               this is a full index scan, otherwise this is a ref + insideout
               scan (and keyno matches the KEUSE's)
     MAX_KEY - This is not an InsideOut scan
   */
-  uint insideout_key;
-  /* Number of key parts to be used by insideout */
-  uint insideout_parts;
+  uint loosescan_key;  // final (one for strategy instance )
+  uint loosescan_parts; /* Number of keyparts to be kept distinct */
   
+
+/* SJ-Materialization[-scan] strategy */
   /*
     0         - not using semi-join materialization
     sj_mat_*  - using semi-join materialization, the value specifies whether 
                 this is a first/last/just some inner tab.
   */
-  uint use_sj_mat;
-  /* TRUE <=> sj materialization plus scan */
-  //bool sj_mat_scan;
+  uint use_sj_mat;  // final(one for strategy instance)
 
-  bool use_join_buffer;
-  
-  /* cumulative fanout of all sj-inner tables */
-  //double sj_fanout;
-  
-  /*
-    Semi-join strategy to be used.
-    Join optimizer sets this for the *last* join tab in the
-    duplicate-generating range.
-  */
-  uint sj_strategy;
-  /* How many tables are covered by the above */
-  uint n_tables;
 
-  /* Semi-join's optimization stack space: */
-  uint cur_sj_strategy;
-  bool cur_disable_jbuf;
-  int first_firstmatch_table;
-  int first_firstmatch_rtbl;
-  
-  /* Stack of partial join order costs and fanout sizes */
-  COST_VECT prefix_cost;
-  double    prefix_record_count;
-  
-  table_map cur_fanout_generators;
+/* FirstMatch strategy */
+  uint first_firstmatch_table; //state
+  table_map first_firstmatch_rtbl; // state Tables before the firstmatch table
+  table_map firstmatch_need_tables; // state
+
+
+/* Duplicate Weedout strategy */
   table_map dupsweedout_tables;
   uint  first_dupsweedout_table;
- 
-  table_map cur_forks;
 
-  table_map sjm_scan_finish;
-  uint      sjm_scan_edge;
+/* SJM-Scan strategy */
+  // When all these tables are in the prefix, the fanout is gone?
+  table_map sjm_scan_need_tables; // state
+  uint      sjm_scan_last_inner;  // state
+  
+  /*
+    Used at plan refinement stage.
+  */
+  uint n_sj_tables;
 } POSITION;
 
 
@@ -468,6 +481,13 @@ public:
   SJ_TMP_TABLE *next; 
 };
 
+#define SJ_OPT_NONE 0
+#define SJ_OPT_DUPS_WEEDOUT 1
+#define SJ_OPT_LOOSE_SCAN   2
+#define SJ_OPT_FIRST_MATCH  3
+#define SJ_OPT_MATERIALIZE  4
+#define SJ_OPT_MATERIALIZE_SCAN  5
+
 
 class JOIN :public Sql_alloc
 {
@@ -517,9 +537,9 @@ public:
     NULL    - otherwise
   */
   TABLE_LIST *emb_sjm_nest;
-  
 
   POSITION positions[MAX_TABLES+1];
+  //POSITION loose_scan_pos;
   
   /*
     Bitmap of nested joins embedding the position at the end of the current 
@@ -528,16 +548,9 @@ public:
   nested_join_map cur_embedding_map;
 
   table_map cur_emb_sj_nests;
- // bool disable_join_buffering;
-#define SJ_OPT_NONE 0
-#define SJ_OPT_DUPS_WEEDOUT 1
-#define SJ_OPT_LOOSE_SCAN   2
-#define SJ_OPT_FIRST_MATCH  3
-#define SJ_OPT_MATERIALIZE  4
-#define SJ_OPT_MATERIALIZE_SCAN  5
+  table_map cur_unhandled_sj_fanout;
 
-  //int cur_sj_strategy;
-  //int first_firstmatch_table;
+  /* We also maintain a stack of join optimization states in * join->positions[] */
 /******* Join optimization members end *******/
 
 
@@ -631,8 +644,7 @@ public:
   
   Array<Item_in_subselect> sj_subselects;
 
-  /* Descriptions of temporary tables used to weed-out semi-join duplicates */
-  //SJ_TMP_TABLE  *sj_tmp_tables;
+  /* Temporary tables used to weed-out semi-join duplicates */
   List<TABLE> sj_tmp_tables;
 
   /* 
@@ -708,7 +720,6 @@ public:
     tmp_table_param.init();
     tmp_table_param.end_write_records= HA_POS_ERROR;
     rollup.state= ROLLUP::STATE_NONE;
-    //sj_tmp_tables= NULL;
 
     no_const_tables= FALSE;
     first_select= sub_select;

=== modified file 'sql/sql_test.cc'
--- a/sql/sql_test.cc	2008-07-12 19:26:05 +0000
+++ b/sql/sql_test.cc	2008-07-26 19:37:13 +0000
@@ -318,7 +318,6 @@ print_plan(JOIN* join, uint idx, double 
     table= pos.table->table;
     if (table)
       fputs(table->alias, DBUG_FILE);
-      //fputs(table->s->table_name.str, DBUG_FILE);
     fputc(' ', DBUG_FILE);
   }
   fputc('\n', DBUG_FILE);
@@ -336,7 +335,6 @@ print_plan(JOIN* join, uint idx, double 
       table= pos.table->table;
       if (table)
         fputs(table->alias, DBUG_FILE);
-        //fputs(table->s->table_name.str, DBUG_FILE);
       fputc(' ', DBUG_FILE);
     }
   }
@@ -359,6 +357,28 @@ print_plan(JOIN* join, uint idx, double 
   DBUG_UNLOCK_FILE;
 }
 
+
+#ifndef DBUG_OFF
+void print_sjm(SJ_MATERIALIZE_INFO *sjm)
+{
+  DBUG_LOCK_FILE;
+  fprintf(DBUG_FILE, "\nsemi-join nest{\n");
+  fprintf(DBUG_FILE, "  tables { \n");
+  for (uint i= 0;i < sjm->n_tables; i++)
+  {
+    fprintf(DBUG_FILE, "    %s%s\n", 
+            sjm->positions[i].table->table->alias,
+            (i == sjm->n_tables -1)? "": ",");
+  }
+  fprintf(DBUG_FILE, "  }\n");
+  fprintf(DBUG_FILE, "  materialize_cost= %g\n",
+          sjm->materialization_cost.total_cost());
+  fprintf(DBUG_FILE, "  rows= %g\n", sjm->rows);
+  fprintf(DBUG_FILE, "}\n");
+  DBUG_UNLOCK_FILE;
+}
+#endif 
+
 #endif
 
 typedef struct st_debug_lock

Thread
bzr push into mysql-6.0-opt-subqueries branch (sergefp:2678 to 2680) WL#3985Sergey Petrunia26 Jul