From 155a970264c979ecbed922615d028aa717578528 Mon Sep 17 00:00:00 2001 From: Oleksandr Byelkin Date: Thu, 8 Oct 2026 16:13:36 +0200 Subject: [PATCH] MDEV-32401 expression cache lead to crash if table of wrong type created and the cache switched off 1) take into account TMP_TABLE_ALL_COLUMNS when we are modifying agg_item->result_field 2) remove unused now "bool materialized_subquery;" Co-Authored-By: Claude Opus 5.5 --- mysql-test/main/subselect_cache.result | 75 +++++++++++++++++++++++++- mysql-test/main/subselect_cache.test | 35 ++++++++++-- sql/item_subselect.cc | 2 - sql/sql_class.cc | 1 - sql/sql_class.h | 4 +- sql/sql_priv.h | 10 ++++ sql/sql_select.cc | 33 ++++++------ 7 files changed, 133 insertions(+), 27 deletions(-) diff --git a/mysql-test/main/subselect_cache.result b/mysql-test/main/subselect_cache.result index 701a3dca3ad..ac0a118a383 100644 --- a/mysql-test/main/subselect_cache.result +++ b/mysql-test/main/subselect_cache.result @@ -3888,6 +3888,77 @@ pk b 29 3 drop view v1; drop table t1,t2,t3,t4; +# +# MDEV-32401 expression cache lead to crash if table of wrong type +# created and the cache switched off +# +CREATE TABLE t0 (c TEXT); +INSERT INTO t0 VALUES (71), (-79); +SELECT t1.d FROM (SELECT c AS d FROM t0) AS t1 JOIN t0 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +d +71 +-79 +71 +-79 +EXPLAIN EXTENDED SELECT t1.d FROM (SELECT c AS d FROM t0) AS t1 JOIN t0 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 100.00 Using where +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) +3 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort +4 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1276 Field or reference 'd' of SELECT #4 was resolved in SELECT #1 +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`.`t0`.`c` AS `d` from `test`.`t0` join `test`.`t0` where <`test`.`t0`.`c`>((/* select#3 */ select ord(<`test`.`t0`.`c`,bit_or(`test`.`t0`.`c`),bit_or(`test`.`t0`.`c`)>((/* select#4 */ select bit_or(`test`.`t0`.`c`)))) from `test`.`t0` group by `test`.`t0`.`c`)) +SELECT t1.d FROM (SELECT CAST(c as int) AS d FROM t0) AS t1 JOIN t0 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +d +71 +-79 +71 +-79 +EXPLAIN EXTENDED SELECT t1.d FROM (SELECT CAST(c as int) AS d FROM t0) AS t1 JOIN t0 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 100.00 Using where +1 PRIMARY t0 ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) +3 DEPENDENT SUBQUERY t0 ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort +4 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1276 Field or reference 'd' of SELECT #4 was resolved in SELECT #1 +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 cast(`test`.`t0`.`c` as signed) AS `d` from `test`.`t0` join `test`.`t0` where ((/* select#3 */ select ord(((/* select#4 */ select bit_or(cast(`test`.`t0`.`c` as signed))))) from `test`.`t0` group by cast(`test`.`t0`.`c` as signed))) +CREATE TABLE t1 (c INT); +INSERT INTO t1 VALUES (71), (-79); +SELECT t2.d FROM (SELECT c AS d FROM t1) AS t2 JOIN t1 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t1 GROUP BY d +); +d +71 +-79 +71 +-79 +EXPLAIN EXTENDED SELECT t2.d FROM (SELECT c AS d FROM t1) AS t2 JOIN t1 ON ( +SELECT ORD((SELECT BIT_OR(d))) FROM t1 GROUP BY d +); +id select_type table type possible_keys key key_len ref rows filtered Extra +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 100.00 Using where +1 PRIMARY t1 ALL NULL NULL NULL NULL 2 100.00 Using join buffer (flat, BNL join) +3 DEPENDENT SUBQUERY t1 ALL NULL NULL NULL NULL 2 100.00 Using temporary; Using filesort +4 DEPENDENT SUBQUERY NULL NULL NULL NULL NULL NULL NULL NULL No tables used +Warnings: +Note 1276 Field or reference 'd' of SELECT #4 was resolved in SELECT #1 +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; +# End of 11.4 tests SET optimizer_switch=@save_optimizer_switch; -# restore default -set @@optimizer_switch= default; diff --git a/mysql-test/main/subselect_cache.test b/mysql-test/main/subselect_cache.test index b125d35f38e..ebf797c2713 100644 --- a/mysql-test/main/subselect_cache.test +++ b/mysql-test/main/subselect_cache.test @@ -1739,7 +1739,36 @@ SELECT * FROM t4 WHERE b NOT IN ( SELECT * FROM v1 ); drop view v1; drop table t1,t2,t3,t4; -SET optimizer_switch=@save_optimizer_switch; +--echo # +--echo # MDEV-32401 expression cache lead to crash if table of wrong type +--echo # created and the cache switched off +--echo # ---echo # restore default -set @@optimizer_switch= default; +CREATE TABLE t0 (c TEXT); +INSERT INTO t0 VALUES (71), (-79); +SELECT t1.d FROM (SELECT c AS d FROM t0) AS t1 JOIN t0 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +EXPLAIN EXTENDED SELECT t1.d FROM (SELECT c AS d FROM t0) AS t1 JOIN t0 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +SELECT t1.d FROM (SELECT CAST(c as int) AS d FROM t0) AS t1 JOIN t0 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +EXPLAIN EXTENDED SELECT t1.d FROM (SELECT CAST(c as int) AS d FROM t0) AS t1 JOIN t0 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t0 GROUP BY d +); +CREATE TABLE t1 (c INT); +INSERT INTO t1 VALUES (71), (-79); +SELECT t2.d FROM (SELECT c AS d FROM t1) AS t2 JOIN t1 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t1 GROUP BY d +); +EXPLAIN EXTENDED SELECT t2.d FROM (SELECT c AS d FROM t1) AS t2 JOIN t1 ON ( + SELECT ORD((SELECT BIT_OR(d))) FROM t1 GROUP BY d +); + +DROP TABLE t0, t1; + +--echo # End of 11.4 tests + +SET optimizer_switch=@save_optimizer_switch; diff --git a/sql/item_subselect.cc b/sql/item_subselect.cc index 6a629ded020..850ba1706e6 100644 --- a/sql/item_subselect.cc +++ b/sql/item_subselect.cc @@ -5329,8 +5329,6 @@ bool subselect_hash_sj_engine::init(List *tmp_columns, uint subquery_id) if (!(name.str= (char*) thd->memdup(buf, name.length + 1))) DBUG_RETURN(TRUE); - result_sink->get_tmp_table_param()->materialized_subquery= true; - if (item->substype() == Item_subselect::IN_SUBS && (item->get_IN_subquery()->is_jtbm_merged)) { diff --git a/sql/sql_class.cc b/sql/sql_class.cc index e744bbd5f8f..78660265b00 100644 --- a/sql/sql_class.cc +++ b/sql/sql_class.cc @@ -4641,7 +4641,6 @@ void TMP_TABLE_PARAM::init() table_charset= 0; precomputed_group_by= 0; bit_fields_as_long= 0; - materialized_subquery= 0; force_not_null_cols= 0; skip_create_table= 0; tmp_name= "temptable"; // Name of temp table on disk diff --git a/sql/sql_class.h b/sql/sql_class.h index e33952ee2a3..c65c04b653a 100644 --- a/sql/sql_class.h +++ b/sql/sql_class.h @@ -6803,8 +6803,6 @@ public: bool using_outer_summary_function; CHARSET_INFO *table_charset; bool schema_table; - /* TRUE if the temp table is created for subquery materialization. */ - bool materialized_subquery; /* TRUE if all columns of the table are guaranteed to be non-nullable */ bool force_not_null_cols; /* @@ -6832,7 +6830,7 @@ public: :copy_field(0), group_parts(0), group_length(0), group_null_parts(0), using_outer_summary_function(0), - schema_table(0), materialized_subquery(0), force_not_null_cols(0), + schema_table(0), force_not_null_cols(0), precomputed_group_by(0), group_concat(0), force_copy_fields(0), bit_fields_as_long(0), skip_create_table(0) { diff --git a/sql/sql_priv.h b/sql/sql_priv.h index f76b994f83d..25c135b23b4 100644 --- a/sql/sql_priv.h +++ b/sql/sql_priv.h @@ -58,6 +58,16 @@ #define OPTION_BIG_SELECTS (1ULL << 9) // THD, user #define OPTION_LOG_OFF (1ULL << 10) // THD, user #define OPTION_QUOTE_SHOW_CREATE (1ULL << 11) // THD, user +/* + This flag has two effects: + 1. Include all select list columns in the temporary table. Do not skip + constant expressions or expressions that include aggregate functions. + 2. For items in select list, do NOT set item->result_field to point to + item's image field in the created temporary table. + + Roughly speaking, TMP_TABLE_ALL_COLUMNS is NOT used for temporary tables + that do GROUP BY operation, and used for most other tables. +*/ #define TMP_TABLE_ALL_COLUMNS (1ULL << 12) // SELECT, intern #define OPTION_WARNINGS (1ULL << 13) // THD, user #define OPTION_AUTO_IS_NULL (1ULL << 14) // THD, user, binlog diff --git a/sql/sql_select.cc b/sql/sql_select.cc index fe304f710e9..f7e76942120 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -22326,6 +22326,8 @@ bool Create_tmp_table::add_fields(THD *thd, if (type == Item::SUM_FUNC_ITEM && !m_group && !m_save_sum_fields) { /* Can't calc group yet */ Item_sum *sum_item= (Item_sum *) item; + /* There is actually no need to clear result_field */ + DBUG_ASSERT(!sum_item->result_field); sum_item->result_field=0; for (uint i= 0 ; i < sum_item->get_arg_count() ; i++) { @@ -22412,26 +22414,25 @@ bool Create_tmp_table::add_fields(THD *thd, { Item_sum *agg_item= (Item_sum *) item; /* - Update the result field only if it has never been set, or if the - created temporary table is not to be used for subquery - materialization. + Set agg_item->result_field to point to its image in the temporary + table. Do this only when this temporary table is created without + TMP_TABLE_ALL_COLUMNS flag (and so not_all_columns=true). - The reason is that for subqueries that require - materialization as part of their plan, we create the - 'external' temporary table needed for IN execution, after - the 'internal' temporary table needed for grouping. Since - both the external and the internal temporary tables are - created for the same list of SELECT fields of the subquery, - setting 'result_field' for each invocation of - create_tmp_table overrides the previous value of - 'result_field'. + In this case, the temporary table will be read by going through the + select list and its item->result_field pointers. The most common + examples of this are GROUP BY operation and buffering for + SQL_BUFFER_RESULT). - The condition below prevents the creation of the external - temp table to override the 'result_field' that was set for - the internal temp table. + Note that SELECT has one "select list" but may be processed with + multiple temporary tables. It is therefore important to only set + result_field to point to the right temporary table. + That's why we assert here that we don't set it twice. */ - if (!agg_item->result_field || !param->materialized_subquery) + if (not_all_columns) + { + DBUG_ASSERT(!agg_item->result_field); agg_item->result_field= new_field; + } } tmp_from_field++;