blob: a8a9bdf0dc2c0b3810195538cd8539ca871621d6 [file] [log] [blame]
use crate::schema::*;
use diesel::result::Error;
use diesel::*;
#[test]
#[cfg(not(feature = "sqlite"))] // FIXME: This test is only valid when operating on a file and not :memory:
fn transaction_executes_fn_in_a_sql_transaction() {
const TEST_NAME: &str = "transaction_executes_fn_in_a_sql_transaction";
let conn1 = &mut connection_without_transaction();
let conn2 = &mut connection_without_transaction();
setup_test_table(conn1, TEST_NAME);
fn get_count(conn: &mut TestConnection) -> i64 {
count_test_table(conn, TEST_NAME)
}
conn1
.transaction::<_, Error, _>(|conn1| {
assert_eq!(0, get_count(conn1));
assert_eq!(0, get_count(conn2));
diesel::sql_query(format!("INSERT INTO {TEST_NAME} DEFAULT VALUES")).execute(conn1)?;
assert_eq!(1, get_count(conn1));
assert_eq!(0, get_count(conn2));
Ok(())
})
.unwrap();
assert_eq!(1, get_count(conn1));
assert_eq!(1, get_count(conn2));
drop_test_table(conn1, TEST_NAME);
}
#[test]
fn transaction_returns_the_returned_value() {
let conn1 = &mut connection_without_transaction();
assert_eq!(Ok(1), conn1.transaction::<_, Error, _>(|_| Ok(1)));
}
#[test]
fn transaction_is_rolled_back_when_returned_an_error() {
let connection = &mut connection_without_transaction();
let test_name = "transaction_is_rolled_back_when_returned_an_error";
setup_test_table(connection, test_name);
let _ = connection.transaction::<(), _, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {test_name} DEFAULT VALUES"))
.execute(connection)
.unwrap();
Err(Error::RollbackTransaction)
});
assert_eq!(0, count_test_table(connection, test_name));
drop_test_table(connection, test_name);
}
// This test uses a SQLite3 fact to generate a rollback error,
// so that we can verify error. Reference:
// https://www.sqlite.org/lang_transaction.html
//
// The same trick cannot be used for PostgreSQL as it generates
// warning, but not error if a rollback is called twice. Reference:
// https://www.postgresql.org/docs/9.4/sql-rollback.html
//
// The same trick seems to work for MySQL as well based on the
// test result, but I cannot find a document support yet. Hence
// this test is marked for "sqlite" only as this moment. FIXME.
#[test]
#[cfg(feature = "sqlite")]
fn transaction_rollback_returns_error() {
let connection = &mut connection_without_transaction();
let test_name = "transaction_rollback_returns_error";
setup_test_table(connection, test_name);
// Create a transaction that will fail to rollback.
let r = connection.transaction::<usize, _, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {test_name} DEFAULT VALUES"))
.execute(connection)
.unwrap();
// This rollback would succeed, and cause any rollback later to fail.
diesel::sql_query("ROLLBACK").execute(connection).unwrap();
// Return any error to trigger a rollback that fails in this case.
Err(Error::NotFound)
});
// Verify that the transaction failed with an error from database (and not the original
// "NotFound").
assert!(matches!(r.unwrap_err(), Error::DatabaseError(_, _)));
assert_eq!(0, count_test_table(connection, test_name));
drop_test_table(connection, test_name);
}
#[test]
fn transactions_can_be_nested() {
let connection = &mut connection_without_transaction();
const TEST_NAME: &str = "transactions_can_be_nested";
setup_test_table(connection, TEST_NAME);
fn get_count(connection: &mut TestConnection) -> i64 {
count_test_table(connection, TEST_NAME)
}
let _ = connection.transaction::<(), _, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {TEST_NAME} DEFAULT VALUES"))
.execute(connection)
.unwrap();
assert_eq!(1, get_count(connection));
let _ = connection.transaction::<(), _, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {TEST_NAME} DEFAULT VALUES"))
.execute(connection)
.unwrap();
assert_eq!(2, get_count(connection));
Err(Error::RollbackTransaction)
});
assert_eq!(1, get_count(connection));
let _ = connection.transaction::<(), Error, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {TEST_NAME} DEFAULT VALUES"))
.execute(connection)
.unwrap();
assert_eq!(2, get_count(connection));
Ok(())
});
assert_eq!(2, get_count(connection));
Err(Error::RollbackTransaction)
});
assert_eq!(0, get_count(connection));
drop_test_table(connection, TEST_NAME);
}
#[test]
fn test_transaction_always_rolls_back() {
let connection = &mut connection_without_transaction();
let test_name = "test_transaction_always_rolls_back";
setup_test_table(connection, test_name);
let result = connection.test_transaction::<_, Error, _>(|connection| {
diesel::sql_query(format!("INSERT INTO {test_name} DEFAULT VALUES")).execute(connection)?;
assert_eq!(1, count_test_table(connection, test_name));
Ok("success")
});
assert_eq!(0, count_test_table(connection, test_name));
assert_eq!("success", result);
drop_test_table(connection, test_name);
}
#[test]
#[should_panic(expected = "Transaction did not succeed")]
fn test_transaction_panics_on_error() {
let connection = &mut connection_without_transaction();
connection.test_transaction::<(), _, _>(|_| Err(()));
}
fn setup_test_table(connection: &mut TestConnection, table_name: &str) {
use crate::schema_dsl::*;
create_table(table_name, (integer("id").primary_key().auto_increment(),))
.execute(connection)
.unwrap();
}
fn drop_test_table(connection: &mut TestConnection, table_name: &str) {
diesel::sql_query(format!("DROP TABLE {table_name}"))
.execute(connection)
.unwrap();
}
fn count_test_table(connection: &mut TestConnection, table_name: &str) -> i64 {
use diesel::dsl::sql;
select(sql::<sql_types::BigInt>(&format!(
"COUNT(*) FROM {table_name}"
)))
.first(connection)
.unwrap()
}
#[test]
#[cfg(feature = "postgres")]
fn regression_test_for_2123() {
let conn = &mut connection_without_transaction();
// fail once
let ret = conn.transaction(|conn| {
let _ = conn.transaction(|conn| {
// handling error
match diesel::sql_query("SELECT foo").execute(conn) {
// do nothing
Ok(_) => unreachable!("This query should fail"),
// ignore the error
Err(e) => eprintln!("error occurred: {e}"),
};
Ok::<_, Error>(())
});
conn.transaction(|conn| {
let ret = diesel::sql_query("SELECT 1").execute(conn);
assert_eq!(Ok(1), ret);
Ok::<_, Error>(())
})
});
println!("{ret:?}");
// other transaction
let ret = conn
.build_transaction()
.serializable()
.run(|conn| diesel::sql_query("SELECT 1").execute(conn));
// must be Ok(1), but get Err(AlreadyInTransaction)
println!("{ret:?}");
assert_eq!(Ok(1), ret);
}