sqlorm 0.8.2

An ergonomic and type-safe ORM for database interactions
Documentation
mod common;

use common::create_clean_db;
use common::entities::{Donation, Jar, User};
use sqlorm::GenericExecutor;
use uuid::Uuid;

async fn setup_select_test_data(pool: &sqlorm::Pool) -> (User, Jar, Donation) {
    let mut user = User::test_user("select@example.com", "selectuser");
    user.bio = Some("A test bio".to_string());
    let user = user.save(pool).await.expect("Failed to save user");

    let mut jar = Jar::test_jar(user.id, "selectjar");
    jar.title = "Select Test Jar".to_string();
    jar.description = Some("A jar for testing selections".to_string());
    let jar = jar.save(pool).await.expect("Failed to save jar");

    let mut donation = Donation::test_donation(jar.id, user.id, 42.0);
    donation.note = Some("Test donation".to_string());
    let donation = donation.save(pool).await.expect("Failed to save donation");

    (user, jar, donation)
}

#[tokio::test]
async fn test_user_select_id_and_email() {
    let pool = create_clean_db().await;
    let (user, _jar, _donation) = setup_select_test_data(&pool).await;

    let (id, email): (i64, String) = User::query()
        .filter(User::ID.eq(user.id))
        .select((User::ID, User::EMAIL))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select id and email");

    assert_eq!(id, user.id);
    assert_eq!(email, "select@example.com");
}

#[tokio::test]
async fn test_user_select_multiple_fields() {
    let pool = create_clean_db().await;
    let (user, _jar, _donation) = setup_select_test_data(&pool).await;

    let (id, email, username, first_name): (i64, String, String, String) = User::query()
        .filter(User::ID.eq(user.id))
        .select((User::ID, User::EMAIL, User::USERNAME, User::FIRST_NAME))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select multiple user fields");

    assert_eq!(id, user.id);
    assert_eq!(email, "select@example.com");
    assert_eq!(username, "selectuser");
    assert_eq!(first_name, "Test");
}

#[tokio::test]
async fn test_jar_select_with_foreign_key() {
    let pool = create_clean_db().await;
    let (user, jar, _donation) = setup_select_test_data(&pool).await;

    let (alias, owner_id, title): (String, i64, String) = Jar::query()
        .filter(Jar::ID.eq(jar.id))
        .select((Jar::ALIAS, Jar::OWNER_ID, Jar::TITLE))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select jar fields");

    assert_eq!(alias, "selectjar");
    assert_eq!(owner_id, user.id);
    assert_eq!(title, "Select Test Jar");
}

#[tokio::test]
async fn test_donation_select_with_uuid() {
    let pool = create_clean_db().await;
    let (_user, jar, donation) = setup_select_test_data(&pool).await;
    let (id, jar_id, amount): (Uuid, i64, f64) = Donation::query()
        .filter(Donation::ID.eq(donation.id.clone()))
        .select((Donation::ID, Donation::JAR_ID, Donation::AMOUNT))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select donation fields");

    assert_eq!(&id.to_string(), &donation.id.to_string());
    assert_eq!(jar_id, jar.id);
    assert_eq!(amount, 42.0);
}

#[tokio::test]
async fn test_select_nullable_fields() {
    let pool = create_clean_db().await;
    let (user, _jar, _donation) = setup_select_test_data(&pool).await;

    let (id, bio, wallpaper): (i64, Option<String>, Option<String>) = User::query()
        .filter(User::ID.eq(user.id))
        .select((User::ID, User::BIO, User::WALLPAPER_URL))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select nullable fields");

    assert_eq!(id, user.id);
    assert_eq!(bio, Some("A test bio".to_string()));
    assert!(wallpaper.is_none());
}

#[tokio::test]
async fn test_select_with_filtering() {
    let pool = create_clean_db().await;

    let _user1 = User::test_user("select1@example.com", "select1")
        .save(&pool)
        .await
        .expect("Failed to save user1");

    let _user2 = User::test_user("select2@example.com", "select2")
        .save(&pool)
        .await
        .expect("Failed to save user2");

    let (email, username): (String, String) = User::query()
        .filter(User::USERNAME.eq("select1".to_string()))
        .select((User::EMAIL, User::USERNAME))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select with filtering");

    assert_eq!(email, "select1@example.com");
    assert_eq!(username, "select1");

    let results: Vec<(String, String)> = User::query()
        .filter(User::EMAIL.like("%select%".to_string()))
        .select((User::EMAIL, User::USERNAME))
        .fetch_all_as(&pool)
        .await
        .expect("Failed to select multiple with filtering");

    assert!(results.len() >= 2);
    assert!(
        results
            .iter()
            .any(|(e, u)| e == "select1@example.com" && u == "select1")
    );
    assert!(
        results
            .iter()
            .any(|(e, u)| e == "select2@example.com" && u == "select2")
    );
}

#[tokio::test]
async fn test_select_boolean_and_numeric_fields() {
    let pool = create_clean_db().await;
    let (_user, jar, donation) = setup_select_test_data(&pool).await;

    let (minimal_donation, total_amount, total_donations): (f64, f64, i32) = Jar::query()
        .filter(Jar::ID.eq(jar.id))
        .select((
            Jar::MINIMAL_DONATION,
            Jar::TOTAL_AMOUNT,
            Jar::TOTAL_DONATIONS,
        ))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select numeric fields");

    assert_eq!(minimal_donation, 1.0);
    assert_eq!(total_amount, 0.0);
    assert_eq!(total_donations, 0);

    let (is_payed, is_refunded): (bool, bool) = Donation::query()
        .filter(Donation::ID.eq(donation.id))
        .select((Donation::IS_PAYED, Donation::IS_REFUNDED))
        .fetch_one_as(&pool)
        .await
        .expect("Failed to select boolean fields");

    assert_eq!(is_payed, true);
    assert_eq!(is_refunded, false);
}

#[tokio::test]
async fn test_select_timestamp_fields() {
    let pool = create_clean_db().await;
    let (user, _jar, _donation) = setup_select_test_data(&pool).await;

    let (created_at, updated_at): (chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>) =
        User::query()
            .filter(User::ID.eq(user.id))
            .select((User::CREATED_AT, User::UPDATED_AT))
            .fetch_one_as(&pool)
            .await
            .expect("Failed to select timestamp fields");

    let now = chrono::Utc::now();
    let one_minute_ago = now - chrono::Duration::minutes(1);

    assert!(created_at > one_minute_ago, "created_at should be recent");
    assert!(updated_at > one_minute_ago, "updated_at should be recent");
    assert!(created_at <= now, "created_at should not be in the future");
    assert!(updated_at <= now, "updated_at should not be in the future");
}