Skip to content

Commit

Permalink
Merge 'feat(optimizer): eliminate between statement' from KaguraMilet
Browse files Browse the repository at this point in the history
Rewrite `Y BETWEEN X AND Z` as `X <= Y AND Y <= Z`. And due to the
support of this optimization rule, limbo should now be able to execute
the `BETWEEN AND` statement.

Closes #490
  • Loading branch information
jussisaurio committed Dec 20, 2024
2 parents 5b4ef44 + ef39f11 commit 82bc950
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 0 deletions.
100 changes: 100 additions & 0 deletions core/translate/optimizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Plan> {
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)?
{
Expand Down Expand Up @@ -498,6 +499,46 @@ fn push_scan_direction(operator: &mut SourceOperator, direction: &Direction) {
}
}

fn eliminate_between(
operator: &mut SourceOperator,
where_clauses: &mut Option<Vec<ast::Expr>>,
) -> 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,
Expand Down Expand Up @@ -808,6 +849,65 @@ 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,
} => {
// 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 {
ast::Expr::Binary(
Box::new(lower_bound),
ast::Operator::Or,
Box::new(upper_bound),
)
} else {
ast::Expr::Binary(
Box::new(lower_bound),
ast::Operator::And,
Box::new(upper_bound),
)
}
}
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)),
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;
}
Expand Down
17 changes: 17 additions & 0 deletions testing/where.test
Original file line number Diff line number Diff line change
Expand Up @@ -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}

0 comments on commit 82bc950

Please sign in to comment.