From da781dffa0bfab98f864d7a8549bcea9307b50d9 Mon Sep 17 00:00:00 2001 From: KaguraMilet Date: Mon, 16 Dec 2024 20:34:25 +0800 Subject: [PATCH 1/4] feat(optimizer): eliminate between statement --- core/translate/optimizer.rs | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 3e394699..45d4a829 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -15,6 +15,7 @@ use super::plan::{ * but having them separate makes them easier to understand */ pub fn optimize_plan(mut select_plan: Plan) -> Result { + eliminate_between(&mut select_plan.source, &mut select_plan.where_clause)?; if let ConstantConditionEliminationResult::ImpossibleCondition = eliminate_constants(&mut select_plan.source, &mut select_plan.where_clause)? { @@ -500,6 +501,46 @@ fn push_scan_direction(operator: &mut SourceOperator, direction: &Direction) { } } +fn eliminate_between( + operator: &mut SourceOperator, + where_clauses: &mut Option>, +) -> Result<()> { + if let Some(predicates) = where_clauses { + *predicates = predicates.drain(..).map(convert_between_expr).collect(); + } + + match operator { + SourceOperator::Join { + left, + right, + predicates, + .. + } => { + eliminate_between(left, where_clauses)?; + eliminate_between(right, where_clauses)?; + + if let Some(predicates) = predicates { + *predicates = predicates.drain(..).map(convert_between_expr).collect(); + } + } + SourceOperator::Scan { + predicates: Some(preds), + .. + } => { + *preds = preds.drain(..).map(convert_between_expr).collect(); + } + SourceOperator::Search { + predicates: Some(preds), + .. + } => { + *preds = preds.drain(..).map(convert_between_expr).collect(); + } + _ => (), + } + + Ok(()) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConstantPredicate { AlwaysTrue, @@ -807,6 +848,59 @@ pub fn try_extract_index_search_expression( } } +fn convert_between_expr(expr: ast::Expr) -> ast::Expr { + match expr { + ast::Expr::Between { + lhs, + not, + start, + end, + } => { + let lower_bound = ast::Expr::Binary(start, ast::Operator::LessEquals, lhs.clone()); + let upper_bound = ast::Expr::Binary(lhs, ast::Operator::LessEquals, end); + + if not { + // Convert NOT BETWEEN to NOT (x <= start AND y <= end) + ast::Expr::Unary( + ast::UnaryOperator::Not, + Box::new(ast::Expr::Binary( + Box::new(lower_bound), + ast::Operator::And, + Box::new(upper_bound), + )), + ) + } else { + // Convert BETWEEN to (start <= y AND y <= end) + ast::Expr::Binary( + Box::new(lower_bound), + ast::Operator::And, + Box::new(upper_bound), + ) + } + } + // Process other expressions recursively + ast::Expr::Binary(lhs, op, rhs) => ast::Expr::Binary( + Box::new(convert_between_expr(*lhs)), + op, + Box::new(convert_between_expr(*rhs)), + ), + ast::Expr::FunctionCall { + name, + distinctness, + args, + order_by, + filter_over, + } => ast::Expr::FunctionCall { + name, + distinctness, + args: args.map(|args| args.into_iter().map(convert_between_expr).collect()), + order_by, + filter_over, + }, + _ => expr, + } +} + trait TakeOwnership { fn take_ownership(&mut self) -> Self; } From 1df3189db63d086140ed7e524c8e41aaee1f0748 Mon Sep 17 00:00:00 2001 From: KaguraMilet Date: Mon, 16 Dec 2024 23:28:54 +0800 Subject: [PATCH 2/4] feat(optimizer): support NOT BETWEEN AND with De Morgan's Laws. --- core/translate/optimizer.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 45d4a829..962e2ea8 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -856,21 +856,24 @@ fn convert_between_expr(expr: ast::Expr) -> ast::Expr { start, end, } => { - let lower_bound = ast::Expr::Binary(start, ast::Operator::LessEquals, lhs.clone()); - let upper_bound = ast::Expr::Binary(lhs, ast::Operator::LessEquals, end); + // Convert `y NOT BETWEEN x AND z` to `x > y OR y > z` + let (lower_op, upper_op) = if not { + (ast::Operator::Greater, ast::Operator::Greater) + } else { + // Convert `y BETWEEN x AND z` to `x <= y AND y <= z` + (ast::Operator::LessEquals, ast::Operator::LessEquals) + }; + + let lower_bound = ast::Expr::Binary(start, lower_op, lhs.clone()); + let upper_bound = ast::Expr::Binary(lhs, upper_op, end); if not { - // Convert NOT BETWEEN to NOT (x <= start AND y <= end) - ast::Expr::Unary( - ast::UnaryOperator::Not, - Box::new(ast::Expr::Binary( - Box::new(lower_bound), - ast::Operator::And, - Box::new(upper_bound), - )), + ast::Expr::Binary( + Box::new(lower_bound), + ast::Operator::Or, + Box::new(upper_bound), ) } else { - // Convert BETWEEN to (start <= y AND y <= end) ast::Expr::Binary( Box::new(lower_bound), ast::Operator::And, From d5d71859955522fe61802a6334570495bede8153 Mon Sep 17 00:00:00 2001 From: KaguraMilet Date: Fri, 20 Dec 2024 22:49:44 +0800 Subject: [PATCH 3/4] add between expr tests --- testing/where.test | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/testing/where.test b/testing/where.test index 28ce70f8..264bdfdd 100755 --- a/testing/where.test +++ b/testing/where.test @@ -317,3 +317,20 @@ do_execsql_test where-age-index-seek-regression-test { do_execsql_test where-age-index-seek-regression-test-2 { select count(1) from users where age > 0; } {10000} + +do_execsql_test where-simple-between { + SELECT * FROM products WHERE price BETWEEN 70 AND 100; +} {1|hat|79.0 +2|cap|82.0 +5|sweatshirt|74.0 +6|shorts|70.0 +7|jeans|78.0 +8|sneakers|82.0 +11|accessories|81.0} + +do_execsql_test between-price-range-with-names { + SELECT * FROM products + WHERE (price BETWEEN 70 AND 100) + AND (name = 'sweatshirt' OR name = 'sneakers'); +} {5|sweatshirt|74.0 +8|sneakers|82.0} From ef39f11a9fcde3c5ddc6629e39a4782aa08a8250 Mon Sep 17 00:00:00 2001 From: KaguraMilet Date: Fri, 20 Dec 2024 23:11:17 +0800 Subject: [PATCH 4/4] fix(optimizer): process `Parenthesized` expression --- core/translate/optimizer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/translate/optimizer.rs b/core/translate/optimizer.rs index 962e2ea8..44344886 100644 --- a/core/translate/optimizer.rs +++ b/core/translate/optimizer.rs @@ -881,6 +881,9 @@ fn convert_between_expr(expr: ast::Expr) -> ast::Expr { ) } } + ast::Expr::Parenthesized(mut exprs) => { + ast::Expr::Parenthesized(exprs.drain(..).map(convert_between_expr).collect()) + } // Process other expressions recursively ast::Expr::Binary(lhs, op, rhs) => ast::Expr::Binary( Box::new(convert_between_expr(*lhs)),