use std::env;
use std::sync::atomic::{AtomicU32, Ordering};
use zero_mysql::error::Error;
use zero_mysql::sync::Conn;
include!("common/check.rs");
include!("common/check_eq.rs");
static TABLE_COUNTER: AtomicU32 = AtomicU32::new(0);
fn get_conn() -> Result<Conn, Error> {
let url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "mysql://test:1234@localhost:3306/test".to_string());
Conn::new(url.as_str())
}
struct TestTable {
name: String,
}
impl TestTable {
fn new(conn: &mut Conn) -> Result<Self, Error> {
let id = TABLE_COUNTER.fetch_add(1, Ordering::SeqCst);
let name = format!("tx_test_{}", id);
conn.query_drop(&format!("DROP TABLE IF EXISTS {}", name))?;
conn.query_drop(&format!(
"CREATE TABLE {} (id INT AUTO_INCREMENT PRIMARY KEY, value INT)",
name
))?;
Ok(Self { name })
}
fn count(&self, conn: &mut Conn) -> Result<i64, Error> {
let mut stmt = conn.prepare(&format!("SELECT COUNT(*) FROM {}", self.name))?;
let rows: Vec<(i64,)> = conn.exec_collect(&mut stmt, ())?;
Ok(rows[0].0)
}
fn cleanup(&self, conn: &mut Conn) {
let _ = conn.query_drop(&format!("DROP TABLE IF EXISTS {}", self.name));
}
}
#[test]
fn transaction_explicit_commit() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
conn.transaction(|conn, tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (42)", table.name))?;
tx.commit(conn)
})?;
check_eq!(table.count(&mut conn)?, 1);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_explicit_rollback() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
conn.transaction(|conn, tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (42)", table.name))?;
tx.rollback(conn)
})?;
check_eq!(table.count(&mut conn)?, 0);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_implicit_commit_on_ok() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (42)", table.name))?;
Ok(())
})?;
check_eq!(table.count(&mut conn)?, 1);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_implicit_rollback_on_err() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
let result: Result<(), Error> = conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (42)", table.name))?;
Err(Error::BadUsageError("intentional error".into()))
});
check!(result.is_err());
check_eq!(table.count(&mut conn)?, 0);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_implicit_commit_with_return_value() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
let result: i32 = conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (42)", table.name))?;
Ok(123)
})?;
check_eq!(result, 123);
check_eq!(table.count(&mut conn)?, 1);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_implicit_commit_multiple_inserts() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
conn.transaction(|conn, _tx| {
for i in 1..=5 {
conn.query_drop(&format!(
"INSERT INTO {} (value) VALUES ({})",
table.name, i
))?;
}
Ok(())
})?;
check_eq!(table.count(&mut conn)?, 5);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_implicit_rollback_partial_work() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
let result: Result<(), Error> = conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (1)", table.name))?;
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (2)", table.name))?;
Err(Error::BadUsageError("intentional error".into()))
});
check!(result.is_err());
check_eq!(table.count(&mut conn)?, 0);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_connection_usable_after_implicit_commit() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (1)", table.name))?;
Ok(())
})?;
conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (2)", table.name))?;
Ok(())
})?;
check_eq!(table.count(&mut conn)?, 2);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_connection_usable_after_implicit_rollback() -> Result<(), Error> {
let mut conn = get_conn()?;
let table = TestTable::new(&mut conn)?;
let _: Result<(), Error> = conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (1)", table.name))?;
Err(Error::BadUsageError("intentional error".into()))
});
conn.transaction(|conn, _tx| {
conn.query_drop(&format!("INSERT INTO {} (value) VALUES (2)", table.name))?;
Ok(())
})?;
check_eq!(table.count(&mut conn)?, 1);
table.cleanup(&mut conn);
Ok(())
}
#[test]
fn transaction_not_in_transaction_after_implicit_commit() -> Result<(), Error> {
let mut conn = get_conn()?;
conn.transaction(|conn, _tx| {
check!(conn.in_transaction());
Ok(())
})?;
check!(!conn.in_transaction());
Ok(())
}
#[test]
fn transaction_not_in_transaction_after_implicit_rollback() -> Result<(), Error> {
let mut conn = get_conn()?;
let _: Result<(), Error> =
conn.transaction(|_conn, _tx| Err(Error::BadUsageError("intentional error".into())));
check!(!conn.in_transaction());
Ok(())
}