floz-orm 0.1.7

A lightweight, typesafe Rust ORM — unifying DAO and DSL from a single schema
Documentation
use floz_orm::migration::Migrator;
use floz_macros_core::model::snapshot::{SnapshotField, SnapshotModel};

// We create a dummy pool to instantiate the Migrator for pure fn tests
async fn dummy_migrator() -> Migrator {
    sqlx::any::install_default_drivers();
    let pool = sqlx::any::AnyPoolOptions::new()
        .connect("sqlite::memory:")
        .await
        .unwrap();
    Migrator::new(pool)
}

#[tokio::test]
async fn test_create_table_sql_generation() {
    let migrator = dummy_migrator().await;

    let target = SnapshotModel {
        name: "User".to_string(),
        table_name: "users".to_string(),
        db_columns: vec![
            SnapshotField {
                rust_name: "id".to_string(),
                column_name: "id".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec!["Primary".to_string(), "AutoIncrement".to_string()],
                renamed_from: None,
            },
            SnapshotField {
                rust_name: "email".to_string(),
                column_name: "email".to_string(),
                type_info: "Varchar(255)".to_string(),
                modifiers: vec!["Unique".to_string()],
                renamed_from: None, // Implicit NOT NULL because no "Nullable" modifier
            },
        ],
        relationships: vec![],
        constraints: vec![],
    };

    let queries = migrator.diff_snapshots(None, &target);
    
    assert_eq!(queries.len(), 1);
    let expected = "CREATE TABLE users (\n  id SERIAL PRIMARY KEY NOT NULL,\n  email VARCHAR(255) UNIQUE NOT NULL\n);";
    assert_eq!(queries[0], expected);
}

#[tokio::test]
async fn test_alter_table_add_column() {
    let migrator = dummy_migrator().await;

    let a = SnapshotModel {
        name: "User".to_string(),
        table_name: "users".to_string(),
        db_columns: vec![
            SnapshotField {
                rust_name: "id".to_string(),
                column_name: "id".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec!["Primary".to_string()],
                renamed_from: None,
            },
        ],
        relationships: vec![],
        constraints: vec![],
    };

    let mut b = a.clone();
    b.db_columns.push(SnapshotField {
        rust_name: "name".to_string(),
        column_name: "name".to_string(),
        type_info: "Text".to_string(),
        modifiers: vec!["Nullable".to_string()],
        renamed_from: None,
    });

    let queries = migrator.diff_snapshots(Some(&a), &b);
    
    assert_eq!(queries.len(), 1);
    let expected = "ALTER TABLE users ADD COLUMN name TEXT";
    assert_eq!(queries[0], expected);
}

#[tokio::test]
async fn test_alter_table_drop_column() {
    let migrator = dummy_migrator().await;

    let a = SnapshotModel {
        name: "User".to_string(),
        table_name: "users".to_string(),
        db_columns: vec![
            SnapshotField {
                rust_name: "id".to_string(),
                column_name: "id".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec!["Primary".to_string()],
                renamed_from: None,
            },
            SnapshotField {
                rust_name: "age".to_string(),
                column_name: "age".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec![],
                renamed_from: None,
            },
        ],
        relationships: vec![],
        constraints: vec![],
    };

    let mut b = a.clone();
    b.db_columns.pop(); // Removing age

    let queries = migrator.diff_snapshots(Some(&a), &b);
    
    assert_eq!(queries.len(), 1);
    let expected = "ALTER TABLE users DROP COLUMN age;";
    assert_eq!(queries[0], expected);
}

#[tokio::test]
async fn test_alter_table_rename_column() {
    let migrator = dummy_migrator().await;

    let a = SnapshotModel {
        name: "User".to_string(),
        table_name: "users".to_string(),
        db_columns: vec![
            SnapshotField {
                rust_name: "id".to_string(),
                column_name: "id".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec!["Primary".to_string()],
                renamed_from: None,
            },
            SnapshotField {
                rust_name: "user_age".to_string(),
                column_name: "user_age".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec![],
                renamed_from: None,
            },
        ],
        relationships: vec![],
        constraints: vec![],
    };

    let b = SnapshotModel {
        name: "User".to_string(),
        table_name: "users".to_string(),
        db_columns: vec![
            SnapshotField {
                rust_name: "id".to_string(),
                column_name: "id".to_string(),
                type_info: "Integer".to_string(),
                modifiers: vec!["Primary".to_string()],
                renamed_from: None,
            },
            SnapshotField {
                rust_name: "age".to_string(),
                column_name: "age".to_string(), // Renamed
                type_info: "Integer".to_string(),
                modifiers: vec![],
                renamed_from: Some("user_age".to_string()),
            },
        ],
        relationships: vec![],
        constraints: vec![],
    };

    let queries = migrator.diff_snapshots(Some(&a), &b);
    
    assert_eq!(queries.len(), 1);
    let expected = "ALTER TABLE users RENAME COLUMN user_age TO age;";
    assert_eq!(queries[0], expected);
}

#[tokio::test]
async fn test_map_type_to_pg() {
    let migrator = dummy_migrator().await;

    assert_eq!(migrator.map_type_to_pg("Integer"), "INTEGER");
    assert_eq!(migrator.map_type_to_pg("Text"), "TEXT");
    assert_eq!(migrator.map_type_to_pg("Varchar(255)"), "VARCHAR(255)");
    assert_eq!(migrator.map_type_to_pg("Decimal(10,2)"), "DECIMAL(10,2)");
    assert_eq!(migrator.map_type_to_pg("DateTime"), "TIMESTAMPTZ");
    assert_eq!(migrator.map_type_to_pg("UnknownBlah"), "TEXT"); // fallback
}