From 308bd4f2c455f82e4044707282444d1a85d0cbe8 Mon Sep 17 00:00:00 2001 From: Jaeheon Shim Date: Sat, 7 Mar 2026 23:19:52 -0600 Subject: [PATCH] MDEV-37713/MDEV-37714 Fold boolean literals in SELECT-list The order of evaluation of the expressions that appear in a SELECT-list is undefined. This change exploits this fact by recursively folding TRUE/FALSE literals in OR/AND expressions which may allow for skipping evaluation of some parts of the expression or even the whole expression. --- .../main/literal_boolean_cond_folding.result | 95 +++++++++++++++++++ .../main/literal_boolean_cond_folding.test | 55 +++++++++++ sql/item_cmpfunc.cc | 57 +++++++++++ sql/item_cmpfunc.h | 1 + sql/sql_select.cc | 20 ++++ 5 files changed, 228 insertions(+) create mode 100644 mysql-test/main/literal_boolean_cond_folding.result create mode 100644 mysql-test/main/literal_boolean_cond_folding.test diff --git a/mysql-test/main/literal_boolean_cond_folding.result b/mysql-test/main/literal_boolean_cond_folding.result new file mode 100644 index 0000000000000..fc7a037d7eaa8 --- /dev/null +++ b/mysql-test/main/literal_boolean_cond_folding.result @@ -0,0 +1,95 @@ +# +# MDEV-37713/MDEV-37714: Test correctness of boolean folding +# +SELECT TRUE AND TRUE; +TRUE AND TRUE +1 +SELECT TRUE AND FALSE; +TRUE AND FALSE +0 +SELECT FALSE AND TRUE; +FALSE AND TRUE +0 +SELECT FALSE AND FALSE; +FALSE AND FALSE +0 +SELECT TRUE OR TRUE; +TRUE OR TRUE +1 +SELECT TRUE OR FALSE; +TRUE OR FALSE +1 +SELECT TRUE OR TRUE; +TRUE OR TRUE +1 +SELECT FALSE OR FALSE; +FALSE OR FALSE +0 +SELECT TRUE AND NULL; +TRUE AND NULL +NULL +SELECT NULL AND TRUE; +NULL AND TRUE +NULL +SELECT FALSE AND NULL; +FALSE AND NULL +0 +SELECT NULL AND FALSE; +NULL AND FALSE +0 +SELECT TRUE OR NULL; +TRUE OR NULL +1 +SELECT NULL OR TRUE; +NULL OR TRUE +1 +SELECT FALSE OR NULL; +FALSE OR NULL +NULL +SELECT NULL OR FALSE; +NULL OR FALSE +NULL +SELECT NULL AND NULL; +NULL AND NULL +NULL +SELECT NULL OR NULL; +NULL OR NULL +NULL +SELECT NOT NULL OR TRUE, NOT NULL AND FALSE; +NOT NULL OR TRUE NOT NULL AND FALSE +1 0 +SELECT NULL AND TRUE, NULL OR FALSE; +NULL AND TRUE NULL OR FALSE +NULL NULL +PREPARE s FROM 'SELECT NOT NULL OR TRUE, NOT NULL AND FALSE'; +EXECUTE s; +NOT NULL OR TRUE NOT NULL AND FALSE +1 0 +EXECUTE s; +NOT NULL OR TRUE NOT NULL AND FALSE +1 0 +DEALLOCATE PREPARE s; +CREATE TABLE t1(c0 INT); +CREATE TABLE t2(c0 INT); +INSERT INTO t1 VALUES(1),(2),(3); +INSERT INTO t2 VALUES(4),(5),(6); +SELECT (SELECT MIN(c0) FROM t2)<0 OR true; +(SELECT MIN(c0) FROM t2)<0 OR true +1 +SELECT ((SELECT MIN(c0) FROM t2)<0 AND false) OR (SELECT MAX(c0) FROM t1)>0 AS f; +f +1 +ANALYZE SELECT (SELECT MIN(c0) FROM t2)<0 OR true; +id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra +1 SIMPLE NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used +ANALYZE SELECT ((SELECT MIN(c0) FROM t2)<0 AND false) OR (SELECT MAX(c0) FROM t1)>0; +id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra +1 PRIMARY NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL No tables used +3 SUBQUERY t1 ALL NULL NULL NULL NULL 3 3.00 100.00 100.00 +CREATE VIEW v1 AS SELECT (SELECT MIN(c0) FROM t2)<0 OR true FROM t1; +SHOW CREATE VIEW v1; +View Create View character_set_client collation_connection +v1 CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `v1` AS select (select min(`t2`.`c0`) from `t2`) < 0 or 1 AS `(SELECT MIN(c0) FROM t2)<0 OR true` from `t1` latin1 latin1_swedish_ci +DROP VIEW v1; +DROP TABLE t1,t2; +# End of 13.1 tests diff --git a/mysql-test/main/literal_boolean_cond_folding.test b/mysql-test/main/literal_boolean_cond_folding.test new file mode 100644 index 0000000000000..13ca2acd02244 --- /dev/null +++ b/mysql-test/main/literal_boolean_cond_folding.test @@ -0,0 +1,55 @@ +--echo # +--echo # MDEV-37713/MDEV-37714: Test correctness of boolean folding +--echo # + +SELECT TRUE AND TRUE; +SELECT TRUE AND FALSE; +SELECT FALSE AND TRUE; +SELECT FALSE AND FALSE; +SELECT TRUE OR TRUE; +SELECT TRUE OR FALSE; +SELECT TRUE OR TRUE; +SELECT FALSE OR FALSE; + +SELECT TRUE AND NULL; +SELECT NULL AND TRUE; +SELECT FALSE AND NULL; +SELECT NULL AND FALSE; +SELECT TRUE OR NULL; +SELECT NULL OR TRUE; +SELECT FALSE OR NULL; +SELECT NULL OR FALSE; + +SELECT NULL AND NULL; +SELECT NULL OR NULL; + +SELECT NOT NULL OR TRUE, NOT NULL AND FALSE; +SELECT NULL AND TRUE, NULL OR FALSE; + +PREPARE s FROM 'SELECT NOT NULL OR TRUE, NOT NULL AND FALSE'; +EXECUTE s; +EXECUTE s; +DEALLOCATE PREPARE s; + +CREATE TABLE t1(c0 INT); +CREATE TABLE t2(c0 INT); +INSERT INTO t1 VALUES(1),(2),(3); +INSERT INTO t2 VALUES(4),(5),(6); + +SELECT (SELECT MIN(c0) FROM t2)<0 OR true; +SELECT ((SELECT MIN(c0) FROM t2)<0 AND false) OR (SELECT MAX(c0) FROM t1)>0 AS f; + +# Folds to constant true, t2 subselect is eliminated +ANALYZE SELECT (SELECT MIN(c0) FROM t2)<0 OR true; + +# (... AND false) folds to false and is dropped; only the t1 subselect remains +ANALYZE SELECT ((SELECT MIN(c0) FROM t2)<0 AND false) OR (SELECT MAX(c0) FROM t1)>0; + +# Check that views are not malformed by the folding +CREATE VIEW v1 AS SELECT (SELECT MIN(c0) FROM t2)<0 OR true FROM t1; +SHOW CREATE VIEW v1; + +DROP VIEW v1; +DROP TABLE t1,t2; + +--echo # End of 13.1 tests diff --git a/sql/item_cmpfunc.cc b/sql/item_cmpfunc.cc index d5a6fdd275f48..f8a8457d4b76e 100644 --- a/sql/item_cmpfunc.cc +++ b/sql/item_cmpfunc.cc @@ -5736,6 +5736,63 @@ bool Item_cond::excl_dep_on_grouping_fields(st_select_lex *sel) return true; } +/** + Recursively simplifies AND/OR expressions containing boolean literals. + + @note + This can potentially allow skipping evaluation of some non-literal + parts of the entire expression. + + E.g. a query like + SELECT (SELECT MIN(c0) FROM t2)<0 OR true; + will be simplified to + SELECT true; + which allows skipping the evaluation of SELECT MIN(c0) FROM t2. +*/ +Item *Item_cond::simplify_cond(THD *thd) +{ + List_iterator li(list); + Item *child; + + if (check_stack_overrun(thd, STACK_MIN_SIZE, NULL)) + return this; + + while ((child= li++)) + { + if (child->type() == Item::COND_ITEM) + { + Item *new_child= static_cast(child)->simplify_cond(thd); + if (new_child != child) + li.replace(new_child); + } + } + bool is_and= functype() == Item_func::COND_AND_FUNC; + bool is_or= functype() == Item_func::COND_OR_FUNC; + if (is_and || is_or) + { + List_iterator li2(list); + while ((child= li2++)) + { + Item *real= child->real_item(); + if (real->is_bool_literal()) + { + if (real->val_bool() == is_or) + return new (thd->mem_root) Item_bool(thd, is_or); + else + li2.remove(); + } + } + if (list.is_empty()) + return new (thd->mem_root) Item_bool(thd, is_and); + if (list.elements == 1) + { + Item *item= list.head(); + list.empty(); + return item; + } + } + return this; +} void Item_cond_and::mark_as_condition_AND_part(TABLE_LIST *embedding) { diff --git a/sql/item_cmpfunc.h b/sql/item_cmpfunc.h index 4fa07ccc8c971..1353a086db541 100644 --- a/sql/item_cmpfunc.h +++ b/sql/item_cmpfunc.h @@ -3450,6 +3450,7 @@ class Item_cond :public Item_bool_func Item *deep_copy(THD *thd) const override; bool excl_dep_on_table(table_map tab_map) override; bool excl_dep_on_grouping_fields(st_select_lex *sel) override; + Item *simplify_cond(THD *thd); private: void merge_sub_condition(List_iterator& li); diff --git a/sql/sql_select.cc b/sql/sql_select.cc index b8fbbe2f62f58..430526f8967f6 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -1595,6 +1595,26 @@ JOIN::prepare(TABLE_LIST *tables_init, COND *conds_init, uint og_num, } } + if (thd->lex->current_select->first_cond_optimization && + !thd->lex->is_view_context_analysis()) + { + Query_arena_stmt on_stmt_arena(thd); + List_iterator li(select_lex->item_list); + Item *item; + while ((item= li++)) + { + if (item->type() == Item::COND_ITEM) + { + Item *new_item= static_cast(item)->simplify_cond(thd); + if (new_item != item) + { + new_item->share_name_with(item); + li.replace(new_item); + } + } + } + } + if (setup_fields(thd, ref_ptrs, fields_list, select_lex->item_list_usage, &all_fields, &select_lex->pre_fix, 1)) DBUG_RETURN(-1);