ormer 0.2.7

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

use ormer::{Database, DbType, Model, Value};
use std::sync::{Arc, Mutex};
use std::time::Duration;

static TRACE_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());

#[derive(Debug, Model)]
#[table = "sql_trace_users"]
struct SqlTraceUser {
    #[primary]
    id: i32,
    name: String,
}

fn is_trace_test_sql(sql: &str) -> bool {
    sql.contains("sql_trace_users") || sql.contains("missing_sql_trace_table")
}

#[tokio::test]
async fn sql_trace_records_callbacks_and_parameter_view() -> ormer::Result<()> {
    let _guard = TRACE_TEST_LOCK.lock().await;

    let db = Database::connect(DbType::Sqlite, ":memory:").await?;
    let before_sql = Arc::new(Mutex::new(Vec::<String>::new()));
    let after_sql = Arc::new(Mutex::new(Vec::<String>::new()));
    let error_sql = Arc::new(Mutex::new(Vec::<String>::new()));
    let slow_sql = Arc::new(Mutex::new(Vec::<String>::new()));
    let seen_params = Arc::new(Mutex::new(Vec::<Vec<Value>>::new()));

    db.sql_trace()
        .clear()
        .redact_params(|params| {
            params
                .iter()
                .map(|_| Value::Text("***".to_string()))
                .collect()
        })
        .rewrite(|sql| {
            if is_trace_test_sql(sql) {
                format!("{sql} /* traced */")
            } else {
                sql.to_string()
            }
        })
        .before_with({
            let before_sql = Arc::clone(&before_sql);
            let seen_params = Arc::clone(&seen_params);
            move |event| {
                if is_trace_test_sql(event.sql()) {
                    before_sql.lock().unwrap().push(event.sql().to_string());
                    seen_params.lock().unwrap().push(event.params().to_vec());
                }
            }
        })
        .after({
            let after_sql = Arc::clone(&after_sql);
            move |sql, _elapsed| {
                if is_trace_test_sql(sql) {
                    after_sql.lock().unwrap().push(sql.to_string());
                }
            }
        })
        .on_error({
            let error_sql = Arc::clone(&error_sql);
            move |sql, _err| {
                if is_trace_test_sql(sql) {
                    error_sql.lock().unwrap().push(sql.to_string());
                }
            }
        })
        .slow_sql_threshold(Duration::ZERO)
        .slow({
            let slow_sql = Arc::clone(&slow_sql);
            move |sql, _elapsed| {
                if is_trace_test_sql(sql) {
                    slow_sql.lock().unwrap().push(sql.to_string());
                }
            }
        });

    db.create_table::<SqlTraceUser>().execute().await?;
    db.insert(&SqlTraceUser {
        id: 1,
        name: "Alice".to_string(),
    })
    .execute()
    .await?;
    let users: Vec<SqlTraceUser> = db
        .select::<SqlTraceUser>()
        .filter(|user| user.name.eq("Alice".to_string()))
        .collect()
        .await?;

    assert_eq!(users.len(), 1);

    let err = db
        .execute_sql("INSERT INTO missing_sql_trace_table (id) VALUES (1)")
        .await
        .unwrap_err();
    assert!(err.to_string().contains("missing_sql_trace_table"));

    db.sql_trace().clear();

    let before_sql = before_sql.lock().unwrap();
    let after_sql = after_sql.lock().unwrap();
    let error_sql = error_sql.lock().unwrap();
    let slow_sql = slow_sql.lock().unwrap();
    let seen_params = seen_params.lock().unwrap();

    assert!(before_sql.iter().any(|sql| sql.contains("CREATE TABLE")));
    assert!(before_sql.iter().any(|sql| sql.contains("INSERT INTO")));
    assert!(before_sql.iter().any(|sql| sql.contains("SELECT")));
    assert!(before_sql.iter().all(|sql| sql.contains("/* traced */")));
    assert!(after_sql.iter().any(|sql| sql.contains("SELECT")));
    assert_eq!(error_sql.len(), 1);
    assert!(error_sql[0].contains("missing_sql_trace_table"));
    assert!(!slow_sql.is_empty());
    assert!(
        seen_params
            .iter()
            .flatten()
            .any(|value| matches!(value, Value::Text(value) if value == "***"))
    );

    Ok(())
}