rjango 0.1.1

A full-stack Rust backend framework inspired by Django
Documentation
use super::base::DatabaseBackend;

#[derive(Debug, Default, Clone, Copy)]
pub struct SqliteBackend;

#[must_use]
fn parse_optional_length(field_type: &str, base: &str) -> Option<Option<usize>> {
    let field_type = field_type.trim();
    if field_type == base {
        return Some(None);
    }

    let rest = field_type.strip_prefix(base)?;
    let length = rest.strip_prefix('(')?.strip_suffix(')')?.parse().ok()?;
    Some(Some(length))
}

impl DatabaseBackend for SqliteBackend {
    fn name(&self) -> &str {
        "sqlite"
    }

    fn quote_name(&self, name: &str) -> String {
        format!("\"{}\"", name.replace('"', "\"\""))
    }

    fn db_type(&self, field_type: &str) -> String {
        if let Some(length) = parse_optional_length(field_type, "CharField") {
            return length
                .map_or_else(|| "TEXT".to_string(), |length| format!("VARCHAR({length})"));
        }

        match field_type.trim() {
            "AutoField"
            | "BigAutoField"
            | "SmallAutoField"
            | "IntegerField"
            | "BigIntegerField"
            | "SmallIntegerField"
            | "PositiveIntegerField"
            | "PositiveBigIntegerField"
            | "PositiveSmallIntegerField" => "INTEGER".to_string(),
            "BooleanField" | "NullBooleanField" => "BOOL".to_string(),
            "FloatField" => "REAL".to_string(),
            "DateTimeField" => "DATETIME".to_string(),
            "TextField" => "TEXT".to_string(),
            "JSONField" => "TEXT".to_string(),
            "UUIDField" => "CHAR(32)".to_string(),
            "BinaryField" => "BLOB".to_string(),
            other => other.to_string(),
        }
    }

    fn last_insert_id_sql(&self, _table: &str, _pk_name: &str) -> String {
        String::new()
    }
}

impl SqliteBackend {
    #[must_use]
    pub fn supports_json(&self) -> bool {
        self.supports_json_field()
    }

    #[must_use]
    pub fn max_query_params(&self) -> usize {
        999
    }
}

#[cfg(test)]
mod tests {
    use super::{DatabaseBackend, SqliteBackend};

    #[test]
    fn sqlite_backend_reports_feature_flags() {
        let backend = SqliteBackend;

        assert_eq!(backend.name(), "sqlite");
        assert!(backend.supports_transactions());
        assert!(backend.supports_json());
        assert_eq!(backend.max_query_params(), 999);
        assert!(!backend.supports_covering_indexes());
    }

    #[test]
    fn test_sqlite_quote_name() {
        let backend = SqliteBackend;

        assert_eq!(backend.quote_name("table_name"), "\"table_name\"");
        assert_eq!(backend.quote_name("table\"name"), "\"table\"\"name\"");
    }

    #[test]
    fn test_sqlite_db_type_mappings() {
        let backend = SqliteBackend;

        assert_eq!(backend.db_type("AutoField"), "INTEGER");
        assert_eq!(backend.db_type("CharField"), "TEXT");
        assert_eq!(backend.db_type("CharField(64)"), "VARCHAR(64)");
        assert_eq!(backend.db_type("BooleanField"), "BOOL");
        assert_eq!(backend.db_type("DateTimeField"), "DATETIME");
        assert_eq!(backend.db_type("JSONField"), "TEXT");
        assert_eq!(backend.db_type("UUIDField"), "CHAR(32)");
        assert_eq!(backend.db_type("BinaryField"), "BLOB");
    }

    #[test]
    fn test_sqlite_supports_json() {
        let backend = SqliteBackend;

        assert!(backend.supports_json_field());
    }
}