sql-orm 0.1.0

Public API crate for the sql-orm workspace.
Documentation
use crate::AliasedEntityColumn;
use sql_orm_core::{Entity, EntityColumn, SqlTypeMapping};
use sql_orm_query::{Expr, Predicate};

// The plan maestro fija explĂ­citamente `is_null` e `is_not_null` como API pĂșblica.
#[allow(clippy::wrong_self_convention)]
pub trait EntityColumnPredicateExt<E: Entity> {
    fn eq<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn ne<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn gt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn gte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn lt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn lte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping;

    fn is_null(self) -> Predicate;

    fn is_not_null(self) -> Predicate;

    fn contains(self, value: impl Into<String>) -> Predicate;

    fn starts_with(self, value: impl Into<String>) -> Predicate;

    fn ends_with(self, value: impl Into<String>) -> Predicate;
}

impl<E: Entity> EntityColumnPredicateExt<E> for EntityColumn<E> {
    fn eq<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::eq(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn ne<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::ne(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn gt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::gt(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn gte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::gte(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn lt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::lt(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn lte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::lte(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn is_null(self) -> Predicate {
        Predicate::is_null(Expr::from(self))
    }

    fn is_not_null(self) -> Predicate {
        Predicate::is_not_null(Expr::from(self))
    }

    fn contains(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!(
                "%{}%",
                value.into()
            ))),
        )
    }

    fn starts_with(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!("{}%", value.into()))),
        )
    }

    fn ends_with(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!("%{}", value.into()))),
        )
    }
}

impl<E: Entity> EntityColumnPredicateExt<E> for AliasedEntityColumn<E> {
    fn eq<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::eq(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn ne<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::ne(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn gt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::gt(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn gte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::gte(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn lt<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::lt(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn lte<V>(self, value: V) -> Predicate
    where
        V: SqlTypeMapping,
    {
        Predicate::lte(Expr::from(self), Expr::value(value.to_sql_value()))
    }

    fn is_null(self) -> Predicate {
        Predicate::is_null(Expr::from(self))
    }

    fn is_not_null(self) -> Predicate {
        Predicate::is_not_null(Expr::from(self))
    }

    fn contains(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!(
                "%{}%",
                value.into()
            ))),
        )
    }

    fn starts_with(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!("{}%", value.into()))),
        )
    }

    fn ends_with(self, value: impl Into<String>) -> Predicate {
        Predicate::like(
            Expr::from(self),
            Expr::value(sql_orm_core::SqlValue::String(format!("%{}", value.into()))),
        )
    }
}

#[cfg(test)]
mod tests {
    use super::EntityColumnPredicateExt;
    use crate::EntityColumnAliasExt;
    use sql_orm_core::{
        ColumnMetadata, Entity, EntityColumn, EntityMetadata, PrimaryKeyMetadata, SqlServerType,
        SqlValue,
    };
    use sql_orm_query::{ColumnRef, Expr, Predicate, TableRef};

    struct TestEntity;

    static TEST_ENTITY_COLUMNS: [ColumnMetadata; 2] = [
        ColumnMetadata {
            rust_field: "id",
            column_name: "id",
            renamed_from: None,
            sql_type: SqlServerType::BigInt,
            nullable: false,
            primary_key: true,
            identity: None,
            default_sql: None,
            computed_sql: None,
            rowversion: false,
            insertable: false,
            updatable: false,
            max_length: None,
            precision: None,
            scale: None,
        },
        ColumnMetadata {
            rust_field: "name",
            column_name: "name",
            renamed_from: None,
            sql_type: SqlServerType::NVarChar,
            nullable: true,
            primary_key: false,
            identity: None,
            default_sql: None,
            computed_sql: None,
            rowversion: false,
            insertable: true,
            updatable: true,
            max_length: Some(120),
            precision: None,
            scale: None,
        },
    ];

    static TEST_ENTITY_METADATA: EntityMetadata = EntityMetadata {
        rust_name: "TestEntity",
        schema: "dbo",
        table: "test_entities",
        renamed_from: None,
        columns: &TEST_ENTITY_COLUMNS,
        primary_key: PrimaryKeyMetadata {
            name: None,
            columns: &["id"],
        },
        indexes: &[],
        foreign_keys: &[],
        navigations: &[],
    };

    impl Entity for TestEntity {
        fn metadata() -> &'static EntityMetadata {
            &TEST_ENTITY_METADATA
        }
    }

    #[allow(non_upper_case_globals)]
    impl TestEntity {
        const id: EntityColumn<TestEntity> = EntityColumn::new("id", "id");
        const name: EntityColumn<TestEntity> = EntityColumn::new("name", "name");
    }

    #[test]
    fn comparison_methods_build_expected_predicates() {
        let expected_column = Expr::Column(ColumnRef::new(
            TableRef::new("dbo", "test_entities"),
            "id",
            "id",
        ));

        assert_eq!(
            TestEntity::id.eq(7_i64),
            Predicate::eq(expected_column.clone(), Expr::Value(SqlValue::I64(7)))
        );
        assert_eq!(
            TestEntity::id.ne(8_i64),
            Predicate::ne(expected_column.clone(), Expr::Value(SqlValue::I64(8)))
        );
        assert_eq!(
            TestEntity::id.gt(9_i64),
            Predicate::gt(expected_column.clone(), Expr::Value(SqlValue::I64(9)))
        );
        assert_eq!(
            TestEntity::id.gte(10_i64),
            Predicate::gte(expected_column.clone(), Expr::Value(SqlValue::I64(10)))
        );
        assert_eq!(
            TestEntity::id.lt(11_i64),
            Predicate::lt(expected_column.clone(), Expr::Value(SqlValue::I64(11)))
        );
        assert_eq!(
            TestEntity::id.lte(12_i64),
            Predicate::lte(expected_column, Expr::Value(SqlValue::I64(12)))
        );
    }

    #[test]
    fn null_predicate_methods_build_expected_predicates() {
        let expected_column = Expr::Column(ColumnRef::new(
            TableRef::new("dbo", "test_entities"),
            "name",
            "name",
        ));

        assert_eq!(
            TestEntity::name.is_null(),
            Predicate::is_null(expected_column.clone())
        );
        assert_eq!(
            TestEntity::name.is_not_null(),
            Predicate::is_not_null(expected_column)
        );
    }

    #[test]
    fn string_predicate_methods_build_expected_like_patterns() {
        let expected_column = Expr::Column(ColumnRef::new(
            TableRef::new("dbo", "test_entities"),
            "name",
            "name",
        ));

        assert_eq!(
            TestEntity::name.contains("ana"),
            Predicate::like(
                expected_column.clone(),
                Expr::Value(SqlValue::String("%ana%".to_string()))
            )
        );
        assert_eq!(
            TestEntity::name.starts_with("ana"),
            Predicate::like(
                expected_column.clone(),
                Expr::Value(SqlValue::String("ana%".to_string()))
            )
        );
        assert_eq!(
            TestEntity::name.ends_with("ana"),
            Predicate::like(
                expected_column,
                Expr::Value(SqlValue::String("%ana".to_string()))
            )
        );
    }

    #[test]
    fn aliased_columns_build_predicates_against_table_alias() {
        let expected_column = Expr::Column(ColumnRef::new(
            TableRef::with_alias("dbo", "test_entities", "t"),
            "name",
            "name",
        ));

        assert_eq!(
            TestEntity::name.aliased("t").contains("ana"),
            Predicate::like(
                expected_column.clone(),
                Expr::Value(SqlValue::String("%ana%".to_string()))
            )
        );
        assert_eq!(
            TestEntity::name.aliased("t").is_not_null(),
            Predicate::is_not_null(expected_column)
        );
    }
}