diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index d6caba85e..6b70ee2cb 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -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(()) @@ -1067,9 +1079,123 @@ 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 either_one_is_null = **lhs == expr_null() || **rhs == expr_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 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(()); + } + // If one condition is always true, the AND operation simplifies to the other condition + if rhs_always_true { + *expr = lhs.take_ownership(); + return Ok(()); + } + if lhs_always_true { + *expr = rhs.take_ownership(); + 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(()); + } + // If one condition is always false, the OR operation simplifies to the other condition + if rhs_always_false { + *expr = lhs.take_ownership(); + return Ok(()); + } + if lhs_always_false { + *expr = rhs.take_ownership(); + 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, .. } => { diff --git a/testing/where.test b/testing/where.test index c613f784b..0e0668cc6 100755 --- a/testing/where.test +++ b/testing/where.test @@ -365,3 +365,121 @@ do_execsql_test nested-parens-conditionals-and-double-or { 8171|Andrea|Lee|dgarrison@example.com|001-594-430-0646|452 Anthony Stravenue|Sandraville|CA|28572|12 9110|Anthony|Barrett|steven05@example.net|(562)928-9177x8454|86166 Foster Inlet Apt. 284|North Jeffreyburgh|CA|80147|97 9279|Annette|Lynn|joanne37@example.com|(272)700-7181|2676 Laura Points Apt. 683|Tristanville|NY|48646|91}} + +# 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; +} {} + +do_execsql_test const-binary-and-null-false { + select null and false; +} {} + +# 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; +} {} + +# 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; +} {{}} + +# 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; +} {{}}