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 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 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 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 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 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}