use crate::expr::function::Function;
use crate::expr::limit::Limit;
use crate::expr::operator::BinaryOperator;
use crate::expr::part::Part;
use crate::expr::visit::{MutVisitor, VisitMut};
use crate::expr::{Expr, Literal};
pub(crate) struct CountLimitRewriter;
impl MutVisitor for CountLimitRewriter {
type Error = ();
fn visit_mut_expr(&mut self, e: &mut Expr) -> Result<(), Self::Error> {
if let Expr::Binary {
left,
op,
right,
} = e
{
if let Some(limit) = count_comparison_limit(left, op, right) {
inject_limit(left, limit);
}
if let Some(flipped) = flip_comparison(op)
&& let Some(limit) = count_comparison_limit(right, &flipped, left)
{
inject_limit(right, limit);
}
}
match e {
Expr::Param(_)
| Expr::Table(_)
| Expr::Mock(_)
| Expr::Constant(_)
| Expr::Closure(_)
| Expr::Break
| Expr::Continue
| Expr::Return(_)
| Expr::Throw(_)
| Expr::IfElse(_)
| Expr::Select(_)
| Expr::Create(_)
| Expr::Update(_)
| Expr::Delete(_)
| Expr::Relate(_)
| Expr::Insert(_)
| Expr::Define(_)
| Expr::Remove(_)
| Expr::Rebuild(_)
| Expr::Upsert(_)
| Expr::Alter(_)
| Expr::Info(_)
| Expr::Foreach(_)
| Expr::Let(_)
| Expr::Sleep(_) => {}
_ => {
e.visit_mut(self)?;
}
}
Ok(())
}
}
fn count_comparison_limit(count_expr: &Expr, op: &BinaryOperator, n_expr: &Expr) -> Option<i64> {
if !is_count_of_graph_traversal(count_expr) {
return None;
}
let Expr::Literal(Literal::Integer(n)) = n_expr else {
return None;
};
compute_limit(op, *n)
}
fn compute_limit(op: &BinaryOperator, n: i64) -> Option<i64> {
let limit = match op {
BinaryOperator::MoreThan => n.checked_add(1)?,
BinaryOperator::MoreThanEqual => n,
BinaryOperator::LessThan => n,
BinaryOperator::LessThanEqual => n.checked_add(1)?,
BinaryOperator::Equal | BinaryOperator::ExactEqual => n.checked_add(1)?,
BinaryOperator::NotEqual => n.checked_add(1)?,
_ => return None,
};
(limit >= 1).then_some(limit)
}
fn flip_comparison(op: &BinaryOperator) -> Option<BinaryOperator> {
Some(match op {
BinaryOperator::LessThan => BinaryOperator::MoreThan,
BinaryOperator::LessThanEqual => BinaryOperator::MoreThanEqual,
BinaryOperator::MoreThan => BinaryOperator::LessThan,
BinaryOperator::MoreThanEqual => BinaryOperator::LessThanEqual,
BinaryOperator::Equal => BinaryOperator::Equal,
BinaryOperator::ExactEqual => BinaryOperator::ExactEqual,
BinaryOperator::NotEqual => BinaryOperator::NotEqual,
_ => return None,
})
}
fn is_count_of_graph_traversal(expr: &Expr) -> bool {
let Expr::FunctionCall(fc) = expr else {
return false;
};
if !matches!(&fc.receiver, Function::Normal(name) if name == "count") {
return false;
}
if fc.arguments.len() != 1 {
return false;
}
let Expr::Idiom(idiom) = &fc.arguments[0] else {
return false;
};
idiom.0.iter().any(|p| matches!(p, Part::Lookup(_)))
}
fn inject_limit(count_expr: &mut Expr, limit_value: i64) {
let Expr::FunctionCall(fc) = count_expr else {
return;
};
if fc.arguments.len() != 1 {
return;
}
let Expr::Idiom(idiom) = &mut fc.arguments[0] else {
return;
};
for part in idiom.0.iter_mut().rev() {
if let Part::Lookup(lookup) = part {
if lookup.limit.is_none() {
lookup.limit = Some(Limit(Expr::Literal(Literal::Integer(limit_value))));
}
break;
}
}
}