From cfea659cf091ff0caebe1488d9759ea14aabfc9d Mon Sep 17 00:00:00 2001 From: Oleg Smirnov Date: Fri, 16 Jan 2026 23:07:57 +0700 Subject: [PATCH 1/2] MDEV-32868 SELECT NULL,NULL IN (SUBQUERY) returns 0 instead of NULL When evaluating (SELECT NULL, NULL) IN (SELECT 1, 2 FROM t), the result was incorrectly 0 instead of NULL. The IN-to-EXISTS transformation wraps comparison predicates with trigcond() guards: WHERE trigcond(NULL = 1) AND trigcond(NULL = 2) During optimization, make_join_select() evaluated this as a "constant condition". With guards ON, the condition evaluated to FALSE (NULL treated as FALSE), triggering "Impossible WHERE". At runtime, guards would be turned OFF for NULL columns, but the optimizer had already marked the subquery as returning no rows. Fix: check can_eval_in_optimize() instead of is_expensive() in make_join_select(). Unlike is_expensive(), can_eval_in_optimize() also verifies const_item(), which returns FALSE for Item_func_trig_cond --- mysql-test/main/subselect_nulls.result | 47 ++++++++++++++++++++++++++ mysql-test/main/subselect_nulls.test | 45 ++++++++++++++++++++++++ sql/sql_select.cc | 2 +- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/mysql-test/main/subselect_nulls.result b/mysql-test/main/subselect_nulls.result index 9d3b7b2cfd508..e57b02a4ead6a 100644 --- a/mysql-test/main/subselect_nulls.result +++ b/mysql-test/main/subselect_nulls.result @@ -154,3 +154,50 @@ drop table t1, t2; # # End of 10.10 tests # +# +# MDEV-32868 SELECT NULL,NULL IN (SUBQUERY) returns 0 instead of NULL +# +create table t1 (a int); +insert into t1 values (1); +create table t2 (a int, b int); +insert into t2 values (null, null); +# Scalar subquery returning NULLs as left part of IN +# All NULLs on left, no match possible -> NULL (subquery has rows) +select (select null, null) in (select 1, 2 from t1) from t1; +(select null, null) in (select 1, 2 from t1) +NULL +# One NULL on left, other column matches -> NULL +select (select null, 2) in (select 1, 2 from t1) from t1; +(select null, 2) in (select 1, 2 from t1) +NULL +# One NULL on left, other column doesn't match -> FALSE +select (select null, 3) in (select 1, 2 from t1) from t1; +(select null, 3) in (select 1, 2 from t1) +0 +# Table with NULLs as left part +select (select * from t2) in (select 1, 2 from t1) from t1; +(select * from t2) in (select 1, 2 from t1) +NULL +# Exact match -> TRUE +select (select 1, 2) in (select 1, 2 from t1) from t1; +(select 1, 2) in (select 1, 2 from t1) +1 +# No match, no NULLs -> FALSE +select (select 3, 4) in (select 1, 2 from t1) from t1; +(select 3, 4) in (select 1, 2 from t1) +0 +# Right side has NULLs +create table t3 (a int, b int); +insert into t3 values (1, null), (null, 2); +# Left matches one column, right has NULL in other -> NULL +select (select 1, 2) in (select * from t3) from t1; +(select 1, 2) in (select * from t3) +NULL +# Both sides have NULLs -> NULL +select (select null, 2) in (select * from t3) from t1; +(select null, 2) in (select * from t3) +NULL +drop table t1, t2, t3; +# +# End of 10.11 tests +# diff --git a/mysql-test/main/subselect_nulls.test b/mysql-test/main/subselect_nulls.test index 68575eca9e73a..53b68b63fd656 100644 --- a/mysql-test/main/subselect_nulls.test +++ b/mysql-test/main/subselect_nulls.test @@ -127,3 +127,48 @@ drop table t1, t2; --echo # --echo # End of 10.10 tests --echo # + +--echo # +--echo # MDEV-32868 SELECT NULL,NULL IN (SUBQUERY) returns 0 instead of NULL +--echo # + +create table t1 (a int); +insert into t1 values (1); + +create table t2 (a int, b int); +insert into t2 values (null, null); + +--echo # Scalar subquery returning NULLs as left part of IN +--echo # All NULLs on left, no match possible -> NULL (subquery has rows) +select (select null, null) in (select 1, 2 from t1) from t1; + +--echo # One NULL on left, other column matches -> NULL +select (select null, 2) in (select 1, 2 from t1) from t1; + +--echo # One NULL on left, other column doesn't match -> FALSE +select (select null, 3) in (select 1, 2 from t1) from t1; + +--echo # Table with NULLs as left part +select (select * from t2) in (select 1, 2 from t1) from t1; + +--echo # Exact match -> TRUE +select (select 1, 2) in (select 1, 2 from t1) from t1; + +--echo # No match, no NULLs -> FALSE +select (select 3, 4) in (select 1, 2 from t1) from t1; + +--echo # Right side has NULLs +create table t3 (a int, b int); +insert into t3 values (1, null), (null, 2); + +--echo # Left matches one column, right has NULL in other -> NULL +select (select 1, 2) in (select * from t3) from t1; + +--echo # Both sides have NULLs -> NULL +select (select null, 2) in (select * from t3) from t1; + +drop table t1, t2, t3; + +--echo # +--echo # End of 10.11 tests +--echo # diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 4bdb75f2ed635..9ab45c709cfd1 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -13307,7 +13307,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { Json_writer_object trace_const_cond(thd); trace_const_cond.add("condition_on_constant_tables", const_cond); - if (const_cond->is_expensive()) + if (!const_cond->can_eval_in_optimize()) { trace_const_cond.add("evaluated", "false") .add("cause", "expensive cond"); From ceaef36baba09bb77ac8bac06f9f717d36e2cf8b Mon Sep 17 00:00:00 2001 From: Oleg Smirnov Date: Tue, 27 Jan 2026 22:03:17 +0700 Subject: [PATCH 2/2] MDEV-32868 Cleanup (no change of logic) Switch branches of the `if` condition to make the code flow more naturally. --- sql/sql_select.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 9ab45c709cfd1..8622789e822c7 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -13307,12 +13307,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) { Json_writer_object trace_const_cond(thd); trace_const_cond.add("condition_on_constant_tables", const_cond); - if (!const_cond->can_eval_in_optimize()) - { - trace_const_cond.add("evaluated", "false") - .add("cause", "expensive cond"); - } - else + if (const_cond->can_eval_in_optimize()) { bool const_cond_result; { @@ -13328,6 +13323,11 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) DBUG_RETURN(1); } } + else + { + trace_const_cond.add("evaluated", "false") + .add("cause", "expensive cond"); + } join->exec_const_cond= const_cond; }