From 23de8cbc1ab6c1d0f60ed491b145de7d335a778d Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Thu, 22 Jan 2026 15:08:33 +1300 Subject: [PATCH] MDEV-35168 subselects with outer references to derived tables may be incorrectly evaluated as constant Subselects with outer references to derived tables may be incorrectly evaluated as having no table references. This can lead to these subselects being marked as constant, leading to an incorrect result. During the calculation of the tables used in a subselect, we construct a table map of outer references in our (not necessarily new) "new_parent" select. This is currently done purely by finding Item_fields in our tree and using the attached table to update our bitmap. It can be that a reference to a derived table also needs to have it's table added to this map. If the derived table can be null, this is the case. We add a new processor to our item walk system, enumerate_table_refs_processor which is defined at this stage only for Item_direct_view_ref items. This called alongside enumerate_field_refs_processor in Item_subselect::recalc_used_tables(). --- mysql-test/main/subselect4.result | 46 ++++++++++++++++++++++++++++ mysql-test/main/subselect4.test | 50 +++++++++++++++++++++++++++++++ sql/item.cc | 15 ++++++++++ sql/item.h | 22 ++++++++++++++ sql/item_subselect.cc | 28 +++++------------ sql/sql_lex.h | 10 +++++++ 6 files changed, 151 insertions(+), 20 deletions(-) diff --git a/mysql-test/main/subselect4.result b/mysql-test/main/subselect4.result index c0c67707231c4..db1bc190feff4 100644 --- a/mysql-test/main/subselect4.result +++ b/mysql-test/main/subselect4.result @@ -3373,4 +3373,50 @@ CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; (SELECT 0 GROUP BY c HAVING (SELECT c)) DROP TABLE t; +# +# MDEV-35168 subselects with outer references to derived tables may be incorrectly evaluated as constant +# +create table t1 (a int); +insert into t1 (a) values (5); +select 1 as one from +( +t1 as t2 left join +( +select 0 as c1 +from t1 +) as dt1 +on dt1.c1 = t2.a +) +where exists +( +select 1 from t1 +where dt1.c1 <> t1.a +); +one +create view v1 as ( select 0 as c1 from t1 ); +select 1 as one from +( +t1 left join v1 +on v1.c1 = t1.a +) +where exists +( +select 1 from t1 +where v1.c1 <> t1.a +); +one +with cte1 as ( select 0 as c1 from t1 ) +select 1 as one from +( +t1 left join cte1 +on cte1.c1 = t1.a +) +where exists +( +select 1 from t1 +where cte1.c1 <> t1.a +); +one +drop view v1; +drop table t1; # End of 10.11 tests diff --git a/mysql-test/main/subselect4.test b/mysql-test/main/subselect4.test index 083da1647b27c..e0e28698346ae 100644 --- a/mysql-test/main/subselect4.test +++ b/mysql-test/main/subselect4.test @@ -2695,4 +2695,54 @@ CREATE TABLE t(c INT); SELECT (SELECT 0 GROUP BY c HAVING (SELECT c)) FROM t GROUP BY c; DROP TABLE t; +--echo # +--echo # MDEV-35168 subselects with outer references to derived tables may be incorrectly evaluated as constant +--echo # + +create table t1 (a int); +insert into t1 (a) values (5); + +select 1 as one from + ( + t1 as t2 left join + ( + select 0 as c1 + from t1 + ) as dt1 + on dt1.c1 = t2.a + ) + where exists + ( + select 1 from t1 + where dt1.c1 <> t1.a + ); + +create view v1 as ( select 0 as c1 from t1 ); + +select 1 as one from + ( + t1 left join v1 + on v1.c1 = t1.a + ) + where exists + ( + select 1 from t1 + where v1.c1 <> t1.a + ); + +with cte1 as ( select 0 as c1 from t1 ) +select 1 as one from + ( + t1 left join cte1 + on cte1.c1 = t1.a + ) + where exists + ( + select 1 from t1 + where cte1.c1 <> t1.a + ); + +drop view v1; +drop table t1; + --echo # End of 10.11 tests diff --git a/sql/item.cc b/sql/item.cc index 0f7aff680f7de..217d649f0d5c4 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -3274,6 +3274,21 @@ bool Item_field::enumerate_field_refs_processor(void *arg) return FALSE; } + +bool Item_direct_view_ref::enumerate_table_refs_processor(void *arg) +{ + Field_fixer *ff= (Field_fixer*)arg; + st_select_lex *cmp= ff->new_parent->get_merged_into(); + + // our table is resolved in the select of interest + if (null_ref_table && + null_ref_table != NO_NULL_TABLE && + null_ref_table->pos_in_table_list->select_lex == cmp) + ff->used_tables |= null_ref_table->map; + return FALSE; +} + + bool Item_field::update_table_bitmaps_processor(void *arg) { update_table_bitmaps(); diff --git a/sql/item.h b/sql/item.h index 76bcf4f073af4..988067dbc942e 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2303,6 +2303,7 @@ class Item :public Value_source, virtual bool update_table_bitmaps_processor(void *arg) { return 0; } virtual bool enumerate_field_refs_processor(void *arg) { return 0; } + virtual bool enumerate_table_refs_processor(void *arg) { return 0; } virtual bool mark_as_eliminated_processor(void *arg) { return 0; } virtual bool eliminate_subselect_processor(void *arg) { return 0; } virtual bool view_used_tables_processor(void *arg) { return 0; } @@ -6342,6 +6343,7 @@ class Item_direct_view_ref :public Item_direct_ref set_null_ref_table(); } + bool enumerate_table_refs_processor(void *arg) override; bool fix_fields(THD *, Item **) override; bool eq(const Item *item, const Eq_config &config) const override; Item *get_tmp_table_item(THD *thd) override @@ -8351,4 +8353,24 @@ inline void TABLE::use_all_stored_columns() bitmap_clear_bit(read_set, (*vf)->field_index); } + +class Field_fixer: public Field_enumerator +{ +public: + table_map used_tables; /* Collect used_tables here */ + st_select_lex *new_parent; /* Select we're in */ + void visit_field(Item_field *item) override + { + //for (TABLE_LIST *tbl= new_parent->leaf_tables; tbl; tbl= tbl->next_local) + //{ + // if (tbl->table == field->table) + // { + used_tables|= item->field->table->map; + // return; + // } + //} + //used_tables |= OUTER_REF_TABLE_BIT; + } +}; + #endif /* SQL_ITEM_INCLUDED */ diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 0378aad8537da..1a29421fd0aae 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -469,26 +469,6 @@ void Item_subselect::fix_after_pullout(st_select_lex *new_parent, } -class Field_fixer: public Field_enumerator -{ -public: - table_map used_tables; /* Collect used_tables here */ - st_select_lex *new_parent; /* Select we're in */ - void visit_field(Item_field *item) override - { - //for (TABLE_LIST *tbl= new_parent->leaf_tables; tbl; tbl= tbl->next_local) - //{ - // if (tbl->table == field->table) - // { - used_tables|= item->field->table->map; - // return; - // } - //} - //used_tables |= OUTER_REF_TABLE_BIT; - } -}; - - /* Recalculate used_tables_cache */ @@ -538,6 +518,14 @@ void Item_subselect::recalc_used_tables(st_select_lex *new_parent, fixer.used_tables= 0; fixer.new_parent= new_parent; upper->item->walk(&Item::enumerate_field_refs_processor, 0, &fixer); + /* + An reference (that can be null) might refer to a constant item + (that isn't and can't be null). + We need to update the table map to include tables that might be + null to avoid this subselect being marked as constant. + This information is in the Item_direct_view_ref wrapper. + */ + upper->item->walk(&Item::enumerate_table_refs_processor, 0, &fixer); used_tables_cache |= fixer.used_tables; upper->item->walk(&Item::update_table_bitmaps_processor, FALSE, NULL); /* diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 700cd18ae2b5c..7b49b02745780 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1714,6 +1714,16 @@ class st_select_lex: public st_select_lex_node TABLE_LIST *find_table(THD *thd, const LEX_CSTRING *db_name, const LEX_CSTRING *table_name); + + st_select_lex *get_merged_into() + { + st_select_lex *ret= this; + + while(ret->merged_into) + ret= ret->merged_into; + + return ret; + } }; typedef class st_select_lex SELECT_LEX;