ormer 0.2.9

A minimalist ORM framework that supports SQLite, PostgreSQL, MySQL, and SqlServer
#![cfg(feature = "duckdb")]

use std::time::Duration;

use ormer::{DbType, Model};

#[derive(Debug, Model)]
#[table = "duckdb_value_roundtrip_users"]
struct DuckDbValueRoundtripUser {
    #[primary]
    id: i32,
    profile: serde_json::Value,
    duration: Duration,
    tags: Vec<i32>,
    labels: Vec<String>,
}

#[derive(Debug, Model)]
#[table = "duckdb_conflict_regression_users"]
struct DuckDbConflictRegressionUser {
    #[primary]
    id: i32,
    #[unique]
    email: String,
    name: String,
}

#[tokio::test]
async fn duckdb_roundtrips_json_duration_and_arrays() -> Result<(), Box<dyn std::error::Error>> {
    let db = ormer::Database::connect(DbType::DuckDB, ":memory:").await?;
    db.create_table::<DuckDbValueRoundtripUser>()
        .execute()
        .await?;

    let expected_profile = serde_json::json!({
        "roles": ["admin", "operator"],
        "enabled": true
    });
    let expected_duration = Duration::from_micros(1_234_567);

    db.insert(&DuckDbValueRoundtripUser {
        id: 1,
        profile: expected_profile.clone(),
        duration: expected_duration,
        tags: vec![1, 2, 3],
        labels: vec!["first".to_string(), "second".to_string()],
    })
    .execute()
    .await?;

    let user = db
        .find_by_id::<DuckDbValueRoundtripUser>(1)
        .await?
        .expect("inserted DuckDB row");
    assert_eq!(user.profile, expected_profile);
    assert_eq!(user.duration, expected_duration);
    assert_eq!(user.tags, vec![1, 2, 3]);
    assert_eq!(user.labels, vec!["first", "second"]);
    Ok(())
}

#[tokio::test]
async fn duckdb_conflict_executors_use_their_rendered_sql() -> Result<(), Box<dyn std::error::Error>>
{
    let db = ormer::Database::connect(DbType::DuckDB, ":memory:").await?;
    db.create_table::<DuckDbConflictRegressionUser>()
        .execute()
        .await?;

    db.insert_or_update(&DuckDbConflictRegressionUser {
        id: 1,
        email: "alice@example.com".to_string(),
        name: "Alice".to_string(),
    })
    .execute()
    .await?;
    db.insert_or_update(&DuckDbConflictRegressionUser {
        id: 1,
        email: "alice@example.com".to_string(),
        name: "Alice updated".to_string(),
    })
    .execute()
    .await?;

    db.insert_or_ignore(&[
        DuckDbConflictRegressionUser {
            id: 1,
            email: "alice@example.com".to_string(),
            name: "Ignored".to_string(),
        },
        DuckDbConflictRegressionUser {
            id: 2,
            email: "bob@example.com".to_string(),
            name: "Bob".to_string(),
        },
    ])
    .execute()
    .await?;

    let rows = db
        .select::<DuckDbConflictRegressionUser>()
        .order_by(|user| user.id.asc())
        .collect::<Vec<_>>()
        .await?;
    assert_eq!(rows.len(), 2);
    assert_eq!(rows[0].name, "Alice updated");
    assert_eq!(rows[1].name, "Bob");

    let upsert_sql = db
        .insert_or_update(&DuckDbConflictRegressionUser {
            id: 3,
            email: "carol@example.com".to_string(),
            name: "Carol".to_string(),
        })
        .to_sql()?;
    assert!(
        upsert_sql.statements[0]
            .sql
            .contains("ON CONFLICT (id) DO UPDATE SET")
    );

    let ignore_sql = db
        .insert_or_ignore(&DuckDbConflictRegressionUser {
            id: 4,
            email: "dave@example.com".to_string(),
            name: "Dave".to_string(),
        })
        .to_sql()?;
    assert!(
        ignore_sql.statements[0]
            .sql
            .ends_with("ON CONFLICT DO NOTHING")
    );

    Ok(())
}