Commit 1e6bd6b4 authored by unknown's avatar unknown

Fix bug lp:809266

Analysis:
This is a bug in MWL#68, where it was incorrectly assumed
that if there is a match in the only non-null key, then
if there is a covering NULL row on all remaining NULL-able
columns there is a partial match. However, this is not the case,
because even if there is such a null-only sub-row, it is not
guaranteed to be part of the matched sub-row. The matched sub-row
and the NULL-only sub-row may be parts of different rows.

In fact there are two cases:
- there is a complete row with only NULL values, and
- all nullable columns contain only NULL values.

These two cases were incorrectly mixed up in the class member
  subselect_partial_match_engine::covering_null_row_width.


Solution:
The solution is to:
- split covering_null_row_width into two members:
  has_covering_null_row, and has_covering_null_columns, and
- take into account each state during initialization and
  execution.
parent 61eb3423
...@@ -99,4 +99,50 @@ FROM t3 LEFT JOIN t2 ON t3.a = t2.a); ...@@ -99,4 +99,50 @@ FROM t3 LEFT JOIN t2 ON t3.a = t2.a);
d d
r r
drop table t1, t2, t3; drop table t1, t2, t3;
#
# LP BUG#809266 Diverging results with partial_match_rowid_merge=on
#
CREATE TABLE t1 (c int) ;
INSERT INTO t1 VALUES (0),(0);
CREATE TABLE t2 (a int, b int) ;
INSERT INTO t2 VALUES (6,3), (9,NULL);
set @@optimizer_switch='materialization=on,partial_match_rowid_merge=on,partial_match_table_scan=off,in_to_exists=off';
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
c
0
0
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
id select_type table type possible_keys key key_len ref rows Extra
1 PRIMARY t1 ALL NULL NULL NULL NULL 2
2 SUBQUERY t2 ALL NULL NULL NULL NULL 2
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
c
0
0
set @@optimizer_switch='materialization=off,in_to_exists=on';
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
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 2 Using where
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
c
0
0
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
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 2 Using where
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
c
0
0
drop table t1, t2;
set @@optimizer_switch=@save_optimizer_switch; set @@optimizer_switch=@save_optimizer_switch;
...@@ -109,4 +109,36 @@ WHERE (t1.d , t1.d) NOT IN ( ...@@ -109,4 +109,36 @@ WHERE (t1.d , t1.d) NOT IN (
drop table t1, t2, t3; drop table t1, t2, t3;
--echo #
--echo # LP BUG#809266 Diverging results with partial_match_rowid_merge=on
--echo #
CREATE TABLE t1 (c int) ;
INSERT INTO t1 VALUES (0),(0);
CREATE TABLE t2 (a int, b int) ;
INSERT INTO t2 VALUES (6,3), (9,NULL);
set @@optimizer_switch='materialization=on,partial_match_rowid_merge=on,partial_match_table_scan=off,in_to_exists=off';
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
set @@optimizer_switch='materialization=off,in_to_exists=on';
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT b, a FROM t2);
EXPLAIN
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
SELECT * FROM t1 WHERE (6, 4 ) NOT IN (SELECT a, b FROM t2);
drop table t1, t2;
set @@optimizer_switch=@save_optimizer_switch; set @@optimizer_switch=@save_optimizer_switch;
...@@ -4572,29 +4572,36 @@ int subselect_hash_sj_engine::exec() ...@@ -4572,29 +4572,36 @@ int subselect_hash_sj_engine::exec()
if (strategy == PARTIAL_MATCH) if (strategy == PARTIAL_MATCH)
{ {
uint count_pm_keys; /* Total number of keys needed for partial matching. */ 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. */ MY_BITMAP *nn_key_parts= NULL; /* Key parts of the only non-NULL index. */
uint covering_null_row_width; uint count_non_null_columns= 0; /* Number of columns in nn_key_parts. */
bool has_covering_null_row;
bool has_covering_null_columns;
select_materialize_with_stats *result_sink= select_materialize_with_stats *result_sink=
(select_materialize_with_stats *) result; (select_materialize_with_stats *) result;
uint field_count= tmp_table->s->fields;
nn_key_parts= (count_partial_match_columns < tmp_table->s->fields) ? if (count_partial_match_columns < field_count)
&non_null_key_parts : NULL; {
nn_key_parts= &non_null_key_parts;
if (result_sink->get_max_nulls_in_row() == count_non_null_columns= bitmap_bits_set(nn_key_parts);
tmp_table->s->fields - }
(nn_key_parts ? bitmap_bits_set(nn_key_parts) : 0)) has_covering_null_row= (result_sink->get_max_nulls_in_row() == field_count);
covering_null_row_width= result_sink->get_max_nulls_in_row(); has_covering_null_columns= (count_non_null_columns +
else count_null_only_columns == field_count);
covering_null_row_width= 0;
if (covering_null_row_width) if (has_covering_null_row)
count_pm_keys= nn_key_parts ? 1 : 0; {
DBUG_ASSERT(count_partial_match_columns = field_count);
count_pm_keys= 0;
}
else if (has_covering_null_columns)
count_pm_keys= 1;
else else
count_pm_keys= count_partial_match_columns - count_null_only_columns + count_pm_keys= count_partial_match_columns - count_null_only_columns +
(nn_key_parts ? 1 : 0); (nn_key_parts ? 1 : 0);
choose_partial_match_strategy(test(nn_key_parts), choose_partial_match_strategy(test(nn_key_parts),
test(covering_null_row_width), has_covering_null_row,
&partial_match_key_parts); &partial_match_key_parts);
DBUG_ASSERT(strategy == PARTIAL_MATCH_MERGE || DBUG_ASSERT(strategy == PARTIAL_MATCH_MERGE ||
strategy == PARTIAL_MATCH_SCAN); strategy == PARTIAL_MATCH_SCAN);
...@@ -4604,7 +4611,8 @@ int subselect_hash_sj_engine::exec() ...@@ -4604,7 +4611,8 @@ int subselect_hash_sj_engine::exec()
new subselect_rowid_merge_engine(thd, (subselect_uniquesubquery_engine*) new subselect_rowid_merge_engine(thd, (subselect_uniquesubquery_engine*)
lookup_engine, tmp_table, lookup_engine, tmp_table,
count_pm_keys, count_pm_keys,
covering_null_row_width, has_covering_null_row,
has_covering_null_columns,
item, result, item, result,
semi_join_conds->argument_list()); semi_join_conds->argument_list());
if (!pm_engine || if (!pm_engine ||
...@@ -4629,7 +4637,8 @@ int subselect_hash_sj_engine::exec() ...@@ -4629,7 +4637,8 @@ int subselect_hash_sj_engine::exec()
lookup_engine, tmp_table, lookup_engine, tmp_table,
item, result, item, result,
semi_join_conds->argument_list(), semi_join_conds->argument_list(),
covering_null_row_width))) has_covering_null_row,
has_covering_null_columns)))
{ {
/* This is an irrecoverable error. */ /* This is an irrecoverable error. */
res= 1; res= 1;
...@@ -5065,11 +5074,13 @@ subselect_partial_match_engine::subselect_partial_match_engine( ...@@ -5065,11 +5074,13 @@ subselect_partial_match_engine::subselect_partial_match_engine(
TABLE *tmp_table_arg, Item_subselect *item_arg, TABLE *tmp_table_arg, Item_subselect *item_arg,
select_result_interceptor *result_arg, select_result_interceptor *result_arg,
List<Item> *equi_join_conds_arg, List<Item> *equi_join_conds_arg,
uint covering_null_row_width_arg) bool has_covering_null_row_arg,
bool has_covering_null_columns_arg)
:subselect_engine(thd_arg, item_arg, result_arg), :subselect_engine(thd_arg, item_arg, result_arg),
tmp_table(tmp_table_arg), lookup_engine(engine_arg), tmp_table(tmp_table_arg), lookup_engine(engine_arg),
equi_join_conds(equi_join_conds_arg), equi_join_conds(equi_join_conds_arg),
covering_null_row_width(covering_null_row_width_arg) has_covering_null_row(has_covering_null_row_arg),
has_covering_null_columns(has_covering_null_columns_arg)
{} {}
...@@ -5107,7 +5118,7 @@ int subselect_partial_match_engine::exec() ...@@ -5107,7 +5118,7 @@ int subselect_partial_match_engine::exec()
} }
} }
if (covering_null_row_width == tmp_table->s->fields) if (has_covering_null_row)
{ {
/* /*
If there is a NULL-only row that coveres all columns the result of IN If there is a NULL-only row that coveres all columns the result of IN
...@@ -5187,19 +5198,25 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts, ...@@ -5187,19 +5198,25 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts,
rownum_t cur_rownum= 0; rownum_t cur_rownum= 0;
select_materialize_with_stats *result_sink= select_materialize_with_stats *result_sink=
(select_materialize_with_stats *) result; (select_materialize_with_stats *) result;
uint cur_keyid; uint cur_keyid= 0;
Item_in_subselect *item_in= (Item_in_subselect*) item; Item_in_subselect *item_in= (Item_in_subselect*) item;
int error; int error;
if (merge_keys_count == 0) if (merge_keys_count == 0)
{ {
DBUG_ASSERT(bitmap_bits_set(partial_match_key_parts) == 0 ||
has_covering_null_row);
/* There is nothing to initialize, we will only do regular lookups. */ /* There is nothing to initialize, we will only do regular lookups. */
return FALSE; return FALSE;
} }
DBUG_ASSERT(!covering_null_row_width || (covering_null_row_width && /*
merge_keys_count == 1 && If all nullable columns contain only NULLs, there must be one index
non_null_key_parts)); over all non-null columns.
*/
DBUG_ASSERT(!has_covering_null_columns ||
(has_covering_null_columns &&
merge_keys_count == 1 && non_null_key_parts));
/* /*
Allocate buffers to hold the merged keys and the mapping between rowids and Allocate buffers to hold the merged keys and the mapping between rowids and
row numbers. row numbers.
...@@ -5213,23 +5230,20 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts, ...@@ -5213,23 +5230,20 @@ subselect_rowid_merge_engine::init(MY_BITMAP *non_null_key_parts,
/* Create the only non-NULL key if there is any. */ /* Create the only non-NULL key if there is any. */
if (non_null_key_parts) if (non_null_key_parts)
{ {
non_null_key= new Ordered_key(0, tmp_table, item_in->left_expr, non_null_key= new Ordered_key(cur_keyid, tmp_table, item_in->left_expr,
0, 0, 0, row_num_to_rowid); 0, 0, 0, row_num_to_rowid);
if (non_null_key->init(non_null_key_parts)) if (non_null_key->init(non_null_key_parts))
return TRUE; return TRUE;
merge_keys[0]= non_null_key; merge_keys[cur_keyid]= non_null_key;
merge_keys[0]->first(); merge_keys[cur_keyid]->first();
cur_keyid= 1; ++cur_keyid;
} }
else
cur_keyid= 0;
/* /*
If there is a covering NULL row, the only key that is needed is the If all nullable columns contain NULLs, the only key that is needed is the
only non-NULL key that is already created above. We create keys on only non-NULL key that is already created above.
NULL-able columns only if there is no covering NULL row.
*/ */
if (!covering_null_row_width) if (!has_covering_null_columns)
{ {
if (bitmap_init_memroot(&matching_keys, merge_keys_count, thd->mem_root) || if (bitmap_init_memroot(&matching_keys, merge_keys_count, thd->mem_root) ||
bitmap_init_memroot(&matching_outer_cols, merge_keys_count, thd->mem_root)) bitmap_init_memroot(&matching_outer_cols, merge_keys_count, thd->mem_root))
...@@ -5460,11 +5474,10 @@ bool subselect_rowid_merge_engine::partial_match() ...@@ -5460,11 +5474,10 @@ bool subselect_rowid_merge_engine::partial_match()
} }
/* /*
If there is a NULL (sub)row that covers all NULL-able columns, If all nullable columns contain only NULLs, then there is a guranteed
then there is a guranteed partial match, and we don't need to search partial match, and we don't need to search for a matching row.
for the matching row. */
*/ if (has_covering_null_columns)
if (covering_null_row_width)
{ {
res= TRUE; res= TRUE;
goto end; goto end;
...@@ -5581,10 +5594,12 @@ subselect_table_scan_engine::subselect_table_scan_engine( ...@@ -5581,10 +5594,12 @@ subselect_table_scan_engine::subselect_table_scan_engine(
Item_subselect *item_arg, Item_subselect *item_arg,
select_result_interceptor *result_arg, select_result_interceptor *result_arg,
List<Item> *equi_join_conds_arg, List<Item> *equi_join_conds_arg,
uint covering_null_row_width_arg) bool has_covering_null_row_arg,
bool has_covering_null_columns_arg)
:subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg, item_arg, :subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg, item_arg,
result_arg, equi_join_conds_arg, result_arg, equi_join_conds_arg,
covering_null_row_width_arg) has_covering_null_row_arg,
has_covering_null_columns_arg)
{} {}
...@@ -5623,10 +5638,6 @@ bool subselect_table_scan_engine::partial_match() ...@@ -5623,10 +5638,6 @@ bool subselect_table_scan_engine::partial_match()
tmp_table->file->extra_opt(HA_EXTRA_CACHE, tmp_table->file->extra_opt(HA_EXTRA_CACHE,
current_thd->variables.read_buff_size); current_thd->variables.read_buff_size);
/*
TIMOUR:
scan_table() also calls "table->null_row= 0;", why, do we need it?
*/
for (;;) for (;;)
{ {
error= tmp_table->file->ha_rnd_next(tmp_table->record[0]); error= tmp_table->file->ha_rnd_next(tmp_table->record[0]);
......
...@@ -1131,11 +1131,18 @@ class subselect_partial_match_engine : public subselect_engine ...@@ -1131,11 +1131,18 @@ class subselect_partial_match_engine : public subselect_engine
/* A list of equalities between each pair of IN operands. */ /* A list of equalities between each pair of IN operands. */
List<Item> *equi_join_conds; List<Item> *equi_join_conds;
/* /*
If there is a row, such that all its NULL-able components are NULL, this True if there is an all NULL row in tmp_table. If so, then if there is
member is set to the number of covered columns. If there is no covering no complete match, there is a guaranteed partial match.
row, then this is 0.
*/ */
uint covering_null_row_width; bool has_covering_null_row;
/*
True if all nullable columns of tmp_table consist of only NULL values.
If so, then if there is a match in the non-null columns, there is a
guaranteed partial match.
*/
bool has_covering_null_columns;
protected: protected:
virtual bool partial_match()= 0; virtual bool partial_match()= 0;
public: public:
...@@ -1144,7 +1151,8 @@ class subselect_partial_match_engine : public subselect_engine ...@@ -1144,7 +1151,8 @@ class subselect_partial_match_engine : public subselect_engine
TABLE *tmp_table_arg, Item_subselect *item_arg, TABLE *tmp_table_arg, Item_subselect *item_arg,
select_result_interceptor *result_arg, select_result_interceptor *result_arg,
List<Item> *equi_join_conds_arg, List<Item> *equi_join_conds_arg,
uint covering_null_row_width_arg); bool has_covering_null_row_arg,
bool has_covering_null_columns_arg);
int prepare() { return 0; } int prepare() { return 0; }
int exec(); int exec();
void fix_length_and_dec(Item_cache**) {} void fix_length_and_dec(Item_cache**) {}
...@@ -1232,13 +1240,15 @@ class subselect_rowid_merge_engine: public subselect_partial_match_engine ...@@ -1232,13 +1240,15 @@ class subselect_rowid_merge_engine: public subselect_partial_match_engine
subselect_rowid_merge_engine(THD *thd_arg, subselect_rowid_merge_engine(THD *thd_arg,
subselect_uniquesubquery_engine *engine_arg, subselect_uniquesubquery_engine *engine_arg,
TABLE *tmp_table_arg, uint merge_keys_count_arg, TABLE *tmp_table_arg, uint merge_keys_count_arg,
uint covering_null_row_width_arg, bool has_covering_null_row_arg,
bool has_covering_null_columns_arg,
Item_subselect *item_arg, Item_subselect *item_arg,
select_result_interceptor *result_arg, select_result_interceptor *result_arg,
List<Item> *equi_join_conds_arg) List<Item> *equi_join_conds_arg)
:subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg, :subselect_partial_match_engine(thd_arg, engine_arg, tmp_table_arg,
item_arg, result_arg, equi_join_conds_arg, item_arg, result_arg, equi_join_conds_arg,
covering_null_row_width_arg), has_covering_null_row_arg,
has_covering_null_columns_arg),
merge_keys_count(merge_keys_count_arg), non_null_key(NULL) merge_keys_count(merge_keys_count_arg), non_null_key(NULL)
{} {}
~subselect_rowid_merge_engine(); ~subselect_rowid_merge_engine();
...@@ -1258,7 +1268,8 @@ class subselect_table_scan_engine: public subselect_partial_match_engine ...@@ -1258,7 +1268,8 @@ class subselect_table_scan_engine: public subselect_partial_match_engine
TABLE *tmp_table_arg, Item_subselect *item_arg, TABLE *tmp_table_arg, Item_subselect *item_arg,
select_result_interceptor *result_arg, select_result_interceptor *result_arg,
List<Item> *equi_join_conds_arg, List<Item> *equi_join_conds_arg,
uint covering_null_row_width_arg); bool has_covering_null_row_arg,
bool has_covering_null_columns_arg);
void cleanup(); void cleanup();
virtual enum_engine_type engine_type() { return TABLE_SCAN_ENGINE; } virtual enum_engine_type engine_type() { return TABLE_SCAN_ENGINE; }
}; };
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