1021 lines
29 KiB
Rust
1021 lines
29 KiB
Rust
#![allow(unused_imports, dead_code)]
|
|
|
|
pub mod common;
|
|
|
|
pub use common::{TestContext, bakery_chain::*, setup::*};
|
|
use pretty_assertions::assert_eq;
|
|
use sea_orm::{
|
|
AccessMode, DatabaseTransaction, IsolationLevel, Set, SqliteTransactionMode,
|
|
TransactionOptions, TransactionTrait, prelude::*,
|
|
};
|
|
|
|
fn seaside_bakery() -> bakery::ActiveModel {
|
|
bakery::ActiveModel {
|
|
name: Set("SeaSide Bakery".to_owned()),
|
|
profit_margin: Set(10.4),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
fn top_bakery() -> bakery::ActiveModel {
|
|
bakery::ActiveModel {
|
|
name: Set("Top Bakery".to_owned()),
|
|
profit_margin: Set(15.0),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction() {
|
|
let ctx = TestContext::new("transaction_test").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
ctx.db
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = seaside_bakery().save(txn).await?;
|
|
let _ = top_bakery().save(txn).await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 2);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
#[cfg(feature = "rbac")]
|
|
pub async fn rbac_transaction() {
|
|
use sea_orm::rbac::{RbacEngine, RbacSnapshot, RbacUserId};
|
|
|
|
let ctx = TestContext::new("rbac_transaction_test").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
ctx.db.replace_rbac(RbacEngine::from_snapshot(
|
|
RbacSnapshot::danger_unrestricted(),
|
|
));
|
|
let db = ctx.db.restricted_for(RbacUserId(0)).unwrap();
|
|
|
|
db.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = seaside_bakery().save(txn).await?;
|
|
let _ = top_bakery().save(txn).await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 2);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_with_reference() {
|
|
let ctx = TestContext::new("transaction_with_reference_test").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
let name1 = "SeaSide Bakery";
|
|
let name2 = "Top Bakery";
|
|
let search_name = "Bakery";
|
|
ctx.db
|
|
.transaction_async(async |txn| {
|
|
_transaction_with_reference(txn, name1, name2, search_name).await
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
async fn _transaction_with_reference(
|
|
txn: &DatabaseTransaction,
|
|
name1: &str,
|
|
name2: &str,
|
|
search_name: &str,
|
|
) -> Result<(), DbErr> {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set(name1.to_owned()),
|
|
profit_margin: Set(10.4),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let _ = bakery::ActiveModel {
|
|
name: Set(name2.to_owned()),
|
|
profit_margin: Set(15.0),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains(search_name))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_begin_out_of_scope() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_begin_out_of_scope_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = ctx.db.begin().await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// The scope ended and transaction is dropped without commit
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
#[cfg(feature = "rbac")]
|
|
pub async fn rbac_transaction_begin_out_of_scope() -> Result<(), DbErr> {
|
|
use sea_orm::rbac::{RbacEngine, RbacSnapshot, RbacUserId};
|
|
|
|
let ctx = TestContext::new("rbac_transaction_begin_out_of_scope_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
ctx.db.replace_rbac(RbacEngine::from_snapshot(
|
|
RbacSnapshot::danger_unrestricted(),
|
|
));
|
|
let db = ctx.db.restricted_for(RbacUserId(0)).unwrap();
|
|
|
|
assert_eq!(bakery::Entity::find().all(&db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = db.begin().await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// The scope ended and transaction is dropped without commit
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&db).await?.len(), 0);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_begin_commit() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_begin_commit_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = ctx.db.begin().await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// Commit changes before the end of scope
|
|
txn.commit().await?;
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 2);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
#[cfg(feature = "rbac")]
|
|
pub async fn rbac_transaction_begin_commit() -> Result<(), DbErr> {
|
|
use sea_orm::rbac::{RbacEngine, RbacSnapshot, RbacUserId};
|
|
|
|
let ctx = TestContext::new("rbac_transaction_begin_commit_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
ctx.db.replace_rbac(RbacEngine::from_snapshot(
|
|
RbacSnapshot::danger_unrestricted(),
|
|
));
|
|
let db = ctx.db.restricted_for(RbacUserId(0)).unwrap();
|
|
|
|
assert_eq!(bakery::Entity::find().all(&db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = db.begin().await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// Commit changes before the end of scope
|
|
txn.commit().await?;
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&db).await?.len(), 2);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_begin_rollback() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_begin_rollback_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = ctx.db.begin().await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// Rollback changes before the end of scope
|
|
txn.rollback().await?;
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_closure_commit() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_closure_commit_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
let res = ctx
|
|
.db
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
seaside_bakery().save(txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(txn).await?.len(), 1);
|
|
|
|
top_bakery().save(txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(txn).await?.len(), 2);
|
|
|
|
Ok(())
|
|
})
|
|
.await;
|
|
|
|
assert!(res.is_ok());
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 2);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_closure_rollback() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_closure_rollback_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
let res = ctx
|
|
.db
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
seaside_bakery().save(txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(txn).await?.len(), 1);
|
|
|
|
top_bakery().save(txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(txn).await?.len(), 2);
|
|
|
|
bakery::ActiveModel {
|
|
id: Set(1),
|
|
name: Set("Duplicated primary key".to_owned()),
|
|
profit_margin: Set(20.0),
|
|
}
|
|
.insert(txn)
|
|
.await?; // Throw error and rollback
|
|
|
|
// This line won't be reached
|
|
unreachable!();
|
|
|
|
#[allow(unreachable_code)]
|
|
Ok(())
|
|
})
|
|
.await;
|
|
|
|
assert!(res.is_err());
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_with_active_model_behaviour() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_with_active_model_behaviour_test").await;
|
|
create_bakery_table(&ctx.db).await?;
|
|
create_cake_table(&ctx.db).await?;
|
|
|
|
if let Ok(txn) = ctx.db.begin().await {
|
|
assert_eq!(
|
|
cake::ActiveModel {
|
|
name: Set("Cake with invalid price".to_owned()),
|
|
price: Set(rust_dec(0)),
|
|
gluten_free: Set(false),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await,
|
|
Err(DbErr::Custom(
|
|
"[before_save] Invalid Price, insert: true".to_owned()
|
|
))
|
|
);
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 0);
|
|
|
|
assert_eq!(
|
|
cake::ActiveModel {
|
|
name: Set("Cake with invalid price".to_owned()),
|
|
price: Set(rust_dec(-10)),
|
|
gluten_free: Set(false),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await,
|
|
Err(DbErr::Custom(
|
|
"[after_save] Invalid Price, insert: true".to_owned()
|
|
))
|
|
);
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
let readonly_cake_1 = cake::ActiveModel {
|
|
name: Set("Readonly cake (err_on_before_delete)".to_owned()),
|
|
price: Set(rust_dec(10)),
|
|
gluten_free: Set(true),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
assert_eq!(
|
|
readonly_cake_1.delete(&txn).await.err(),
|
|
Some(DbErr::Custom(
|
|
"[before_delete] Cannot be deleted".to_owned()
|
|
))
|
|
);
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
let readonly_cake_2 = cake::ActiveModel {
|
|
name: Set("Readonly cake (err_on_after_delete)".to_owned()),
|
|
price: Set(rust_dec(10)),
|
|
gluten_free: Set(true),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 3);
|
|
|
|
assert_eq!(
|
|
readonly_cake_2.delete(&txn).await.err(),
|
|
Some(DbErr::Custom("[after_delete] Cannot be deleted".to_owned()))
|
|
);
|
|
|
|
assert_eq!(cake::Entity::find().all(&txn).await?.len(), 2);
|
|
}
|
|
|
|
assert_eq!(cake::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_nested() {
|
|
let ctx = TestContext::new("transaction_nested_test").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
ctx.db
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = seaside_bakery().save(txn).await?;
|
|
|
|
let _ = top_bakery().save(txn).await?;
|
|
|
|
// Try nested transaction committed
|
|
txn.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Nested Bakery".to_owned()),
|
|
profit_margin: Set(88.88),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 3);
|
|
|
|
// Try nested-nested transaction rollbacked
|
|
let is_err = txn
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
if true {
|
|
Err(DbErr::Query(RuntimeErr::Internal(
|
|
"Force Rollback!".to_owned(),
|
|
)))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
.await
|
|
.is_err();
|
|
|
|
assert!(is_err);
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 3);
|
|
|
|
// Try nested-nested transaction committed
|
|
txn.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
// Try nested transaction rollbacked
|
|
let is_err = txn
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 5);
|
|
|
|
// Try nested-nested transaction committed
|
|
txn.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 6);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 6);
|
|
|
|
// Try nested-nested transaction rollbacked
|
|
let is_err = txn
|
|
.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 7);
|
|
|
|
if true {
|
|
Err(DbErr::Query(RuntimeErr::Internal(
|
|
"Force Rollback!".to_owned(),
|
|
)))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
.await
|
|
.is_err();
|
|
|
|
assert!(is_err);
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 6);
|
|
|
|
if true {
|
|
Err(DbErr::Query(RuntimeErr::Internal(
|
|
"Force Rollback!".to_owned(),
|
|
)))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
})
|
|
.await
|
|
.is_err();
|
|
|
|
assert!(is_err);
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&ctx.db)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_manager_nested() -> Result<(), sea_orm::DbErr> {
|
|
let ctx = TestContext::new("transaction_manager_nested").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
let txn = ctx.db.begin().await?;
|
|
let _ = seaside_bakery().save(&txn).await?;
|
|
let _ = top_bakery().save(&txn).await?;
|
|
|
|
// Try nested transaction committed
|
|
{
|
|
let txn = txn.begin().await?;
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Nested Bakery".to_owned()),
|
|
profit_margin: Set(88.88),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 3);
|
|
|
|
// Try nested-nested transaction rollbacked
|
|
{
|
|
let txn = txn.begin().await?;
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
}
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 3);
|
|
|
|
// Try nested-nested transaction committed
|
|
{
|
|
let txn = txn.begin().await?;
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
txn.commit().await?;
|
|
}
|
|
|
|
txn.commit().await?;
|
|
}
|
|
|
|
// Try nested transaction rollbacked
|
|
{
|
|
let txn = txn.begin().await?;
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 5);
|
|
|
|
// Try nested-nested transaction committed
|
|
{
|
|
let txn = txn.begin().await?;
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(&txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 6);
|
|
txn.commit().await?;
|
|
}
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 6);
|
|
}
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
txn.commit().await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&ctx.db)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
ctx.delete().await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
#[cfg(feature = "rbac")]
|
|
pub async fn rbac_transaction_nested() {
|
|
use sea_orm::rbac::{RbacEngine, RbacSnapshot, RbacUserId};
|
|
|
|
let ctx = TestContext::new("rbac_transaction_nested_test").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
ctx.db.replace_rbac(RbacEngine::from_snapshot(
|
|
RbacSnapshot::danger_unrestricted(),
|
|
));
|
|
let db = ctx.db.restricted_for(RbacUserId(0)).unwrap();
|
|
|
|
db.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = seaside_bakery().save(txn).await?;
|
|
|
|
let _ = top_bakery().save(txn).await?;
|
|
|
|
// Try nested transaction committed
|
|
txn.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Nested Bakery".to_owned()),
|
|
profit_margin: Set(88.88),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 3);
|
|
|
|
// Try nested-nested transaction committed
|
|
txn.transaction_async::<_, _, DbErr>(async |txn| {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set("Rock n Roll Bakery".to_owned()),
|
|
profit_margin: Set(28.8),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
Ok(())
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(&ctx.db)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(bakeries.len(), 4);
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_with_config() {
|
|
let ctx = TestContext::new("transaction_with_config").await;
|
|
create_bakery_table(&ctx.db).await.unwrap();
|
|
|
|
for (i, (isolation_level, access_mode)) in [
|
|
(IsolationLevel::RepeatableRead, None),
|
|
(IsolationLevel::ReadCommitted, None),
|
|
(IsolationLevel::ReadUncommitted, Some(AccessMode::ReadWrite)),
|
|
(IsolationLevel::Serializable, Some(AccessMode::ReadWrite)),
|
|
]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let name1 = format!("SeaSide Bakery {}", i);
|
|
let name2 = format!("Top Bakery {}", i);
|
|
let search_name = format!("Bakery {}", i);
|
|
ctx.db
|
|
.transaction_with_config_async(
|
|
async |txn| _transaction_with_config(txn, name1, name2, search_name).await,
|
|
Some(isolation_level),
|
|
access_mode,
|
|
)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
ctx.db
|
|
.transaction_with_config_async::<_, _, DbErr>(
|
|
async |txn| {
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains("Bakery"))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 8);
|
|
|
|
Ok(())
|
|
},
|
|
None,
|
|
Some(AccessMode::ReadOnly),
|
|
)
|
|
.await
|
|
.unwrap();
|
|
|
|
ctx.delete().await;
|
|
}
|
|
|
|
async fn _transaction_with_config(
|
|
txn: &DatabaseTransaction,
|
|
name1: String,
|
|
name2: String,
|
|
search_name: String,
|
|
) -> Result<(), DbErr> {
|
|
let _ = bakery::ActiveModel {
|
|
name: Set(name1),
|
|
profit_margin: Set(10.4),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let _ = bakery::ActiveModel {
|
|
name: Set(name2),
|
|
profit_margin: Set(15.0),
|
|
..Default::default()
|
|
}
|
|
.save(txn)
|
|
.await?;
|
|
|
|
let bakeries = Bakery::find()
|
|
.filter(bakery::Column::Name.contains(&search_name))
|
|
.all(txn)
|
|
.await?;
|
|
|
|
assert_eq!(bakeries.len(), 2);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[sea_orm_macros::test]
|
|
pub async fn transaction_begin_with_options() -> Result<(), DbErr> {
|
|
let ctx = TestContext::new("transaction_begin_with_options_test").await;
|
|
create_tables(&ctx.db).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 0);
|
|
|
|
{
|
|
// Transaction begin in this scope
|
|
let txn = ctx
|
|
.db
|
|
.begin_with_options(TransactionOptions {
|
|
sqlite_transaction_mode: Some(SqliteTransactionMode::Immediate),
|
|
..Default::default()
|
|
})
|
|
.await?;
|
|
|
|
seaside_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 1);
|
|
|
|
top_bakery().save(&txn).await?;
|
|
|
|
assert_eq!(bakery::Entity::find().all(&txn).await?.len(), 2);
|
|
|
|
// Commit changes before the end of scope
|
|
txn.commit().await?;
|
|
}
|
|
|
|
assert_eq!(bakery::Entity::find().all(&ctx.db).await?.len(), 2);
|
|
|
|
ctx.delete().await;
|
|
Ok(())
|
|
}
|