Commit 42221aba authored by unknown's avatar unknown

Fix bug lp:869036

Apart from the fix, the patch also adds few more unrelated test
cases for partial matching, and fixes few typos.

Analysis:
This bug uncovered that partial matching via rowid intersection
didn't handle the case when:
- the left IN argument has some NULLs,
- there are no non-null value matches, and there is no non-null
  column,
- the subquery columns that are not covered with the NULLs in
  the left IN argument contain at least one row, such that it
  has NULL values in all columns where the left IN operand has
  no NULLs.
In this case there is a partial match.

In addition the analysis of the related code uncovered incorrect
handling of few other related cases.

Solution:
The solution for the bug is to check if there exists a row with
NULLs in all columns other than the ones having NULL in the
let IN operand.

The check is implemented via checking whether the bitmaps that
store NULL information in class Ordered_key have a non-empty
intersection for the relevant columns.

The intersection itself is implemented via the function
bitmap_exists_intersection() in my_bitmap.c.
parent c05e5b9c
......@@ -56,6 +56,10 @@ extern my_bool bitmap_test_and_clear(MY_BITMAP *map, uint bitmap_bit);
extern my_bool bitmap_fast_test_and_set(MY_BITMAP *map, uint bitmap_bit);
extern my_bool bitmap_union_is_set_all(const MY_BITMAP *map1,
const MY_BITMAP *map2);
extern my_bool bitmap_exists_intersection(const MY_BITMAP **bitmap_array,
uint bitmap_count,
uint start_bit, uint end_bit);
extern uint bitmap_set_next(MY_BITMAP *map);
extern uint bitmap_get_first(const MY_BITMAP *map);
extern uint bitmap_get_first_set(const MY_BITMAP *map);
......
......@@ -3,11 +3,13 @@ set @save_optimizer_switch=@@optimizer_switch;
Part 1: Feature tests.
-------------------------------
Default for all tests.
set @@optimizer_switch="materialization=on,in_to_exists=off,semijoin=off,subquery_cache=off";
set @@optimizer_switch = "materialization=on,in_to_exists=off,semijoin=off,subquery_cache=off";
set @test_default_opt_switch = @@optimizer_switch;
set @in_exists = "materialization=off,in_to_exists=on,semijoin=off,subquery_cache=off";
-------------------------------------------------------------------------
Schema requires partial matching, but data analysis discoveres there is
no need. This is possible only if all outer columns are not NULL.
-------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8) not null);
create table t2 (b1 char(8), b2 char(8));
insert into t1 values ('1 - 00', '2 - 00');
......@@ -26,9 +28,9 @@ a1 a2 in_res
1 - 00 2 - 00 0
1 - 01 2 - 01 NULL
drop table t1, t2;
-------------------------------------------------------------------------
NULLs in the outer columns, no NULLs in the suqbuery
-------------------------------------------------------------------------
create table t1 (a1 char(8), a2 char(8));
create table t2 (b1 char(8) not null, b2 char(8) not null);
insert into t1 values (NULL , '2 - 00');
......@@ -55,17 +57,532 @@ NULL 2 - 00 NULL
1 - 01 2 - 01 1
NULL NULL NULL
drop table t1, t2;
-------------------------------------------------------------------------
NULLs in the outer column, NULLs in the subquery, there is
no value match in any column, but there is a partial match
such that some of the matching NULLs are in the outer columns,
the other NULLs are in the inner columns.
-------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values ('c', NULL, 'a');
insert into t2 values (NULL, 'x', NULL);
insert into t2 values (NULL, 'y', NULL);
insert into t2 values ('o', 'z', 'p');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 3
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
c NULL a NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 3
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 3
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
c NULL a NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 3
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
c NULL a NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
create table t1 (a1 char(1), a2 char(1) not null, a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values (NULL, 'y', NULL);
insert into t2 values ('v', 'x', NULL);
insert into t2 values (NULL, 'y', 'w');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL y NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL y NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL y NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
-------------------------------------------------------------------------
There is only one column in the subquery to complement the NULLs in the
outer reference. It is a NULL column, so a match is guaranteed.
-------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values (NULL, 'g', NULL);
insert into t2 values ('z', NULL, 'y');
insert into t2 values (NULL, 'z', 'y');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL g NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL g NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
NULL g NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
-------------------------------------------------------------------------
The intersection of the NULL bitmaps is empty because the ranges
of NULL bits do not overlap.
-------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values ('b', 'g', NULL);
insert into t2 values ('z', NULL, 'y');
insert into t2 values (NULL, 'z', 'y');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
b g NULL 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
b g NULL
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
b g NULL 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
b g NULL
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
a1 a2 a3 in_res
b g NULL 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 2 Using where
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
a1 a2 a3
b g NULL
drop table t1, t2;
-------------------------------------------------------------------------
The intersection of the NULL bitmaps is non-empty, and there is a
non-NULL column.
-------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1) not null, a4 char(10));
create table t2 (b1 char(1), b2 char(1), b3 char(1), b4 char(1));
insert into t1 values ('a', 'g', 'x', NULL);
insert into t2 values ('z', NULL, 'y', 'x');
insert into t2 values (NULL, NULL, 'x', 'y');
insert into t2 values ('x', 'w', 'z', NULL);
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
a g x NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
a g x NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
a g x NULL NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 3 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
drop table t1, t2;
-------------------------------------------------------------------------
Value match in one row, but the NULL complement match in another.
The result must be false.
-------------------------------------------------------------------------
create table t1 (a1 char(2), a2 char(1), a3 char(1) not null, a4 char(10));
create table t2 (b1 char(2), b2 char(1), b3 char(1), b4 char(1));
insert into t1 values ('99', NULL, 'j', 'f');
insert into t2 values ('01', NULL, 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'q', 'y', 'x');
insert into t2 values (NULL, 'q', 'm', 'p');
insert into t2 values ('m', 'z', 'j', NULL);
insert into t2 values (NULL, 'z', 'b', NULL);
insert into t2 values (NULL, 'z', 'a', NULL);
insert into t2 values ('34', 'q', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
99 NULL j f 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
99 NULL j f
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
99 NULL j f 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
99 NULL j f
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
99 NULL j f 0
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 system NULL NULL NULL NULL 1
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 8 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
99 NULL j f
drop table t1, t2;
-------------------------------------------------------------------------
Test the intersection of larger number of rows with NULL, such that
the number is at the boundary 32. This test is based on the implementation
of MY_BITMAP which uses 32 bit words, and the intersection operation works
by intersecting the bitmap word by word.
-------------------------------------------------------------------------
create table t1 (a1 char(2), a2 char(2), a3 char(1), a4 char(10)) ENGINE=MyISAM;
create table t2 (b1 char(2), b2 char(2), b3 char(1), b4 char(1)) ENGINE=MyISAM;
insert into t1 values (NULL, '98', NULL, 'b');
insert into t1 values (NULL, '99', NULL, 'c');
insert into t2 values ('00', 'rr', 'y', NULL);
insert into t2 values ('01', 'rr', 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'qq', 'y', 'x');
insert into t2 values ('04', 'qq', 'm', 'p');
insert into t2 values ('05', 'rr', 'y', NULL);
insert into t2 values ('06', NULL, 'x', 'y');
insert into t2 values ('07', 'qq', 'y', 'x');
insert into t2 values ('08', 'qq', 'm', 'q');
insert into t2 values ('09', 'rr', 'y', NULL);
insert into t2 values ('10', NULL, 'x', 'y');
insert into t2 values ('11', 'qq', 'y', 'x');
insert into t2 values ('12', 'qq', 'm', 'k');
insert into t2 values ('13', 'rr', 'y', NULL);
insert into t2 values ('14', NULL, 'x', 'y');
insert into t2 values ('15', 'qq', 'y', 'x');
insert into t2 values ('16', 'qq', 'm', 's');
insert into t2 values ('17', 'rr', 'y', NULL);
insert into t2 values ('18', NULL, 'x', 'y');
insert into t2 values ('19', 'qq', 'y', 'x');
insert into t2 values ('20', 'qq', 'm', 't');
insert into t2 values ('21', 'rr', 'y', NULL);
insert into t2 values ('22', NULL, 'x', 'y');
insert into t2 values ('23', 'qq', 'y', 'x');
insert into t2 values ('24', 'qq', 'm', 'u');
insert into t2 values ('25', 'rr', 'y', NULL);
insert into t2 values ('26', NULL, 'x', 'y');
insert into t2 values ('27', 'qq', 'y', 'x');
insert into t2 values ('28', 'qq', 'm', 'y');
insert into t2 values ('29', 'rr', 'y', NULL);
insert into t2 values ('30', NULL, 'x', 'z');
insert into t2 values ('31', 'ss', 'h', NULL);
insert into t2 values ('32', 'vv', 'i', NULL);
the only partial matching row
insert into t2 values ('33', NULL, 'j', NULL);
insert into t2 values ('34', 'qq', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
change the mathcing row to be the last one in the first bitmap word
update t2 set b2 = 'zz' where b1 = 33;
update t2 set b2 = NULL where b1 = 31;
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b NULL
NULL 99 NULL c NULL
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2 Using where
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 35 Using where
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
a1 a2 a3 a4
drop table t1, t2;
-------------------------------------------------------------------------
All columns require partial matching (no non-null columns)
-------------------------------------------------------------------------
TODO
-------------------------------------------------------------------------
Both non-NULL columns and columns with NULLs
-------------------------------------------------------------------------
TODO
-------------------------------------------------------------------------
Covering NULL rows
-------------------------------------------------------------------------
create table t1 (a1 char(8), a2 char(8));
create table t2 (b1 char(8), b2 char(8));
insert into t1 values ('1 - 00', '2 - 00');
......@@ -98,9 +615,9 @@ a1 a2 in_res
1 - 00 2 - 00 NULL
1 - 01 2 - 01 1
drop table t1, t2;
-------------------------------------------------------------------------
Covering NULL columns
-------------------------------------------------------------------------
this case affects only the rowid-merge algorithm
set @@optimizer_switch="partial_match_rowid_merge=on,partial_match_table_scan=off";
create table t1 (a1 char(8) not null, a2 char(8), a3 char(8) not null);
......@@ -149,9 +666,9 @@ a1 a2 a3 in_res
1 - 00 2 - 00 3 - 00 NULL
1 - 01 2 - 01 3 - 01 0
drop table t1, t2;
-------------------------------------------------------------------------
Covering NULL row, and a NULL column
-------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8), a3 char(8));
create table t2 (b1 char(8), b2 char(8), b3 char(8));
insert into t1 values ('1 - 00', '2 - 00', '3 - 00');
......@@ -174,9 +691,9 @@ a1 a2 a3 in_res
1 - 00 2 - 00 3 - 00 NULL
1 - 01 2 - 01 3 - 01 NULL
drop table t1, t2;
-------------------------------------------------------------------------
Covering NULL row, and covering NULL columns
-------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8), a3 char(8));
create table t2 (b1 char(8), b2 char(8), b3 char(8));
insert into t1 values ('1 - 00', '2 - 00', '3 - 00');
......@@ -198,6 +715,32 @@ a1 a2 a3 in_res
1 - 00 2 - 00 3 - 00 NULL
1 - 01 2 - 01 3 - 01 NULL
drop table t1, t2;
-------------------------------------------------------------------------
Small buffer for the rowid_merge partial match algorithm that forces
reverting to table scan partial match.
-------------------------------------------------------------------------
set @save_rowid_merge_buff_size = @@rowid_merge_buff_size;
set @@rowid_merge_buff_size = 0;
create table t1 (a1 char(2), a2 char(2), a3 char(1), a4 char(10));
create table t2 (b1 char(2), b2 char(2), b3 char(1), b4 char(1));
insert into t1 values (NULL, '98', NULL, 'b');
insert into t1 values (NULL, '99', NULL, 'c');
insert into t2 values ('00', 'rr', 'y', NULL);
insert into t2 values ('01', 'rr', 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'qq', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 DEPENDENT SUBQUERY t2 ALL NULL NULL NULL NULL 4 Using where
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
a1 a2 a3 a4 in_res
NULL 98 NULL b 0
NULL 99 NULL c 0
drop table t1, t2;
set @@rowid_merge_buff_size = @save_rowid_merge_buff_size;
-------------------------------
Part 2: Test cases for bugs.
-------------------------------
......@@ -372,4 +915,37 @@ SELECT * FROM t2 WHERE ( 3 , 1 ) NOT IN ( SELECT f1 , f2 FROM t1 );
f3
5
drop table t1, t2;
#
# LP BUG#869036 Wrong result with in_to_exists=ON and NOT IN
#
create table outer_sq (f1 char(1), f2 char(1));
insert into outer_sq values (NULL, 'c'), ('g', 'c');
create table inner_sq (f3 char(1), f4 char(1));
insert into inner_sq values(null, 'i'), ('v', null);
All three strategies below must produce the same result.
set @@optimizer_switch='in_to_exists=off,materialization=on,partial_match_rowid_merge=on,partial_match_table_scan=off';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
f1 f2 (f1, f2) IN (select * from inner_sq)
NULL c NULL
g c 0
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
f1 f2
g c
set @@optimizer_switch='in_to_exists=off,materialization=on,partial_match_rowid_merge=off,partial_match_table_scan=on';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
f1 f2 (f1, f2) IN (select * from inner_sq)
NULL c NULL
g c 0
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
f1 f2
g c
set @@optimizer_switch='in_to_exists=on,materialization=off';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
f1 f2 (f1, f2) IN (select * from inner_sq)
NULL c NULL
g c 0
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
f1 f2
g c
drop table outer_sq, inner_sq;
set @@optimizer_switch=@save_optimizer_switch;
......@@ -10,12 +10,14 @@ set @save_optimizer_switch=@@optimizer_switch;
--echo -------------------------------
--echo Default for all tests.
set @@optimizer_switch="materialization=on,in_to_exists=off,semijoin=off,subquery_cache=off";
set @@optimizer_switch = "materialization=on,in_to_exists=off,semijoin=off,subquery_cache=off";
set @test_default_opt_switch = @@optimizer_switch;
set @in_exists = "materialization=off,in_to_exists=on,semijoin=off,subquery_cache=off";
--echo
--echo -------------------------------------------------------------------------
--echo Schema requires partial matching, but data analysis discoveres there is
--echo no need. This is possible only if all outer columns are not NULL.
--echo
--echo -------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8) not null);
create table t2 (b1 char(8), b2 char(8));
......@@ -36,9 +38,9 @@ select a1, a2, (a1, a2) not in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo
--echo -------------------------------------------------------------------------
--echo NULLs in the outer columns, no NULLs in the suqbuery
--echo
--echo -------------------------------------------------------------------------
create table t1 (a1 char(8), a2 char(8));
create table t2 (b1 char(8) not null, b2 char(8) not null);
......@@ -63,21 +65,366 @@ select a1, a2, (a1, a2) in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo
--echo -------------------------------------------------------------------------
--echo NULLs in the outer column, NULLs in the subquery, there is
--echo no value match in any column, but there is a partial match
--echo such that some of the matching NULLs are in the outer columns,
--echo the other NULLs are in the inner columns.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values ('c', NULL, 'a');
insert into t2 values (NULL, 'x', NULL);
insert into t2 values (NULL, 'y', NULL);
insert into t2 values ('o', 'z', 'p');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
create table t1 (a1 char(1), a2 char(1) not null, a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values (NULL, 'y', NULL);
insert into t2 values ('v', 'x', NULL);
insert into t2 values (NULL, 'y', 'w');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo There is only one column in the subquery to complement the NULLs in the
--echo outer reference. It is a NULL column, so a match is guaranteed.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values (NULL, 'g', NULL);
insert into t2 values ('z', NULL, 'y');
insert into t2 values (NULL, 'z', 'y');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch = @test_default_opt_switch;
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo The intersection of the NULL bitmaps is empty because the ranges
--echo of NULL bits do not overlap.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1));
create table t2 (b1 char(1), b2 char(1), b3 char(1));
insert into t1 values ('b', 'g', NULL);
insert into t2 values ('z', NULL, 'y');
insert into t2 values (NULL, 'z', 'y');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3) NOT IN (select * from t2);
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo The intersection of the NULL bitmaps is non-empty, and there is a
--echo non-NULL column.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(1), a2 char(1), a3 char(1) not null, a4 char(10));
create table t2 (b1 char(1), b2 char(1), b3 char(1), b4 char(1));
insert into t1 values ('a', 'g', 'x', NULL);
insert into t2 values ('z', NULL, 'y', 'x');
insert into t2 values (NULL, NULL, 'x', 'y');
insert into t2 values ('x', 'w', 'z', NULL);
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo Value match in one row, but the NULL complement match in another.
--echo The result must be false.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(2), a2 char(1), a3 char(1) not null, a4 char(10));
create table t2 (b1 char(2), b2 char(1), b3 char(1), b4 char(1));
insert into t1 values ('99', NULL, 'j', 'f');
insert into t2 values ('01', NULL, 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'q', 'y', 'x');
insert into t2 values (NULL, 'q', 'm', 'p');
insert into t2 values ('m', 'z', 'j', NULL);
insert into t2 values (NULL, 'z', 'b', NULL);
insert into t2 values (NULL, 'z', 'a', NULL);
insert into t2 values ('34', 'q', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo Test the intersection of larger number of rows with NULL, such that
--echo the number is at the boundary 32. This test is based on the implementation
--echo of MY_BITMAP which uses 32 bit words, and the intersection operation works
--echo by intersecting the bitmap word by word.
--echo -------------------------------------------------------------------------
create table t1 (a1 char(2), a2 char(2), a3 char(1), a4 char(10)) ENGINE=MyISAM;
create table t2 (b1 char(2), b2 char(2), b3 char(1), b4 char(1)) ENGINE=MyISAM;
insert into t1 values (NULL, '98', NULL, 'b');
insert into t1 values (NULL, '99', NULL, 'c');
insert into t2 values ('00', 'rr', 'y', NULL);
insert into t2 values ('01', 'rr', 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'qq', 'y', 'x');
insert into t2 values ('04', 'qq', 'm', 'p');
insert into t2 values ('05', 'rr', 'y', NULL);
insert into t2 values ('06', NULL, 'x', 'y');
insert into t2 values ('07', 'qq', 'y', 'x');
insert into t2 values ('08', 'qq', 'm', 'q');
insert into t2 values ('09', 'rr', 'y', NULL);
insert into t2 values ('10', NULL, 'x', 'y');
insert into t2 values ('11', 'qq', 'y', 'x');
insert into t2 values ('12', 'qq', 'm', 'k');
insert into t2 values ('13', 'rr', 'y', NULL);
insert into t2 values ('14', NULL, 'x', 'y');
insert into t2 values ('15', 'qq', 'y', 'x');
insert into t2 values ('16', 'qq', 'm', 's');
insert into t2 values ('17', 'rr', 'y', NULL);
insert into t2 values ('18', NULL, 'x', 'y');
insert into t2 values ('19', 'qq', 'y', 'x');
insert into t2 values ('20', 'qq', 'm', 't');
insert into t2 values ('21', 'rr', 'y', NULL);
insert into t2 values ('22', NULL, 'x', 'y');
insert into t2 values ('23', 'qq', 'y', 'x');
insert into t2 values ('24', 'qq', 'm', 'u');
insert into t2 values ('25', 'rr', 'y', NULL);
insert into t2 values ('26', NULL, 'x', 'y');
insert into t2 values ('27', 'qq', 'y', 'x');
insert into t2 values ('28', 'qq', 'm', 'y');
insert into t2 values ('29', 'rr', 'y', NULL);
insert into t2 values ('30', NULL, 'x', 'z');
insert into t2 values ('31', 'ss', 'h', NULL);
insert into t2 values ('32', 'vv', 'i', NULL);
--echo the only partial matching row
insert into t2 values ('33', NULL, 'j', NULL);
insert into t2 values ('34', 'qq', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
--echo change the mathcing row to be the last one in the first bitmap word
update t2 set b2 = 'zz' where b1 = 33;
update t2 set b2 = NULL where b1 = 31;
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch='partial_match_rowid_merge=off,partial_match_table_scan=on';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
set @@optimizer_switch=@in_exists;
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
EXPLAIN
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
SELECT * from t1 where (a1, a2, a3, a4) NOT IN (select * from t2);
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo All columns require partial matching (no non-null columns)
--echo
--echo -------------------------------------------------------------------------
--echo TODO
--echo
--echo -------------------------------------------------------------------------
--echo Both non-NULL columns and columns with NULLs
--echo
--echo -------------------------------------------------------------------------
--echo TODO
--echo
--echo -------------------------------------------------------------------------
--echo Covering NULL rows
--echo
--echo -------------------------------------------------------------------------
create table t1 (a1 char(8), a2 char(8));
create table t2 (b1 char(8), b2 char(8));
......@@ -110,9 +457,9 @@ select a1, a2, (a1, a2) in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo
--echo -------------------------------------------------------------------------
--echo Covering NULL columns
--echo
--echo -------------------------------------------------------------------------
--echo this case affects only the rowid-merge algorithm
set @@optimizer_switch="partial_match_rowid_merge=on,partial_match_table_scan=off";
......@@ -161,9 +508,9 @@ select *, (a1, a2, a3) in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo
--echo -------------------------------------------------------------------------
--echo Covering NULL row, and a NULL column
--echo
--echo -------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8), a3 char(8));
create table t2 (b1 char(8), b2 char(8), b3 char(8));
......@@ -188,9 +535,9 @@ select *, (a1, a2, a3) in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo
--echo -------------------------------------------------------------------------
--echo Covering NULL row, and covering NULL columns
--echo
--echo -------------------------------------------------------------------------
create table t1 (a1 char(8) not null, a2 char(8), a3 char(8));
create table t2 (b1 char(8), b2 char(8), b3 char(8));
......@@ -213,6 +560,34 @@ select *, (a1, a2, a3) in (select * from t2) as in_res from t1;
drop table t1, t2;
--echo -------------------------------------------------------------------------
--echo Small buffer for the rowid_merge partial match algorithm that forces
--echo reverting to table scan partial match.
--echo -------------------------------------------------------------------------
set @save_rowid_merge_buff_size = @@rowid_merge_buff_size;
set @@rowid_merge_buff_size = 0;
create table t1 (a1 char(2), a2 char(2), a3 char(1), a4 char(10));
create table t2 (b1 char(2), b2 char(2), b3 char(1), b4 char(1));
insert into t1 values (NULL, '98', NULL, 'b');
insert into t1 values (NULL, '99', NULL, 'c');
insert into t2 values ('00', 'rr', 'y', NULL);
insert into t2 values ('01', 'rr', 'y', NULL);
insert into t2 values ('02', NULL, 'x', 'y');
insert into t2 values ('03', 'qq', 'y', 'x');
set @@optimizer_switch='partial_match_rowid_merge=on,partial_match_table_scan=off';
EXPLAIN
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
SELECT t1.*, (a1, a2, a3, a4) IN (select * from t2) as in_res from t1;
drop table t1, t2;
set @@rowid_merge_buff_size = @save_rowid_merge_buff_size;
--echo -------------------------------
--echo Part 2: Test cases for bugs.
......@@ -374,4 +749,30 @@ SELECT * FROM t2 WHERE ( 3 , 1 ) NOT IN ( SELECT f1 , f2 FROM t1 );
drop table t1, t2;
--echo #
--echo # LP BUG#869036 Wrong result with in_to_exists=ON and NOT IN
--echo #
create table outer_sq (f1 char(1), f2 char(1));
insert into outer_sq values (NULL, 'c'), ('g', 'c');
create table inner_sq (f3 char(1), f4 char(1));
insert into inner_sq values(null, 'i'), ('v', null);
--echo All three strategies below must produce the same result.
set @@optimizer_switch='in_to_exists=off,materialization=on,partial_match_rowid_merge=on,partial_match_table_scan=off';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
set @@optimizer_switch='in_to_exists=off,materialization=on,partial_match_rowid_merge=off,partial_match_table_scan=on';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
set @@optimizer_switch='in_to_exists=on,materialization=off';
SELECT outer_sq.*, (f1, f2) IN (select * from inner_sq) from outer_sq;
SELECT * from outer_sq where (f1, f2) NOT IN (select * from inner_sq);
drop table outer_sq, inner_sq;
set @@optimizer_switch=@save_optimizer_switch;
......@@ -100,6 +100,44 @@ void create_last_word_mask(MY_BITMAP *map)
}
static inline my_bitmap_map last_word_mask(uint bit)
{
my_bitmap_map last_word_mask;
uint n_bits= bit + 1;
unsigned char const mask= invers_last_byte_mask(n_bits);
/*
The first bytes are to be set to zero since they represent real bits
in the bitvector. The last bytes are set to 0xFF since they represent
bytes not used by the bitvector. Finally the last byte contains bits
as set by the mask above.
*/
unsigned char *ptr= (unsigned char*)&last_word_mask;
switch ((n_bits + 7)/8 & 3) {
case 1:
last_word_mask= ~0U;
ptr[0]= mask;
break;
case 2:
last_word_mask= ~0U;
ptr[0]= 0;
ptr[1]= mask;
break;
case 3:
last_word_mask= 0U;
ptr[2]= mask;
ptr[3]= 0xFFU;
break;
case 0:
last_word_mask= 0U;
ptr[3]= mask;
break;
}
return last_word_mask;
}
static inline void bitmap_lock(MY_BITMAP *map __attribute__((unused)))
{
#ifdef THREAD
......@@ -410,6 +448,58 @@ void bitmap_intersect(MY_BITMAP *map, const MY_BITMAP *map2)
}
}
/*
Check if there is some bit index between start_bit and end_bit, such that
this is bit is set for all bitmaps in bitmap_list.
SYNOPSIS
bitmap_exists_intersection()
bitmpap_array [in] a set of MY_BITMAPs
bitmap_count [in] number of elements in bitmpap_array
start_bit [in] beginning (inclusive) of the range of bits to search
end_bit [in] end (inclusive) of the range of bits to search, must be
no bigger than the bits of the shortest bitmap.
NOTES
This function assumes that for at least one of the bitmaps in bitmap_array all
bits outside the range [start_bit, end_bit] are 0. As a result is not
necessary to take care of the bits outside the range [start_bit, end_bit].
RETURN
TRUE if an intersecion exists
FALSE no intersection
*/
my_bool bitmap_exists_intersection(const MY_BITMAP **bitmap_array,
uint bitmap_count,
uint start_bit, uint end_bit)
{
uint i, j, start_idx, end_idx;
my_bitmap_map cur_res;
DBUG_ASSERT(bitmap_count && end_bit >= start_bit);
for (j= 0; j < bitmap_count; j++)
DBUG_ASSERT(end_bit < bitmap_array[j]->n_bits);
start_idx= start_bit/8/sizeof(my_bitmap_map);
end_idx= end_bit/8/sizeof(my_bitmap_map);
for (i= start_idx; i < end_idx; i++)
{
cur_res= ~0;
for (j= 0; cur_res && j < bitmap_count; j++)
cur_res &= bitmap_array[j]->bitmap[i];
if (cur_res)
return TRUE;
}
cur_res= ~last_word_mask(end_bit);
for (j= 0; cur_res && j < bitmap_count; j++)
cur_res &= bitmap_array[j]->bitmap[end_idx];
return cur_res != 0;
}
/* True if union of bitmaps have all bits set */
my_bool bitmap_union_is_set_all(const MY_BITMAP *map1, const MY_BITMAP *map2)
......
......@@ -4849,14 +4849,14 @@ bool Ordered_key::init(MY_BITMAP *columns_to_index)
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*));
if (!key_columns || !compare_pred)
return TRUE; /* Revert to table scan partial match. */
for (uint i= 0; i < columns_to_index->n_bits; i++)
{
if (!bitmap_is_set(columns_to_index, i))
......@@ -5316,10 +5316,13 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts,
merge_keys_count == 1 && non_null_key_parts));
/*
Allocate buffers to hold the merged keys and the mapping between rowids and
row numbers.
row numbers. All small buffers are allocated in the runtime memroot. Big
buffers are allocated from the OS via malloc.
*/
if (!(merge_keys= (Ordered_key**) thd->alloc(merge_keys_count *
sizeof(Ordered_key*))) ||
!(null_bitmaps= (MY_BITMAP**) thd->alloc(merge_keys_count *
sizeof(MY_BITMAP*))) ||
!(row_num_to_rowid= (uchar*) my_malloc((size_t)(row_count * rowid_length),
MYF(MY_WME))))
return TRUE;
......@@ -5537,6 +5540,56 @@ bool subselect_rowid_merge_engine::test_null_row(rownum_t row_num)
}
/**
Test if a subset of NULL-able columns contains a row of NULLs.
*/
bool subselect_rowid_merge_engine::
exists_complementing_null_row(MY_BITMAP *keys_to_complement)
{
rownum_t highest_min_row= 0;
rownum_t lowest_max_row= UINT_MAX;
uint count_null_keys, i, j;
Ordered_key *cur_key;
count_null_keys= keys_to_complement->n_bits -
bitmap_bits_set(keys_to_complement);
if (count_null_keys == 1)
{
/*
The caller guarantees that the complement to keys_to_complement
contains only columns with NULLs. Therefore if there is only one column,
it is guaranteed to contain NULLs.
*/
return TRUE;
}
for (i= (non_null_key ? 1 : 0), j= 0; i < merge_keys_count; i++)
{
cur_key= merge_keys[i];
if (bitmap_is_set(keys_to_complement, cur_key->get_keyid()))
continue;
DBUG_ASSERT(cur_key->get_null_count());
if (cur_key->get_min_null_row() > highest_min_row)
highest_min_row= cur_key->get_min_null_row();
if (cur_key->get_max_null_row() < lowest_max_row)
lowest_max_row= cur_key->get_max_null_row();
null_bitmaps[j++]= cur_key->get_null_key();
}
DBUG_ASSERT(count_null_keys == j);
if (lowest_max_row < highest_min_row)
{
/* The intersection of NULL rows is empty. */
return FALSE;
}
return bitmap_exists_intersection((const MY_BITMAP**) null_bitmaps,
count_null_keys,
highest_min_row, lowest_max_row);
}
/*
@retval TRUE there is a partial match (UNKNOWN)
@retval FALSE there is no match at all (FALSE)
......@@ -5549,7 +5602,7 @@ bool subselect_rowid_merge_engine::partial_match()
Ordered_key *cur_key;
rownum_t cur_row_num;
uint count_nulls_in_search_key= 0;
uint max_covering_null_row_len=
uint max_null_in_any_row=
((select_materialize_with_stats *) result)->get_max_nulls_in_row();
bool res= FALSE;
......@@ -5602,29 +5655,52 @@ bool subselect_rowid_merge_engine::partial_match()
/*
If the outer reference consists of only NULLs, or if it has NULLs in all
nullable columns, the result is UNKNOWN.
nullable columns (above we guarantee there is a match for the non-null
coumns), 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))
if (count_nulls_in_search_key == merge_keys_count - test(non_null_key))
{
res= TRUE;
goto end;
}
/*
If the outer row has NULLs in some columns, and
there is no match for any of the remaining columns, and
there is a subquery row with NULLs in all unmatched columns,
then there is a partial match, otherwise the result is FALSE.
*/
if (count_nulls_in_search_key && !pq.elements)
{
DBUG_ASSERT(!non_null_key);
/*
Check if the intersection of all NULL bitmaps of all keys that
are not in matching_outer_cols is non-empty.
*/
res= exists_complementing_null_row(&matching_outer_cols);
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.
match for any of the NULL columns, the result is FALSE. Notice that if there
is a non-null key, and there is only one matching key, the non-null key is
the matching key. This is so, because this method returns FALSE if the
non-null key doesn't have a match.
*/
if ((pq.elements == 1 && non_null_key &&
max_covering_null_row_len < merge_keys_count - 1) ||
pq.elements == 0)
if (!count_nulls_in_search_key &&
(!pq.elements ||
(pq.elements == 1 && non_null_key &&
max_null_in_any_row < merge_keys_count-1)))
{
if (pq.elements == 0)
if (!pq.elements)
{
DBUG_ASSERT(!non_null_key); /* Must follow from the logic of this method */
/* This case must be handled by subselect_partial_match_engine::exec() */
DBUG_ASSERT(max_covering_null_row_len != tmp_table->s->fields);
DBUG_ASSERT(!non_null_key);
/*
The case of a covering null row is handled by
subselect_partial_match_engine::exec()
*/
DBUG_ASSERT(max_null_in_any_row != tmp_table->s->fields);
}
res= FALSE;
goto end;
......
......@@ -999,7 +999,7 @@ class subselect_hash_sj_engine : public subselect_engine
/*
Distinguish the type od (0-based) row numbers from the type of the index into
Distinguish the type of (0-based) row numbers from the type of the index into
an array of row numbers.
*/
typedef ha_rows rownum_t;
......@@ -1075,9 +1075,9 @@ class Ordered_key : public Sql_alloc
/* 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;
rownum_t min_null_row;
/* The row number that contains the last NULL in a column. */
ha_rows max_null_row;
rownum_t max_null_row;
protected:
bool alloc_keys_buffers();
......@@ -1110,6 +1110,10 @@ class Ordered_key : public Sql_alloc
DBUG_ASSERT(i < key_column_count);
return key_columns[i]->field->field_index;
}
rownum_t get_min_null_row() { return min_null_row; }
rownum_t get_max_null_row() { return max_null_row; }
MY_BITMAP * get_null_key() { return &null_key; }
ha_rows get_null_count() { return null_count; }
/*
Get the search key element that corresponds to the i-th key part of this
index.
......@@ -1280,6 +1284,8 @@ class subselect_rowid_merge_engine: public subselect_partial_match_engine
Ordered_key **merge_keys;
/* The number of elements in merge_keys. */
uint merge_keys_count;
/* The NULL bitmaps of merge keys.*/
MY_BITMAP **null_bitmaps;
/*
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
......@@ -1305,6 +1311,7 @@ class subselect_rowid_merge_engine: public subselect_partial_match_engine
static int cmp_keys_by_cur_rownum(void *arg, uchar *k1, uchar *k2);
bool test_null_row(rownum_t row_num);
bool exists_complementing_null_row(MY_BITMAP *keys_to_complement);
bool partial_match();
public:
subselect_rowid_merge_engine(THD *thd_arg,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment