From 5c61f8e8254558568c9cdc6c9060acc27fcffc47 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Thu, 15 Oct 2026 10:15:40 +0200 Subject: [PATCH] MDEV-35592 Duplicate entry 'NULL' for key 'key0' with subquery cache Expression cache lookup is null rejecting, so parameters with NULL never hit and were inserted into the cache table again and again. After conversion of the cache to an on-disk table (where NULLs are equal) such a repeated insert failed with ER_DUP_ENTRY. Do not store rows which can not be looked up. On 11.4 the cache is just switched off when the HEAP table is full, so the test fails only after MDEV-9101 (11.5+, ES 11.4 via MENT-2067). Co-Authored-By: Claude Opus 5.5 --- mysql-test/main/subselect_cache.result | 20 ++++++++++++++++++++ mysql-test/main/subselect_cache.test | 23 +++++++++++++++++++++++ sql/sql_expression_cache.cc | 12 ++++++++++++ 3 files changed, 55 insertions(+) diff --git a/mysql-test/main/subselect_cache.result b/mysql-test/main/subselect_cache.result index ac0a118a383..4e64caf5e66 100644 --- a/mysql-test/main/subselect_cache.result +++ b/mysql-test/main/subselect_cache.result @@ -3960,5 +3960,25 @@ Note 1981 Aggregate function 'bit_or()' of SELECT #4 belongs to SELECT #3 Note 1276 Field or reference 'd' of SELECT #3 was resolved in SELECT #1 Note 1003 /* select#1 */ select `test`.`t1`.`c` AS `d` from `test`.`t1` join `test`.`t1` where <`test`.`t1`.`c`>((/* select#3 */ select ord(<`test`.`t1`.`c`,bit_or(`test`.`t1`.`c`),bit_or(`test`.`t1`.`c`)>((/* select#4 */ select bit_or(`test`.`t1`.`c`)))) from `test`.`t1` group by `test`.`t1`.`c`)) DROP TABLE t0, t1; +# +# MDEV-35592 ERROR 1062 (23000): Duplicate entry 'NULL' for key 'key0' +# on CONCAT in sub-query +# +CREATE TABLE t1 (a INT); +INSERT INTO t1 +WITH RECURSIVE s(n) AS (SELECT 0 UNION ALL SELECT n + 1 FROM s WHERE n < 999) +SELECT n FROM s, (VALUES (1), (2), (3), (4)) AS x ORDER BY n; +INSERT INTO t1 VALUES (NULL), (NULL); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2); +SET @save_tmp_table_size= @@tmp_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +SET tmp_table_size= 16384, max_heap_table_size= 16384; +SELECT COUNT(*) FROM t1 WHERE (SELECT b FROM t2 WHERE t2.b = t1.a) IS NULL; +COUNT(*) +3994 +SET tmp_table_size= @save_tmp_table_size; +SET max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1, t2; # End of 11.4 tests SET optimizer_switch=@save_optimizer_switch; diff --git a/mysql-test/main/subselect_cache.test b/mysql-test/main/subselect_cache.test index ebf797c2713..c6347538994 100644 --- a/mysql-test/main/subselect_cache.test +++ b/mysql-test/main/subselect_cache.test @@ -1769,6 +1769,29 @@ EXPLAIN EXTENDED SELECT t2.d FROM (SELECT c AS d FROM t1) AS t2 JOIN t1 ON ( DROP TABLE t0, t1; +--echo # +--echo # MDEV-35592 ERROR 1062 (23000): Duplicate entry 'NULL' for key 'key0' +--echo # on CONCAT in sub-query +--echo # + +CREATE TABLE t1 (a INT); +INSERT INTO t1 + WITH RECURSIVE s(n) AS (SELECT 0 UNION ALL SELECT n + 1 FROM s WHERE n < 999) + SELECT n FROM s, (VALUES (1), (2), (3), (4)) AS x ORDER BY n; +INSERT INTO t1 VALUES (NULL), (NULL); +CREATE TABLE t2 (b INT); +INSERT INTO t2 VALUES (1), (2); + +SET @save_tmp_table_size= @@tmp_table_size; +SET @save_max_heap_table_size= @@max_heap_table_size; +SET tmp_table_size= 16384, max_heap_table_size= 16384; + +SELECT COUNT(*) FROM t1 WHERE (SELECT b FROM t2 WHERE t2.b = t1.a) IS NULL; + +SET tmp_table_size= @save_tmp_table_size; +SET max_heap_table_size= @save_max_heap_table_size; +DROP TABLE t1, t2; + --echo # End of 11.4 tests SET optimizer_switch=@save_optimizer_switch; diff --git a/sql/sql_expression_cache.cc b/sql/sql_expression_cache.cc index 34bc1e2b2ca..3df9e5d4066 100644 --- a/sql/sql_expression_cache.cc +++ b/sql/sql_expression_cache.cc @@ -271,6 +271,18 @@ my_bool Expression_cache_tmptable::put_value(Item *value) DBUG_RETURN(FALSE); } + /* + check_value() could not build the lookup key (e.g. NULL in the null + rejecting key part), so such a row would never be found. Do not store + it: duplicates of it are rejected after conversion to an on-disk table, + where NULLs are equal. + */ + if (ref.key_err) + { + DBUG_PRINT("info", ("Key can not be looked up, do not store it")); + DBUG_RETURN(FALSE); + } + *(items.head_ref())= value; fill_record(table_thd, cache_table, cache_table->field, items, true, true, true);