From 252102e65b008c6efbb5a03a5efae35a72c1714d 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 a025b7484b541..1a1064ccafc50 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 3811b51b2d3f477498103067c3980e47d2c52709 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 1a1064ccafc50..12ce910fa84d3 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; }