Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP - rewrite_expr: simplify binary expressions whenever possible #699

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
127 changes: 124 additions & 3 deletions core/translate/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1004,16 +1004,28 @@ pub fn try_extract_index_search_expression(
}
}

fn expr_true() -> ast::Expr {
ast::Expr::Literal(ast::Literal::Numeric(1.to_string()))
}

fn expr_false() -> ast::Expr {
ast::Expr::Literal(ast::Literal::Numeric(0.to_string()))
}

fn expr_null() -> ast::Expr {
ast::Expr::Literal(ast::Literal::Null)
}

fn rewrite_expr(expr: &mut ast::Expr) -> Result<()> {
match expr {
ast::Expr::Id(id) => {
// Convert "true" and "false" to 1 and 0
if id.0.eq_ignore_ascii_case("true") {
*expr = ast::Expr::Literal(ast::Literal::Numeric(1.to_string()));
*expr = expr_true();
return Ok(());
}
if id.0.eq_ignore_ascii_case("false") {
*expr = ast::Expr::Literal(ast::Literal::Numeric(0.to_string()));
*expr = expr_false();
return Ok(());
}
Ok(())
Expand Down Expand Up @@ -1067,9 +1079,118 @@ fn rewrite_expr(expr: &mut ast::Expr) -> Result<()> {
Ok(())
}
// Process other expressions recursively
ast::Expr::Binary(lhs, _, rhs) => {
ast::Expr::Binary(lhs, op, rhs) => {
rewrite_expr(lhs)?;
rewrite_expr(rhs)?;

if !matches!(
op,
ast::Operator::And
| ast::Operator::Or
| ast::Operator::Equals
| ast::Operator::NotEquals
) {
return Ok(());
}

let lhs_always_true = lhs.is_always_true()?;
let rhs_always_true = rhs.is_always_true()?;

let lhs_always_false = lhs.is_always_false()?;
let rhs_always_false = rhs.is_always_false()?;

let both_always_true = lhs_always_true && rhs_always_true;
let both_always_false = lhs_always_false && rhs_always_false;

let one_always_true = lhs_always_true || rhs_always_true;
let one_always_false = lhs_always_false || rhs_always_false;

let lhs_is_null = **lhs == expr_null();
let rhs_is_null = **rhs == expr_null();
let both_are_null = lhs_is_null && rhs_is_null;
let either_one_is_null = lhs_is_null || rhs_is_null;

let mixed_truth_values =
(lhs_always_true && rhs_always_false) || (lhs_always_false && rhs_always_true);

let null_if_either_is_null = |expr: ast::Expr| {
if either_one_is_null {
expr_null()
} else {
expr
}
};

match op {
ast::Operator::And => {
// If both conditions are always true, the AND operation simplifies to true
if both_always_true {
*expr = expr_true();
return Ok(());
}
// If both conditions are always false, the AND operation simplifies to false (or NULL if _both_ are NULL)
if both_always_false {
*expr = if both_are_null {
expr_null()
} else {
expr_false()
};
return Ok(());
}
// If one condition is always false, the AND operation simplifies to false (or NULL if either is NULL)
if one_always_false {
*expr = null_if_either_is_null(expr_false());
return Ok(());
}
}
ast::Operator::Or => {
// If one condition is always true, the OR operation simplifies to true
if one_always_true {
*expr = expr_true();
return Ok(());
}
// If both conditions are always false, the OR operation simplifies to false (or NULL if either is NULL)
if both_always_false {
*expr = null_if_either_is_null(expr_false());
return Ok(());
}
}
ast::Operator::Equals => {
// If both conditions are always true, == simplifies to true.
if both_always_true {
*expr = expr_true();
return Ok(());
}
// If both conditions are always false, == simplifies to true (or NULL if either is NULL)
if both_always_false {
*expr = null_if_either_is_null(expr_true());
return Ok(());
}
// If one is true and one is false, equality is always false (or NULL if either is NULL)
if mixed_truth_values {
*expr = null_if_either_is_null(expr_false());
return Ok(());
}
}
ast::Operator::NotEquals => {
// If both conditions are always true, != simplifies to false
if both_always_true {
*expr = expr_false();
return Ok(());
}
// If both conditions are always false, != simplifies to false, or NULL if either is NULL
if both_always_false {
*expr = null_if_either_is_null(expr_false());
return Ok(());
}
// If one is true and one is false, inequality is always true (or NULL if either is NULL)
if mixed_truth_values {
*expr = null_if_either_is_null(expr_true());
return Ok(());
}
}
_ => {}
}
Ok(())
}
ast::Expr::FunctionCall { args, .. } => {
Expand Down
216 changes: 215 additions & 1 deletion testing/where.test
Original file line number Diff line number Diff line change
Expand Up @@ -382,4 +382,218 @@ do_execsql_test nested-parens-and-inside-or-regression-test {
AND
(id = 6 OR FALSE)
);
} {1}
} {1}

# Tests for handling of constant expressions in binary comparisons

# AND operator tests
do_execsql_test const-binary-and-true-true {
select true and true;
} {1}

do_execsql_test const-binary-and-true-false {
select true and false;
} {0}

do_execsql_test const-binary-and-false-true {
select false and true;
} {0}

do_execsql_test const-binary-and-false-false {
select false and false;
} {0}

do_execsql_test const-binary-and-true-null {
select true and null;
} {{}}

do_execsql_test const-binary-and-null-true {
select null and true;
} {{}}

do_execsql_test const-binary-and-false-null {
select false and null;
} {0}

do_execsql_test const-binary-and-null-false {
select null and false;
} {0}

do_execsql_test const-binary-and-null-null {
select null and null;
} {{}}

do_execsql_test const-binary-and-truthy-true {
select 2 and true;
} {1}

do_execsql_test const-binary-and-truthy-false {
select 2 and false;
} {0}

do_execsql_test const-binary-and-true-truthy {
select true and 2;
} {1}

do_execsql_test const-binary-and-false-truthy {
select false and 2;
} {0}

do_execsql_test const-binary-and-truthy-null {
select 2 and null;
} {{}}

do_execsql_test const-binary-and-null-truthy {
select null and 2;
} {{}}

# OR operator tests
do_execsql_test const-binary-or-true-true {
select true or true;
} {1}

do_execsql_test const-binary-or-true-false {
select true or false;
} {1}

do_execsql_test const-binary-or-false-true {
select false or true;
} {1}

do_execsql_test const-binary-or-false-false {
select false or false;
} {0}

do_execsql_test const-binary-or-true-null {
select true or null;
} {1}

do_execsql_test const-binary-or-null-true {
select null or true;
} {1}

do_execsql_test const-binary-or-false-null {
select false or null;
} {{}}

do_execsql_test const-binary-or-null-false {
select null or false;
} {{}}

do_execsql_test const-binary-or-null-null {
select null or null;
} {{}}

do_execsql_test const-binary-or-truthy-false {
select 2 or false;
} {1}

do_execsql_test const-binary-or-false-truthy {
select false or 2;
} {1}

do_execsql_test const-binary-or-truthy-true {
select 2 or true;
} {1}

do_execsql_test const-binary-or-true-truthy {
select true or 2;
} {1}

do_execsql_test const-binary-or-truthy-null {
select 2 or null;
} {1}

do_execsql_test const-binary-or-null-truthy {
select null or 2;
} {1}

# Equals operator tests
do_execsql_test const-binary-eq-true-true {
select true = true;
} {1}

do_execsql_test const-binary-eq-false-false {
select false = false;
} {1}

do_execsql_test const-binary-eq-true-false {
select true = false;
} {0}

do_execsql_test const-binary-eq-false-true {
select false = true;
} {0}

do_execsql_test const-binary-eq-null-true {
select null = true;
} {{}}

do_execsql_test const-binary-eq-true-null {
select true = null;
} {{}}

do_execsql_test const-binary-eq-null-null {
select null = null;
} {{}}

do_execsql_test const-binary-eq-truthy-true {
select 1 = true;
} {1}

do_execsql_test const-binary-eq-truthy-false {
select 42 = false;
} {0}

do_execsql_test const-binary-eq-truthy-null {
select 42 = null;
} {{}}

do_execsql_test const-binary-eq-null-truthy {
select null = 42;
} {{}}

# Not equals operator tests
do_execsql_test const-binary-ne-true-true {
select true != true;
} {0}

do_execsql_test const-binary-ne-false-false {
select false != false;
} {0}

do_execsql_test const-binary-ne-true-false {
select true != false;
} {1}

do_execsql_test const-binary-ne-false-true {
select false != true;
} {1}

do_execsql_test const-binary-ne-null-true {
select null != true;
} {{}}

do_execsql_test const-binary-ne-true-null {
select true != null;
} {{}}

do_execsql_test const-binary-ne-null-null {
select null != null;
} {{}}

do_execsql_test const-binary-ne-truthy-true {
select 1 != true;
} {0}

do_execsql_test const-binary-ne-truthy-false {
select 2 != false;
} {1}

do_execsql_test const-binary-ne-truthy-null {
select 42 != null;
} {{}}

do_execsql_test const-binary-ne-null-truthy {
select null != 42;
} {{}}
Loading