vantage-sql 0.5.0

Vantage extension for SQL databases (Postgres, MySQL, SQLite)
Documentation
//! MySQL-specific operation traits.
//!
//! - `MysqlOperation<T>` — standard comparison ops (eq, gt, lt, etc.) producing
//!   `MysqlCondition`. Generated by `define_sql_operation!` in `condition.rs`.
//! - `MysqlExt` — MySQL-only operators (REGEXP, bitwise AND/OR).

use crate::condition::MysqlCondition;
use crate::define_sql_operation;

define_sql_operation!(
    MysqlOperation,
    MysqlCondition,
    crate::mysql::types::AnyMysqlType
);

use vantage_expressions::{Expression, Expressive, ExpressiveEnum};

use super::types::AnyMysqlType;

type Expr = Expression<AnyMysqlType>;

/// MySQL-specific operations beyond standard comparisons.
pub trait MysqlExt: Expressive<AnyMysqlType> {
    /// `expr REGEXP pattern`
    fn regexp(&self, pattern: impl Expressive<AnyMysqlType>) -> Expr {
        Expression::new(
            "{} REGEXP {}",
            vec![
                ExpressiveEnum::Nested(self.expr()),
                ExpressiveEnum::Nested(pattern.expr()),
            ],
        )
    }

    /// `expr & other` (bitwise AND)
    fn bitand(&self, other: impl Expressive<AnyMysqlType>) -> Expr {
        Expression::new(
            "{} & {}",
            vec![
                ExpressiveEnum::Nested(self.expr()),
                ExpressiveEnum::Nested(other.expr()),
            ],
        )
    }

    /// `expr | other` (bitwise OR)
    fn bitor(&self, other: impl Expressive<AnyMysqlType>) -> Expr {
        Expression::new(
            "{} | {}",
            vec![
                ExpressiveEnum::Nested(self.expr()),
                ExpressiveEnum::Nested(other.expr()),
            ],
        )
    }
}

impl<T: Expressive<AnyMysqlType>> MysqlExt for T {}

#[cfg(test)]
mod tests {
    use super::*;
    use vantage_table::column::core::Column;

    #[test]
    fn test_column_eq() {
        let name = Column::<AnyMysqlType>::new("name");
        let cond = name.eq(AnyMysqlType::from("Alice".to_string()));
        assert_eq!(cond.into_expr().preview(), "name = 'Alice'");
    }

    #[test]
    fn test_typed_column_gt() {
        let price = Column::<i64>::new("price");
        let cond = price.gt(150i64);
        assert_eq!(cond.into_expr().preview(), "price > 150");
    }

    #[test]
    fn test_chaining_gt_eq_false() {
        let price = Column::<i64>::new("price");
        let cond = price.gt(10i64).eq(false);
        assert_eq!(cond.into_expr().preview(), "price > 10 = false");
    }

    #[test]
    fn test_chaining_gt_eq_true() {
        let price = Column::<i64>::new("price");
        let cond = price.gt(10i64).eq(true);
        assert_eq!(cond.into_expr().preview(), "price > 10 = true");
    }

    #[test]
    fn test_typed_bool_column() {
        let is_deleted = Column::<bool>::new("is_deleted");
        let cond = is_deleted.eq(false);
        assert_eq!(cond.into_expr().preview(), "is_deleted = false");
    }

    #[test]
    fn test_cross_type_rejected() {
        let price = Column::<i64>::new("price");
        let _: MysqlCondition = price.gt(150i64);
    }

    #[test]
    fn test_is_null() {
        let deleted_at = Column::<String>::new("deleted_at");
        let cond = deleted_at.is_null();
        assert_eq!(cond.into_expr().preview(), "deleted_at IS NULL");
    }

    #[test]
    fn test_is_not_null() {
        let email = Column::<String>::new("email");
        let cond = email.is_not_null();
        assert_eq!(cond.into_expr().preview(), "email IS NOT NULL");
    }
}