maria-developers team mailing list archive
-
maria-developers team
-
Mailing list archive
-
Message #02630
bzr commit into file:///home/tsk/mprog/src/5.3-subqueries/ branch (timour:2779)
#At file:///home/tsk/mprog/src/5.3-subqueries/ based on revid:psergey@xxxxxxxxxxxx-20100315063535-jsp4jgya6lfqt8e6
2779 timour@xxxxxxxxxxxx 2010-03-15 [merge]
Merge in MWL#68: Subquery optimization: Efficient NOT IN execution with NULLs
modified:
mysql-test/include/mix1.inc
mysql-test/r/index_merge_myisam.result
mysql-test/r/innodb_mysql.result
mysql-test/r/myisam_mrr.result
mysql-test/r/ps.result
mysql-test/r/subselect.result
mysql-test/r/subselect3.result
mysql-test/r/subselect3_jcl6.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_sj.result
mysql-test/r/subselect_sj_jcl6.result
mysql-test/t/ps.test
mysql-test/t/subselect.test
mysql-test/t/subselect3.test
sql/item_cmpfunc.h
sql/item_subselect.cc
sql/item_subselect.h
sql/mysql_priv.h
sql/mysqld.cc
sql/opt_subselect.cc
sql/set_var.cc
sql/sql_class.cc
sql/sql_class.h
sql/sql_select.cc
=== modified file 'mysql-test/include/mix1.inc'
--- a/mysql-test/include/mix1.inc 2009-09-15 06:08:54 +0000
+++ b/mysql-test/include/mix1.inc 2010-03-11 21:43:31 +0000
@@ -1177,8 +1177,11 @@ DROP TABLE t1;
create table t1 (a bit(1) not null,b int) engine=myisam;
create table t2 (c int) engine=innodb;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=off';
explain
select b from t1 where a not in (select b from t1,t2 group by a) group by a;
+set optimizer_switch=@save_optimizer_switch;
DROP TABLE t1,t2;
--echo End of 5.0 tests
=== modified file 'mysql-test/r/index_merge_myisam.result'
--- a/mysql-test/r/index_merge_myisam.result 2010-01-17 14:51:10 +0000
+++ b/mysql-test/r/index_merge_myisam.result 2010-03-11 21:43:31 +0000
@@ -1419,19 +1419,19 @@ drop table t1;
#
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='index_merge=off,index_merge_union=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=off,index_merge_union=off,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=off,index_merge_union=off,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='index_merge_union=on';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=off,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=off,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,index_merge_sort_union=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=off,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=off,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch=4;
ERROR 42000: Variable 'optimizer_switch' can't be set to the value of '4'
set optimizer_switch=NULL;
@@ -1458,21 +1458,21 @@ set optimizer_switch=default;
set optimizer_switch='index_merge=off,index_merge_union=off,default';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=off,index_merge_union=off,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=off,index_merge_union=off,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch=default;
select @@global.optimizer_switch;
@@global.optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set @@global.optimizer_switch=default;
select @@global.optimizer_switch;
@@global.optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
#
# Check index_merge's @@optimizer_switch flags
#
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
create table t1 (a int, b int, c int, filler char(100),
@@ -1582,5 +1582,5 @@ id select_type table type possible_keys
set optimizer_switch=default;
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
drop table t0, t1;
=== modified file 'mysql-test/r/innodb_mysql.result'
--- a/mysql-test/r/innodb_mysql.result 2009-12-15 07:16:46 +0000
+++ b/mysql-test/r/innodb_mysql.result 2010-03-11 21:43:31 +0000
@@ -1425,12 +1425,15 @@ DROP TABLE t1;
#
create table t1 (a bit(1) not null,b int) engine=myisam;
create table t2 (c int) engine=innodb;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=off';
explain
select b from t1 where a not in (select b from t1,t2 group by a) group by a;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables
2 DEPENDENT SUBQUERY t1 system NULL NULL NULL NULL 0 const row not found
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 1
+set optimizer_switch=@save_optimizer_switch;
DROP TABLE t1,t2;
End of 5.0 tests
CREATE TABLE `t2` (
=== modified file 'mysql-test/r/myisam_mrr.result'
--- a/mysql-test/r/myisam_mrr.result 2010-01-17 14:51:10 +0000
+++ b/mysql-test/r/myisam_mrr.result 2010-03-11 21:43:31 +0000
@@ -394,7 +394,7 @@ drop table t0, t1;
# - engine_condition_pushdown does not affect ICP
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
create table t0 (a int);
insert into t0 values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9);
create table t1 (a int, b int, key(a));
=== modified file 'mysql-test/r/ps.result'
--- a/mysql-test/r/ps.result 2009-05-27 15:19:44 +0000
+++ b/mysql-test/r/ps.result 2010-03-11 21:43:31 +0000
@@ -149,6 +149,8 @@ c29 longblob, c30 longtext, c31 enum('on
c32 set('monday', 'tuesday', 'wednesday')
) engine = MYISAM ;
create table t2 like t1;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
set @stmt= ' explain SELECT (SELECT SUM(c1 + c12 + 0.0) FROM t2 where (t1.c2 - 0e-3) = t2.c2 GROUP BY t1.c15 LIMIT 1) as scalar_s, exists (select 1.0e+0 from t2 where t2.c3 * 9.0000000000 = t1.c4) as exists_s, c5 * 4 in (select c6 + 0.3e+1 from t2) as in_s, (c7 - 4, c8 - 4) in (select c9 + 4.0, c10 + 40e-1 from t2) as in_row_s FROM t1, (select c25 x, c32 y from t2) tt WHERE x * 1 = c25 ' ;
prepare stmt1 from @stmt ;
execute stmt1 ;
@@ -177,6 +179,7 @@ id select_type table type possible_keys
2 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL Impossible WHERE noticed after reading const tables
deallocate prepare stmt1;
drop tables t1,t2;
+set @@optimizer_switch=@save_optimizer_switch;
set @arg00=1;
prepare stmt1 from ' create table t1 (m int) as select 1 as m ' ;
execute stmt1 ;
=== modified file 'mysql-test/r/subselect.result'
--- a/mysql-test/r/subselect.result 2010-02-17 21:59:41 +0000
+++ b/mysql-test/r/subselect.result 2010-03-11 21:43:31 +0000
@@ -1,4 +1,6 @@
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t11,t12;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
select (select 2);
(select 2)
2
@@ -4803,4 +4805,5 @@ SELECT 1 FROM t1 GROUP BY
1
1
DROP TABLE t1;
+set @@optimizer_switch=@save_optimizer_switch;
End of 5.1 tests.
=== modified file 'mysql-test/r/subselect3.result'
--- a/mysql-test/r/subselect3.result 2010-02-17 10:05:27 +0000
+++ b/mysql-test/r/subselect3.result 2010-03-11 21:43:31 +0000
@@ -63,12 +63,15 @@ Handler_read_rnd_next 11
select ' ^ This must show 11' Z;
Z
^ This must show 11
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
explain extended select a in (select max(ie) from t1 where oref=4 group by grp) from t3;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY t3 ALL NULL NULL NULL NULL 2 100.00
2 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 6 100.00 Using where; Using temporary; Using filesort
Warnings:
Note 1003 select <in_optimizer>(`test`.`t3`.`a`,<exists>(select max(`test`.`t1`.`ie`) AS `max(ie)` from `test`.`t1` where (`test`.`t1`.`oref` = 4) group by `test`.`t1`.`grp` having trigcond((<cache>(`test`.`t3`.`a`) = <ref_null_helper>(max(`test`.`t1`.`ie`)))))) AS `a in (select max(ie) from t1 where oref=4 group by grp)` from `test`.`t3`
+set @@optimizer_switch=@save_optimizer_switch;
drop table t1, t2, t3;
create table t1 (a int, oref int, key(a));
insert into t1 values
@@ -692,6 +695,8 @@ a MAX(b) test
2 3 h
3 4 i
DROP TABLE t1, t2;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
CREATE TABLE t1 (a int);
CREATE TABLE t2 (b int, PRIMARY KEY(b));
INSERT INTO t1 VALUES (1), (NULL), (4);
@@ -759,6 +764,7 @@ id select_type table type possible_keys
1 PRIMARY t1 ALL NULL NULL NULL NULL 4 Using where
2 DEPENDENT SUBQUERY t2 unique_subquery PRIMARY PRIMARY 4 func 1 Using index; Using where
DROP TABLE t1, t2;
+set @@optimizer_switch=@save_optimizer_switch;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES(1);
CREATE TABLE t2 (placeholder CHAR(11));
@@ -960,7 +966,7 @@ i1 i2
# Baseline:
SHOW STATUS LIKE '%Handler_read_rnd_next';
Variable_name Value
-Handler_read_rnd_next 17
+Handler_read_rnd_next 18
INSERT INTO t1 VALUES (NULL, NULL);
FLUSH STATUS;
@@ -977,7 +983,7 @@ i1 i2
# (read record from t1, but do not read from t2)
SHOW STATUS LIKE '%Handler_read_rnd_next';
Variable_name Value
-Handler_read_rnd_next 18
+Handler_read_rnd_next 19
DROP TABLE t1,t2;
End of 5.1 tests
CREATE TABLE t1 (
=== modified file 'mysql-test/r/subselect3_jcl6.result'
--- a/mysql-test/r/subselect3_jcl6.result 2010-02-17 10:47:55 +0000
+++ b/mysql-test/r/subselect3_jcl6.result 2010-03-11 21:43:31 +0000
@@ -67,12 +67,15 @@ Handler_read_rnd_next 11
select ' ^ This must show 11' Z;
Z
^ This must show 11
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
explain extended select a in (select max(ie) from t1 where oref=4 group by grp) from t3;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY t3 ALL NULL NULL NULL NULL 2 100.00
2 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 6 100.00 Using where; Using temporary; Using filesort
Warnings:
Note 1003 select <in_optimizer>(`test`.`t3`.`a`,<exists>(select max(`test`.`t1`.`ie`) AS `max(ie)` from `test`.`t1` where (`test`.`t1`.`oref` = 4) group by `test`.`t1`.`grp` having trigcond((<cache>(`test`.`t3`.`a`) = <ref_null_helper>(max(`test`.`t1`.`ie`)))))) AS `a in (select max(ie) from t1 where oref=4 group by grp)` from `test`.`t3`
+set @@optimizer_switch=@save_optimizer_switch;
drop table t1, t2, t3;
create table t1 (a int, oref int, key(a));
insert into t1 values
@@ -696,6 +699,8 @@ a MAX(b) test
2 3 h
3 4 i
DROP TABLE t1, t2;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
CREATE TABLE t1 (a int);
CREATE TABLE t2 (b int, PRIMARY KEY(b));
INSERT INTO t1 VALUES (1), (NULL), (4);
@@ -763,6 +768,7 @@ id select_type table type possible_keys
1 PRIMARY t1 ALL NULL NULL NULL NULL 4 Using where
2 DEPENDENT SUBQUERY t2 unique_subquery PRIMARY PRIMARY 4 func 1 Using index; Using where
DROP TABLE t1, t2;
+set @@optimizer_switch=@save_optimizer_switch;
CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES(1);
CREATE TABLE t2 (placeholder CHAR(11));
@@ -964,7 +970,7 @@ i1 i2
# Baseline:
SHOW STATUS LIKE '%Handler_read_rnd_next';
Variable_name Value
-Handler_read_rnd_next 17
+Handler_read_rnd_next 18
INSERT INTO t1 VALUES (NULL, NULL);
FLUSH STATUS;
@@ -981,7 +987,7 @@ i1 i2
# (read record from t1, but do not read from t2)
SHOW STATUS LIKE '%Handler_read_rnd_next';
Variable_name Value
-Handler_read_rnd_next 18
+Handler_read_rnd_next 19
DROP TABLE t1,t2;
End of 5.1 tests
CREATE TABLE t1 (
=== modified file 'mysql-test/r/subselect_no_mat.result'
--- a/mysql-test/r/subselect_no_mat.result 2010-02-21 07:33:54 +0000
+++ b/mysql-test/r/subselect_no_mat.result 2010-03-11 21:43:31 +0000
@@ -1,8 +1,10 @@
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='materialization=off';
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t11,t12;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
select (select 2);
(select 2)
2
@@ -4807,8 +4809,9 @@ SELECT 1 FROM t1 GROUP BY
1
1
DROP TABLE t1;
+set @@optimizer_switch=@save_optimizer_switch;
End of 5.1 tests.
set optimizer_switch=default;
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
=== modified file 'mysql-test/r/subselect_no_opts.result'
--- a/mysql-test/r/subselect_no_opts.result 2010-02-21 07:33:54 +0000
+++ b/mysql-test/r/subselect_no_opts.result 2010-03-11 21:43:31 +0000
@@ -1,8 +1,10 @@
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='materialization=off,semijoin=off';
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t11,t12;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
select (select 2);
(select 2)
2
@@ -4807,8 +4809,9 @@ SELECT 1 FROM t1 GROUP BY
1
1
DROP TABLE t1;
+set @@optimizer_switch=@save_optimizer_switch;
End of 5.1 tests.
set optimizer_switch=default;
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
=== modified file 'mysql-test/r/subselect_no_semijoin.result'
--- a/mysql-test/r/subselect_no_semijoin.result 2010-02-21 07:33:54 +0000
+++ b/mysql-test/r/subselect_no_semijoin.result 2010-03-11 21:43:31 +0000
@@ -1,8 +1,10 @@
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='semijoin=off';
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t11,t12;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
select (select 2);
(select 2)
2
@@ -4807,8 +4809,9 @@ SELECT 1 FROM t1 GROUP BY
1
1
DROP TABLE t1;
+set @@optimizer_switch=@save_optimizer_switch;
End of 5.1 tests.
set optimizer_switch=default;
show variables like 'optimizer_switch';
Variable_name Value
-optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+optimizer_switch index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
=== modified file 'mysql-test/r/subselect_sj.result'
--- a/mysql-test/r/subselect_sj.result 2010-03-15 06:32:54 +0000
+++ b/mysql-test/r/subselect_sj.result 2010-03-15 19:52:58 +0000
@@ -202,39 +202,39 @@ BUG#37120 optimizer_switch allowable val
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,materialization=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off,semijoin=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,materialization=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch=default;
drop table t0, t1, t2;
drop table t10, t11, t12;
=== modified file 'mysql-test/r/subselect_sj_jcl6.result'
--- a/mysql-test/r/subselect_sj_jcl6.result 2010-03-15 06:32:54 +0000
+++ b/mysql-test/r/subselect_sj_jcl6.result 2010-03-15 19:52:58 +0000
@@ -206,39 +206,39 @@ BUG#37120 optimizer_switch allowable val
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=on,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,materialization=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off,semijoin=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=on,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,materialization=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,semijoin=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=off
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=on,semijoin=off,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch='default,materialization=off,loosescan=off';
select @@optimizer_switch;
@@optimizer_switch
-index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=on
+index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_condition_pushdown=on,firstmatch=on,loosescan=off,materialization=off,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on
set optimizer_switch=default;
drop table t0, t1, t2;
drop table t10, t11, t12;
=== modified file 'mysql-test/t/ps.test'
--- a/mysql-test/t/ps.test 2009-05-27 15:19:44 +0000
+++ b/mysql-test/t/ps.test 2010-03-11 21:43:31 +0000
@@ -163,6 +163,9 @@ create table t1
) engine = MYISAM ;
create table t2 like t1;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
+
set @stmt= ' explain SELECT (SELECT SUM(c1 + c12 + 0.0) FROM t2 where (t1.c2 - 0e-3) = t2.c2 GROUP BY t1.c15 LIMIT 1) as scalar_s, exists (select 1.0e+0 from t2 where t2.c3 * 9.0000000000 = t1.c4) as exists_s, c5 * 4 in (select c6 + 0.3e+1 from t2) as in_s, (c7 - 4, c8 - 4) in (select c9 + 4.0, c10 + 40e-1 from t2) as in_row_s FROM t1, (select c25 x, c32 y from t2) tt WHERE x * 1 = c25 ' ;
prepare stmt1 from @stmt ;
execute stmt1 ;
@@ -171,6 +174,8 @@ explain SELECT (SELECT SUM(c1 + c12 + 0.
deallocate prepare stmt1;
drop tables t1,t2;
+set @@optimizer_switch=@save_optimizer_switch;
+
#
# parameters from variables (for field creation)
#
=== modified file 'mysql-test/t/subselect.test'
--- a/mysql-test/t/subselect.test 2010-01-17 20:52:20 +0000
+++ b/mysql-test/t/subselect.test 2010-03-11 21:43:31 +0000
@@ -11,6 +11,9 @@
drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t11,t12;
--enable_warnings
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
+
select (select 2);
explain extended select (select 2);
SELECT (SELECT 1) UNION SELECT (SELECT 2);
@@ -4061,4 +4064,6 @@ SELECT 1 FROM t1 GROUP BY
(SELECT LAST_INSERT_ID() FROM t1 ORDER BY MIN(a) ASC LIMIT 1);
DROP TABLE t1;
+set @@optimizer_switch=@save_optimizer_switch;
+
--echo End of 5.1 tests.
=== modified file 'mysql-test/t/subselect3.test'
--- a/mysql-test/t/subselect3.test 2010-01-17 14:51:10 +0000
+++ b/mysql-test/t/subselect3.test 2010-03-11 21:43:31 +0000
@@ -59,9 +59,13 @@ select a in (select max(ie) from t1 wher
show status like 'Handler_read_rnd_next';
select ' ^ This must show 11' Z;
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
+
# This must show trigcond:
explain extended select a in (select max(ie) from t1 where oref=4 group by grp) from t3;
+set @@optimizer_switch=@save_optimizer_switch;
drop table t1, t2, t3;
#
@@ -529,6 +533,9 @@ SELECT a, MAX(b),
DROP TABLE t1, t2;
+# The next three test cases must be executed with the IN=>EXISTS strategy
+set @save_optimizer_switch=@@optimizer_switch;
+set @@optimizer_switch="partial_match_rowid_merge=off,partial_match_table_scan=off";
#
# Bug #27870: crash of an equijoin query with WHERE condition containing
@@ -588,6 +595,8 @@ EXPLAIN SELECT a FROM t1 WHERE a NOT IN
DROP TABLE t1, t2;
+set @@optimizer_switch=@save_optimizer_switch;
+
#
# Bug #34763: item_subselect.cc:1235:Item_in_subselect::row_value_transformer:
# Assertion failed, unexpected error message:
=== modified file 'sql/item_cmpfunc.h'
--- a/sql/item_cmpfunc.h 2010-03-13 20:04:52 +0000
+++ b/sql/item_cmpfunc.h 2010-03-15 19:52:58 +0000
@@ -350,6 +350,7 @@ public:
CHARSET_INFO *compare_collation() { return cmp.cmp_collation.collation; }
uint decimal_precision() const { return 1; }
void top_level_item() { abort_on_null= TRUE; }
+ Arg_comparator *get_comparator() { return &cmp; }
friend class Arg_comparator;
};
=== modified file 'sql/item_subselect.cc'
--- a/sql/item_subselect.cc 2010-02-21 06:32:23 +0000
+++ b/sql/item_subselect.cc 2010-03-09 10:14:06 +0000
@@ -138,6 +138,7 @@ void Item_in_subselect::cleanup()
left_expr_cache= NULL;
}
first_execution= TRUE;
+ is_constant= FALSE;
Item_subselect::cleanup();
DBUG_VOID_RETURN;
}
@@ -449,8 +450,10 @@ bool Item_subselect::exec()
int res;
if (thd->is_error())
- /* Do not execute subselect in case of a fatal error */
+ {
+ /* Do not execute subselect in case of a fatal error */
return 1;
+ }
/*
Simulate a failure in sub-query execution. Used to test e.g.
out of memory or query being killed conditions.
@@ -475,9 +478,6 @@ bool Item_subselect::exec()
bool Item_in_subselect::exec()
{
DBUG_ENTER("Item_in_subselect::exec");
- DBUG_ASSERT(exec_method != MATERIALIZATION ||
- (exec_method == MATERIALIZATION &&
- engine->engine_type() == subselect_engine::HASH_SJ_ENGINE));
/*
Initialize the cache of the left predicate operand. This has to be done as
late as now, because Cached_item directly contains a resolved field (not
@@ -493,14 +493,14 @@ bool Item_in_subselect::exec()
if (!left_expr_cache && exec_method == MATERIALIZATION)
init_left_expr_cache();
- /* If the new left operand is already in the cache, reuse the old result. */
- if (left_expr_cache && test_if_item_cache_changed(*left_expr_cache) < 0)
- {
- /* Always compute IN for the first row as the cache is not valid for it. */
- if (!first_execution)
- DBUG_RETURN(FALSE);
- first_execution= FALSE;
- }
+ /*
+ If the new left operand is already in the cache, reuse the old result.
+ Use the cached result only if this is not the first execution of IN
+ because the cache is not valid for the first execution.
+ */
+ if (!first_execution && left_expr_cache &&
+ test_if_item_cache_changed(*left_expr_cache) < 0)
+ DBUG_RETURN(FALSE);
/*
The exec() method below updates item::value, and item::null_value, thus if
@@ -910,8 +910,8 @@ bool Item_in_subselect::test_limit(st_se
Item_in_subselect::Item_in_subselect(Item * left_exp,
st_select_lex *select_lex):
Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE),
- optimizer(0), pushed_cond_guards(NULL), exec_method(NOT_TRANSFORMED),
- upper_item(0)
+ is_constant(FALSE), optimizer(0), pushed_cond_guards(NULL),
+ exec_method(NOT_TRANSFORMED), upper_item(0)
{
DBUG_ENTER("Item_in_subselect::Item_in_subselect");
left_expr= left_exp;
@@ -1105,6 +1105,8 @@ bool Item_in_subselect::val_bool()
{
DBUG_ASSERT(fixed == 1);
null_value= 0;
+ if (is_constant)
+ return value;
if (exec())
{
reset();
@@ -1571,9 +1573,9 @@ Item_in_subselect::row_value_transformer
DBUG_ENTER("Item_in_subselect::row_value_transformer");
// psergey: duplicated_subselect_card_check
- if (select_lex->item_list.elements != left_expr->cols())
+ if (select_lex->item_list.elements != cols_num)
{
- my_error(ER_OPERAND_COLUMNS, MYF(0), left_expr->cols());
+ my_error(ER_OPERAND_COLUMNS, MYF(0), cols_num);
DBUG_RETURN(RES_ERROR);
}
@@ -1980,17 +1982,69 @@ void Item_in_subselect::print(String *st
bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref)
{
- bool result = 0;
+ uint outer_cols_num;
+ List<Item> *inner_cols;
if (exec_method == SEMI_JOIN)
return !( (*ref)= new Item_int(1));
- if (thd_arg->lex->view_prepare_mode && left_expr && !left_expr->fixed)
- result = left_expr->fix_fields(thd_arg, &left_expr);
+ /*
+ Check if the outer and inner IN operands match in those cases when we
+ will not perform IN=>EXISTS transformation. Currently this is when we
+ use subquery materialization.
+
+ The condition below is true when this method was called recursively from
+ inside JOIN::prepare for the JOIN object created by the call chain
+ Item_subselect::fix_fields -> subselect_single_select_engine::prepare,
+ which creates a JOIN object for the subquery and calls JOIN::prepare for
+ the JOIN of the subquery.
+ Notice that in some cases, this doesn't happen, and the check_cols()
+ test for each Item happens later in
+ Item_in_subselect::row_value_in_to_exists_transformer.
+ The reason for this mess is that our JOIN::prepare phase works top-down
+ instead of bottom-up, so we first do name resoluton and semantic checks
+ for the outer selects, then for the inner.
+ */
+ if (engine &&
+ engine->engine_type() == subselect_engine::SINGLE_SELECT_ENGINE &&
+ ((subselect_single_select_engine*)engine)->join)
+ {
+ outer_cols_num= left_expr->cols();
+
+ if (unit->is_union())
+ inner_cols= &(unit->types);
+ else
+ inner_cols= &(unit->first_select()->item_list);
+ if (outer_cols_num != inner_cols->elements)
+ {
+ my_error(ER_OPERAND_COLUMNS, MYF(0), outer_cols_num);
+ return TRUE;
+ }
+ if (outer_cols_num > 1)
+ {
+ List_iterator<Item> inner_col_it(*inner_cols);
+ Item *inner_col;
+ for (uint i= 0; i < outer_cols_num; i++)
+ {
+ inner_col= inner_col_it++;
+ if (inner_col->check_cols(left_expr->element_index(i)->cols()))
+ return TRUE;
+ }
+ }
+ }
+
+ if (thd_arg->lex->view_prepare_mode && left_expr && !left_expr->fixed &&
+ left_expr->fix_fields(thd_arg, &left_expr))
+ return TRUE;
+ if (Item_subselect::fix_fields(thd_arg, ref))
+ return TRUE;
- return result || Item_subselect::fix_fields(thd_arg, ref);
+ fixed= TRUE;
+
+ return FALSE;
}
+
void Item_in_subselect::fix_after_pullout(st_select_lex *new_parent, Item **ref)
{
left_expr->fix_after_pullout(new_parent, &left_expr);
@@ -2267,10 +2321,9 @@ bool subselect_union_engine::no_rows()
void subselect_uniquesubquery_engine::cleanup()
{
DBUG_ENTER("subselect_uniquesubquery_engine::cleanup");
- /*
- subselect_uniquesubquery_engine have not 'result' assigbed, so we do not
- cleanup() it
- */
+ /* Tell handler we don't need the index anymore */
+ if (tab->table->file->inited)
+ tab->table->file->ha_index_end();
DBUG_VOID_RETURN;
}
@@ -2291,7 +2344,7 @@ subselect_union_engine::subselect_union_
Create and prepare the JOIN object that represents the query execution
plan for the subquery.
- @detail
+ @details
This method is called from Item_subselect::fix_fields. For prepared
statements it is called both during the PREPARE and EXECUTE phases in the
following ways:
@@ -2593,14 +2646,23 @@ int subselect_uniquesubquery_engine::sca
for (;;)
{
error=table->file->ha_rnd_next(table->record[0]);
- if (error && error != HA_ERR_END_OF_FILE)
- {
- error= report_error(table, error);
- break;
+ if (error) {
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ error= 0;
+ continue;
+ }
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ error= 0;
+ break;
+ }
+ else
+ {
+ error= report_error(table, error);
+ break;
+ }
}
- /* No more rows */
- if (table->status)
- break;
if (!cond || cond->val_int())
{
@@ -2711,6 +2773,56 @@ bool subselect_uniquesubquery_engine::co
/*
+ @retval 1 A NULL was found in the outer reference, index lookup is
+ not applicable, the outer ref is unsusable as a lookup key,
+ use some other method to find a match.
+ @retval 0 The outer ref was copied into an index lookup key.
+ @retval -1 The outer ref cannot possibly match any row, IN is FALSE.
+*/
+/* TIMOUR: this method is a variant of copy_ref_key(), needs refactoring. */
+
+int subselect_uniquesubquery_engine::copy_ref_key_simple()
+{
+ for (store_key **copy= tab->ref.key_copy ; *copy ; copy++)
+ {
+ enum store_key::store_key_result store_res;
+ store_res= (*copy)->copy();
+ tab->ref.key_err= store_res;
+
+ /*
+ When there is a NULL part in the key we don't need to make index
+ lookup for such key thus we don't need to copy whole key.
+ If we later should do a sequential scan return OK. Fail otherwise.
+
+ See also the comment for the subselect_uniquesubquery_engine::exec()
+ function.
+ */
+ null_keypart= (*copy)->null_key;
+ if (null_keypart)
+ return 1;
+
+ /*
+ Check if the error is equal to STORE_KEY_FATAL. This is not expressed
+ using the store_key::store_key_result enum because ref.key_err is a
+ boolean and we want to detect both TRUE and STORE_KEY_FATAL from the
+ space of the union of the values of [TRUE, FALSE] and
+ store_key::store_key_result.
+ TODO: fix the variable an return types.
+ */
+ if (store_res == store_key::STORE_KEY_FATAL)
+ {
+ /*
+ Error converting the left IN operand to the column type of the right
+ IN operand.
+ */
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/*
Execute subselect
SYNOPSIS
@@ -2750,7 +2862,13 @@ int subselect_uniquesubquery_engine::exe
/* TODO: change to use of 'full_scan' here? */
if (copy_ref_key())
+ {
+ /*
+ TIMOUR: copy_ref_key() == 1 means NULL result, not error, why return 1?
+ Check who reiles on this result.
+ */
DBUG_RETURN(1);
+ }
if (table->status)
{
/*
@@ -2791,6 +2909,46 @@ int subselect_uniquesubquery_engine::exe
}
+/*
+ TIMOUR: write comment
+*/
+
+int subselect_uniquesubquery_engine::index_lookup()
+{
+ DBUG_ENTER("subselect_uniquesubquery_engine::index_lookup");
+ int error;
+ TABLE *table= tab->table;
+
+ if (!table->file->inited)
+ table->file->ha_index_init(tab->ref.key, 0);
+ error= table->file->ha_index_read_map(table->record[0],
+ tab->ref.key_buff,
+ make_prev_keypart_map(tab->
+ ref.key_parts),
+ HA_READ_KEY_EXACT);
+ DBUG_PRINT("info", ("lookup result: %i", error));
+
+ if (error && error != HA_ERR_KEY_NOT_FOUND && error != HA_ERR_END_OF_FILE)
+ {
+ /*
+ TIMOUR: I don't understand at all when do we need to call report_error.
+ In most places where we access an index, we don't do this. Why here?
+ */
+ error= report_error(table, error);
+ DBUG_RETURN(error);
+ }
+
+ table->null_row= 0;
+ if (!error && (!cond || cond->val_int()))
+ ((Item_in_subselect *) item)->value= 1;
+ else
+ ((Item_in_subselect *) item)->value= 0;
+
+ DBUG_RETURN(0);
+}
+
+
+
subselect_uniquesubquery_engine::~subselect_uniquesubquery_engine()
{
/* Tell handler we don't need the index anymore */
@@ -3225,6 +3383,7 @@ bool subselect_union_engine::no_tables()
bool subselect_uniquesubquery_engine::no_tables()
{
/* returning value is correct, but this method should never be called */
+ DBUG_ASSERT(FALSE);
return 0;
}
@@ -3235,16 +3394,259 @@ bool subselect_uniquesubquery_engine::no
/**
+ Check if an IN predicate should be executed via partial matching using
+ only schema information.
+
+ @details
+ This test essentially has three results:
+ - partial matching is applicable, but cannot be executed due to a
+ limitation in the total number of indexes, as a result we can't
+ use subquery materialization at all.
+ - partial matching is either applicable or not, and this can be
+ determined by looking at 'this->max_keys'.
+ If max_keys > 1, then we need partial matching because there are
+ more indexes than just the one we use during materialization to
+ remove duplicates.
+
+ @note
+ TIMOUR: The schema-based analysis for partial matching can be done once for
+ prepared statement and remembered. It is done here to remove the need to
+ save/restore all related variables between each re-execution, thus making
+ the code simpler.
+
+ @retval PARTIAL_MATCH if a partial match should be used
+ @retval COMPLETE_MATCH if a complete match (index lookup) should be used
+*/
+
+subselect_hash_sj_engine::exec_strategy
+subselect_hash_sj_engine::get_strategy_using_schema()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+
+ if (item_in->is_top_level_item())
+ return COMPLETE_MATCH;
+ else
+ {
+ List_iterator<Item> inner_col_it(*item_in->unit->get_unit_column_types());
+ Item *outer_col, *inner_col;
+
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
+ {
+ outer_col= item_in->left_expr->element_index(i);
+ inner_col= inner_col_it++;
+
+ if (!inner_col->maybe_null && !outer_col->maybe_null)
+ bitmap_set_bit(&non_null_key_parts, i);
+ else
+ {
+ bitmap_set_bit(&partial_match_key_parts, i);
+ ++count_partial_match_columns;
+ }
+ }
+ }
+
+ /* If no column contains NULLs use regular hash index lookups. */
+ if (count_partial_match_columns)
+ return PARTIAL_MATCH;
+ return COMPLETE_MATCH;
+}
+
+
+/**
+ Test whether an IN predicate must be computed via partial matching
+ based on the NULL statistics for each column of a materialized subquery.
+
+ @details The procedure analyzes column NULL statistics, updates the
+ matching type of columns that cannot be NULL or that contain only NULLs.
+ Based on this, the procedure determines the final execution strategy for
+ the [NOT] IN predicate.
+
+ @retval PARTIAL_MATCH if a partial match should be used
+ @retval COMPLETE_MATCH if a complete match (index lookup) should be used
+*/
+
+subselect_hash_sj_engine::exec_strategy
+subselect_hash_sj_engine::get_strategy_using_data()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ Item *outer_col;
+
+ /*
+ If we already determined that a complete match is enough based on schema
+ information, nothing can be better.
+ */
+ if (strategy == COMPLETE_MATCH)
+ return COMPLETE_MATCH;
+
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
+ {
+ if (!bitmap_is_set(&partial_match_key_parts, i))
+ continue;
+ outer_col= item_in->left_expr->element_index(i);
+ /*
+ If column 'i' doesn't contain NULLs, and the corresponding outer reference
+ cannot have a NULL value, then 'i' is a non-nullable column.
+ */
+ if (result_sink->get_null_count_of_col(i) == 0 && !outer_col->maybe_null)
+ {
+ bitmap_clear_bit(&partial_match_key_parts, i);
+ bitmap_set_bit(&non_null_key_parts, i);
+ --count_partial_match_columns;
+ }
+ if (result_sink->get_null_count_of_col(i) ==
+ tmp_table->file->stats.records)
+ ++count_null_only_columns;
+ }
+
+ /* If no column contains NULLs use regular hash index lookups. */
+ if (!count_partial_match_columns)
+ return COMPLETE_MATCH;
+ return PARTIAL_MATCH;
+}
+
+
+void
+subselect_hash_sj_engine::choose_partial_match_strategy(
+ bool has_non_null_key, bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts)
+{
+ size_t pm_buff_size;
+
+ DBUG_ASSERT(strategy == PARTIAL_MATCH);
+ /*
+ Choose according to global optimizer switch. If only one of the switches is
+ 'ON', then the remaining strategy is the only possible one. The only cases
+ when this will be overriden is when the total size of all buffers for the
+ merge strategy is bigger than the 'rowid_merge_buff_size' system variable,
+ or if there isn't enough physical memory to allocate the buffers.
+ */
+ if (!optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) &&
+ optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN))
+ strategy= PARTIAL_MATCH_SCAN;
+ else if
+ ( optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) &&
+ !optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN))
+ strategy= PARTIAL_MATCH_MERGE;
+
+ /*
+ If both switches are ON, or both are OFF, we interpret that as "let the
+ optimizer decide". Perform a cost based choice between the two partial
+ matching strategies.
+ */
+ /*
+ TIMOUR: the above interpretation of the switch values could be changed to:
+ - if both are ON - let the optimizer decide,
+ - if both are OFF - do not use partial matching, therefore do not use
+ materialization in non-top-level predicates.
+ The problem with this is that we know for sure if we need partial matching
+ only after the subquery is materialized, and this is too late to revert to
+ the IN=>EXISTS strategy.
+ */
+ if (strategy == PARTIAL_MATCH)
+ {
+ /*
+ TIMOUR: Currently we use a super simplistic measure. This will be
+ addressed in a separate task.
+ */
+ if (tmp_table->file->stats.records < 100)
+ strategy= PARTIAL_MATCH_SCAN;
+ else
+ strategy= PARTIAL_MATCH_MERGE;
+ }
+
+ /* Check if there is enough memory for the rowid merge strategy. */
+ if (strategy == PARTIAL_MATCH_MERGE)
+ {
+ pm_buff_size= rowid_merge_buff_size(has_non_null_key,
+ has_covering_null_row,
+ partial_match_key_parts);
+ if (pm_buff_size > thd->variables.rowid_merge_buff_size)
+ strategy= PARTIAL_MATCH_SCAN;
+ }
+}
+
+
+/*
+ Compute the memory size of all buffers proportional to the number of rows
+ in tmp_table.
+
+ @details
+ If the result is bigger than thd->variables.rowid_merge_buff_size, partial
+ matching via merging is not applicable.
+*/
+
+size_t subselect_hash_sj_engine::rowid_merge_buff_size(
+ bool has_non_null_key, bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts)
+{
+ size_t buff_size; /* Total size of all buffers used by partial matching. */
+ ha_rows row_count= tmp_table->file->stats.records;
+ uint rowid_length= tmp_table->file->ref_length;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+
+ /* Size of the subselect_rowid_merge_engine::row_num_to_rowid buffer. */
+ buff_size= row_count * rowid_length * sizeof(uchar);
+
+ if (has_non_null_key)
+ {
+ /* Add the size of Ordered_key::key_buff of the only non-NULL key. */
+ buff_size+= row_count * sizeof(rownum_t);
+ }
+
+ if (!has_covering_null_row)
+ {
+ for (uint i= 0; i < partial_match_key_parts->n_bits; i++)
+ {
+ if (!bitmap_is_set(partial_match_key_parts, i) ||
+ result_sink->get_null_count_of_col(i) == row_count)
+ continue; /* In these cases we wouldn't construct Ordered keys. */
+
+ /* Add the size of Ordered_key::key_buff */
+ buff_size+= (row_count - result_sink->get_null_count_of_col(i)) *
+ sizeof(rownum_t);
+ /* Add the size of Ordered_key::null_key */
+ buff_size+= bitmap_buffer_size(result_sink->get_max_null_of_col(i));
+ }
+ }
+
+ return buff_size;
+}
+
+
+/*
+ Initialize a MY_BITMAP with a buffer allocated on the current
+ memory root.
+ TIMOUR: move to bitmap C file?
+*/
+
+static my_bool
+bitmap_init_memroot(MY_BITMAP *map, uint n_bits, MEM_ROOT *mem_root)
+{
+ my_bitmap_map *bitmap_buf;
+
+ if (!(bitmap_buf= (my_bitmap_map*) alloc_root(mem_root,
+ bitmap_buffer_size(n_bits))) ||
+ bitmap_init(map, bitmap_buf, n_bits, FALSE))
+ return TRUE;
+ bitmap_clear_all(map);
+ return FALSE;
+}
+
+
+/**
Create all structures needed for IN execution that can live between PS
reexecution.
- @detail
+ @param tmp_columns the items that produce the data for the temp table
+
+ @details
- Create a temporary table to store the result of the IN subquery. The
temporary table has one hash index on all its columns.
- Create a new result sink that sends the result stream of the subquery to
the temporary table,
- - Create and initialize a new JOIN_TAB, and TABLE_REF objects to perform
- lookups into the indexed temporary table.
@notice:
Currently Item_subselect::init() already chooses and creates at parse
@@ -3256,145 +3658,210 @@ bool subselect_uniquesubquery_engine::no
bool subselect_hash_sj_engine::init_permanent(List<Item> *tmp_columns)
{
- /* The result sink where we will materialize the subquery result. */
- select_union *tmp_result_sink;
- /* The table into which the subquery is materialized. */
- TABLE *tmp_table;
- KEY *tmp_key; /* The only index on the temporary table. */
- uint tmp_key_parts; /* Number of keyparts in tmp_key. */
- Item_in_subselect *item_in= (Item_in_subselect *) item;
+ /* Options to create_tmp_table. */
+ ulonglong tmp_create_options= thd->options | TMP_TABLE_ALL_COLUMNS;
+ /* | TMP_TABLE_FORCE_MYISAM; TIMOUR: force MYISAM */
DBUG_ENTER("subselect_hash_sj_engine::init_permanent");
- /* 1. Create/initialize materialization related objects. */
+ if (bitmap_init_memroot(&non_null_key_parts, tmp_columns->elements,
+ thd->mem_root) ||
+ bitmap_init_memroot(&partial_match_key_parts, tmp_columns->elements,
+ thd->mem_root))
+ DBUG_RETURN(TRUE);
/*
Create and initialize a select result interceptor that stores the
result stream in a temporary table. The temporary table itself is
managed (created/filled/etc) internally by the interceptor.
*/
- if (!(tmp_result_sink= new select_union))
+/*
+ TIMOUR:
+ Select a more efficient result sink when we know there is no need to collect
+ data statistics.
+
+ if (strategy == COMPLETE_MATCH)
+ {
+ if (!(result= new select_union))
+ DBUG_RETURN(TRUE);
+ }
+ else if (strategy == PARTIAL_MATCH)
+ {
+ if (!(result= new select_materialize_with_stats))
+ DBUG_RETURN(TRUE);
+ }
+*/
+ if (!(result= new select_materialize_with_stats))
DBUG_RETURN(TRUE);
- if (tmp_result_sink->create_result_table(
- thd, tmp_columns, TRUE,
- thd->options | TMP_TABLE_ALL_COLUMNS,
+
+ if (((select_union*) result)->create_result_table(
+ thd, tmp_columns, TRUE, tmp_create_options,
"materialized subselect", TRUE))
DBUG_RETURN(TRUE);
- tmp_table= tmp_result_sink->table;
- tmp_key= tmp_table->key_info;
- tmp_key_parts= tmp_key->key_parts;
+ tmp_table= ((select_union*) result)->table;
/*
- If the subquery has blobs, or the total key lenght is bigger than some
- length, then the created index cannot be used for lookups and we
- can't use hash semi join. If this is the case, delete the temporary
- table since it will not be used, and tell the caller we failed to
- initialize the engine.
+ If the subquery has blobs, or the total key lenght is bigger than
+ some length, or the total number of key parts is more than the
+ allowed maximum (currently MAX_REF_PARTS == 16), then the created
+ index cannot be used for lookups and we can't use hash semi
+ join. If this is the case, delete the temporary table since it
+ will not be used, and tell the caller we failed to initialize the
+ engine.
*/
if (tmp_table->s->keys == 0)
{
-#ifndef DBUG_OFF
- handlerton *tmp_table_hton= tmp_table->s->db_type();
-#ifdef USE_MARIA_FOR_TMP_TABLES
- DBUG_ASSERT(tmp_table_hton == maria_hton);
-#else
- DBUG_ASSERT(tmp_table_hton == myisam_hton);
-#endif
-#endif
DBUG_ASSERT(
tmp_table->s->uniques ||
tmp_table->key_info->key_length >= tmp_table->file->max_key_length() ||
tmp_table->key_info->key_parts > tmp_table->file->max_key_parts());
free_tmp_table(thd, tmp_table);
+ tmp_table= NULL;
delete result;
result= NULL;
DBUG_RETURN(TRUE);
}
- result= tmp_result_sink;
/*
Make sure there is only one index on the temp table, and it doesn't have
the extra key part created when s->uniques > 0.
*/
- DBUG_ASSERT(tmp_table->s->keys == 1 && tmp_columns->elements == tmp_key_parts);
+ DBUG_ASSERT(tmp_table->s->keys == 1 &&
+ ((Item_in_subselect *) item)->left_expr->cols() ==
+ tmp_table->key_info->key_parts);
+
+ if (make_semi_join_conds() ||
+ /* A unique_engine is used both for complete and partial matching. */
+ !(lookup_engine= make_unique_engine()))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
- /* 2. Create/initialize execution related objects. */
+/*
+ Create an artificial condition to post-filter those rows matched by index
+ lookups that cannot be distinguished by the index lookup procedure.
- /*
- Create and initialize the JOIN_TAB that represents an index lookup
- plan operator into the materialized subquery result. Notice that:
- - this JOIN_TAB has no corresponding JOIN (and doesn't need one), and
- - here we initialize only those members that are used by
- subselect_uniquesubquery_engine, so these objects are incomplete.
- */
- if (!(tab= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB))))
- DBUG_RETURN(TRUE);
- tab->table= tmp_table;
- tab->ref.key= 0; /* The only temp table index. */
- tab->ref.key_length= tmp_key->key_length;
- if (!(tab->ref.key_buff=
- (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
- !(tab->ref.key_copy=
- (store_key**) thd->alloc((sizeof(store_key*) *
- (tmp_key_parts + 1)))) ||
- !(tab->ref.items=
- (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
- DBUG_RETURN(TRUE);
+ @notes
+ The need for post-filtering may occur e.g. because of
+ truncation. Prepared statements execution requires that fix_fields is
+ called for every execution. In order to call fix_fields we need to
+ create a Name_resolution_context and a corresponding TABLE_LIST for
+ the temporary table for the subquery, so that all column references
+ to the materialized subquery table can be resolved correctly.
- KEY_PART_INFO *cur_key_part= tmp_key->key_part;
- store_key **ref_key= tab->ref.key_copy;
- uchar *cur_ref_buff= tab->ref.key_buff;
+ @returns
+ @retval TRUE memory allocation error occurred
+ @retval FALSE the conditions were created and resolved (fixed)
+*/
- /*
- Create an artificial condition to post-filter those rows matched by index
- lookups that cannot be distinguished by the index lookup procedure, e.g.
- because of truncation. Prepared statements execution requires that
- fix_fields is called for every execution. In order to call fix_fields we
- need to create a Name_resolution_context and a corresponding TABLE_LIST
- for the temporary table for the subquery, so that all column references
- to the materialized subquery table can be resolved correctly.
- */
- DBUG_ASSERT(cond == NULL);
- if (!(cond= new Item_cond_and))
- DBUG_RETURN(TRUE);
+bool subselect_hash_sj_engine::make_semi_join_conds()
+{
/*
Table reference for tmp_table that is used to resolve column references
(Item_fields) to columns in tmp_table.
*/
TABLE_LIST *tmp_table_ref;
+ /* Name resolution context for all tmp_table columns created below. */
+ Name_resolution_context *context;
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+
+ DBUG_ENTER("subselect_hash_sj_engine::make_semi_join_conds");
+ DBUG_ASSERT(semi_join_conds == NULL);
+
+ if (!(semi_join_conds= new Item_cond_and))
+ DBUG_RETURN(TRUE);
+
if (!(tmp_table_ref= (TABLE_LIST*) thd->alloc(sizeof(TABLE_LIST))))
DBUG_RETURN(TRUE);
tmp_table_ref->init_one_table("", "materialized subselect", TL_READ);
tmp_table_ref->table= tmp_table;
- /* Name resolution context for all tmp_table columns created below. */
- Name_resolution_context *context= new Name_resolution_context;
+ context= new Name_resolution_context;
context->init();
context->first_name_resolution_table=
context->last_name_resolution_table= tmp_table_ref;
- for (uint i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
+ for (uint i= 0; i < item_in->left_expr->cols(); i++)
{
Item_func_eq *eq_cond; /* New equi-join condition for the current column. */
/* Item for the corresponding field from the materialized temp table. */
Item_field *right_col_item;
- int null_count= test(cur_key_part->field->real_maybe_null());
- tab->ref.items[i]= item_in->left_expr->element_index(i);
- if (!(right_col_item= new Item_field(thd, context, cur_key_part->field)) ||
- !(eq_cond= new Item_func_eq(tab->ref.items[i], right_col_item)) ||
- ((Item_cond_and*)cond)->add(eq_cond))
+ if (!(right_col_item= new Item_field(thd, context, tmp_table->field[i])) ||
+ !(eq_cond= new Item_func_eq(item_in->left_expr->element_index(i),
+ right_col_item)) ||
+ (((Item_cond_and*)semi_join_conds)->add(eq_cond)))
{
- delete cond;
- cond= NULL;
+ delete semi_join_conds;
+ semi_join_conds= NULL;
DBUG_RETURN(TRUE);
}
+ }
+ if (semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
+ DBUG_RETURN(TRUE);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Create a new uniquesubquery engine for the execution of an IN predicate.
+
+ @details
+ Create and initialize a new JOIN_TAB, and Table_ref objects to perform
+ lookups into the indexed temporary table.
+
+ @retval A new subselect_hash_sj_engine object
+ @retval NULL if a memory allocation error occurs
+*/
+
+subselect_uniquesubquery_engine*
+subselect_hash_sj_engine::make_unique_engine()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ /* The only index on the temporary table. */
+ KEY *tmp_key= tmp_table->key_info;
+ /* Number of keyparts in tmp_key. */
+ uint tmp_key_parts= tmp_key->key_parts;
+ JOIN_TAB *tab;
+
+ DBUG_ENTER("subselect_hash_sj_engine::make_unique_engine");
+
+ /*
+ Create and initialize the JOIN_TAB that represents an index lookup
+ plan operator into the materialized subquery result. Notice that:
+ - this JOIN_TAB has no corresponding JOIN (and doesn't need one), and
+ - here we initialize only those members that are used by
+ subselect_uniquesubquery_engine, so these objects are incomplete.
+ */
+ if (!(tab= (JOIN_TAB*) thd->alloc(sizeof(JOIN_TAB))))
+ DBUG_RETURN(NULL);
+ tab->table= tmp_table;
+ tab->ref.key= 0; /* The only temp table index. */
+ tab->ref.key_length= tmp_key->key_length;
+ if (!(tab->ref.key_buff=
+ (uchar*) thd->calloc(ALIGN_SIZE(tmp_key->key_length) * 2)) ||
+ !(tab->ref.key_copy=
+ (store_key**) thd->alloc((sizeof(store_key*) *
+ (tmp_key_parts + 1)))) ||
+ !(tab->ref.items=
+ (Item**) thd->alloc(sizeof(Item*) * tmp_key_parts)))
+ DBUG_RETURN(NULL);
+ KEY_PART_INFO *cur_key_part= tmp_key->key_part;
+ store_key **ref_key= tab->ref.key_copy;
+ uchar *cur_ref_buff= tab->ref.key_buff;
+
+ for (uint i= 0; i < tmp_key_parts; i++, cur_key_part++, ref_key++)
+ {
+ tab->ref.items[i]= item_in->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,
- /* TODO:
+ /* TIMOUR:
the NULL byte is taken into account in
cur_key_part->store_length, so instead of
cur_ref_buff + test(maybe_null), we could
@@ -3409,10 +3876,8 @@ bool subselect_hash_sj_engine::init_perm
tab->ref.key_err= 1;
tab->ref.key_parts= tmp_key_parts;
- if (cond->fix_fields(thd, &cond))
- DBUG_RETURN(TRUE);
-
- DBUG_RETURN(FALSE);
+ DBUG_RETURN(new subselect_uniquesubquery_engine(thd, tab, item,
+ semi_join_conds));
}
@@ -3435,7 +3900,8 @@ bool subselect_hash_sj_engine::init_runt
Repeat name resolution for 'cond' since cond is not part of any
clause of the query, and it is not 'fixed' during JOIN::prepare.
*/
- if (cond && !cond->fixed && cond->fix_fields(thd, &cond))
+ if (semi_join_conds && !semi_join_conds->fixed &&
+ semi_join_conds->fix_fields(thd, (Item**)&semi_join_conds))
return TRUE;
/* Let our engine reuse this query plan for materialization. */
materialize_join= materialize_engine->join;
@@ -3446,32 +3912,53 @@ bool subselect_hash_sj_engine::init_runt
subselect_hash_sj_engine::~subselect_hash_sj_engine()
{
+ delete lookup_engine;
delete result;
- if (tab)
- free_tmp_table(thd, tab->table);
+ if (tmp_table)
+ free_tmp_table(thd, tmp_table);
}
/**
Cleanup performed after each PS execution.
- @detail
+ @details
Called in the end of JOIN::prepare for PS from Item_subselect::cleanup.
*/
void subselect_hash_sj_engine::cleanup()
{
+ enum_engine_type lookup_engine_type= lookup_engine->engine_type();
is_materialized= FALSE;
- result->cleanup(); /* Resets the temp table as well. */
+ bitmap_clear_all(&non_null_key_parts);
+ bitmap_clear_all(&partial_match_key_parts);
+ count_partial_match_columns= 0;
+ count_null_only_columns= 0;
+ strategy= UNDEFINED;
materialize_engine->cleanup();
- subselect_uniquesubquery_engine::cleanup();
+ if (lookup_engine_type == TABLE_SCAN_ENGINE ||
+ lookup_engine_type == ROWID_MERGE_ENGINE)
+ {
+ subselect_engine *inner_lookup_engine;
+ inner_lookup_engine=
+ ((subselect_partial_match_engine*) lookup_engine)->lookup_engine;
+ /*
+ Partial match engines are recreated for each PS execution inside
+ subselect_hash_sj_engine::exec().
+ */
+ delete lookup_engine;
+ lookup_engine= inner_lookup_engine;
+ }
+ DBUG_ASSERT(lookup_engine->engine_type() == UNIQUESUBQUERY_ENGINE);
+ lookup_engine->cleanup();
+ result->cleanup(); /* Resets the temp table as well. */
}
/**
Execute a subquery IN predicate via materialization.
- @detail
+ @details
If needed materialize the subquery into a temporary table, then
copmpute the predicate via a lookup into this table.
@@ -3482,6 +3969,9 @@ void subselect_hash_sj_engine::cleanup()
int subselect_hash_sj_engine::exec()
{
Item_in_subselect *item_in= (Item_in_subselect *) item;
+ SELECT_LEX *save_select= thd->lex->current_select;
+ subselect_partial_match_engine *pm_engine= NULL;
+ int res= 0;
DBUG_ENTER("subselect_hash_sj_engine::exec");
@@ -3489,56 +3979,126 @@ int subselect_hash_sj_engine::exec()
Optimize and materialize the subquery during the first execution of
the subquery predicate.
*/
- if (!is_materialized)
- {
- int res= 0;
- SELECT_LEX *save_select= thd->lex->current_select;
- thd->lex->current_select= materialize_engine->select_lex;
- if ((res= materialize_join->optimize()))
- goto err; /* purecov: inspected */
- materialize_join->exec();
- if ((res= test(materialize_join->error || thd->is_fatal_error)))
- goto err;
-
- /*
- TODO:
- - Unlock all subquery tables as we don't need them. To implement this
- we need to add new functionality to JOIN::join_free that can unlock
- all tables in a subquery (and all its subqueries).
- - The temp table used for grouping in the subquery can be freed
- immediately after materialization (yet it's done together with
- unlocking).
- */
- is_materialized= TRUE;
- /*
- If the subquery returned no rows, the temporary table is empty, so we know
- directly that the result of IN is FALSE. We first update the table
- statistics, then we test if the temporary table for the query result is
- empty.
- */
- tab->table->file->info(HA_STATUS_VARIABLE);
- if (!tab->table->file->stats.records)
- {
- empty_result_set= TRUE;
- item_in->value= FALSE;
- /* TODO: check we need this: item_in->null_value= FALSE; */
- DBUG_RETURN(FALSE);
- }
- /* Set tmp_param only if its usable, i.e. tmp_param->copy_field != NULL. */
- tmp_param= &(item_in->unit->outer_select()->join->tmp_table_param);
- if (tmp_param && !tmp_param->copy_field)
- tmp_param= NULL;
+ thd->lex->current_select= materialize_engine->select_lex;
+ if ((res= materialize_join->optimize()))
+ goto err; /* purecov: inspected */
+ DBUG_ASSERT(!is_materialized); /* We should materialize only once. */
+ materialize_join->exec();
+ if ((res= test(materialize_join->error || thd->is_fatal_error)))
+ goto err;
-err:
- thd->lex->current_select= save_select;
- if (res)
- DBUG_RETURN(res);
+ /*
+ TODO:
+ - Unlock all subquery tables as we don't need them. To implement this
+ we need to add new functionality to JOIN::join_free that can unlock
+ all tables in a subquery (and all its subqueries).
+ - The temp table used for grouping in the subquery can be freed
+ immediately after materialization (yet it's done together with
+ unlocking).
+ */
+ is_materialized= TRUE;
+ /*
+ If the subquery returned no rows, the temporary table is empty, so we know
+ directly that the result of IN is FALSE. We first update the table
+ statistics, then we test if the temporary table for the query result is
+ empty.
+ */
+ tmp_table->file->info(HA_STATUS_VARIABLE);
+ if (!tmp_table->file->stats.records)
+ {
+ item_in->value= FALSE;
+ /* The value of IN will not change during this execution. */
+ item_in->is_constant= TRUE;
+ item_in->set_first_execution();
+ /* TIMOUR: check if we need this: item_in->null_value= FALSE; */
+ DBUG_RETURN(FALSE);
}
/*
- Lookup the left IN operand in the hash index of the materialized subquery.
+ TIMOUR: The schema-based analysis for partial matching can be done once for
+ prepared statement and remembered. It is done here to remove the need to
+ save/restore all related variables between each re-execution, thus making
+ the code simpler.
*/
- DBUG_RETURN(subselect_uniquesubquery_engine::exec());
+ strategy= get_strategy_using_schema();
+ /* This call may discover that we don't need partial matching at all. */
+ strategy= get_strategy_using_data();
+ if (strategy == PARTIAL_MATCH)
+ {
+ uint count_pm_keys; /* Total number of keys needed for partial matching. */
+ MY_BITMAP *nn_key_parts; /* The key parts of the only non-NULL index. */
+ uint covering_null_row_width;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+
+ nn_key_parts= (count_partial_match_columns < tmp_table->s->fields) ?
+ &non_null_key_parts : NULL;
+
+ if (result_sink->get_max_nulls_in_row() ==
+ tmp_table->s->fields -
+ (nn_key_parts ? bitmap_bits_set(nn_key_parts) : 0))
+ covering_null_row_width= result_sink->get_max_nulls_in_row();
+ else
+ covering_null_row_width= 0;
+
+ if (covering_null_row_width)
+ count_pm_keys= nn_key_parts ? 1 : 0;
+ else
+ count_pm_keys= count_partial_match_columns - count_null_only_columns +
+ (nn_key_parts ? 1 : 0);
+
+ choose_partial_match_strategy(test(nn_key_parts),
+ test(covering_null_row_width),
+ &partial_match_key_parts);
+ DBUG_ASSERT(strategy == PARTIAL_MATCH_MERGE ||
+ strategy == PARTIAL_MATCH_SCAN);
+ if (strategy == PARTIAL_MATCH_MERGE)
+ {
+ pm_engine=
+ new subselect_rowid_merge_engine((subselect_uniquesubquery_engine*)
+ lookup_engine, tmp_table,
+ count_pm_keys,
+ covering_null_row_width,
+ item, result,
+ semi_join_conds->argument_list());
+ if (!pm_engine ||
+ ((subselect_rowid_merge_engine*) pm_engine)->
+ init(nn_key_parts, &partial_match_key_parts))
+ {
+ /*
+ The call to init() would fail if there was not enough memory to allocate
+ all buffers for the rowid merge strategy. In this case revert to table
+ scanning which doesn't need any big buffers.
+ */
+ delete pm_engine;
+ pm_engine= NULL;
+ strategy= PARTIAL_MATCH_SCAN;
+ }
+ }
+
+ if (strategy == PARTIAL_MATCH_SCAN)
+ {
+ if (!(pm_engine=
+ new subselect_table_scan_engine((subselect_uniquesubquery_engine*)
+ lookup_engine, tmp_table,
+ item, result,
+ semi_join_conds->argument_list(),
+ covering_null_row_width)))
+ {
+ /* This is an irrecoverable error. */
+ res= 1;
+ goto err;
+ }
+ }
+ }
+
+ if (pm_engine)
+ lookup_engine= pm_engine;
+ item_in->change_engine(lookup_engine);
+
+err:
+ thd->lex->current_select= save_select;
+ DBUG_RETURN(res);
}
@@ -3551,10 +4111,1008 @@ void subselect_hash_sj_engine::print(Str
str->append(STRING_WITH_LEN(" <materialize> ("));
materialize_engine->print(str, query_type);
str->append(STRING_WITH_LEN(" ), "));
- if (tab)
- subselect_uniquesubquery_engine::print(str, query_type);
+
+ if (lookup_engine)
+ lookup_engine->print(str, query_type);
else
str->append(STRING_WITH_LEN(
- "<the access method for lookups is not yet created>"
+ "<engine selected at execution time>"
));
}
+
+void subselect_hash_sj_engine::fix_length_and_dec(Item_cache** row)
+{
+ DBUG_ASSERT(FALSE);
+}
+
+void subselect_hash_sj_engine::exclude()
+{
+ DBUG_ASSERT(FALSE);
+}
+
+bool subselect_hash_sj_engine::no_tables()
+{
+ DBUG_ASSERT(FALSE);
+ return FALSE;
+}
+
+bool subselect_hash_sj_engine::change_result(Item_subselect *si,
+ select_result_interceptor *res)
+{
+ DBUG_ASSERT(FALSE);
+ return TRUE;
+}
+
+
+Ordered_key::Ordered_key(uint keyid_arg, TABLE *tbl_arg, Item *search_key_arg,
+ ha_rows null_count_arg, ha_rows min_null_row_arg,
+ ha_rows max_null_row_arg, uchar *row_num_to_rowid_arg)
+ : keyid(keyid_arg), tbl(tbl_arg), search_key(search_key_arg),
+ row_num_to_rowid(row_num_to_rowid_arg), null_count(null_count_arg)
+{
+ DBUG_ASSERT(tbl->file->stats.records > null_count);
+ key_buff_elements= tbl->file->stats.records - null_count;
+ cur_key_idx= HA_POS_ERROR;
+
+ DBUG_ASSERT((null_count && min_null_row_arg && max_null_row_arg) ||
+ (!null_count && !min_null_row_arg && !max_null_row_arg));
+ if (null_count)
+ {
+ /* The counters are 1-based, for key access we need 0-based indexes. */
+ min_null_row= min_null_row_arg - 1;
+ max_null_row= max_null_row_arg - 1;
+ }
+ else
+ min_null_row= max_null_row= 0;
+}
+
+
+Ordered_key::~Ordered_key()
+{
+ my_free((char*) key_buff, MYF(0));
+ bitmap_free(&null_key);
+}
+
+
+/*
+ Cleanup that needs to be done for each PS (re)execution.
+*/
+
+void Ordered_key::cleanup()
+{
+ /*
+ Currently these keys are recreated for each PS re-execution, thus
+ there is nothing to cleanup, the whole object goes away after execution
+ is over. All handler related initialization/deinitialization is done by
+ the parent subselect_rowid_merge_engine object.
+ */
+}
+
+
+/*
+ Initialize a multi-column index.
+*/
+
+bool Ordered_key::init(MY_BITMAP *columns_to_index)
+{
+ THD *thd= tbl->in_use;
+ uint cur_key_col= 0;
+ Item_field *cur_tmp_field;
+ Item_func_lt *fn_less_than;
+
+ key_column_count= bitmap_bits_set(columns_to_index);
+
+ // TIMOUR: check for mem allocation err, revert to scan
+
+ key_columns= (Item_field**) thd->alloc(key_column_count *
+ sizeof(Item_field*));
+ compare_pred= (Item_func_lt**) thd->alloc(key_column_count *
+ sizeof(Item_func_lt*));
+
+ for (uint i= 0; i < columns_to_index->n_bits; i++)
+ {
+ if (!bitmap_is_set(columns_to_index, i))
+ continue;
+ cur_tmp_field= new Item_field(tbl->field[i]);
+ /* Create the predicate (tmp_column[i] < outer_ref[i]). */
+ fn_less_than= new Item_func_lt(cur_tmp_field,
+ search_key->element_index(i));
+ fn_less_than->fix_fields(thd, (Item**) &fn_less_than);
+ key_columns[cur_key_col]= cur_tmp_field;
+ compare_pred[cur_key_col]= fn_less_than;
+ ++cur_key_col;
+ }
+
+ if (alloc_keys_buffers())
+ {
+ /* TIMOUR revert to partial match via table scan. */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Initialize a single-column index.
+*/
+
+bool Ordered_key::init(int col_idx)
+{
+ THD *thd= tbl->in_use;
+
+ key_column_count= 1;
+
+ // TIMOUR: check for mem allocation err, revert to scan
+
+ key_columns= (Item_field**) thd->alloc(sizeof(Item_field*));
+ compare_pred= (Item_func_lt**) thd->alloc(sizeof(Item_func_lt*));
+
+ key_columns[0]= new Item_field(tbl->field[col_idx]);
+ /* Create the predicate (tmp_column[i] < outer_ref[i]). */
+ compare_pred[0]= new Item_func_lt(key_columns[0],
+ search_key->element_index(col_idx));
+ compare_pred[0]->fix_fields(thd, (Item**)&compare_pred[0]);
+
+ if (alloc_keys_buffers())
+ {
+ /* TIMOUR revert to partial match via table scan. */
+ return TRUE;
+ }
+ return FALSE;
+}
+
+
+/*
+ Allocate the buffers for both the row number, and the NULL-bitmap indexes.
+*/
+
+bool Ordered_key::alloc_keys_buffers()
+{
+ DBUG_ASSERT(key_buff_elements > 0);
+
+ if (!(key_buff= (rownum_t*) my_malloc(key_buff_elements * sizeof(rownum_t),
+ MYF(MY_WME))))
+ return TRUE;
+
+ /*
+ TIMOUR: it is enough to create bitmaps with size
+ (max_null_row - min_null_row), and then use min_null_row as
+ lookup offset.
+ */
+ /* Notice that max_null_row is max array index, we need count, so +1. */
+ if (bitmap_init(&null_key, NULL, max_null_row + 1, FALSE))
+ return TRUE;
+
+ cur_key_idx= HA_POS_ERROR;
+
+ return FALSE;
+}
+
+
+/*
+ Quick sort comparison function that compares two rows of the same table
+ indentfied with their row numbers.
+
+ @retval -1
+ @retval 0
+ @retval +1
+*/
+
+int
+Ordered_key::cmp_keys_by_row_data(ha_rows a, ha_rows b)
+{
+ uchar *rowid_a, *rowid_b;
+ int error, cmp_res;
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tbl->file->ref_length;
+
+ if (a == b)
+ return 0;
+ /* Get the corresponding rowids. */
+ rowid_a= row_num_to_rowid + a * rowid_length;
+ rowid_b= row_num_to_rowid + b * rowid_length;
+ /* Fetch the rows for comparison. */
+ error= tbl->file->ha_rnd_pos(tbl->record[0], rowid_a);
+ DBUG_ASSERT(!error);
+ error= tbl->file->ha_rnd_pos(tbl->record[1], rowid_b);
+ DBUG_ASSERT(!error);
+ /*
+ Compare the two rows by the corresponding values of the indexed
+ columns.
+ */
+ for (uint i= 0; i < key_column_count; i++)
+ {
+ Field *cur_field= key_columns[i]->field;
+ if ((cmp_res= cur_field->cmp_offset(tbl->s->rec_buff_length)))
+ return (cmp_res > 0 ? 1 : -1);
+ }
+ return 0;
+}
+
+
+int
+Ordered_key::cmp_keys_by_row_data_and_rownum(Ordered_key *key,
+ rownum_t* a, rownum_t* b)
+{
+ /* The result of comparing the two keys according to their row data. */
+ int cmp_row_res= key->cmp_keys_by_row_data(*a, *b);
+ if (cmp_row_res)
+ return cmp_row_res;
+ return (*a < *b) ? -1 : (*a > *b) ? 1 : 0;
+}
+
+
+void Ordered_key::sort_keys()
+{
+ my_qsort2(key_buff, key_buff_elements, sizeof(rownum_t),
+ (qsort2_cmp) &cmp_keys_by_row_data_and_rownum, (void*) this);
+ /* Invalidate the current row position. */
+ cur_key_idx= HA_POS_ERROR;
+}
+
+
+/*
+ The fraction of rows that do not contain NULL in the columns indexed by
+ this key.
+
+ @retval 1 if there are no NULLs
+ @retval 0 if only NULLs
+*/
+
+double Ordered_key::null_selectivity()
+{
+ /* We should not be processing empty tables. */
+ DBUG_ASSERT(tbl->file->stats.records);
+ return (1 - (double) null_count / (double) tbl->file->stats.records);
+}
+
+
+/*
+ Compare the value(s) of the current key in 'search_key' with the
+ data of the current table record.
+
+ @notes The comparison result follows from the way compare_pred
+ is created in Ordered_key::init. Currently compare_pred compares
+ a field in of the current row with the corresponding Item that
+ contains the search key.
+
+ @param row_num Number of the row (not index in the key_buff array)
+
+ @retval -1 if (current row < search_key)
+ @retval 0 if (current row == search_key)
+ @retval +1 if (current row > search_key)
+*/
+
+int Ordered_key::cmp_key_with_search_key(rownum_t row_num)
+{
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tbl->file->ref_length;
+ uchar *cur_rowid= row_num_to_rowid + row_num * rowid_length;
+ int error, cmp_res;
+
+ error= tbl->file->ha_rnd_pos(tbl->record[0], cur_rowid);
+ DBUG_ASSERT(!error);
+
+ for (uint i= 0; i < key_column_count; i++)
+ {
+ cmp_res= compare_pred[i]->get_comparator()->compare();
+ /* Unlike Arg_comparator::compare_row() here there should be no NULLs. */
+ DBUG_ASSERT(!compare_pred[i]->null_value);
+ if (cmp_res)
+ return (cmp_res > 0 ? 1 : -1);
+ }
+ return 0;
+}
+
+
+/*
+ Find a key in a sorted array of keys via binary search.
+
+ see create_subq_in_equalities()
+*/
+
+bool Ordered_key::lookup()
+{
+ DBUG_ASSERT(key_buff_elements);
+
+ ha_rows lo= 0;
+ ha_rows hi= key_buff_elements - 1;
+ ha_rows mid;
+ int cmp_res;
+
+ while (lo <= hi)
+ {
+ mid= lo + (hi - lo) / 2;
+ cmp_res= cmp_key_with_search_key(key_buff[mid]);
+ /*
+ In order to find the minimum match, check if the pevious element is
+ equal or smaller than the found one. If equal, we need to search further
+ to the left.
+ */
+ if (!cmp_res && mid > 0)
+ cmp_res= !cmp_key_with_search_key(key_buff[mid - 1]) ? 1 : 0;
+
+ if (cmp_res == -1)
+ {
+ /* row[mid] < search_key */
+ lo= mid + 1;
+ }
+ else if (cmp_res == 1)
+ {
+ /* row[mid] > search_key */
+ if (!mid)
+ goto not_found;
+ hi= mid - 1;
+ }
+ else
+ {
+ /* row[mid] == search_key */
+ cur_key_idx= mid;
+ return TRUE;
+ }
+ }
+not_found:
+ cur_key_idx= HA_POS_ERROR;
+ return FALSE;
+}
+
+
+/*
+ Move the current index pointer to the next key with the same column
+ values as the current key. Since the index is sorted, all such keys
+ are contiguous.
+*/
+
+bool Ordered_key::next_same()
+{
+ DBUG_ASSERT(key_buff_elements);
+
+ if (cur_key_idx < key_buff_elements - 1)
+ {
+ /*
+ TIMOUR:
+ The below is quite inefficient, since as a result we will fetch every
+ row (except the last one) twice. There must be a more efficient way,
+ e.g. swapping record[0] and record[1], and reading only the new record.
+ */
+ if (!cmp_keys_by_row_data(key_buff[cur_key_idx], key_buff[cur_key_idx + 1]))
+ {
+ ++cur_key_idx;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+void Ordered_key::print(String *str)
+{
+ uint i;
+ str->append("{idx=");
+ str->qs_append(keyid);
+ str->append(", (");
+ for (i= 0; i < key_column_count - 1; i++)
+ {
+ str->append(key_columns[i]->field->field_name);
+ str->append(", ");
+ }
+ str->append(key_columns[i]->field->field_name);
+ str->append("), ");
+
+ str->append("null_bitmap: (bits=");
+ str->qs_append(null_key.n_bits);
+ str->append(", nulls= ");
+ str->qs_append((double)null_count);
+ str->append(", min_null= ");
+ str->qs_append((double)min_null_row);
+ str->append(", max_null= ");
+ str->qs_append((double)max_null_row);
+ str->append("), ");
+
+ str->append('}');
+}
+
+
+subselect_partial_match_engine::subselect_partial_match_engine(
+ subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ uint covering_null_row_width_arg)
+ :subselect_engine(item_arg, result_arg),
+ tmp_table(tmp_table_arg), lookup_engine(engine_arg),
+ equi_join_conds(equi_join_conds_arg),
+ covering_null_row_width(covering_null_row_width_arg)
+{}
+
+
+int subselect_partial_match_engine::exec()
+{
+ Item_in_subselect *item_in= (Item_in_subselect *) item;
+ int res;
+
+ /* Try to find a matching row by index lookup. */
+ res= lookup_engine->copy_ref_key_simple();
+ if (res == -1)
+ {
+ /* The result is FALSE based on the outer reference. */
+ item_in->value= 0;
+ item_in->null_value= 0;
+ return 0;
+ }
+ else if (res == 0)
+ {
+ /* Search for a complete match. */
+ if ((res= lookup_engine->index_lookup()))
+ {
+ /* An error occured during lookup(). */
+ item_in->value= 0;
+ item_in->null_value= 0;
+ return res;
+ }
+ else if (item_in->value)
+ {
+ /*
+ A complete match was found, the result of IN is TRUE.
+ Notice: (this->item == lookup_engine->item)
+ */
+ return 0;
+ }
+ }
+
+ if (covering_null_row_width == tmp_table->s->fields)
+ {
+ /*
+ If there is a NULL-only row that coveres all columns the result of IN
+ is UNKNOWN.
+ */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 1;
+ item_in->null_value= 1;
+ return 0;
+ }
+
+ /*
+ There is no complete match. Look for a partial match (UNKNOWN result), or
+ no match (FALSE).
+ */
+ if (tmp_table->file->inited)
+ tmp_table->file->ha_index_end();
+
+ if (partial_match())
+ {
+ /* The result of IN is UNKNOWN. */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 1;
+ item_in->null_value= 1;
+ }
+ else
+ {
+ /* The result of IN is FALSE. */
+ item_in->value= 0;
+ /*
+ TIMOUR: which one is the right way to propagate an UNKNOWN result?
+ Should we also set empty_result_set= FALSE; ???
+ */
+ //item_in->was_null= 0;
+ item_in->null_value= 0;
+ }
+
+ return 0;
+}
+
+
+void subselect_partial_match_engine::print(String *str,
+ enum_query_type query_type)
+{
+ /*
+ Should never be called as the actual engine cannot be known at query
+ optimization time.
+ */
+ DBUG_ASSERT(FALSE);
+}
+
+
+/*
+ @param non_null_key_parts
+ @param partial_match_key_parts A union of all single-column NULL key parts.
+ @param count_partial_match_columns Number of NULL keyparts (set bits above).
+
+ @retval FALSE the engine was initialized successfully
+ @retval TRUE there was some (memory allocation) error during initialization,
+ such errors should be interpreted as revert to other strategy
+*/
+
+bool
+subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts,
+ MY_BITMAP *partial_match_key_parts)
+{
+ /* The length in bytes of the rowids (positions) of tmp_table. */
+ uint rowid_length= tmp_table->file->ref_length;
+ ha_rows row_count= tmp_table->file->stats.records;
+ rownum_t cur_rownum= 0;
+ select_materialize_with_stats *result_sink=
+ (select_materialize_with_stats *) result;
+ uint cur_keyid= 0;
+ Item_in_subselect *item_in= (Item_in_subselect*) item;
+ int error;
+
+ if (keys_count == 0)
+ {
+ /* There is nothing to initialize, we will only do regular lookups. */
+ return FALSE;
+ }
+
+ DBUG_ASSERT(!covering_null_row_width || (covering_null_row_width &&
+ keys_count == 1 &&
+ non_null_key_parts));
+ /*
+ Allocate buffers to hold the merged keys and the mapping between rowids and
+ row numbers.
+ */
+ if (!(merge_keys= (Ordered_key**) thd->alloc(keys_count *
+ sizeof(Ordered_key*))) ||
+ !(row_num_to_rowid= (uchar*) my_malloc(row_count * rowid_length *
+ sizeof(uchar), MYF(MY_WME))))
+ return TRUE;
+
+ /* Create the only non-NULL key if there is any. */
+ if (non_null_key_parts)
+ {
+ non_null_key= new Ordered_key(cur_keyid, tmp_table, item_in->left_expr,
+ 0, 0, 0, row_num_to_rowid);
+ if (non_null_key->init(non_null_key_parts))
+ return TRUE;
+ merge_keys[cur_keyid]= non_null_key;
+ merge_keys[cur_keyid]->first();
+ ++cur_keyid;
+ }
+
+ /*
+ If there is a covering NULL row, the only key that is needed is the
+ only non-NULL key that is already created above. We create keys on
+ NULL-able columns only if there is no covering NULL row.
+ */
+ if (!covering_null_row_width)
+ {
+ if (bitmap_init_memroot(&matching_keys, keys_count, thd->mem_root) ||
+ bitmap_init_memroot(&matching_outer_cols, keys_count, thd->mem_root) ||
+ bitmap_init_memroot(&null_only_columns, keys_count, thd->mem_root))
+ return TRUE;
+
+ /*
+ Create one single-column NULL-key for each column in
+ partial_match_key_parts.
+ */
+ for (uint i= 0; i < partial_match_key_parts->n_bits; i++)
+ {
+ if (!bitmap_is_set(partial_match_key_parts, i))
+ continue;
+
+ if (result_sink->get_null_count_of_col(i) == row_count)
+ bitmap_set_bit(&null_only_columns, cur_keyid);
+ else
+ {
+ merge_keys[cur_keyid]= new Ordered_key(
+ cur_keyid, tmp_table,
+ item_in->left_expr->element_index(i),
+ result_sink->get_null_count_of_col(i),
+ result_sink->get_min_null_of_col(i),
+ result_sink->get_max_null_of_col(i),
+ row_num_to_rowid);
+ if (merge_keys[cur_keyid]->init(i))
+ return TRUE;
+ merge_keys[cur_keyid]->first();
+ }
+ ++cur_keyid;
+ }
+ }
+
+ /* Populate the indexes with data from the temporary table. */
+ tmp_table->file->ha_rnd_init(1);
+ tmp_table->file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ tmp_table->null_row= 0;
+ while (TRUE)
+ {
+ error= tmp_table->file->ha_rnd_next(tmp_table->record[0]);
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ /* We get this for duplicate records that should not be in tmp_table. */
+ continue;
+ }
+ /*
+ This is a temp table that we fully own, there should be no other
+ cause to stop the iteration than EOF.
+ */
+ DBUG_ASSERT(!error || error == HA_ERR_END_OF_FILE);
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ DBUG_ASSERT(cur_rownum == tmp_table->file->stats.records);
+ break;
+ }
+
+ /*
+ Save the position of this record in the row_num -> rowid mapping.
+ */
+ tmp_table->file->position(tmp_table->record[0]);
+ memcpy(row_num_to_rowid + cur_rownum * rowid_length,
+ tmp_table->file->ref, rowid_length);
+
+ /* Add the current row number to the corresponding keys. */
+ if (non_null_key)
+ {
+ /* By definition there are no NULLs in the non-NULL key. */
+ non_null_key->add_key(cur_rownum);
+ }
+
+ for (uint i= (non_null_key ? 1 : 0); i < keys_count; i++)
+ {
+ /*
+ Check if the first and only indexed column contains NULL in the curent
+ row, and add the row number to the corresponding key.
+ */
+ if (tmp_table->field[merge_keys[i]->get_field_idx(0)]->is_null())
+ merge_keys[i]->set_null(cur_rownum);
+ else
+ merge_keys[i]->add_key(cur_rownum);
+ }
+ ++cur_rownum;
+ }
+
+ tmp_table->file->ha_rnd_end();
+
+ /* Sort all the keys by their NULL selectivity. */
+ my_qsort(merge_keys, keys_count, sizeof(Ordered_key*),
+ (qsort_cmp) cmp_keys_by_null_selectivity);
+
+ /* Sort the keys in each of the indexes. */
+ for (uint i= 0; i < keys_count; i++)
+ merge_keys[i]->sort_keys();
+
+ if (init_queue(&pq, keys_count, 0, FALSE,
+ subselect_rowid_merge_engine::cmp_keys_by_cur_rownum, NULL))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+subselect_rowid_merge_engine::~subselect_rowid_merge_engine()
+{
+ /* None of the resources below is allocated if there are no ordered keys. */
+ if (keys_count)
+ {
+ my_free((char*) row_num_to_rowid, MYF(0));
+ for (uint i= 0; i < keys_count; i++)
+ delete merge_keys[i];
+ delete_queue(&pq);
+ if (tmp_table->file->inited == handler::RND)
+ tmp_table->file->ha_rnd_end();
+ }
+}
+
+
+void subselect_rowid_merge_engine::cleanup()
+{
+}
+
+
+/*
+ Quick sort comparison function to compare keys in order of decreasing bitmap
+ selectivity, so that the most selective keys come first.
+
+ @param k1 first key to compare
+ @param k2 second key to compare
+
+ @retval 1 if k1 is less selective than k2
+ @retval 0 if k1 is equally selective as k2
+ @retval -1 if k1 is more selective than k2
+*/
+
+int
+subselect_rowid_merge_engine::cmp_keys_by_null_selectivity(Ordered_key **k1,
+ Ordered_key **k2)
+{
+ double k1_sel= (*k1)->null_selectivity();
+ double k2_sel= (*k2)->null_selectivity();
+ if (k1_sel < k2_sel)
+ return 1;
+ if (k1_sel > k2_sel)
+ return -1;
+ return 0;
+}
+
+
+/*
+*/
+
+int
+subselect_rowid_merge_engine::cmp_keys_by_cur_rownum(void *arg,
+ uchar *k1, uchar *k2)
+{
+ rownum_t r1= ((Ordered_key*) k1)->current();
+ rownum_t r2= ((Ordered_key*) k2)->current();
+
+ return (r1 < r2) ? -1 : (r1 > r2) ? 1 : 0;
+}
+
+
+/*
+ Check if certain table row contains a NULL in all columns for which there is
+ no match in the corresponding value index.
+
+ @retval TRUE if a NULL row exists
+ @retval FALSE otherwise
+*/
+
+bool subselect_rowid_merge_engine::test_null_row(rownum_t row_num)
+{
+ Ordered_key *cur_key;
+ uint cur_id;
+ for (uint i = 0; i < keys_count; i++)
+ {
+ cur_key= merge_keys[i];
+ cur_id= cur_key->get_keyid();
+ if (bitmap_is_set(&matching_keys, cur_id))
+ {
+ /*
+ The key 'i' (with id 'cur_keyid') already matches a value in row 'row_num',
+ thus we skip it as it can't possibly match a NULL.
+ */
+ continue;
+ }
+ if (!cur_key->is_null(row_num))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+
+/*
+ @retval TRUE there is a partial match (UNKNOWN)
+ @retval FALSE there is no match at all (FALSE)
+*/
+
+bool subselect_rowid_merge_engine::partial_match()
+{
+ Ordered_key *min_key; /* Key that contains the current minimum position. */
+ rownum_t min_row_num; /* Current row number of min_key. */
+ Ordered_key *cur_key;
+ rownum_t cur_row_num;
+ uint count_nulls_in_search_key= 0;
+ bool res= FALSE;
+
+ /* If there is a non-NULL key, it must be the first key in the keys array. */
+ DBUG_ASSERT(!non_null_key || (non_null_key && merge_keys[0] == non_null_key));
+
+ /* All data accesses during execution are via handler::ha_rnd_pos() */
+ tmp_table->file->ha_rnd_init(0);
+
+ /* Check if there is a match for the columns of the only non-NULL key. */
+ if (non_null_key && !non_null_key->lookup())
+ {
+ res= FALSE;
+ goto end;
+ }
+
+ /*
+ If there is a NULL (sub)row that covers all NULL-able columns,
+ then there is a guranteed partial match, and we don't need to search
+ for the matching row.
+ */
+ if (covering_null_row_width)
+ {
+ res= TRUE;
+ goto end;
+ }
+
+ if (non_null_key)
+ queue_insert(&pq, (uchar *) non_null_key);
+ /*
+ Do not add the non_null_key, since it was already processed above.
+ */
+ bitmap_clear_all(&matching_outer_cols);
+ for (uint i= test(non_null_key); i < keys_count; i++)
+ {
+ DBUG_ASSERT(merge_keys[i]->get_column_count() == 1);
+ if (merge_keys[i]->get_search_key(0)->is_null())
+ {
+ ++count_nulls_in_search_key;
+ bitmap_set_bit(&matching_outer_cols, merge_keys[i]->get_keyid());
+ }
+ else if (merge_keys[i]->lookup())
+ queue_insert(&pq, (uchar *) merge_keys[i]);
+ }
+
+ /*
+ If the outer reference consists of only NULLs, or if it has NULLs in all
+ nullable columns, the result is UNKNOWN.
+ */
+ if (count_nulls_in_search_key ==
+ ((Item_in_subselect *) item)->left_expr->cols() -
+ (non_null_key ? non_null_key->get_column_count() : 0))
+ {
+ res= TRUE;
+ goto end;
+ }
+
+ /*
+ If there is no NULL (sub)row that covers all NULL columns, and there is no
+ single match for any of the NULL columns, the result is FALSE.
+ */
+ if (pq.elements - test(non_null_key) == 0)
+ {
+ res= FALSE;
+ goto end;
+ }
+
+ DBUG_ASSERT(pq.elements);
+
+ min_key= (Ordered_key*) queue_remove(&pq, 0);
+ min_row_num= min_key->current();
+ bitmap_copy(&matching_keys, &null_only_columns);
+ bitmap_set_bit(&matching_keys, min_key->get_keyid());
+ bitmap_union(&matching_keys, &matching_outer_cols);
+ if (min_key->next_same())
+ queue_insert(&pq, (uchar *) min_key);
+
+ if (pq.elements == 0)
+ {
+ /*
+ Check the only matching row of the only key min_key for NULL matches
+ in the other columns.
+ */
+ res= test_null_row(min_row_num);
+ goto end;
+ }
+
+ while (TRUE)
+ {
+ cur_key= (Ordered_key*) queue_remove(&pq, 0);
+ cur_row_num= cur_key->current();
+
+ if (cur_row_num == min_row_num)
+ bitmap_set_bit(&matching_keys, cur_key->get_keyid());
+ else
+ {
+ /* Follows from the correct use of priority queue. */
+ DBUG_ASSERT(cur_row_num > min_row_num);
+ if (test_null_row(min_row_num))
+ {
+ res= TRUE;
+ goto end;
+ }
+ else
+ {
+ min_key= cur_key;
+ min_row_num= cur_row_num;
+ bitmap_copy(&matching_keys, &null_only_columns);
+ bitmap_set_bit(&matching_keys, min_key->get_keyid());
+ bitmap_union(&matching_keys, &matching_outer_cols);
+ }
+ }
+
+ if (cur_key->next_same())
+ queue_insert(&pq, (uchar *) cur_key);
+
+ if (pq.elements == 0)
+ {
+ /* Check the last row of the last column in PQ for NULL matches. */
+ res= test_null_row(min_row_num);
+ goto end;
+ }
+ }
+
+ /* We should never get here - all branches must be handled explicitly above. */
+ DBUG_ASSERT(FALSE);
+
+end:
+ tmp_table->file->ha_rnd_end();
+ return res;
+}
+
+
+subselect_table_scan_engine::subselect_table_scan_engine(
+ subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg,
+ Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ uint covering_null_row_width_arg)
+ :subselect_partial_match_engine(engine_arg, tmp_table_arg, item_arg,
+ result_arg, equi_join_conds_arg,
+ covering_null_row_width_arg)
+{}
+
+
+/*
+ TIMOUR:
+ This method is based on subselect_uniquesubquery_engine::scan_table().
+ Consider refactoring somehow, 80% of the code is the same.
+
+ for each row_i in tmp_table
+ {
+ count_matches= 0;
+ for each row element row_i[j]
+ {
+ if (outer_ref[j] is NULL || row_i[j] is NULL || outer_ref[j] == row_i[j])
+ ++count_matches;
+ }
+ if (count_matches == outer_ref.elements)
+ return TRUE
+ }
+ return FALSE
+*/
+
+bool subselect_table_scan_engine::partial_match()
+{
+ List_iterator_fast<Item> equality_it(*equi_join_conds);
+ Item *cur_eq;
+ uint count_matches;
+ int error;
+ bool res;
+
+ tmp_table->file->ha_rnd_init(1);
+ tmp_table->file->extra_opt(HA_EXTRA_CACHE,
+ current_thd->variables.read_buff_size);
+ /*
+ TIMOUR:
+ scan_table() also calls "table->null_row= 0;", why, do we need it?
+ */
+ for (;;)
+ {
+ error= tmp_table->file->ha_rnd_next(tmp_table->record[0]);
+ if (error) {
+ if (error == HA_ERR_RECORD_DELETED)
+ {
+ error= 0;
+ continue;
+ }
+ if (error == HA_ERR_END_OF_FILE)
+ {
+ error= 0;
+ break;
+ }
+ else
+ {
+ error= report_error(tmp_table, error);
+ break;
+ }
+ }
+
+ equality_it.rewind();
+ count_matches= 0;
+ while ((cur_eq= equality_it++))
+ {
+ DBUG_ASSERT(cur_eq->type() == Item::FUNC_ITEM &&
+ ((Item_func*)cur_eq)->functype() == Item_func::EQ_FUNC);
+ if (!cur_eq->val_int() && !cur_eq->null_value)
+ break;
+ ++count_matches;
+ }
+ if (count_matches == tmp_table->s->fields)
+ {
+ res= TRUE; /* Found a matching row. */
+ goto end;
+ }
+ }
+
+ res= FALSE;
+end:
+ tmp_table->file->ha_rnd_end();
+ return res;
+}
+
+
+void subselect_table_scan_engine::cleanup()
+{
+}
=== modified file 'sql/item_subselect.h'
--- a/sql/item_subselect.h 2010-02-11 23:59:58 +0000
+++ b/sql/item_subselect.h 2010-03-09 10:14:06 +0000
@@ -297,7 +297,7 @@ public:
Representation of IN subquery predicates of the form
"left_expr IN (SELECT ...)".
- @detail
+ @details
This class has:
- A "subquery execution engine" (as a subclass of Item_subselect) that allows
it to evaluate subqueries. (and this class participates in execution by
@@ -319,6 +319,12 @@ protected:
*/
List<Cached_item> *left_expr_cache;
bool first_execution;
+ /*
+ Set to TRUE if at query execution time we determine that this item's
+ value is a constant during this execution. We need this member because
+ it is not possible to substitute 'this' with a constant item.
+ */
+ bool is_constant;
/*
expr & optimizer used in subselect rewriting to store Item for
@@ -387,8 +393,8 @@ public:
Item_in_subselect(Item * left_expr, st_select_lex *select_lex);
Item_in_subselect()
:Item_exists_subselect(), left_expr_cache(0), first_execution(TRUE),
- optimizer(0), abort_on_null(0), pushed_cond_guards(NULL),
- exec_method(NOT_TRANSFORMED), upper_item(0)
+ is_constant(FALSE), optimizer(0), abort_on_null(0),
+ pushed_cond_guards(NULL), exec_method(NOT_TRANSFORMED), upper_item(0)
{}
void cleanup();
subs_type substype() { return IN_SUBS; }
@@ -421,6 +427,8 @@ public:
void update_used_tables();
bool setup_engine();
bool init_left_expr_cache();
+ /* Inform 'this' that it was computed, and contains a valid result. */
+ void set_first_execution() { if (first_execution) first_execution= FALSE; }
bool is_expensive_processor(uchar *arg);
friend class Item_ref_null_helper;
@@ -428,6 +436,7 @@ public:
friend class Item_in_optimizer;
friend class subselect_indexsubquery_engine;
friend class subselect_hash_sj_engine;
+ friend class subselect_partial_match_engine;
};
@@ -462,7 +471,8 @@ public:
enum enum_engine_type {ABSTRACT_ENGINE, SINGLE_SELECT_ENGINE,
UNION_ENGINE, UNIQUESUBQUERY_ENGINE,
- INDEXSUBQUERY_ENGINE, HASH_SJ_ENGINE};
+ INDEXSUBQUERY_ENGINE, HASH_SJ_ENGINE,
+ ROWID_MERGE_ENGINE, TABLE_SCAN_ENGINE};
subselect_engine(Item_subselect *si, select_result_interceptor *res)
:thd(0)
@@ -635,8 +645,10 @@ public:
virtual void print (String *str, enum_query_type query_type);
bool change_result(Item_subselect *si, select_result_interceptor *result);
bool no_tables();
+ int index_lookup(); /* TIMOUR: this method needs refactoring. */
int scan_table();
bool copy_ref_key();
+ int copy_ref_key_simple(); /* TIMOUR: this method needs refactoring. */
bool no_rows() { return empty_result_set; }
virtual enum_engine_type engine_type() { return UNIQUESUBQUERY_ENGINE; }
};
@@ -705,50 +717,439 @@ inline bool Item_subselect::is_uncacheab
/**
- Compute an IN predicate via a hash semi-join. The subquery is materialized
- during the first evaluation of the IN predicate. The IN predicate is executed
- via the functionality inherited from subselect_uniquesubquery_engine.
+ Compute an IN predicate via a hash semi-join. This class is responsible for
+ the materialization of the subquery, and the selection of the correct and
+ optimal execution method (e.g. direct index lookup, or partial matching) for
+ the IN predicate.
*/
-class subselect_hash_sj_engine: public subselect_uniquesubquery_engine
+class subselect_hash_sj_engine : public subselect_engine
{
protected:
+ /* The table into which the subquery is materialized. */
+ TABLE *tmp_table;
/* TRUE if the subquery was materialized into a temp table. */
bool is_materialized;
/*
The old engine already chosen at parse time and stored in permanent memory.
Through this member we can re-create and re-prepare materialize_join for
- each execution of a prepared statement. We akso resuse the functionality
+ each execution of a prepared statement. We also reuse the functionality
of subselect_single_select_engine::[prepare | cols].
*/
subselect_single_select_engine *materialize_engine;
+ /* The engine used to compute the IN predicate. */
+ subselect_engine *lookup_engine;
/*
QEP to execute the subquery and materialize its result into a
temporary table. Created during the first call to exec().
*/
JOIN *materialize_join;
- /* Temp table context of the outer select's JOIN. */
- TMP_TABLE_PARAM *tmp_param;
+
+ /* Keyparts of the only non-NULL composite index in a rowid merge. */
+ MY_BITMAP non_null_key_parts;
+ /* Keyparts of the single column indexes with NULL, one keypart per index. */
+ MY_BITMAP partial_match_key_parts;
+ uint count_partial_match_columns;
+ uint count_null_only_columns;
+ /*
+ A conjunction of all the equality condtions between all pairs of expressions
+ that are arguments of an IN predicate. We need these to post-filter some
+ IN results because index lookups sometimes match values that are actually
+ not equal to the search key in SQL terms.
+ */
+ Item_cond_and *semi_join_conds;
+ /* Possible execution strategies that can be used to compute hash semi-join.*/
+ enum exec_strategy {
+ UNDEFINED,
+ COMPLETE_MATCH, /* Use regular index lookups. */
+ PARTIAL_MATCH, /* Use some partial matching strategy. */
+ PARTIAL_MATCH_MERGE, /* Use partial matching through index merging. */
+ PARTIAL_MATCH_SCAN, /* Use partial matching through table scan. */
+ IMPOSSIBLE /* Subquery materialization is not applicable. */
+ };
+ /* The chosen execution strategy. Computed after materialization. */
+ exec_strategy strategy;
+protected:
+ exec_strategy get_strategy_using_schema();
+ exec_strategy get_strategy_using_data();
+ size_t rowid_merge_buff_size(bool has_non_null_key,
+ bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts);
+ void choose_partial_match_strategy(bool has_non_null_key,
+ bool has_covering_null_row,
+ MY_BITMAP *partial_match_key_parts);
+ bool make_semi_join_conds();
+ subselect_uniquesubquery_engine* make_unique_engine();
public:
subselect_hash_sj_engine(THD *thd, Item_subselect *in_predicate,
- subselect_single_select_engine *old_engine)
- :subselect_uniquesubquery_engine(thd, NULL, in_predicate, NULL),
- is_materialized(FALSE), materialize_engine(old_engine),
- materialize_join(NULL), tmp_param(NULL)
- {}
+ subselect_single_select_engine *old_engine)
+ :subselect_engine(in_predicate, NULL), tmp_table(NULL),
+ is_materialized(FALSE), materialize_engine(old_engine), lookup_engine(NULL),
+ materialize_join(NULL), count_partial_match_columns(0),
+ count_null_only_columns(0), semi_join_conds(NULL), strategy(UNDEFINED)
+ {
+ set_thd(thd);
+ }
~subselect_hash_sj_engine();
bool init_permanent(List<Item> *tmp_columns);
bool init_runtime();
void cleanup();
- int prepare() { return 0; }
+ int prepare() { return 0; } /* Override virtual function in base class. */
int exec();
- virtual void print (String *str, enum_query_type query_type);
+ virtual void print(String *str, enum_query_type query_type);
uint cols()
{
return materialize_engine->cols();
}
+ uint8 uncacheable() { return UNCACHEABLE_DEPENDENT; }
+ table_map upper_select_const_tables() { return 0; }
+ bool no_rows() { return !tmp_table->file->stats.records; }
virtual enum_engine_type engine_type() { return HASH_SJ_ENGINE; }
+ /*
+ TODO: factor out all these methods in a base subselect_index_engine class
+ because all of them have dummy implementations and should never be called.
+ */
+ void fix_length_and_dec(Item_cache** row);//=>base class
+ void exclude(); //=>base class
+ //=>base class
+ bool change_result(Item_subselect *si, select_result_interceptor *result);
+ bool no_tables();//=>base class
+};
+
+
+/*
+ Distinguish the type od (0-based) row numbers from the type of the index into
+ an array of row numbers.
+*/
+typedef ha_rows rownum_t;
+
+
+/*
+ An Ordered_key is an in-memory table index that allows O(log(N)) time
+ lookups of a multi-part key.
+
+ If the index is over a single column, then this column may contain NULLs, and
+ the NULLs are stored and tested separately for NULL in O(1) via is_null().
+ Multi-part indexes assume that the indexed columns do not contain NULLs.
+
+ TODO:
+ = Due to the unnatural assymetry between single and multi-part indexes, it
+ makes sense to somehow refactor or extend the class.
+
+ = This class can be refactored into a base abstract interface, and two
+ subclasses:
+ - one to represent single-column indexes, and
+ - another to represent multi-column indexes.
+ Such separation would allow slightly more efficient implementation of
+ the single-column indexes.
+ = The current design requires such indexes to be fully recreated for each
+ PS (re)execution, however most of the comprising objects can be reused.
+*/
+
+class Ordered_key : public Sql_alloc
+{
+protected:
+ /*
+ Index of the key in an array of keys. This index allows to
+ construct (sub)sets of keys represented by bitmaps.
+ */
+ uint keyid;
+ /* The table being indexed. */
+ TABLE *tbl;
+ /* The columns being indexed. */
+ Item_field **key_columns;
+ /* Number of elements in 'key_columns' (number of key parts). */
+ uint key_column_count;
+ /*
+ An expression, or sequence of expressions that forms the search key.
+ The search key is a sequence when it is Item_row. Each element of the
+ sequence is accessible via Item::element_index(int i).
+ */
+ Item *search_key;
+
+/* Value index related members. */
+ /*
+ The actual value index, consists of a sorted sequence of row numbers.
+ */
+ rownum_t *key_buff;
+ /* Number of elements in key_buff. */
+ ha_rows key_buff_elements;
+ /* Current element in 'key_buff'. */
+ ha_rows cur_key_idx;
+ /*
+ Mapping from row numbers to row ids. The element row_num_to_rowid[i]
+ contains a buffer with the rowid for the row numbered 'i'.
+ The memory for this member is not maintanined by this class because
+ all Ordered_key indexes of the same table share the same mapping.
+ */
+ uchar *row_num_to_rowid;
+ /*
+ A sequence of predicates to compare the search key with the corresponding
+ columns of a table row from the index.
+ */
+ Item_func_lt **compare_pred;
+
+/* Null index related members. */
+ MY_BITMAP null_key;
+ /* Count of NULLs per column. */
+ ha_rows null_count;
+ /* The row number that contains the first NULL in a column. */
+ ha_rows min_null_row;
+ /* The row number that contains the last NULL in a column. */
+ ha_rows max_null_row;
+
+protected:
+ bool alloc_keys_buffers();
+ /*
+ Quick sort comparison function that compares two rows of the same table
+ indentfied with their row numbers.
+ */
+ int cmp_keys_by_row_data(rownum_t a, rownum_t b);
+ static int cmp_keys_by_row_data_and_rownum(Ordered_key *key,
+ rownum_t* a, rownum_t* b);
+
+ int cmp_key_with_search_key(rownum_t row_num);
+
+public:
+ Ordered_key(uint keyid_arg, TABLE *tbl_arg,
+ Item *search_key_arg, ha_rows null_count_arg,
+ ha_rows min_null_row_arg, ha_rows max_null_row_arg,
+ uchar *row_num_to_rowid_arg);
+ ~Ordered_key();
+ void cleanup();
+ /* Initialize a multi-column index. */
+ bool init(MY_BITMAP *columns_to_index);
+ /* Initialize a single-column index. */
+ bool init(int col_idx);
+
+ uint get_column_count() { return key_column_count; }
+ uint get_keyid() { return keyid; }
+ uint get_field_idx(uint i)
+ {
+ DBUG_ASSERT(i < key_column_count);
+ return key_columns[i]->field->field_index;
+ }
+ /*
+ Get the search key element that corresponds to the i-th key part of this
+ index.
+ */
+ Item *get_search_key(uint i)
+ {
+ return search_key->element_index(key_columns[i]->field->field_index);
+ }
+ void add_key(rownum_t row_num)
+ {
+ /* The caller must know how many elements to add. */
+ DBUG_ASSERT(key_buff_elements && cur_key_idx < key_buff_elements);
+ key_buff[cur_key_idx]= row_num;
+ ++cur_key_idx;
+ }
+
+ void sort_keys();
+ double null_selectivity();
+
+ /*
+ Position the current element at the first row that matches the key.
+ The key itself is propagated by evaluating the current value(s) of
+ this->search_key.
+ */
+ bool lookup();
+ /* Move the current index cursor to the first key. */
+ void first()
+ {
+ DBUG_ASSERT(key_buff_elements);
+ cur_key_idx= 0;
+ }
+ /* TODO */
+ bool next_same();
+ /* Move the current index cursor to the next key. */
+ bool next()
+ {
+ DBUG_ASSERT(key_buff_elements);
+ if (cur_key_idx < key_buff_elements - 1)
+ {
+ ++cur_key_idx;
+ return TRUE;
+ }
+ return FALSE;
+ };
+ /* Return the current index element. */
+ rownum_t current()
+ {
+ DBUG_ASSERT(key_buff_elements && cur_key_idx < key_buff_elements);
+ return key_buff[cur_key_idx];
+ }
+
+ void set_null(rownum_t row_num)
+ {
+ bitmap_set_bit(&null_key, row_num);
+ }
+ bool is_null(rownum_t row_num)
+ {
+ /*
+ Indexes consisting of only NULLs do not have a bitmap buffer at all.
+ Their only initialized member is 'n_bits', which is equal to the number
+ of temp table rows.
+ */
+ if (null_count == tbl->file->stats.records)
+ {
+ DBUG_ASSERT(tbl->file->stats.records == null_key.n_bits);
+ return TRUE;
+ }
+ if (row_num > max_null_row || row_num < min_null_row)
+ return FALSE;
+ return bitmap_is_set(&null_key, row_num);
+ }
+ void print(String *str);
+};
+
+
+class subselect_partial_match_engine : public subselect_engine
+{
+protected:
+ /* The temporary table that contains a materialized subquery. */
+ TABLE *tmp_table;
+ /*
+ The engine used to check whether an IN predicate is TRUE or not. If not
+ TRUE, then subselect_rowid_merge_engine further distinguishes between
+ FALSE and UNKNOWN.
+ */
+ subselect_uniquesubquery_engine *lookup_engine;
+ /* A list of equalities between each pair of IN operands. */
+ List<Item> *equi_join_conds;
+ /*
+ If there is a row, such that all its NULL-able components are NULL, this
+ member is set to the number of covered columns. If there is no covering
+ row, then this is 0.
+ */
+ uint covering_null_row_width;
+protected:
+ virtual bool partial_match()= 0;
+public:
+ subselect_partial_match_engine(subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ uint covering_null_row_width_arg);
+ int prepare() { return 0; }
+ int exec();
+ void fix_length_and_dec(Item_cache**) {}
+ uint cols() { /* TODO: what is the correct value? */ return 1; }
+ uint8 uncacheable() { return UNCACHEABLE_DEPENDENT; }
+ void exclude() {}
+ table_map upper_select_const_tables() { return 0; }
+ bool change_result(Item_subselect*, select_result_interceptor*)
+ { DBUG_ASSERT(FALSE); return false; }
+ bool no_tables() { return false; }
+ bool no_rows()
+ {
+ /*
+ TODO: It is completely unclear what is the semantics of this
+ method. The current result is computed so that the call to no_rows()
+ from Item_in_optimizer::val_int() sets Item_in_optimizer::null_value
+ correctly.
+ */
+ return !(((Item_in_subselect *) item)->null_value);
+ }
+ void print(String*, enum_query_type);
+
+ friend void subselect_hash_sj_engine::cleanup();
+};
+
+
+class subselect_rowid_merge_engine: public subselect_partial_match_engine
+{
+protected:
+ /*
+ Mapping from row numbers to row ids. The rowids are stored sequentially
+ in the array - rowid[i] is located in row_num_to_rowid + i * rowid_length.
+ */
+ uchar *row_num_to_rowid;
+ /*
+ A subset of all the keys for which there is a match for the same row.
+ Used during execution. Computed for each outer reference
+ */
+ MY_BITMAP matching_keys;
+ /*
+ The columns of the outer reference that are NULL. Computed for each
+ outer reference.
+ */
+ MY_BITMAP matching_outer_cols;
+ /*
+ Columns that consist of only NULLs. Such columns match any value.
+ Computed once per query execution.
+ */
+ MY_BITMAP null_only_columns;
+ /*
+ Indexes of row numbers, sorted by <column_value, row_number>. If an
+ index may contain NULLs, the NULLs are stored efficiently in a bitmap.
+
+ The indexes are sorted by the selectivity of their NULL sub-indexes, the
+ one with the fewer NULLs is first. Thus, if there is any index on
+ non-NULL columns, it is contained in keys[0].
+ */
+ Ordered_key **merge_keys;
+ /* The number of elements in keys. */
+ uint keys_count;
+ /*
+ An index on all non-NULL columns of 'tmp_table'. The index has the
+ logical form: <[v_i1 | ... | v_ik], rownum>. It allows to find the row
+ number where the columns c_i1,...,c1_k contain the values v_i1,...,v_ik.
+ If such an index exists, it is always the first element of 'keys'.
+ */
+ Ordered_key *non_null_key;
+ /*
+ Priority queue of Ordered_key indexes, one per NULLable column.
+ This queue is used by the partial match algorithm in method exec().
+ */
+ QUEUE pq;
+protected:
+ /*
+ Comparison function to compare keys in order of decreasing bitmap
+ selectivity.
+ */
+ static int cmp_keys_by_null_selectivity(Ordered_key **k1, Ordered_key **k2);
+ /*
+ Comparison function used by the priority queue pq, the 'smaller' key
+ is the one with the smaller current row number.
+ */
+ static int cmp_keys_by_cur_rownum(void *arg, uchar *k1, uchar *k2);
+
+ bool test_null_row(rownum_t row_num);
+ bool partial_match();
+public:
+ subselect_rowid_merge_engine(subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, uint keys_count_arg,
+ uint covering_null_row_width_arg,
+ Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg)
+ :subselect_partial_match_engine(engine_arg, tmp_table_arg, item_arg,
+ result_arg, equi_join_conds_arg,
+ covering_null_row_width_arg),
+ keys_count(keys_count_arg), non_null_key(NULL)
+ {
+ thd= lookup_engine->get_thd();
+ }
+ ~subselect_rowid_merge_engine();
+ bool init(MY_BITMAP *non_null_key_parts, MY_BITMAP *partial_match_key_parts);
+ void cleanup();
+ virtual enum_engine_type engine_type() { return ROWID_MERGE_ENGINE; }
};
+
+class subselect_table_scan_engine: public subselect_partial_match_engine
+{
+protected:
+ bool partial_match();
+public:
+ subselect_table_scan_engine(subselect_uniquesubquery_engine *engine_arg,
+ TABLE *tmp_table_arg, Item_subselect *item_arg,
+ select_result_interceptor *result_arg,
+ List<Item> *equi_join_conds_arg,
+ uint covering_null_row_width_arg);
+ void cleanup();
+ virtual enum_engine_type engine_type() { return TABLE_SCAN_ENGINE; }
+};
=== modified file 'sql/mysql_priv.h'
--- a/sql/mysql_priv.h 2010-01-17 14:55:08 +0000
+++ b/sql/mysql_priv.h 2010-03-09 10:14:06 +0000
@@ -552,12 +552,14 @@ protected:
#define OPTIMIZER_SWITCH_LOOSE_SCAN 64
#define OPTIMIZER_SWITCH_MATERIALIZATION 128
#define OPTIMIZER_SWITCH_SEMIJOIN 256
+#define OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE 512
+#define OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN 1024
#ifdef DBUG_OFF
-# define OPTIMIZER_SWITCH_LAST 512
+# define OPTIMIZER_SWITCH_LAST 2048
#else
-# define OPTIMIZER_SWITCH_TABLE_ELIMINATION 512
-# define OPTIMIZER_SWITCH_LAST 1024
+# define OPTIMIZER_SWITCH_TABLE_ELIMINATION 2048
+# define OPTIMIZER_SWITCH_LAST 4096
#endif
#ifdef DBUG_OFF
@@ -570,8 +572,10 @@ protected:
OPTIMIZER_SWITCH_FIRSTMATCH | \
OPTIMIZER_SWITCH_LOOSE_SCAN | \
OPTIMIZER_SWITCH_MATERIALIZATION | \
- OPTIMIZER_SWITCH_SEMIJOIN)
-#else
+ OPTIMIZER_SWITCH_SEMIJOIN | \
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)
+#else
# define OPTIMIZER_SWITCH_DEFAULT (OPTIMIZER_SWITCH_INDEX_MERGE | \
OPTIMIZER_SWITCH_INDEX_MERGE_UNION | \
OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION | \
@@ -581,7 +585,9 @@ protected:
OPTIMIZER_SWITCH_FIRSTMATCH | \
OPTIMIZER_SWITCH_LOOSE_SCAN | \
OPTIMIZER_SWITCH_MATERIALIZATION | \
- OPTIMIZER_SWITCH_SEMIJOIN)
+ OPTIMIZER_SWITCH_SEMIJOIN | \
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE|\
+ OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)
#endif
/*
=== modified file 'sql/mysqld.cc'
--- a/sql/mysqld.cc 2010-01-17 14:55:08 +0000
+++ b/sql/mysqld.cc 2010-03-09 10:14:06 +0000
@@ -301,7 +301,9 @@ static const char *optimizer_switch_name
"index_merge","index_merge_union","index_merge_sort_union",
"index_merge_intersection",
"index_condition_pushdown",
- "firstmatch","loosescan","materialization", "semijoin",
+ "firstmatch","loosescan","materialization", "semijoin",
+ "partial_match_rowid_merge",
+ "partial_match_table_scan",
#ifndef DBUG_OFF
"table_elimination",
#endif
@@ -320,6 +322,8 @@ static const unsigned int optimizer_swit
sizeof("loosescan") - 1,
sizeof("materialization") - 1,
sizeof("semijoin") - 1,
+ sizeof("partial_match_rowid_merge") - 1,
+ sizeof("partial_match_table_scan") - 1,
#ifndef DBUG_OFF
sizeof("table_elimination") - 1,
#endif
@@ -5794,7 +5798,8 @@ enum options_mysqld
OPT_RECORD_RND_BUFFER, OPT_DIV_PRECINCREMENT, OPT_RELAY_LOG_SPACE_LIMIT,
OPT_RELAY_LOG_PURGE,
OPT_SLAVE_NET_TIMEOUT, OPT_SLAVE_COMPRESSED_PROTOCOL, OPT_SLOW_LAUNCH_TIME,
- OPT_SLAVE_TRANS_RETRIES, OPT_READONLY, OPT_DEBUGGING, OPT_DEBUG_FLUSH,
+ OPT_SLAVE_TRANS_RETRIES, OPT_READONLY, OPT_ROWID_MERGE_BUFF_SIZE,
+ OPT_DEBUGGING, OPT_DEBUG_FLUSH,
OPT_SORT_BUFFER, OPT_TABLE_OPEN_CACHE, OPT_TABLE_DEF_CACHE,
OPT_THREAD_CONCURRENCY, OPT_THREAD_CACHE_SIZE,
OPT_TMP_TABLE_SIZE, OPT_THREAD_STACK,
@@ -7130,6 +7135,11 @@ The minimum value for this variable is 4
(uchar**) &max_system_variables.range_alloc_block_size, 0, GET_ULONG,
REQUIRED_ARG, RANGE_ALLOC_BLOCK_SIZE, RANGE_ALLOC_BLOCK_SIZE,
(longlong) ULONG_MAX, 0, 1024, 0},
+ {"rowid_merge_buff_size", OPT_ROWID_MERGE_BUFF_SIZE,
+ "The size of the buffers used [NOT] IN evaluation via partial matching.",
+ (uchar**) &global_system_variables.rowid_merge_buff_size,
+ (uchar**) &max_system_variables.rowid_merge_buff_size, 0, GET_ULONG,
+ REQUIRED_ARG, 8*1024*1024L, 0, MAX_MEM_TABLE_SIZE/2, 0, 1, 0},
{"read_buffer_size", OPT_RECORD_BUFFER,
"Each thread that does a sequential scan allocates a buffer of this size for each table it scans. If you do many sequential scans, you may want to increase this value.",
(uchar**) &global_system_variables.read_buff_size,
=== modified file 'sql/opt_subselect.cc'
--- a/sql/opt_subselect.cc 2010-03-15 06:32:54 +0000
+++ b/sql/opt_subselect.cc 2010-03-15 19:52:58 +0000
@@ -187,10 +187,10 @@ int check_and_do_in_subquery_rewrites(JO
does not call setup_subquery_materialization(). We could make
SELECT ... FROM DUAL call that function but that doesn't seem
to be the case that is worth handling.
- 4. Subquery predicate is a top-level predicate
- (this implies it is not negated)
- TODO: this is a limitation that should be lifted once we
- implement correct NULL semantics (WL#3830)
+ 4. Either the subquery predicate is a top-level predicate, or at
+ least one partial match strategy is enabled. If no partial match
+ strategy is enabled, then materialization cannot be used for
+ non-top-level queries because it cannot handle NULLs correctly.
5. Subquery is non-correlated
TODO:
This is an overly restrictive condition. It can be extended to:
@@ -204,8 +204,8 @@ int check_and_do_in_subquery_rewrites(JO
(*) The subquery must be part of a SELECT statement. The current
condition also excludes multi-table update statements.
- We have to determine whether we will perform subquery materialization
- before calling the IN=>EXISTS transformation, so that we know whether to
+ Determine whether we will perform subquery materialization before
+ calling the IN=>EXISTS transformation, so that we know whether to
perform the whole transformation or only that part of it which wraps
Item_in_subselect in an Item_in_optimizer.
*/
@@ -215,12 +215,14 @@ int check_and_do_in_subquery_rewrites(JO
select_lex->master_unit()->first_select()->leaf_tables && // 3
thd->lex->sql_command == SQLCOM_SELECT && // *
select_lex->outer_select()->leaf_tables && // 3A
- subquery_types_allow_materialization(in_subs))
+ subquery_types_allow_materialization(in_subs) &&
+ // psergey-todo: duplicated_subselect_card_check: where it's done?
+ (in_subs->is_top_level_item() ||
+ optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_ROWID_MERGE) ||
+ optimizer_flag(thd, OPTIMIZER_SWITCH_PARTIAL_MATCH_TABLE_SCAN)) &&//4
+ !in_subs->is_correlated && // 5
+ in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6
{
- // psergey-todo: duplicated_subselect_card_check: where it's done?
- if (in_subs->is_top_level_item() && // 4
- !in_subs->is_correlated && // 5
- in_subs->exec_method == Item_in_subselect::NOT_TRANSFORMED) // 6
in_subs->exec_method= Item_in_subselect::MATERIALIZATION;
}
=== modified file 'sql/set_var.cc'
--- a/sql/set_var.cc 2009-12-22 12:49:15 +0000
+++ b/sql/set_var.cc 2010-03-09 10:14:06 +0000
@@ -540,6 +540,9 @@ static sys_var_long_ptr sys_query_cache_
static sys_var_thd_ulong sys_range_alloc_block_size(&vars, "range_alloc_block_size",
&SV::range_alloc_block_size);
+static sys_var_thd_ulong sys_rowid_merge_buff_size(&vars, "rowid_merge_buff_size",
+ &SV::rowid_merge_buff_size);
+
static sys_var_thd_ulong sys_query_alloc_block_size(&vars, "query_alloc_block_size",
&SV::query_alloc_block_size,
0, fix_thd_mem_root);
=== modified file 'sql/sql_class.cc'
--- a/sql/sql_class.cc 2010-02-17 21:59:41 +0000
+++ b/sql/sql_class.cc 2010-02-19 21:55:57 +0000
@@ -42,6 +42,7 @@
#include "sp_rcontext.h"
#include "sp_cache.h"
+#include "sql_select.h" /* declares create_tmp_table() */
/*
The following is used to initialise Table_ident with a internal
@@ -2877,6 +2878,71 @@ bool select_dumpvar::send_eof()
return 0;
}
+
+bool
+select_materialize_with_stats::
+create_result_table(THD *thd_arg, List<Item> *column_types,
+ bool is_union_distinct, ulonglong options,
+ const char *table_alias, bool bit_fields_as_long)
+{
+ DBUG_ASSERT(table == 0);
+ tmp_table_param.field_count= column_types->elements;
+ tmp_table_param.bit_fields_as_long= bit_fields_as_long;
+
+ if (! (table= create_tmp_table(thd_arg, &tmp_table_param, *column_types,
+ (ORDER*) 0, is_union_distinct, 1,
+ options, HA_POS_ERROR, (char*) table_alias)))
+ return TRUE;
+
+ col_stat= (Column_statistics*) table->in_use->alloc(table->s->fields *
+ sizeof(Column_statistics));
+ if (!stat)
+ return TRUE;
+
+ cleanup();
+
+ table->file->extra(HA_EXTRA_WRITE_CACHE);
+ table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
+ return FALSE;
+}
+
+
+/**
+ Override select_union::send_data to analyze each row for NULLs and to
+ update null_statistics before sending data to the client.
+
+ @return TRUE if fatal error when sending data to the client
+ @return FALSE on success
+*/
+
+bool select_materialize_with_stats::send_data(List<Item> &items)
+{
+ List_iterator_fast<Item> item_it(items);
+ Item *cur_item;
+ Column_statistics *cur_col_stat= col_stat;
+ uint nulls_in_row= 0;
+
+ ++count_rows;
+
+ while ((cur_item= item_it++))
+ {
+ if (cur_item->is_null())
+ {
+ ++cur_col_stat->null_count;
+ cur_col_stat->max_null_row= count_rows;
+ if (!cur_col_stat->min_null_row)
+ cur_col_stat->min_null_row= count_rows;
+ ++nulls_in_row;
+ }
+ ++cur_col_stat;
+ }
+ if (nulls_in_row > max_nulls_in_row)
+ max_nulls_in_row= nulls_in_row;
+
+ return select_union::send_data(items);
+}
+
+
/****************************************************************************
TMP_TABLE_PARAM
****************************************************************************/
=== modified file 'sql/sql_class.h'
--- a/sql/sql_class.h 2010-02-17 21:59:41 +0000
+++ b/sql/sql_class.h 2010-03-09 10:14:06 +0000
@@ -343,6 +343,8 @@ struct system_variables
ulong mrr_buff_size;
ulong div_precincrement;
ulong sortbuff_size;
+ /* Total size of all buffers used by the subselect_rowid_merge_engine. */
+ ulong rowid_merge_buff_size;
ulong thread_handling;
ulong tx_isolation;
ulong completion_type;
@@ -2740,19 +2742,20 @@ public:
class select_union :public select_result_interceptor
{
+protected:
TMP_TABLE_PARAM tmp_table_param;
public:
TABLE *table;
- select_union() :table(0) {}
+ select_union() :table(0) { tmp_table_param.init(); }
int prepare(List<Item> &list, SELECT_LEX_UNIT *u);
bool send_data(List<Item> &items);
bool send_eof();
bool flush();
- bool create_result_table(THD *thd, List<Item> *column_types,
- bool is_distinct, ulonglong options,
- const char *alias, bool bit_fields_as_long);
+ virtual bool create_result_table(THD *thd, List<Item> *column_types,
+ bool is_distinct, ulonglong options,
+ const char *alias, bool bit_fields_as_long);
};
/* Base subselect interface class */
@@ -2776,6 +2779,74 @@ public:
bool send_data(List<Item> &items);
};
+
+/*
+ This class specializes select_union to collect statistics about the
+ data stored in the temp table. Currently the class collects statistcs
+ about NULLs.
+*/
+
+class select_materialize_with_stats : public select_union
+{
+protected:
+ class Column_statistics
+ {
+ public:
+ /* Count of NULLs per column. */
+ ha_rows null_count;
+ /* The row number that contains the first NULL in a column. */
+ ha_rows min_null_row;
+ /* The row number that contains the last NULL in a column. */
+ ha_rows max_null_row;
+ };
+
+ /* Array of statistics data per column. */
+ Column_statistics* col_stat;
+
+ /*
+ The number of columns in the biggest sub-row that consists of only
+ NULL values.
+ */
+ ha_rows max_nulls_in_row;
+ /*
+ Count of rows writtent to the temp table. This is redundant as it is
+ already stored in handler::stats.records, however that one is relatively
+ expensive to compute (given we need that for evry row).
+ */
+ ha_rows count_rows;
+
+public:
+ select_materialize_with_stats() {}
+ virtual bool create_result_table(THD *thd, List<Item> *column_types,
+ bool is_distinct, ulonglong options,
+ const char *alias, bool bit_fields_as_long);
+ bool init_result_table(ulonglong select_options);
+ bool send_data(List<Item> &items);
+ void cleanup()
+ {
+ memset(col_stat, 0, table->s->fields * sizeof(Column_statistics));
+ max_nulls_in_row= 0;
+ count_rows= 0;
+ }
+ ha_rows get_null_count_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].null_count;
+ }
+ ha_rows get_max_null_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].max_null_row;
+ }
+ ha_rows get_min_null_of_col(uint idx)
+ {
+ DBUG_ASSERT(idx < table->s->fields);
+ return col_stat[idx].min_null_row;
+ }
+ ha_rows get_max_nulls_in_row() { return max_nulls_in_row; }
+};
+
+
/* used in independent ALL/ANY optimisation */
class select_max_min_finder_subselect :public select_subselect
{
=== modified file 'sql/sql_select.cc'
--- a/sql/sql_select.cc 2010-03-14 18:25:43 +0000
+++ b/sql/sql_select.cc 2010-03-15 19:52:58 +0000
@@ -874,6 +874,9 @@ JOIN::optimize()
{
DBUG_PRINT("info",("No tables"));
error= 0;
+ /* Create all structures needed for materialized subquery execution. */
+ if (setup_subquery_materialization())
+ DBUG_RETURN(1);
DBUG_RETURN(0);
}
error= -1; // Error is sent to client
@@ -11258,7 +11261,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARA
param->group_buff=group_buff;
share->keys=1;
share->uniques= test(using_unique_constraint);
- table->key_info=keyinfo;
+ table->key_info= table->s->key_info= keyinfo;
keyinfo->key_part=key_part_info;
keyinfo->flags=HA_NOSAME;
keyinfo->usable_key_parts=keyinfo->key_parts= param->group_parts;
@@ -11344,7 +11347,7 @@ create_tmp_table(THD *thd,TMP_TABLE_PARA
keyinfo->key_parts * sizeof(KEY_PART_INFO))))
goto err;
bzero((void*) key_part_info, keyinfo->key_parts * sizeof(KEY_PART_INFO));
- table->key_info=keyinfo;
+ table->key_info= table->s->key_info= keyinfo;
keyinfo->key_part=key_part_info;
keyinfo->flags=HA_NOSAME | HA_NULL_ARE_EQUAL;
keyinfo->key_length= 0; // Will compute the sum of the parts below.
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: timour@xxxxxxxxxxxx-20100315195258-nhomb3anbb1tv3mi
# target_branch: file:///home/tsk/mprog/src/5.3-subqueries/
# testament_sha1: f1be35084724ebd60bad5c5b297ec86525f2e70e
# timestamp: 2010-03-15 21:53:08 +0200
# source_branch: file:///home/tsk/mprog/src/5.3-mwl68/
# base_revision_id: psergey@xxxxxxxxxxxx-20100315063535-\
# jsp4jgya6lfqt8e6
#
# Begin bundle
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