diff --git a/mysql-test/main/subselect_extra.result b/mysql-test/main/subselect_extra.result index c654fdfca1303..bf866eb7d5a44 100644 --- a/mysql-test/main/subselect_extra.result +++ b/mysql-test/main/subselect_extra.result @@ -482,3 +482,26 @@ a b DROP VIEW v2; DROP TABLE t1,t2; set optimizer_switch= @tmp_subselect_extra_derived; +# +# MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause +# +create table t0 (vkey int); +insert into t0 (vkey) values (5); +select 1 as c0 +from +((select +0 as c_0 +from +t0 +) as subq_2 +right outer join t0 as ref_6 +on (subq_2.c_0 = ref_6.vkey )) +where exists ( +select +1 +from +t0 +where (subq_2.c_0 <> t0.vkey)); +c0 +drop table t0; +# End of 10.11 tests diff --git a/mysql-test/main/subselect_extra.test b/mysql-test/main/subselect_extra.test index 3f2f7b611fff2..3f8f26464d673 100644 --- a/mysql-test/main/subselect_extra.test +++ b/mysql-test/main/subselect_extra.test @@ -398,3 +398,27 @@ DROP VIEW v2; DROP TABLE t1,t2; set optimizer_switch= @tmp_subselect_extra_derived; + +--echo # +--echo # MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause +--echo # +create table t0 (vkey int); +insert into t0 (vkey) values (5); +select 1 as c0 + from + ((select + 0 as c_0 + from + t0 + ) as subq_2 + right outer join t0 as ref_6 + on (subq_2.c_0 = ref_6.vkey )) + where exists ( + select + 1 + from + t0 + where (subq_2.c_0 <> t0.vkey)); +drop table t0; + +--echo # End of 10.11 tests diff --git a/mysql-test/main/subselect_extra_no_semijoin.result b/mysql-test/main/subselect_extra_no_semijoin.result index faeaf75c5900d..7f125672d43a6 100644 --- a/mysql-test/main/subselect_extra_no_semijoin.result +++ b/mysql-test/main/subselect_extra_no_semijoin.result @@ -484,6 +484,29 @@ a b DROP VIEW v2; DROP TABLE t1,t2; set optimizer_switch= @tmp_subselect_extra_derived; +# +# MDEV-35168: Potential Bug in Database Handling of NULL Values in EXISTS Clause +# +create table t0 (vkey int); +insert into t0 (vkey) values (5); +select 1 as c0 +from +((select +0 as c_0 +from +t0 +) as subq_2 +right outer join t0 as ref_6 +on (subq_2.c_0 = ref_6.vkey )) +where exists ( +select +1 +from +t0 +where (subq_2.c_0 <> t0.vkey)); +c0 +drop table t0; +# End of 10.11 tests set optimizer_switch= @subselect_extra_no_sj_tmp; set @optimizer_switch_for_subselect_extra_test=null; # diff --git a/sql/item.h b/sql/item.h index 570c24bf041ed..af61715af5e18 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2455,6 +2455,7 @@ class Item :public Value_source, */ virtual bool check_index_dependence(void *arg) { return 0; } virtual bool check_sequence_privileges(void *arg) { return 0; } + virtual bool where_exists_processor(void *arg) { return 0; } /*============== End of Item processor list ======================*/ /* diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 9c9e97fed2d9e..9ba99f97cfc73 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -3505,6 +3505,12 @@ bool Item_exists_subselect::fix_fields(THD *thd, Item **ref) } +bool Item_exists_subselect::where_exists_processor(void *arg) +{ + return walk(&Item::enumerate_field_refs_processor, true, arg); +} + + bool Item_in_subselect::fix_fields(THD *thd_arg, Item **ref) { uint outer_cols_num; diff --git a/sql/item_subselect.h b/sql/item_subselect.h index 1e71bf6fddb3f..afd19ca40f270 100644 --- a/sql/item_subselect.h +++ b/sql/item_subselect.h @@ -437,6 +437,7 @@ class Item_exists_subselect :public Item_subselect void under_not(Item_func_not *upper) override { upper_not= upper; }; void set_exists_transformed() { exists_transformed= TRUE; } + bool where_exists_processor(void *arg) override; friend class select_exists_subselect; friend class subselect_uniquesubquery_engine; @@ -783,6 +784,7 @@ class Item_in_subselect :public Item_exists_subselect void init_subq_materialization_tracker(THD *thd); Subq_materialization_tracker *get_materialization_tracker() const { return materialization_tracker; } + bool where_exists_processor(void *arg) override { return 0; } friend class Item_ref_null_helper; friend class Item_is_not_null_test; diff --git a/sql/table.cc b/sql/table.cc index d7fd01309ba96..a540e6bc12fdc 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -9827,6 +9827,92 @@ static inline bool derived_table_optimization_done(TABLE_LIST *table) } +/* + 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. + + This can't be done later, during DT_MERGE, because by that point the WHERE + EXISTS subquery has already had its WHERE clause updated with the field + from the merged query and it's impossible to detect that the merge should + be prevented by that time. Doing this here prevents merging from occurring + in any case. +*/ +static bool where_exists_depends_on_mergeable_derived(TABLE_LIST *derived, + SELECT_LEX *select_lex) +{ + if (!derived->on_expr || !select_lex->where) + return false; + + /* + The WhereExistsVisitor visits the fields of the WHERE clause within a + subquery of an outer WHERE EXISTS clause. For each field found, it + checks to see if the same field is referenced in the derived table and + if so, blocks derived table merging. + */ + class WhereExistsVisitor : public Field_enumerator + { + struct DerivedTableVisitor : public Field_enumerator + { + WhereExistsVisitor *outer{nullptr}; + Item_field *where_exists_field{nullptr}; + + void visit_field(Item_field *derived_table_field) override + { + if (outer->block_merging || !derived_table_field->field_name) + return; + outer->block_merging= + (derived_table_field->field_name.streq(where_exists_field->field_name) && + derived_table_field->table_name.streq(where_exists_field->table_name)); + } + + public: + DerivedTableVisitor(WhereExistsVisitor *wev, Item_field *field) + : outer(wev) + , where_exists_field(field) + { + DBUG_ASSERT(outer); + DBUG_ASSERT(field); + } + }; + + Item *dt_expr{nullptr}; + + void visit_field(Item_field *where_exists_field) override + { + if (!dt_expr || !where_exists_field->field_name) + return; + DerivedTableVisitor dt_visitor(this, where_exists_field); + dt_expr->walk(&Item::enumerate_field_refs_processor, + true, &dt_visitor); + } + + public: + bool block_merging{false}; + + WhereExistsVisitor(Item *derived_expr) + : dt_expr(derived_expr) + { + DBUG_ASSERT(dt_expr); + } + }; + + // Visit each field in the WHERE clause of the subquery in the WHERE EXISTS + // and check to see if any field references a constant field from the given + // derived table of the outer query. + WhereExistsVisitor visitor(derived->on_expr); + select_lex->where->walk(&Item::where_exists_processor, + true, &visitor); + return visitor.block_merging; +} + + /** @brief Initialize this derived table/view @@ -9886,8 +9972,13 @@ bool TABLE_LIST::init_derived(THD *thd, bool init_view) if (!derived_table_optimization_done(this)) { + const bool force_materialization= + where_exists_depends_on_mergeable_derived(this, + select_lex); + /* A subquery might be forced to be materialized due to a side-effect. */ - if (!is_materialized_derived() && unit->can_be_merged() && + if (!force_materialization && !is_materialized_derived() && + unit->can_be_merged() && /* Following is special case of SELECT * FROM () WHERE ROWNUM() <= nnn