#[path = "fixtures/libsql_db.rs"]
mod db;
#[path = "fixtures/libsql_users.rs"]
mod users;
use toolu_orm_core::query_column::CommonOps;
use toolu_orm_core::value::Value;
use toolu_orm_query::executor::Executor;
use toolu_orm_query::insert::InsertBuilder;
use toolu_orm_query::select::SelectBuilder;
use users::{insert_user, User, USER_AGE, USER_COLUMNS, USER_EMAIL, USER_ID, USER_NAME};
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[tokio::test]
async fn or_replace_replaces_conflicting_row() -> TestResult {
let conn = db::setup_db().await?;
insert_user(&conn, "bystander", "Zoe", "zoe@example.com", Some(22)).await?;
InsertBuilder::new("users")
.set(&USER_ID, "u1")
.set(&USER_NAME, "Ann")
.set(&USER_EMAIL, "ann@example.com")
.set(&USER_AGE, 30_i64)
.or_replace()
.conflict_columns(&["id"])
.execute(&conn)
.await?;
InsertBuilder::new("users")
.set(&USER_ID, "u1")
.set(&USER_NAME, "Bea")
.set(&USER_EMAIL, "bea@example.com")
.set(&USER_AGE, 31_i64)
.or_replace()
.conflict_columns(&["id"])
.execute(&conn)
.await?;
let rows: Vec<User> = SelectBuilder::new("users")
.columns_raw(&USER_COLUMNS)
.fetch_all(&conn)
.await?;
assert_eq!(rows.len(), 2, "replace must not touch unrelated rows");
let replaced: User = SelectBuilder::new("users")
.columns_raw(&USER_COLUMNS)
.filter(USER_ID.eq("u1"))
.fetch_one(&conn)
.await?;
assert_eq!(replaced.name, "Bea");
assert_eq!(replaced.email, "bea@example.com");
assert_eq!(replaced.age, Some(31));
let bystander: User = SelectBuilder::new("users")
.columns_raw(&USER_COLUMNS)
.filter(USER_ID.eq("bystander"))
.fetch_one(&conn)
.await?;
assert_eq!(
(
bystander.name.as_str(),
bystander.email.as_str(),
bystander.age
),
("Zoe", "zoe@example.com", Some(22))
);
Ok(())
}
#[tokio::test]
async fn or_ignore_keeps_original_row() -> TestResult {
let conn = db::setup_db().await?;
InsertBuilder::new("users")
.set(&USER_ID, "u1")
.set(&USER_NAME, "Ann")
.set(&USER_EMAIL, "ann@example.com")
.set(&USER_AGE, 30_i64)
.or_ignore()
.execute(&conn)
.await?;
InsertBuilder::new("users")
.set(&USER_ID, "u1")
.set(&USER_NAME, "Bea")
.set(&USER_EMAIL, "bea@example.com")
.set(&USER_AGE, 31_i64)
.or_ignore()
.execute(&conn)
.await?;
let user: User = SelectBuilder::new("users")
.columns_raw(&USER_COLUMNS)
.filter(USER_ID.eq("u1"))
.fetch_one(&conn)
.await?;
assert_eq!(user.name, "Ann");
assert_eq!(user.email, "ann@example.com");
assert_eq!(user.age, Some(30));
Ok(())
}
async fn setup_probe() -> Result<libsql::Connection, Box<dyn std::error::Error>> {
let db = libsql::Builder::new_local(":memory:").build().await?;
let conn = db.connect()?;
conn
.execute(
"CREATE TABLE probe (id INTEGER PRIMARY KEY, val_int INTEGER, val_real REAL, val_text TEXT, val_blob BLOB)",
(),
)
.await?;
Ok(conn)
}
#[tokio::test]
async fn value_null_round_trips_as_none() -> TestResult {
let conn = setup_probe().await?;
conn
.execute_sql(
"INSERT INTO probe (id, val_int) VALUES (?1, ?2)",
vec![Value::Integer(1), Value::Null],
)
.await?;
let mut rows = conn
.query("SELECT val_int FROM probe WHERE id = 1", ())
.await?;
let row = rows.next().await?.ok_or("missing row")?;
let val: Option<i64> = row.get(0)?;
assert_eq!(val, None);
Ok(())
}
#[tokio::test]
async fn value_integer_round_trips_i64_max() -> TestResult {
let conn = setup_probe().await?;
conn
.execute_sql(
"INSERT INTO probe (id, val_int) VALUES (?1, ?2)",
vec![Value::Integer(1), Value::Integer(i64::MAX)],
)
.await?;
let mut rows = conn
.query("SELECT val_int FROM probe WHERE id = 1", ())
.await?;
let row = rows.next().await?.ok_or("missing row")?;
let val: i64 = row.get(0)?;
assert_eq!(val, i64::MAX);
Ok(())
}
#[tokio::test]
async fn value_real_round_trips() -> TestResult {
let conn = setup_probe().await?;
conn
.execute_sql(
"INSERT INTO probe (id, val_real) VALUES (?1, ?2)",
vec![Value::Integer(1), Value::Real(1.5)],
)
.await?;
let mut rows = conn
.query("SELECT val_real FROM probe WHERE id = 1", ())
.await?;
let row = rows.next().await?.ok_or("missing row")?;
let val: f64 = row.get(0)?;
assert!((val - 1.5).abs() < f64::EPSILON);
Ok(())
}
#[tokio::test]
async fn value_text_round_trips_unicode() -> TestResult {
let conn = setup_probe().await?;
conn
.execute_sql(
"INSERT INTO probe (id, val_text) VALUES (?1, ?2)",
vec![Value::Integer(1), Value::Text("héllo".to_owned())],
)
.await?;
let mut rows = conn
.query("SELECT val_text FROM probe WHERE id = 1", ())
.await?;
let row = rows.next().await?.ok_or("missing row")?;
let val: String = row.get(0)?;
assert_eq!(val, "héllo");
Ok(())
}
#[tokio::test]
async fn value_blob_round_trips_byte_exact() -> TestResult {
let conn = setup_probe().await?;
conn
.execute_sql(
"INSERT INTO probe (id, val_blob) VALUES (?1, ?2)",
vec![Value::Integer(1), Value::Blob(vec![0, 255, 7])],
)
.await?;
let mut rows = conn
.query("SELECT val_blob FROM probe WHERE id = 1", ())
.await?;
let row = rows.next().await?.ok_or("missing row")?;
let val: Vec<u8> = row.get(0)?;
assert_eq!(val, vec![0, 255, 7]);
Ok(())
}