Skip to content
/ server Public
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions mysql-test/main/mdev_37713.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
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;
33 changes: 33 additions & 0 deletions mysql-test/main/mdev_37713.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# MDEV-37713/MDEV-37714
# Test correctness of boolean folding
#

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;
39 changes: 39 additions & 0 deletions sql/item_cmpfunc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5736,6 +5736,45 @@ bool Item_cond::excl_dep_on_grouping_fields(st_select_lex *sel)
return true;
}

Item *Item_cond::simplify_cond(THD *thd)
{
List_iterator<Item> li(list);
Item *child;
while ((child= li++))
{
if (child->type() == Item::COND_ITEM)
{
Item *new_child= static_cast<Item_cond *>(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<Item> li2(list);
while ((child= li2++))
{
Item *real= child->real_item();
if (real->is_bool_literal())
{
bool v= real->val_bool();
if (is_and && v)
li2.remove();
if (is_and && !v)
return new (thd->mem_root) Item_bool(thd, false);
if (is_or && v)
return new (thd->mem_root) Item_bool(thd, true);
if (is_or && !v)
li2.remove();
}
}
if (list.is_empty())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the list ever be empty, given that you're removing one of the branches only if another branch exists?

I'd say no. And then this is dead code. And should be turned into an assert.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list can be empty. For example if we are dealing with something like TRUE AND TRUE, both TRUEs will be dropped during the while loop, and we will be left with an empty list

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow. You will only simplify a X AND|OR Y by replacing it with X or Y. This is a multiple parameter expression. You will do nothing on single parameter expressions.

Can you please demonstrate how the list becomes empty with an example SQL

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an expression or sub-expression composed of only boolean literals, the list can become empty. An example SQL expression where this happens is SELECT TRUE AND TRUE, or SELECT FALSE OR FALSE. Since all TRUEs are dropped from AND clauses and all FALSEs are dropped from OR clauses. I tested this on my end and I can confirm that the code after if (list.is_empty()) is run in certain cases, but I'd be happy to clarify/demonstrate further.

return new (thd->mem_root) Item_bool(thd, is_and);
}
return this;
}

void Item_cond_and::mark_as_condition_AND_part(TABLE_LIST *embedding)
{
Expand Down
1 change: 1 addition & 0 deletions sql/item_cmpfunc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Item>& li);
Expand Down
17 changes: 17 additions & 0 deletions sql/sql_base.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8223,6 +8223,23 @@ bool setup_fields(THD *thd, Ref_ptr_array ref_pointer_array,
DBUG_RETURN(TRUE); /* purecov: inspected */
}
item= *(it.ref()); // Item might have changed in fix_fields()
/*
If item is a SELECT-list COND_ITEM, rewrite it on the first time this
query is optimized to fold boolean expressions
*/
if (thd->lex->current_select->context_analysis_place == SELECT_LIST &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason why you're doing this only for the SELECT list expressions?
Why not do this during fix_fields for all Item_conds for example?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention one thing: When doing the optimization you are doing you need to check for NULL-ness!

SELECT true AND NULL FROM t1

should return NULL and not true.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any specific reason why you're doing this only for the SELECT list expressions? Why not do this during fix_fields for all Item_conds for example?

Clauses like WHERE/HAVING/ON already implement this folding through the remove_eq_conds function. However, its semantics are not correct in the SELECT-list (I tried using remove_eq_conds it at first but it failed a bunch of tests). For example, something like NULL AND TRUE would evaluate to FALSE after remove_eq_conds, when it should be NULL.

I guess you can say the simplify_cond function is a 'weaker' version of the remove_eq_conds in order to preserve correct semantics.

Copy link
Author

@jaeheonshim jaeheonshim Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to mention one thing: When doing the optimization you are doing you need to check for NULL-ness!

SELECT true AND NULL FROM t1

should return NULL and not true.

This works! The true simply gets folded out and the expression is left as NULL.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about TRUE or NULL ? This should still return NULL. And in your example above it's returning TRUE (1).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the latest stable version of MariaDB it returns TRUE

Your MariaDB connection id is 5
Server version: 11.8.6-MariaDB-ubu2404 mariadb.org binary distribution

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> SELECT TRUE OR NULL;
+--------------+
| TRUE OR NULL |
+--------------+
|            1 |
+--------------+
1 row in set (0.000 sec)

btw, for all of the test cases I wrote I tested it against a distribution version of MariaDB running in a Docker container

item->type() == Item::COND_ITEM &&
thd->lex->current_select->first_cond_optimization)
{
Query_arena_stmt on_stmt_arena(thd);
Item *new_item= static_cast<Item_cond *>(item)->simplify_cond(thd);
if (new_item != item)
{
new_item->share_name_with(item);
it.replace(new_item);
item= new_item;
}
}
if (!ref.is_null())
{
ref[0]= item;
Expand Down