Skip to main content

tank_tests/
transaction2.rs

1use rust_decimal::Decimal;
2use std::sync::LazyLock;
3use tank::{Connection, Entity, FixedDecimal, Transaction, expr};
4use tokio::sync::Mutex;
5
6static MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
7
8#[derive(Entity, Debug, Clone)]
9#[tank(primary_key = Self::id)]
10struct Account {
11    id: String,
12    balance: FixedDecimal<12, 2>,
13    active: bool,
14    metadata: Option<String>,
15    #[tank(column_type = (sqlite = "BLOB"))]
16    payload: Option<Box<[u8]>>,
17}
18
19#[derive(Entity, Debug, Clone)]
20#[tank(primary_key = Self::id)]
21struct Transfer {
22    id: i64,
23    from: String,
24    to: String,
25    amount: FixedDecimal<12, 2>,
26    note: Option<String>,
27}
28
29pub async fn transaction2(connection: &mut impl Connection) {
30    let _lock = MUTEX.lock().await;
31
32    // Setup
33    Account::drop_table(connection, true, false)
34        .await
35        .expect("Failed to drop Account table");
36    Account::create_table(connection, true, true)
37        .await
38        .expect("Failed to create Account table");
39    Transfer::drop_table(connection, true, false)
40        .await
41        .expect("Failed to drop Transfer table");
42    Transfer::create_table(connection, true, true)
43        .await
44        .expect("Failed to create Transfer table");
45
46    // Insert initial accounts
47    let accounts = [
48        Account {
49            id: "A".into(),
50            balance: Decimal::new(1000_00, 2).into(),
51            active: true,
52            metadata: Some("primary".into()),
53            payload: None,
54        },
55        Account {
56            id: "B".into(),
57            balance: Decimal::new(500_00, 2).into(),
58            active: true,
59            metadata: None,
60            payload: Some(vec![0x1, 0x2, 0x3].into()),
61        },
62        Account {
63            id: "C".into(),
64            balance: Decimal::new(0_00, 2).into(),
65            active: true,
66            metadata: None,
67            payload: None,
68        },
69    ];
70    Account::insert_many(connection, &accounts)
71        .await
72        .expect("Could not insert initial accounts");
73
74    let mut tx = connection
75        .begin()
76        .await
77        .expect("Could not begin transaction");
78
79    // Transfer 200.00 A -> B
80    let mut a = Account::find_one(&mut tx, expr!(Account::id == "A"))
81        .await
82        .expect("Failed to query A")
83        .expect("Account A missing");
84    let mut b = Account::find_one(&mut tx, expr!(Account::id == "B"))
85        .await
86        .expect("Failed to query B")
87        .expect("Account B missing");
88
89    let amount = Decimal::new(200_00, 2);
90    a.balance.0 -= amount;
91    b.balance.0 += amount;
92
93    a.save(&mut tx).await.expect("Could not save A in tx");
94    b.save(&mut tx).await.expect("Could not save B in tx");
95
96    Transfer::insert_one(
97        &mut tx,
98        &Transfer {
99            id: 1,
100            from: "A".into(),
101            to: "B".into(),
102            amount: amount.into(),
103            note: Some("A->B first transfer".into()),
104        },
105    )
106    .await
107    .expect("Could not insert transfer log");
108
109    tx.commit().await.expect("Could not commit tx");
110
111    let a_after = Account::find_one(connection, expr!(Account::id == "A"))
112        .await
113        .expect("Failed to read A after commit")
114        .expect("Account A missing after commit");
115    let b_after = Account::find_one(connection, expr!(Account::id == "B"))
116        .await
117        .expect("Failed to read B after commit")
118        .expect("Account B missing after commit");
119    let a_after_dec: Decimal = a_after.balance.into();
120    let b_after_dec: Decimal = b_after.balance.into();
121    assert_eq!(a_after_dec, Decimal::new(800_00, 2));
122    assert_eq!(b_after_dec, Decimal::new(700_00, 2));
123
124    // Transfer 300_00 B -> C and rollback
125    let mut tx2 = connection
126        .begin()
127        .await
128        .expect("Could not begin second transaction");
129    let mut b2 = Account::find_one(&mut tx2, expr!(Account::id == "B"))
130        .await
131        .expect("Failed to read B in tx2")
132        .expect("Account B missing in tx2");
133    let mut c2 = Account::find_one(&mut tx2, expr!(Account::id == "C"))
134        .await
135        .expect("Failed to read C in tx2")
136        .expect("Account C missing in tx2");
137
138    let t2_amount = Decimal::new(300_00, 2);
139    b2.balance.0 -= t2_amount;
140    c2.balance.0 += t2_amount;
141    b2.save(&mut tx2).await.expect("Could not save B in tx2");
142    c2.save(&mut tx2).await.expect("Could not save C in tx2");
143
144    Transfer::insert_one(
145        &mut tx2,
146        &Transfer {
147            id: 2,
148            from: "B".into(),
149            to: "C".into(),
150            amount: t2_amount.into(),
151            note: Some("B->C rolled back".into()),
152        },
153    )
154    .await
155    .expect("Could not insert transfer log in tx2");
156
157    tx2.rollback().await.expect("Could not rollback tx2");
158
159    let b_after_rb = Account::find_one(connection, expr!(Account::id == "B"))
160        .await
161        .expect("Failed to read B after rollback")
162        .expect("Account B missing after rollback");
163    let c_after_rb = Account::find_one(connection, expr!(Account::id == "C"))
164        .await
165        .expect("Failed to read C after rollback")
166        .expect("Account C missing after rollback");
167    let b_after_rb_dec: Decimal = b_after_rb.balance.into();
168    let c_after_rb_dec: Decimal = c_after_rb.balance.into();
169    assert_eq!(b_after_rb_dec, Decimal::new(700_00, 2));
170    assert_eq!(c_after_rb_dec, Decimal::new(0_00, 2));
171
172    // Delete account C and commit
173    let mut tx3 = connection
174        .begin()
175        .await
176        .expect("Could not begin third transaction");
177    let c_entity = Account::find_one(&mut tx3, expr!(Account::id == "C"))
178        .await
179        .expect("Failed to read C in tx3")
180        .expect("Account C missing in tx3");
181    c_entity
182        .delete(&mut tx3)
183        .await
184        .expect("Could not delete C in tx3");
185    tx3.commit().await.expect("Could not commit tx3");
186    let c_final = Account::find_one(connection, expr!(Account::id == "C"))
187        .await
188        .expect("Failed to read C final");
189    assert!(c_final.is_none(), "Account C should be deleted");
190}