formualizer_eval/engine/graph/editor/
transaction_context.rs1use crate::engine::graph::DependencyGraph;
9use crate::engine::graph::editor::transaction_manager::{
10 TransactionError, TransactionId, TransactionManager,
11};
12use crate::engine::graph::editor::{EditorError, VertexEditor};
13use crate::engine::{ChangeEvent, ChangeLog};
14
15pub struct TransactionContext<'g> {
17 graph: &'g mut DependencyGraph,
18 change_log: ChangeLog,
19 tx_manager: TransactionManager,
20}
21
22impl<'g> TransactionContext<'g> {
23 pub fn new(graph: &'g mut DependencyGraph) -> Self {
25 Self {
26 graph,
27 change_log: ChangeLog::new(),
28 tx_manager: TransactionManager::new(),
29 }
30 }
31
32 pub fn with_max_size(graph: &'g mut DependencyGraph, max_size: usize) -> Self {
34 Self {
35 graph,
36 change_log: ChangeLog::new(),
37 tx_manager: TransactionManager::with_max_size(max_size),
38 }
39 }
40
41 pub fn begin(&mut self) -> Result<TransactionId, TransactionError> {
49 self.tx_manager.begin(self.change_log.len())
50 }
51
52 pub fn editor(&mut self) -> VertexEditor<'_> {
61 unsafe {
65 let graph_ptr = self.graph as *mut DependencyGraph;
66 let logger_ptr = &mut self.change_log as *mut ChangeLog;
67 VertexEditor::with_logger(&mut *graph_ptr, &mut *logger_ptr)
68 }
69 }
70
71 pub fn commit(&mut self) -> Result<TransactionId, TransactionError> {
79 self.tx_manager.check_size(self.change_log.len())?;
81 self.tx_manager.commit()
82 }
83
84 pub fn rollback(&mut self) -> Result<(), TransactionError> {
90 let (_tx_id, start_index) = self.tx_manager.rollback_info()?;
91
92 let changes = self.change_log.take_from(start_index);
94
95 self.apply_rollback(changes)?;
97
98 Ok(())
99 }
100
101 pub fn savepoint(&mut self, name: &str) -> Result<(), TransactionError> {
109 self.tx_manager
110 .add_savepoint(name.to_string(), self.change_log.len())
111 }
112
113 pub fn rollback_to_savepoint(&mut self, name: &str) -> Result<(), TransactionError> {
123 let savepoint_index = self.tx_manager.get_savepoint(name)?;
124
125 let changes = self.change_log.take_from(savepoint_index);
127
128 self.tx_manager.truncate_savepoints(savepoint_index);
130
131 self.apply_rollback(changes)?;
133
134 Ok(())
135 }
136
137 pub fn is_active(&self) -> bool {
139 self.tx_manager.is_active()
140 }
141
142 pub fn active_id(&self) -> Option<TransactionId> {
144 self.tx_manager.active_id()
145 }
146
147 pub fn change_count(&self) -> usize {
149 self.change_log.len()
150 }
151
152 pub fn change_log(&self) -> &ChangeLog {
154 &self.change_log
155 }
156
157 pub fn clear_change_log(&mut self) {
159 self.change_log.clear();
160 }
161
162 fn apply_rollback(&mut self, changes: Vec<ChangeEvent>) -> Result<(), TransactionError> {
164 self.change_log.set_enabled(false);
166
167 let mut compound_stack = Vec::new();
169
170 for change in changes.into_iter().rev() {
172 match change {
173 ChangeEvent::CompoundEnd { depth } => {
174 compound_stack.push(depth);
176 }
177 ChangeEvent::CompoundStart { depth, .. } => {
178 if compound_stack.last() == Some(&depth) {
180 compound_stack.pop();
181 }
182 }
183 _ => {
184 if let Err(e) = self.apply_inverse(change) {
186 self.change_log.set_enabled(true);
187 return Err(TransactionError::RollbackFailed(e.to_string()));
188 }
189 }
190 }
191 }
192
193 self.change_log.set_enabled(true);
194 Ok(())
195 }
196
197 fn apply_inverse(&mut self, change: ChangeEvent) -> Result<(), EditorError> {
199 let mut editor = VertexEditor::new(self.graph);
200 editor.apply_inverse(change)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::engine::EvalConfig;
208 use crate::{CellRef, reference::Coord};
209 use formualizer_common::LiteralValue;
210 use formualizer_parse::parse;
211
212 fn create_test_graph() -> DependencyGraph {
213 DependencyGraph::new_with_config(EvalConfig::default())
214 }
215
216 fn cell_ref(sheet_id: u16, row: u32, col: u32) -> CellRef {
217 CellRef::new(sheet_id, Coord::from_excel(row, col, true, true))
220 }
221
222 #[test]
223 fn test_transaction_context_basic() {
224 let mut graph = create_test_graph();
225 let mut ctx = TransactionContext::new(&mut graph);
226
227 let tx_id = ctx.begin().unwrap();
229 assert!(ctx.is_active());
230 assert_eq!(ctx.active_id(), Some(tx_id));
231
232 {
234 let mut editor = ctx.editor();
235 editor.set_cell_value(cell_ref(0, 1, 1), LiteralValue::Number(42.0));
236 }
237
238 assert_eq!(ctx.change_count(), 1);
240
241 let committed_id = ctx.commit().unwrap();
243 assert_eq!(tx_id, committed_id);
244 assert!(!ctx.is_active());
245 }
246
247 #[test]
248 fn test_transaction_context_rollback_new_value() {
249 let mut graph = create_test_graph();
250
251 {
252 let mut ctx = TransactionContext::new(&mut graph);
253
254 ctx.begin().unwrap();
255
256 {
258 let mut editor = ctx.editor();
259 editor.set_cell_value(cell_ref(0, 1, 1), LiteralValue::Number(20.0));
260 }
261
262 ctx.rollback().unwrap();
264 assert_eq!(ctx.change_count(), 0);
265 }
266
267 assert!(
269 graph
270 .get_vertex_id_for_address(&cell_ref(0, 1, 1))
271 .is_none()
272 );
273 }
274
275 #[test]
276 fn test_transaction_context_rollback_value_update() {
277 let mut graph = create_test_graph();
278
279 let original = parse("=1").unwrap();
281 let _ = graph.set_cell_formula("Sheet1", 1, 1, original.clone());
282 let vid = *graph
283 .get_vertex_id_for_address(&cell_ref(0, 1, 1))
284 .expect("vertex for A1");
285
286 {
287 let mut ctx = TransactionContext::new(&mut graph);
288 ctx.begin().unwrap();
289
290 {
292 let mut editor = ctx.editor();
293 editor.set_cell_formula(cell_ref(0, 1, 1), parse("=2").unwrap());
294 }
295
296 ctx.rollback().unwrap();
298 }
299
300 let f = graph.get_formula(vid).expect("formula restored");
302 assert_eq!(f.node_type, original.node_type);
303 }
304
305 #[test]
306 fn test_transaction_context_multiple_changes() {
307 let mut graph = create_test_graph();
308
309 {
310 let mut ctx = TransactionContext::new(&mut graph);
311
312 ctx.begin().unwrap();
313
314 {
316 let mut editor = ctx.editor();
317 editor.set_cell_formula(cell_ref(0, 1, 1), parse("=1").unwrap());
318 editor.set_cell_formula(cell_ref(0, 2, 1), parse("=2").unwrap());
319 editor.set_cell_formula(cell_ref(0, 3, 1), parse("=A1+A2").unwrap());
320 }
321
322 assert_eq!(ctx.change_count(), 3);
323
324 ctx.commit().unwrap();
326 }
327
328 assert!(
330 graph
331 .get_vertex_id_for_address(&cell_ref(0, 1, 1))
332 .is_some()
333 );
334 assert!(
335 graph
336 .get_vertex_id_for_address(&cell_ref(0, 2, 1))
337 .is_some()
338 );
339 assert!(
340 graph
341 .get_vertex_id_for_address(&cell_ref(0, 3, 1))
342 .is_some()
343 );
344 }
345
346 #[test]
347 fn test_transaction_context_savepoints() {
348 let mut graph = create_test_graph();
349
350 {
351 let mut ctx = TransactionContext::new(&mut graph);
352
353 ctx.begin().unwrap();
354
355 {
357 let mut editor = ctx.editor();
358 editor.set_cell_formula(cell_ref(0, 1, 1), parse("=1").unwrap());
359 }
360
361 ctx.savepoint("after_first").unwrap();
363
364 {
366 let mut editor = ctx.editor();
367 editor.set_cell_formula(cell_ref(0, 2, 1), parse("=2").unwrap());
368 editor.set_cell_formula(cell_ref(0, 3, 1), parse("=3").unwrap());
369 }
370
371 assert_eq!(ctx.change_count(), 3);
372
373 ctx.rollback_to_savepoint("after_first").unwrap();
375
376 assert_eq!(ctx.change_count(), 1);
378
379 ctx.commit().unwrap();
381 }
382
383 assert!(
385 graph
386 .get_vertex_id_for_address(&cell_ref(0, 1, 1))
387 .is_some()
388 );
389 assert!(
390 graph
391 .get_vertex_id_for_address(&cell_ref(0, 2, 1))
392 .is_none()
393 );
394 assert!(
395 graph
396 .get_vertex_id_for_address(&cell_ref(0, 3, 1))
397 .is_none()
398 );
399 }
400
401 #[test]
402 fn test_transaction_context_size_limit() {
403 let mut graph = create_test_graph();
404 let mut ctx = TransactionContext::with_max_size(&mut graph, 2);
405
406 ctx.begin().unwrap();
407
408 {
410 let mut editor = ctx.editor();
411 editor.set_cell_value(cell_ref(0, 1, 1), LiteralValue::Number(1.0));
412 editor.set_cell_value(cell_ref(0, 2, 1), LiteralValue::Number(2.0));
413 }
414
415 assert!(ctx.commit().is_ok());
417
418 ctx.begin().unwrap();
419
420 {
422 let mut editor = ctx.editor();
423 editor.set_cell_value(cell_ref(0, 3, 1), LiteralValue::Number(3.0));
424 editor.set_cell_value(cell_ref(0, 4, 1), LiteralValue::Number(4.0));
425 editor.set_cell_value(cell_ref(0, 5, 1), LiteralValue::Number(5.0));
426 }
427
428 match ctx.commit() {
430 Err(TransactionError::TransactionTooLarge { size, max }) => {
431 assert_eq!(size, 3);
432 assert_eq!(max, 2);
433 }
434 _ => panic!("Expected TransactionTooLarge error"),
435 }
436 }
437
438 #[test]
439 fn test_transaction_context_no_active_transaction() {
440 let mut graph = create_test_graph();
441 let mut ctx = TransactionContext::new(&mut graph);
442
443 assert!(ctx.commit().is_err());
445 assert!(ctx.rollback().is_err());
446 assert!(ctx.savepoint("test").is_err());
447 assert!(ctx.rollback_to_savepoint("test").is_err());
448 }
449
450 #[test]
451 fn test_transaction_context_clear_change_log() {
452 let mut graph = create_test_graph();
453 let mut ctx = TransactionContext::new(&mut graph);
454
455 {
457 let mut editor = ctx.editor();
458 editor.set_cell_value(cell_ref(0, 1, 1), LiteralValue::Number(1.0));
459 editor.set_cell_value(cell_ref(0, 2, 1), LiteralValue::Number(2.0));
460 }
461
462 assert_eq!(ctx.change_count(), 2);
463
464 ctx.clear_change_log();
466 assert_eq!(ctx.change_count(), 0);
467 }
468
469 #[test]
470 fn test_transaction_context_compound_operations() {
471 let mut graph = create_test_graph();
472 let mut ctx = TransactionContext::new(&mut graph);
473
474 ctx.begin().unwrap();
475
476 ctx.change_log.begin_compound("test_compound".to_string());
478
479 {
480 let mut editor = ctx.editor();
481 editor.set_cell_value(cell_ref(0, 1, 1), LiteralValue::Number(1.0));
482 editor.set_cell_value(cell_ref(0, 2, 1), LiteralValue::Number(2.0));
483 }
484
485 ctx.change_log.end_compound();
486
487 assert_eq!(ctx.change_count(), 4);
489
490 ctx.rollback().unwrap();
492 assert_eq!(ctx.change_count(), 0);
493 }
494}