Skip to content

Commit de9a051

Browse files
MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause
Queries of the form SELECT ... FROM (SELECT constant AS alias_N FROM t0) dt ... WHERE EXISTS (SELECT ... WHERE (dt.alias_N ...)); must force derived table dt to be materialized, or the WHERE EXISTS will not filter rows correctly. If we allow derived table dt to be merged, then references to dt.alias_N are replaced with their constant values directly, so a WHERE EXISTS subquery will attempt to filter rows from the outer query based on those constant values rather than the columns' values computed during outer query evaluation.
1 parent 40e55f9 commit de9a051

File tree

7 files changed

+171
-1
lines changed

7 files changed

+171
-1
lines changed

mysql-test/main/subselect_extra.result

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,3 +482,26 @@ a b
482482
DROP VIEW v2;
483483
DROP TABLE t1,t2;
484484
set optimizer_switch= @tmp_subselect_extra_derived;
485+
#
486+
# MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause
487+
#
488+
create table t0 (vkey int);
489+
insert into t0 (vkey) values (5);
490+
select 1 as c0
491+
from
492+
((select
493+
0 as c_0
494+
from
495+
t0
496+
) as subq_2
497+
right outer join t0 as ref_6
498+
on (subq_2.c_0 = ref_6.vkey ))
499+
where exists (
500+
select
501+
1
502+
from
503+
t0
504+
where (subq_2.c_0 <> t0.vkey));
505+
c0
506+
drop table t0;
507+
# End of 10.11 tests

mysql-test/main/subselect_extra.test

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,27 @@ DROP VIEW v2;
398398
DROP TABLE t1,t2;
399399

400400
set optimizer_switch= @tmp_subselect_extra_derived;
401+
402+
--echo #
403+
--echo # MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause
404+
--echo #
405+
create table t0 (vkey int);
406+
insert into t0 (vkey) values (5);
407+
select 1 as c0
408+
from
409+
((select
410+
0 as c_0
411+
from
412+
t0
413+
) as subq_2
414+
right outer join t0 as ref_6
415+
on (subq_2.c_0 = ref_6.vkey ))
416+
where exists (
417+
select
418+
1
419+
from
420+
t0
421+
where (subq_2.c_0 <> t0.vkey));
422+
drop table t0;
423+
424+
--echo # End of 10.11 tests

mysql-test/main/subselect_extra_no_semijoin.result

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,29 @@ a b
484484
DROP VIEW v2;
485485
DROP TABLE t1,t2;
486486
set optimizer_switch= @tmp_subselect_extra_derived;
487+
#
488+
# MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause
489+
#
490+
create table t0 (vkey int);
491+
insert into t0 (vkey) values (5);
492+
select 1 as c0
493+
from
494+
((select
495+
0 as c_0
496+
from
497+
t0
498+
) as subq_2
499+
right outer join t0 as ref_6
500+
on (subq_2.c_0 = ref_6.vkey ))
501+
where exists (
502+
select
503+
1
504+
from
505+
t0
506+
where (subq_2.c_0 <> t0.vkey));
507+
c0
508+
drop table t0;
509+
# End of 10.11 tests
487510
set optimizer_switch= @subselect_extra_no_sj_tmp;
488511
set @optimizer_switch_for_subselect_extra_test=null;
489512
#

sql/item.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,6 +2455,7 @@ class Item :public Value_source,
24552455
*/
24562456
virtual bool check_index_dependence(void *arg) { return 0; }
24572457
virtual bool check_sequence_privileges(void *arg) { return 0; }
2458+
virtual bool where_exists_processor(void *arg) { return 0; }
24582459
/*============== End of Item processor list ======================*/
24592460

24602461
/*

sql/item_subselect.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3505,6 +3505,12 @@ bool Item_exists_subselect::fix_fields(THD *thd, Item **ref)
35053505
}
35063506

35073507

3508+
bool Item_exists_subselect::where_exists_processor(void *arg)
3509+
{
3510+
return walk(&Item::enumerate_field_refs_processor, true, arg);
3511+
}
3512+
3513+
35083514
bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref)
35093515
{
35103516
uint outer_cols_num;

sql/item_subselect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ class Item_exists_subselect :public Item_subselect
437437
void under_not(Item_func_not *upper) override { upper_not= upper; };
438438

439439
void set_exists_transformed() { exists_transformed= TRUE; }
440+
bool where_exists_processor(void *arg) override;
440441

441442
friend class select_exists_subselect;
442443
friend class subselect_uniquesubquery_engine;
@@ -783,6 +784,7 @@ class Item_in_subselect :public Item_exists_subselect
783784
void init_subq_materialization_tracker(THD *thd);
784785
Subq_materialization_tracker *get_materialization_tracker() const
785786
{ return materialization_tracker; }
787+
bool where_exists_processor(void *arg) override { return 0; }
786788

787789
friend class Item_ref_null_helper;
788790
friend class Item_is_not_null_test;

sql/table.cc

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9827,6 +9827,92 @@ static inline bool derived_table_optimization_done(TABLE_LIST *table)
98279827
}
98289828

98299829

9830+
/*
9831+
Queries of the form
9832+
SELECT ... FROM (SELECT constant AS alias_N FROM t0) dt ... WHERE EXISTS
9833+
(SELECT ... WHERE (dt.alias_N ...));
9834+
must force derived table dt to be materialized, or the WHERE EXISTS will
9835+
not filter rows correctly. If we allow derived table dt to be merged,
9836+
then references to dt.alias_N are replaced with their constant values
9837+
directly, so a WHERE EXISTS subquery will attempt to filter rows from the
9838+
outer query based on those constant values rather than the columns'
9839+
values computed during outer query evaluation.
9840+
9841+
This can't be done later, during DT_MERGE, because by that point the WHERE
9842+
EXISTS subquery has already had its WHERE clause updated with the field
9843+
from the merged query and it's impossible to detect that the merge should
9844+
be prevented by that time. Doing this here prevents merging from occurring
9845+
in any case.
9846+
*/
9847+
static bool where_exists_depends_on_mergeable_derived(TABLE_LIST *derived,
9848+
SELECT_LEX *select_lex)
9849+
{
9850+
if (!derived->on_expr || !select_lex->where)
9851+
return false;
9852+
9853+
/*
9854+
The WhereExistsVisitor visits the fields of the WHERE clause within a
9855+
subquery of an outer WHERE EXISTS clause. For each field found, it
9856+
checks to see if the same field is referenced in the derived table and
9857+
if so, blocks derived table merging.
9858+
*/
9859+
class WhereExistsVisitor : public Field_enumerator
9860+
{
9861+
struct DerivedTableVisitor : public Field_enumerator
9862+
{
9863+
WhereExistsVisitor *outer{nullptr};
9864+
Item_field *where_exists_field{nullptr};
9865+
9866+
void visit_field(Item_field *derived_table_field) override
9867+
{
9868+
if (outer->block_merging || !derived_table_field->field_name)
9869+
return;
9870+
outer->block_merging=
9871+
(derived_table_field->field_name.streq(where_exists_field->field_name) &&
9872+
derived_table_field->table_name.streq(where_exists_field->table_name));
9873+
}
9874+
9875+
public:
9876+
DerivedTableVisitor(WhereExistsVisitor *wev, Item_field *field)
9877+
: outer(wev)
9878+
, where_exists_field(field)
9879+
{
9880+
DBUG_ASSERT(outer);
9881+
DBUG_ASSERT(field);
9882+
}
9883+
};
9884+
9885+
Item *dt_expr{nullptr};
9886+
9887+
void visit_field(Item_field *where_exists_field) override
9888+
{
9889+
if (!dt_expr || !where_exists_field->field_name)
9890+
return;
9891+
DerivedTableVisitor dt_visitor(this, where_exists_field);
9892+
dt_expr->walk(&Item::enumerate_field_refs_processor,
9893+
true, &dt_visitor);
9894+
}
9895+
9896+
public:
9897+
bool block_merging{false};
9898+
9899+
WhereExistsVisitor(Item *derived_expr)
9900+
: dt_expr(derived_expr)
9901+
{
9902+
DBUG_ASSERT(dt_expr);
9903+
}
9904+
};
9905+
9906+
// Visit each field in the WHERE clause of the subquery in the WHERE EXISTS
9907+
// and check to see if any field references a constant field from the given
9908+
// derived table of the outer query.
9909+
WhereExistsVisitor visitor(derived->on_expr);
9910+
select_lex->where->walk(&Item::where_exists_processor,
9911+
true, &visitor);
9912+
return visitor.block_merging;
9913+
}
9914+
9915+
98309916
/**
98319917
@brief
98329918
Initialize this derived table/view
@@ -9886,8 +9972,13 @@ bool TABLE_LIST::init_derived(THD *thd, bool init_view)
98869972

98879973
if (!derived_table_optimization_done(this))
98889974
{
9975+
const bool force_materialization=
9976+
where_exists_depends_on_mergeable_derived(this,
9977+
select_lex);
9978+
98899979
/* A subquery might be forced to be materialized due to a side-effect. */
9890-
if (!is_materialized_derived() && unit->can_be_merged() &&
9980+
if (!force_materialization && !is_materialized_derived() &&
9981+
unit->can_be_merged() &&
98919982
/*
98929983
Following is special case of
98939984
SELECT * FROM (<limited-select>) WHERE ROWNUM() <= nnn

0 commit comments

Comments
 (0)