From 65e8a9c7de51a724479795a8c93120e21fa96b78 Mon Sep 17 00:00:00 2001 From: Rex Johnston Date: Tue, 22 Jul 2025 12:38:15 +1100 Subject: [PATCH] MDEV-29360 Crash when pushing condition with always false IS NULL into derived When a condition containing an always FALSE range part is pushed down into a derived table, an enclosed outer reference can cause a null pointer crash. An example SELECT * FROM (SELECT id FROM t1 GROUP BY id) dt1, (SELECT id FROM t2) dt2, t3 WHERE dt2.id = t3.id AND dt1.id BETWEEN 0 AND (dt2.id IS NULL); is considered as one that can be pushed into the derived table dt1 because the expression (dt2.id IS NULL) is identified as always false. On the on other hand the condition still contains a reference to a column of the table dt2. When pushing the condition to the where clause of the derived table dt1 a copy of it is created and this copy is transformed to be used in the new where clause. When the transformation procedure encounters a reference dt2.id it crashes the server as only the references to the columns used in the group by list of the specification of dt1 or those equal to them are expected in the condition that can be pushed into the where clause of the derived table. The fix here is transform the argument to the function Item_func_isnull from something that may be a column reference, but cannot be null to an Item_literal that also cannot be null (1). Based on patch by Igor Babaev. --- mysql-test/main/derived_cond_pushdown.result | 21 +++++++++++++++++++ mysql-test/main/derived_cond_pushdown.test | 22 ++++++++++++++++++++ sql/item.cc | 5 ++++- sql/item_cmpfunc.cc | 19 +++++++++++++++++ sql/item_cmpfunc.h | 1 + 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/derived_cond_pushdown.result b/mysql-test/main/derived_cond_pushdown.result index 2824e3061d304..05ba3050fcb39 100644 --- a/mysql-test/main/derived_cond_pushdown.result +++ b/mysql-test/main/derived_cond_pushdown.result @@ -21784,4 +21784,25 @@ select * from v2 where greatest(a, default(a)); a count(*) drop view v2, v1; drop table t1; +# +# MDEV-29360 Crash when pushing condition with always false IS NULL +# into derived contains constant TRUE/FALSE as subformula +# +create table t1 (id int not null); +insert into t1 values (0),(1),(3); +create table t2 select * from t1; +create table t3 select * from t1; +set optimizer_trace='enabled=on'; +select * from (select id from t1 group by id) dt1, (select id from t2) dt2, t3 +where dt2.id = t3.id and dt1.id between 0 and (dt2.id is null); +id id id +0 0 0 +0 1 1 +0 3 3 +select json_extract((select trace from information_schema.optimizer_trace), +'$**.grouping_field_transformer_for_where') as trace; +trace +[{"before": "t2.`id`", "after": "1"}] +set optimizer_trace='enabled=off'; +drop table t1,t2,t3; # End of 10.11 tests diff --git a/mysql-test/main/derived_cond_pushdown.test b/mysql-test/main/derived_cond_pushdown.test index f00cdde5df2c1..79a20675d0afa 100644 --- a/mysql-test/main/derived_cond_pushdown.test +++ b/mysql-test/main/derived_cond_pushdown.test @@ -4351,4 +4351,26 @@ select * from v2 where greatest(a, default(a)); drop view v2, v1; drop table t1; +--echo # +--echo # MDEV-29360 Crash when pushing condition with always false IS NULL +--echo # into derived contains constant TRUE/FALSE as subformula +--echo # + +create table t1 (id int not null); +insert into t1 values (0),(1),(3); +create table t2 select * from t1; +create table t3 select * from t1; + +set optimizer_trace='enabled=on'; + +select * from (select id from t1 group by id) dt1, (select id from t2) dt2, t3 + where dt2.id = t3.id and dt1.id between 0 and (dt2.id is null); + +select json_extract((select trace from information_schema.optimizer_trace), + '$**.grouping_field_transformer_for_where') as trace; + +set optimizer_trace='enabled=off'; + +drop table t1,t2,t3; + --echo # End of 10.11 tests diff --git a/sql/item.cc b/sql/item.cc index a3ec31ceea8b0..507fe21e22963 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -8171,7 +8171,10 @@ Item_direct_view_ref::grouping_field_transformer_for_where(THD *thd, st_select_lex *sel= (st_select_lex *)arg; Field_pair *gr_field= find_matching_field_pair(this, sel->grouping_tmp_fields); - return gr_field->corresponding_item->deep_copy_with_checks(thd); + if (gr_field) + return gr_field->corresponding_item->deep_copy_with_checks(thd); + else + return this; } void Item_field::print(String *str, enum_query_type query_type) diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index 5f2c471de7fba..7ae5dc25ebd76 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -7975,3 +7975,22 @@ Item *Item_equal::multiple_equality_transformer(THD *thd, uchar *arg) break; } } + + +Item *Item_func_isnull::grouping_field_transformer_for_where(THD *thd, + uchar *arg) + { + if (const_item_cache) + { + Json_writer_object trace_wrapper(thd); + Json_writer_object trace_xform(thd, "grouping_field_transformer_for_where"); + trace_xform.add("before", args[0]); + + if (Item *item= new (thd->mem_root) Item_int(thd, 1)) + args[0]= item; + else + return 0; + trace_xform.add("after", args[0]); + } + return this; + } diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 753d939f6560d..12af8ba014ce3 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -2859,6 +2859,7 @@ class Item_func_isnull :public Item_func_null_predicate const_item_cache= args[0]->const_item(); } } + Item *grouping_field_transformer_for_where(THD *thd, uchar *arg) override; COND *remove_eq_conds(THD *thd, Item::cond_result *cond_value, bool top_level) override; table_map not_null_tables() const override { return 0; }