rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use sea_orm::sea_query::{Condition, Order};
use sea_orm::{DatabaseBackend, EntityTrait, QueryTrait, Select};

use super::{Query, WhereNode};

/// Wraps SeaORM's query builder to provide Django-compatible query compilation.
pub struct SQLCompiler<E: EntityTrait> {
    query: Query,
    select: Select<E>,
}

impl<E: EntityTrait> SQLCompiler<E> {
    #[must_use]
    pub fn new(select: Select<E>) -> Self {
        Self {
            query: Query::new(),
            select,
        }
    }

    /// Apply where conditions to the select.
    pub fn apply_where(&mut self, condition: Condition) -> &mut Self {
        self.query.where_clause = Some(WhereNode::from_condition(condition));
        self
    }

    /// Set ordering.
    pub fn set_ordering(&mut self, orderings: Vec<(String, Order)>) -> &mut Self {
        self.query.orderings = orderings;
        self
    }

    /// Set limit.
    pub fn set_limit(&mut self, limit: u64) -> &mut Self {
        self.query.limit = Some(limit);
        self
    }

    /// Set offset.
    pub fn set_offset(&mut self, offset: u64) -> &mut Self {
        self.query.offset = Some(offset);
        self
    }

    /// Get the compiled SQL string (for debugging/explain).
    #[must_use]
    pub fn as_sql(&self, backend: DatabaseBackend) -> String {
        self.select.clone().build(backend).to_string()
    }
}

#[cfg(test)]
mod tests {
    use sea_orm::sea_query::Order;
    use sea_orm::{DatabaseBackend, EntityTrait, QuerySelect};

    use super::SQLCompiler;

    mod mock_entity {
        use sea_orm::entity::prelude::*;

        #[sea_orm::model]
        #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
        #[sea_orm(table_name = "widgets")]
        pub struct Model {
            #[sea_orm(primary_key)]
            pub id: i32,
            pub name: String,
        }

        impl ActiveModelBehavior for ActiveModel {}
    }

    #[test]
    fn new_compiler_starts_with_empty_query_state() {
        let compiler = SQLCompiler::new(mock_entity::Entity::find());

        assert!(compiler.query.is_empty());
        assert!(compiler.query.orderings.is_empty());
        assert_eq!(compiler.query.limit, None);
        assert_eq!(compiler.query.offset, None);
    }

    #[test]
    fn apply_where_stores_placeholder_where_node() {
        let mut compiler = SQLCompiler::new(mock_entity::Entity::find());

        compiler.apply_where(sea_orm::Condition::all());

        assert!(compiler.query.where_clause.is_some());
        assert!(
            compiler
                .query
                .where_clause
                .as_ref()
                .is_some_and(crate::db::models::sql::where_node::WhereNode::is_empty)
        );
    }

    #[test]
    fn set_limit_updates_internal_query_limit() {
        let mut compiler = SQLCompiler::new(mock_entity::Entity::find());

        compiler.set_limit(25);

        assert_eq!(compiler.query.limit, Some(25));
    }

    #[test]
    fn set_offset_updates_internal_query_offset() {
        let mut compiler = SQLCompiler::new(mock_entity::Entity::find());

        compiler.set_offset(50);

        assert_eq!(compiler.query.offset, Some(50));
    }

    #[test]
    fn set_ordering_replaces_internal_orderings() {
        let mut compiler = SQLCompiler::new(mock_entity::Entity::find());

        compiler.set_ordering(vec![
            ("name".to_string(), Order::Asc),
            ("id".to_string(), Order::Desc),
        ]);

        assert_eq!(compiler.query.orderings.len(), 2);
        assert!(matches!(compiler.query.orderings[0], (ref field, Order::Asc) if field == "name"));
        assert!(matches!(compiler.query.orderings[1], (ref field, Order::Desc) if field == "id"));
    }

    #[test]
    fn as_sql_returns_compiled_select_sql() {
        let compiler = SQLCompiler::new(mock_entity::Entity::find().limit(1));

        let sql = compiler.as_sql(DatabaseBackend::Sqlite);

        assert!(
            sql.contains("SELECT"),
            "expected SELECT statement, got: {sql}"
        );
        assert!(
            sql.contains("widgets"),
            "expected widgets table, got: {sql}"
        );
        assert!(sql.contains("LIMIT 1"), "expected LIMIT clause, got: {sql}");
    }

    #[test]
    fn compiler_setters_can_be_chained() {
        let mut compiler = SQLCompiler::new(mock_entity::Entity::find());

        compiler
            .set_limit(10)
            .set_offset(20)
            .set_ordering(vec![("name".to_string(), Order::Asc)]);

        assert_eq!(compiler.query.limit, Some(10));
        assert_eq!(compiler.query.offset, Some(20));
        assert_eq!(compiler.query.orderings.len(), 1);
    }
}