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