ormer 0.2.1

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

mod _test_common;

define_test_user_simple!(RawSqlUser, "raw_sql_users_1");

use ormer::model::{FromRowValues, Value};

#[derive(Debug, PartialEq)]
struct UserName {
    id: i32,
    name: String,
}

impl FromRowValues for UserName {
    fn from_row_values(values: &[Value]) -> anyhow::Result<Self> {
        if values.len() < 2 {
            return Err(anyhow::anyhow!("Expected id and name"));
        }
        Ok(Self {
            id: i32::from_row_values(&values[0..1])?,
            name: String::from_row_values(&values[1..2])?,
        })
    }
}

#[test]
fn raw_sql_tokenizer_skips_sql_literal_regions() {
    let raw = ormer::sql(
        "SELECT ':skip', \"quoted:name\", [bracket:name], $$dollar:name$$ \
         FROM raw_sql_users_1 \
         WHERE name = :name AND id = {id} \
         -- :comment\n/* {comment} */",
    )
    .bind_named("name", "Alice".to_string())
    .bind_named("id", 1);

    #[cfg(feature = "sqlite")]
    {
        let (sql, params) = raw.render(ormer::DbType::Sqlite).unwrap();
        assert!(sql.contains("':skip'"));
        assert!(sql.contains("\"quoted:name\""));
        assert!(sql.contains("[bracket:name]"));
        assert!(sql.contains("$$dollar:name$$"));
        assert!(sql.contains("-- :comment"));
        assert!(sql.contains("/* {comment} */"));
        assert!(sql.contains("name = ? AND id = ?"));
        assert_eq!(params.len(), 2);
    }

    #[cfg(feature = "postgresql")]
    {
        let (sql, params) = raw.render(ormer::DbType::PostgreSQL).unwrap();
        assert!(sql.contains("name = $1 AND id = $2"));
        assert_eq!(params.len(), 2);
    }
}

async fn test_raw_sql_binding_and_mapping_impl(
    config: &_test_common::DbConfig,
) -> Result<(), Box<dyn std::error::Error>> {
    let db = _test_common::create_db_connection(config).await?;
    let _ = db.drop_table::<RawSqlUser>().execute().await;
    db.create_table::<RawSqlUser>().execute().await?;

    db.execute_sql(ormer::sql!(
        "INSERT INTO raw_sql_users_1 (id, name, age) VALUES ({id}, {name}, {age})",
        id = 1,
        name = "Alice".to_string(),
        age = 20,
    ))
    .await?;
    db.execute_sql(
        ormer::sql("INSERT INTO raw_sql_users_1 (id, name, age) VALUES (:id, :name, :age)")
            .bind_named("id", 2)
            .bind_named("name", "Bob".to_string())
            .bind_named("age", 30),
    )
    .await?;

    let rows: Vec<UserName> = db
        .select_sql::<UserName>(
            ormer::sql(
                "SELECT id, name FROM raw_sql_users_1 \
                 WHERE name = :name AND ':name' = ':name' -- :ignored\n\
                 ORDER BY id",
            )
            .bind_named("name", "Alice".to_string()),
        )
        .collect()
        .await?;

    assert_eq!(
        rows,
        vec![UserName {
            id: 1,
            name: "Alice".to_string()
        }]
    );

    let mut txn = db.begin().await?;
    txn.execute_sql(
        ormer::sql("UPDATE raw_sql_users_1 SET name = :name WHERE id = :id")
            .bind_named("name", "Carol".to_string())
            .bind_named("id", 2),
    )
    .await?;
    let txn_rows: Vec<UserName> = txn
        .select_sql::<UserName>(
            ormer::sql("SELECT id, name FROM raw_sql_users_1 WHERE id = :id").bind_named("id", 2),
        )
        .collect()
        .await?;
    assert_eq!(
        txn_rows,
        vec![UserName {
            id: 2,
            name: "Carol".to_string()
        }]
    );
    txn.rollback().await?;

    let _ = db.drop_table::<RawSqlUser>().execute().await;
    Ok(())
}

test_on_all_dbs_result!(test_raw_sql_binding_and_mapping_impl);