1pub(crate) mod journal;
6mod overlay;
7pub mod session;
8
9use wasm_dbms_api::prelude::{
10 ColumnDef, DbmsResult, DeleteBehavior, Filter, TableSchema, UpdateRecord as _, Value,
11};
12
13pub use self::overlay::DatabaseOverlay;
14
15#[derive(Debug, Default)]
18pub struct Transaction {
19 pub(crate) operations: Vec<TransactionOp>,
21 overlay: DatabaseOverlay,
23}
24
25impl Transaction {
26 pub fn insert<T>(&mut self, values: Vec<(ColumnDef, Value)>) -> DbmsResult<()>
28 where
29 T: TableSchema,
30 {
31 self.overlay.insert::<T>(values.clone())?;
32 self.operations.push(TransactionOp::Insert {
33 table: T::table_name(),
34 values,
35 });
36 Ok(())
37 }
38
39 pub fn update<T>(
41 &mut self,
42 patch: T::Update,
43 filter: Option<Filter>,
44 primary_keys: Vec<Value>,
45 ) -> DbmsResult<()>
46 where
47 T: TableSchema,
48 {
49 let patch_values = patch.update_values();
50 let overlay_patch: Vec<_> = patch_values
51 .iter()
52 .map(|(col, val)| (col.name, val.clone()))
53 .collect();
54
55 for pk in primary_keys {
56 self.overlay.update::<T>(pk, overlay_patch.clone());
57 }
58
59 self.operations.push(TransactionOp::Update {
60 table: T::table_name(),
61 patch: patch_values,
62 filter,
63 });
64 Ok(())
65 }
66
67 pub fn delete<T>(
69 &mut self,
70 behaviour: DeleteBehavior,
71 filter: Option<Filter>,
72 primary_keys: Vec<Value>,
73 ) -> DbmsResult<()>
74 where
75 T: TableSchema,
76 {
77 for pk in primary_keys {
78 self.overlay.delete::<T>(pk);
79 }
80
81 self.operations.push(TransactionOp::Delete {
82 table: T::table_name(),
83 behaviour,
84 filter,
85 });
86 Ok(())
87 }
88
89 pub fn overlay(&self) -> &DatabaseOverlay {
91 &self.overlay
92 }
93
94 pub fn overlay_mut(&mut self) -> &mut DatabaseOverlay {
96 &mut self.overlay
97 }
98}
99
100#[derive(Debug)]
102pub enum TransactionOp {
103 Insert {
104 table: &'static str,
105 values: Vec<(ColumnDef, Value)>,
106 },
107 Delete {
108 table: &'static str,
109 behaviour: DeleteBehavior,
110 filter: Option<Filter>,
111 },
112 Update {
113 table: &'static str,
114 patch: Vec<(ColumnDef, Value)>,
115 filter: Option<Filter>,
116 },
117}
118
119#[cfg(test)]
120mod tests {
121
122 use wasm_dbms_api::prelude::{
123 Database as _, InsertRecord as _, Query, TableSchema as _, Text, Uint32, UpdateRecord as _,
124 Value,
125 };
126 use wasm_dbms_macros::{DatabaseSchema, Table};
127 use wasm_dbms_memory::prelude::HeapMemoryProvider;
128
129 use super::*;
130 use crate::prelude::{DbmsContext, WasmDbmsDatabase};
131
132 #[derive(Debug, Table, Clone, PartialEq, Eq)]
133 #[table = "items"]
134 pub struct Item {
135 #[primary_key]
136 pub id: Uint32,
137 pub name: Text,
138 }
139
140 #[derive(DatabaseSchema)]
141 #[tables(Item = "items")]
142 pub struct TestSchema;
143
144 fn setup() -> DbmsContext<HeapMemoryProvider> {
145 let ctx = DbmsContext::new(HeapMemoryProvider::default());
146 TestSchema::register_tables(&ctx).unwrap();
147 ctx
148 }
149
150 #[test]
151 fn test_transaction_insert_records_operation() {
152 let mut tx = Transaction::default();
153 let values = vec![
154 (Item::columns()[0], Value::Uint32(Uint32(1))),
155 (Item::columns()[1], Value::Text(Text("foo".to_string()))),
156 ];
157 tx.insert::<Item>(values).unwrap();
158 assert_eq!(tx.operations.len(), 1);
159 assert!(matches!(
160 &tx.operations[0],
161 TransactionOp::Insert { table: "items", .. }
162 ));
163 }
164
165 #[test]
166 fn test_transaction_update_records_operation() {
167 let mut tx = Transaction::default();
168 let patch = ItemUpdateRequest::from_values(
169 &[(Item::columns()[1], Value::Text(Text("bar".to_string())))],
170 Some(Filter::eq("id", Value::Uint32(Uint32(1)))),
171 );
172 tx.update::<Item>(
173 patch,
174 Some(Filter::eq("id", Value::Uint32(Uint32(1)))),
175 vec![Value::Uint32(Uint32(1))],
176 )
177 .unwrap();
178 assert_eq!(tx.operations.len(), 1);
179 assert!(matches!(
180 &tx.operations[0],
181 TransactionOp::Update { table: "items", .. }
182 ));
183 }
184
185 #[test]
186 fn test_transaction_delete_records_operation() {
187 let mut tx = Transaction::default();
188 tx.delete::<Item>(
189 DeleteBehavior::Restrict,
190 Some(Filter::eq("id", Value::Uint32(Uint32(1)))),
191 vec![Value::Uint32(Uint32(1))],
192 )
193 .unwrap();
194 assert_eq!(tx.operations.len(), 1);
195 assert!(matches!(
196 &tx.operations[0],
197 TransactionOp::Delete {
198 table: "items",
199 behaviour: DeleteBehavior::Restrict,
200 ..
201 }
202 ));
203 }
204
205 #[test]
206 fn test_transaction_overlay_accessors() {
207 let mut tx = Transaction::default();
208 let overlay = tx.overlay();
210 let overlay_str = format!("{overlay:?}");
211 assert!(overlay_str.contains("DatabaseOverlay"));
212
213 let _overlay_mut = tx.overlay_mut();
214 }
215
216 #[test]
217 fn test_transaction_multiple_operations() {
218 let mut tx = Transaction::default();
219 let insert_values = vec![
220 (Item::columns()[0], Value::Uint32(Uint32(1))),
221 (Item::columns()[1], Value::Text(Text("a".to_string()))),
222 ];
223 tx.insert::<Item>(insert_values).unwrap();
224 tx.delete::<Item>(
225 DeleteBehavior::Cascade,
226 None,
227 vec![Value::Uint32(Uint32(1))],
228 )
229 .unwrap();
230 assert_eq!(tx.operations.len(), 2);
231 }
232
233 #[test]
234 fn test_rollback_discards_transaction() {
235 let ctx = setup();
236 let owner = vec![1, 2, 3];
237 let tx_id = ctx.begin_transaction(owner);
238 let mut db = WasmDbmsDatabase::from_transaction(&ctx, TestSchema, tx_id);
239
240 let insert = ItemInsertRequest::from_values(&[
241 (Item::columns()[0], Value::Uint32(Uint32(42))),
242 (
243 Item::columns()[1],
244 Value::Text(Text("rolled_back".to_string())),
245 ),
246 ])
247 .unwrap();
248 db.insert::<Item>(insert).unwrap();
249
250 db.rollback().unwrap();
251
252 let db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
254 let rows = db.select::<Item>(Query::builder().build()).unwrap();
255 assert!(rows.is_empty());
256 }
257
258 #[test]
259 fn test_rollback_without_transaction_returns_error() {
260 let ctx = setup();
261 let mut db = WasmDbmsDatabase::oneshot(&ctx, TestSchema);
262 let result = db.rollback();
263 assert!(result.is_err());
264 }
265}