use super::simple_expr::{Keyword, SimpleExpr};
use crate::types::{BinOper, UnOper};
use crate::value::Value;
fn escape_like_pattern(input: &str) -> String {
let mut escaped = String::with_capacity(input.len());
for ch in input.chars() {
match ch {
'\\' => escaped.push_str("\\\\"),
'%' => escaped.push_str("\\%"),
'_' => escaped.push_str("\\_"),
_ => escaped.push(ch),
}
}
escaped
}
fn like_with_escape(expr: SimpleExpr, pattern: String) -> SimpleExpr {
SimpleExpr::CustomWithExpr(
"? LIKE ? ESCAPE '\\'".to_string(),
vec![
expr,
SimpleExpr::Value(Value::String(Some(Box::new(pattern)))),
],
)
}
#[allow(clippy::wrong_self_convention)]
pub trait ExprTrait: Sized {
fn into_simple_expr(self) -> SimpleExpr;
fn eq<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Equal,
Box::new(v.into()),
)
}
fn ne<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::NotEqual,
Box::new(v.into()),
)
}
fn lt<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::SmallerThan,
Box::new(v.into()),
)
}
fn lte<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::SmallerThanOrEqual,
Box::new(v.into()),
)
}
fn gt<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::GreaterThan,
Box::new(v.into()),
)
}
fn gte<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::GreaterThanOrEqual,
Box::new(v.into()),
)
}
fn is_null(self) -> SimpleExpr {
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Is,
Box::new(SimpleExpr::Constant(Keyword::Null)),
)
}
fn is_not_null(self) -> SimpleExpr {
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::IsNot,
Box::new(SimpleExpr::Constant(Keyword::Null)),
)
}
fn between<A, B>(self, a: A, b: B) -> SimpleExpr
where
A: Into<SimpleExpr>,
B: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Between,
Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
)
}
fn not_between<A, B>(self, a: A, b: B) -> SimpleExpr
where
A: Into<SimpleExpr>,
B: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::NotBetween,
Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
)
}
fn is_in<I, V>(self, values: I) -> SimpleExpr
where
I: IntoIterator<Item = V>,
V: Into<SimpleExpr>,
{
let collected: Vec<SimpleExpr> = values.into_iter().map(|v| v.into()).collect();
if collected.is_empty() {
return SimpleExpr::Constant(Keyword::False);
}
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::In,
Box::new(SimpleExpr::Tuple(collected)),
)
}
fn is_not_in<I, V>(self, values: I) -> SimpleExpr
where
I: IntoIterator<Item = V>,
V: Into<SimpleExpr>,
{
let collected: Vec<SimpleExpr> = values.into_iter().map(|v| v.into()).collect();
if collected.is_empty() {
return SimpleExpr::Constant(Keyword::True);
}
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::NotIn,
Box::new(SimpleExpr::Tuple(collected)),
)
}
fn like<V>(self, pattern: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Like,
Box::new(pattern.into()),
)
}
fn not_like<V>(self, pattern: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::NotLike,
Box::new(pattern.into()),
)
}
fn ilike<V>(self, pattern: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::ILike,
Box::new(pattern.into()),
)
}
fn not_ilike<V>(self, pattern: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::NotILike,
Box::new(pattern.into()),
)
}
fn starts_with<S>(self, prefix: S) -> SimpleExpr
where
S: Into<String>,
{
let escaped = escape_like_pattern(&prefix.into());
let pattern = format!("{}%", escaped);
like_with_escape(self.into_simple_expr(), pattern)
}
fn ends_with<S>(self, suffix: S) -> SimpleExpr
where
S: Into<String>,
{
let escaped = escape_like_pattern(&suffix.into());
let pattern = format!("%{}", escaped);
like_with_escape(self.into_simple_expr(), pattern)
}
fn contains<S>(self, substring: S) -> SimpleExpr
where
S: Into<String>,
{
let escaped = escape_like_pattern(&substring.into());
let pattern = format!("%{}%", escaped);
like_with_escape(self.into_simple_expr(), pattern)
}
fn and<E>(self, other: E) -> SimpleExpr
where
E: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::And,
Box::new(other.into()),
)
}
fn or<E>(self, other: E) -> SimpleExpr
where
E: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Or,
Box::new(other.into()),
)
}
fn not(self) -> SimpleExpr {
SimpleExpr::Unary(UnOper::Not, Box::new(self.into_simple_expr()))
}
fn add<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Add,
Box::new(v.into()),
)
}
fn sub<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Sub,
Box::new(v.into()),
)
}
fn mul<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Mul,
Box::new(v.into()),
)
}
fn div<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Div,
Box::new(v.into()),
)
}
fn modulo<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::Mod,
Box::new(v.into()),
)
}
fn bit_and<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::BitAnd,
Box::new(v.into()),
)
}
fn bit_or<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::BitOr,
Box::new(v.into()),
)
}
fn left_shift<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::LShift,
Box::new(v.into()),
)
}
fn right_shift<V>(self, v: V) -> SimpleExpr
where
V: Into<SimpleExpr>,
{
SimpleExpr::Binary(
Box::new(self.into_simple_expr()),
BinOper::RShift,
Box::new(v.into()),
)
}
fn cast_as<T>(self, type_name: T) -> SimpleExpr
where
T: crate::types::IntoIden,
{
SimpleExpr::Cast(Box::new(self.into_simple_expr()), type_name.into_iden())
}
fn as_enum<T>(self, type_name: T) -> SimpleExpr
where
T: crate::types::IntoIden,
{
SimpleExpr::AsEnum(type_name.into_iden(), Box::new(self.into_simple_expr()))
}
}
impl ExprTrait for SimpleExpr {
fn into_simple_expr(self) -> SimpleExpr {
self
}
}
impl ExprTrait for super::expr::Expr {
fn into_simple_expr(self) -> SimpleExpr {
self.into_simple_expr()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expr::Expr;
use rstest::rstest;
#[rstest]
fn test_eq() {
let expr = Expr::col("name").eq("Alice");
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Equal, _)));
}
#[rstest]
fn test_ne() {
let expr = Expr::col("name").ne("Bob");
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotEqual, _)));
}
#[rstest]
fn test_lt() {
let expr = Expr::col("age").lt(18);
assert!(matches!(
expr,
SimpleExpr::Binary(_, BinOper::SmallerThan, _)
));
}
#[rstest]
fn test_lte() {
let expr = Expr::col("age").lte(65);
assert!(matches!(
expr,
SimpleExpr::Binary(_, BinOper::SmallerThanOrEqual, _)
));
}
#[rstest]
fn test_gt() {
let expr = Expr::col("age").gt(18);
assert!(matches!(
expr,
SimpleExpr::Binary(_, BinOper::GreaterThan, _)
));
}
#[rstest]
fn test_gte() {
let expr = Expr::col("age").gte(18);
assert!(matches!(
expr,
SimpleExpr::Binary(_, BinOper::GreaterThanOrEqual, _)
));
}
#[rstest]
fn test_is_null() {
let expr = Expr::col("deleted_at").is_null();
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Is, _)));
}
#[rstest]
fn test_is_not_null() {
let expr = Expr::col("name").is_not_null();
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::IsNot, _)));
}
#[rstest]
fn test_between() {
let expr = Expr::col("age").between(18, 65);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Between, _)));
}
#[rstest]
fn test_not_between() {
let expr = Expr::col("age").not_between(0, 17);
assert!(matches!(
expr,
SimpleExpr::Binary(_, BinOper::NotBetween, _)
));
}
#[rstest]
fn test_is_in() {
let expr = Expr::col("status").is_in(["active", "pending"]);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::In, _)));
}
#[rstest]
fn test_is_not_in() {
let expr = Expr::col("status").is_not_in(["deleted", "banned"]);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotIn, _)));
}
#[rstest]
fn test_like() {
let expr = Expr::col("name").like("%john%");
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
}
#[rstest]
fn test_not_like() {
let expr = Expr::col("name").not_like("%admin%");
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotLike, _)));
}
#[rstest]
fn test_starts_with() {
let expr = Expr::col("name").starts_with("John");
assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
if let SimpleExpr::CustomWithExpr(template, _) = &expr {
assert_eq!(template, "? LIKE ? ESCAPE '\\'");
}
}
#[rstest]
fn test_ends_with() {
let expr = Expr::col("email").ends_with("@example.com");
assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
if let SimpleExpr::CustomWithExpr(template, _) = &expr {
assert_eq!(template, "? LIKE ? ESCAPE '\\'");
}
}
#[rstest]
fn test_contains() {
let expr = Expr::col("description").contains("important");
assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
if let SimpleExpr::CustomWithExpr(template, _) = &expr {
assert_eq!(template, "? LIKE ? ESCAPE '\\'");
}
}
#[rstest]
fn test_and() {
let expr = Expr::col("active")
.eq(true)
.and(Expr::col("verified").eq(true));
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
}
#[rstest]
fn test_or() {
let expr = Expr::col("role")
.eq("admin")
.or(Expr::col("role").eq("moderator"));
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Or, _)));
}
#[rstest]
fn test_not() {
let expr = Expr::col("deleted").not();
assert!(matches!(expr, SimpleExpr::Unary(UnOper::Not, _)));
}
#[rstest]
fn test_add() {
let expr = Expr::col("price").add(10);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Add, _)));
}
#[rstest]
fn test_sub() {
let expr = Expr::col("quantity").sub(1);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Sub, _)));
}
#[rstest]
fn test_mul() {
let expr = Expr::col("price").mul(Expr::col("quantity"));
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mul, _)));
}
#[rstest]
fn test_div() {
let expr = Expr::col("total").div(Expr::col("count"));
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Div, _)));
}
#[rstest]
fn test_modulo() {
let expr = Expr::col("value").modulo(2);
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mod, _)));
}
#[rstest]
fn test_cast_as() {
let expr = Expr::col("age").cast_as("TEXT");
assert!(matches!(expr, SimpleExpr::Cast(_, _)));
}
#[rstest]
fn test_chained_operations() {
let expr = Expr::col("age")
.gte(18)
.and(Expr::col("active").eq(true))
.and(Expr::col("verified").is_not_null());
assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
}
}