sqlmodel 0.2.2

SQL databases in Rust, designed to be intuitive and type-safe
Documentation
#![cfg(feature = "c-sqlite-tests")]

use asupersync::runtime::RuntimeBuilder;
use asupersync::{Cx, Outcome};

use sqlmodel::SchemaBuilder;
use sqlmodel::prelude::*;
use sqlmodel_sqlite::SqliteConnection;

fn unwrap_outcome<T>(outcome: Outcome<T, Error>) -> std::result::Result<T, String> {
    match outcome {
        Outcome::Ok(v) => Ok(v),
        Outcome::Err(e) => Err(format!("unexpected error: {e}")),
        Outcome::Cancelled(r) => Err(format!("cancelled: {r:?}")),
        Outcome::Panicked(p) => Err(format!("panicked: {p:?}")),
    }
}

#[derive(sqlmodel::Model, Debug, Clone, PartialEq)]
#[sqlmodel(table)]
struct User {
    #[sqlmodel(primary_key)]
    id: i64,
    name: String,
}

#[test]
fn sqlite_select_one_enforces_exactly_one_row() {
    let rt = RuntimeBuilder::current_thread()
        .build()
        .expect("create asupersync runtime");
    let cx = Cx::for_testing();

    rt.block_on(async {
        let conn = SqliteConnection::open_memory().expect("open sqlite memory db");

        let stmts = SchemaBuilder::new().create_table::<User>().build();
        for stmt in stmts {
            unwrap_outcome(conn.execute(&cx, &stmt, &[]).await).expect("execute ddl");
        }

        unwrap_outcome(
            conn.execute(
                &cx,
                "INSERT INTO users (id, name) VALUES (?1, ?2)",
                &[Value::BigInt(1), Value::Text("Alice".to_string())],
            )
            .await,
        )
        .expect("insert alice");
        unwrap_outcome(
            conn.execute(
                &cx,
                "INSERT INTO users (id, name) VALUES (?1, ?2)",
                &[Value::BigInt(2), Value::Text("Bob".to_string())],
            )
            .await,
        )
        .expect("insert bob");

        let alice = unwrap_outcome(
            select!(User)
                .filter(Expr::col("id").eq(1_i64))
                .one(&cx, &conn)
                .await,
        )
        .expect("one should return alice");
        assert_eq!(
            alice,
            User {
                id: 1,
                name: "Alice".to_string()
            }
        );

        let none = select!(User)
            .filter(Expr::col("id").eq(999_i64))
            .one(&cx, &conn)
            .await;
        assert!(matches!(none, Outcome::Err(Error::Custom(_))));
        if let Outcome::Err(Error::Custom(msg)) = none {
            assert!(msg.contains("found none"));
        }

        let many = select!(User).one(&cx, &conn).await;
        assert!(matches!(many, Outcome::Err(Error::Custom(_))));
        if let Outcome::Err(Error::Custom(msg)) = many {
            assert!(
                msg.contains("Expected zero or one row, found 2"),
                "unexpected message: {msg}"
            );
        }

        let one_or_none_hit = unwrap_outcome(
            select!(User)
                .filter(Expr::col("id").eq(1_i64))
                .one_or_none(&cx, &conn)
                .await,
        )
        .expect("one_or_none should return alice");
        assert_eq!(
            one_or_none_hit,
            Some(User {
                id: 1,
                name: "Alice".to_string()
            })
        );

        let one_or_none_missing = unwrap_outcome(
            select!(User)
                .filter(Expr::col("id").eq(999_i64))
                .one_or_none(&cx, &conn)
                .await,
        )
        .expect("one_or_none should return none");
        assert!(one_or_none_missing.is_none());

        let one_or_none_many = select!(User).one_or_none(&cx, &conn).await;
        assert!(matches!(one_or_none_many, Outcome::Err(Error::Custom(_))));
        if let Outcome::Err(Error::Custom(msg)) = one_or_none_many {
            assert!(
                msg.contains("Expected zero or one row, found 2"),
                "unexpected message: {msg}"
            );
        }
    });
}