1use crate::SheetId;
2use crate::arrow_store::{OverlayFragment, OverlayValue, SheetStore};
3use crate::engine::eval_delta::{DeltaCollector, DeltaMode, EvalDelta};
4use crate::engine::named_range::{NameScope, NamedDefinition};
5use crate::engine::range_view::RangeView;
6use crate::engine::row_visibility::RowVisibilityState;
7use crate::engine::spill::{RegionLockManager, SpillMeta, SpillShape};
8use crate::engine::virtual_deps::VirtualDepBuilder;
9use crate::engine::{
10 DependencyGraph, EvalConfig, FormulaParseDiagnostic, FormulaParsePolicy, RowVisibilitySource,
11 Scheduler, VertexId, VertexKind, VisibilityMaskMode,
12};
13use crate::interpreter::Interpreter;
14use crate::reference::{CellRef, Coord, RangeRef};
15use crate::traits::FunctionProvider;
16use crate::traits::{EvaluationContext, Resolver};
17use chrono::Timelike;
18use formualizer_common::{CoordBuildHasher, col_letters_from_1based, parse_a1_1based};
19use formualizer_parse::parser::ReferenceType;
20use formualizer_parse::{ASTNode, ASTNodeType, ExcelError, ExcelErrorKind, LiteralValue};
21use rayon::ThreadPoolBuilder;
22use rustc_hash::{FxHashMap, FxHashSet};
23use std::collections::BTreeMap;
24use std::sync::Arc;
25use std::sync::atomic::{AtomicBool, Ordering};
26
27type StagedFormulaEntry = (u32, u32, String);
28type ParsedFormulaEntry = (u32, u32, ASTNode);
29type StagedFormulaMap = std::collections::HashMap<String, Vec<StagedFormulaEntry>>;
30type PreparedFormulaBatches = Vec<(String, Vec<ParsedFormulaEntry>)>;
31type StagedFormulaBatches = Vec<(String, Vec<StagedFormulaEntry>)>;
32
33const COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH: usize = 8;
37
38#[derive(Debug, Clone, PartialEq)]
39pub(crate) enum ComputedWrite {
40 Cell {
41 seq: u64,
42 sheet_id: SheetId,
43 row0: u32,
44 col0: u32,
45 value: OverlayValue,
46 },
47 Rect {
48 seq: u64,
49 sheet_id: SheetId,
50 sr0: u32,
51 sc0: u32,
52 values: Vec<Vec<OverlayValue>>,
53 },
54}
55
56impl ComputedWrite {
57 #[inline]
58 pub(crate) fn seq(&self) -> u64 {
59 match self {
60 ComputedWrite::Cell { seq, .. } | ComputedWrite::Rect { seq, .. } => *seq,
61 }
62 }
63}
64
65#[derive(Debug, Default)]
66pub(crate) struct ComputedWriteBuffer {
67 writes: Vec<ComputedWrite>,
68 next_seq: u64,
69 estimated_bytes: usize,
70}
71
72impl ComputedWriteBuffer {
73 const ENTRY_BASE_BYTES: usize = 32;
74
75 #[inline]
76 pub(crate) fn is_empty(&self) -> bool {
77 self.writes.is_empty()
78 }
79
80 #[inline]
81 pub(crate) fn len(&self) -> usize {
82 self.writes.len()
83 }
84
85 #[inline]
86 pub(crate) fn estimated_bytes(&self) -> usize {
87 self.estimated_bytes
88 }
89
90 #[inline]
91 pub(crate) fn writes(&self) -> &[ComputedWrite] {
92 &self.writes
93 }
94
95 pub(crate) fn push_cell(
96 &mut self,
97 sheet_id: SheetId,
98 row0: u32,
99 col0: u32,
100 value: OverlayValue,
101 ) {
102 let seq = self.next_sequence();
103 self.estimated_bytes = self
104 .estimated_bytes
105 .saturating_add(Self::estimate_value_bytes(&value));
106 self.writes.push(ComputedWrite::Cell {
107 seq,
108 sheet_id,
109 row0,
110 col0,
111 value,
112 });
113 }
114
115 pub(crate) fn push_rect(
116 &mut self,
117 sheet_id: SheetId,
118 sr0: u32,
119 sc0: u32,
120 values: Vec<Vec<OverlayValue>>,
121 ) {
122 let seq = self.next_sequence();
123 let added = values
124 .iter()
125 .flat_map(|row| row.iter())
126 .map(Self::estimate_value_bytes)
127 .fold(0usize, usize::saturating_add);
128 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
129 self.writes.push(ComputedWrite::Rect {
130 seq,
131 sheet_id,
132 sr0,
133 sc0,
134 values,
135 });
136 }
137
138 pub(crate) fn clear(&mut self) {
139 self.writes.clear();
140 self.estimated_bytes = 0;
141 }
142
143 fn take_writes(&mut self) -> Vec<ComputedWrite> {
144 self.estimated_bytes = 0;
145 std::mem::take(&mut self.writes)
146 }
147
148 fn next_sequence(&mut self) -> u64 {
149 let seq = self.next_seq;
150 self.next_seq = self.next_seq.wrapping_add(1);
151 seq
152 }
153
154 #[inline]
155 fn estimate_value_bytes(value: &OverlayValue) -> usize {
156 Self::ENTRY_BASE_BYTES.saturating_add(value.estimated_payload_bytes())
157 }
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
161struct ComputedWriteChunkKey {
162 sheet_id: SheetId,
163 col0: u32,
164 chunk_idx: usize,
165 chunk_start_row0: u32,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub(crate) struct ComputedWriteChunkEntryPlan {
170 pub(crate) row_in_chunk: usize,
171 pub(crate) seq: u64,
172 pub(crate) value: OverlayValue,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub(crate) enum ComputedWriteChunkPlanShape {
177 Point,
178 SparseOffsets {
179 entries: usize,
180 span_len: usize,
181 },
182 DenseRange {
183 start: usize,
184 len: usize,
185 },
186 RunRange {
187 start: usize,
188 len: usize,
189 runs: usize,
190 },
191}
192
193#[derive(Debug, Clone, PartialEq)]
194pub(crate) struct ComputedWriteChunkPlan {
195 pub(crate) sheet_id: SheetId,
196 pub(crate) col0: u32,
197 pub(crate) chunk_idx: usize,
198 pub(crate) chunk_start_row0: u32,
199 pub(crate) entries: Vec<ComputedWriteChunkEntryPlan>,
200 pub(crate) shape: ComputedWriteChunkPlanShape,
201}
202
203#[derive(Debug, Clone, Default, PartialEq)]
204pub(crate) struct ComputedWriteCoalescingPlan {
205 pub(crate) chunks: Vec<ComputedWriteChunkPlan>,
206 pub(crate) input_cells: usize,
207 pub(crate) coalesced_cells: usize,
208 pub(crate) overwritten_cells: usize,
209}
210
211impl ComputedWriteCoalescingPlan {
212 #[inline]
213 pub(crate) fn is_empty(&self) -> bool {
214 self.chunks.is_empty()
215 }
216}
217
218impl ComputedWriteChunkPlan {
219 fn from_group(
220 key: ComputedWriteChunkKey,
221 mut entries: Vec<ComputedWriteChunkEntryPlan>,
222 ) -> (Self, usize) {
223 entries.sort_by_key(|entry| (entry.row_in_chunk, entry.seq));
224 let input_len = entries.len();
225 let mut coalesced: Vec<ComputedWriteChunkEntryPlan> = Vec::with_capacity(input_len);
226 for entry in entries {
227 if let Some(prev) = coalesced.last_mut()
228 && prev.row_in_chunk == entry.row_in_chunk
229 {
230 *prev = entry;
231 continue;
232 }
233 coalesced.push(entry);
234 }
235 let overwritten = input_len.saturating_sub(coalesced.len());
236 let shape = Self::classify_shape(&coalesced);
237 (
238 Self {
239 sheet_id: key.sheet_id,
240 col0: key.col0,
241 chunk_idx: key.chunk_idx,
242 chunk_start_row0: key.chunk_start_row0,
243 entries: coalesced,
244 shape,
245 },
246 overwritten,
247 )
248 }
249
250 fn classify_shape(entries: &[ComputedWriteChunkEntryPlan]) -> ComputedWriteChunkPlanShape {
251 debug_assert!(!entries.is_empty());
252 if entries.len() == 1 {
253 return ComputedWriteChunkPlanShape::Point;
254 }
255
256 let start = entries[0].row_in_chunk;
257 let end = entries[entries.len() - 1].row_in_chunk;
258 let span_len = end.saturating_sub(start).saturating_add(1);
259 if span_len != entries.len() {
260 return ComputedWriteChunkPlanShape::SparseOffsets {
261 entries: entries.len(),
262 span_len,
263 };
264 }
265
266 let runs = Self::run_count(entries);
267 if runs < entries.len() {
268 ComputedWriteChunkPlanShape::RunRange {
269 start,
270 len: entries.len(),
271 runs,
272 }
273 } else {
274 ComputedWriteChunkPlanShape::DenseRange {
275 start,
276 len: entries.len(),
277 }
278 }
279 }
280
281 fn run_count(entries: &[ComputedWriteChunkEntryPlan]) -> usize {
282 let mut runs = 0usize;
283 let mut prev: Option<&OverlayValue> = None;
284 for entry in entries {
285 if prev != Some(&entry.value) {
286 runs = runs.saturating_add(1);
287 prev = Some(&entry.value);
288 }
289 }
290 runs
291 }
292}
293
294pub struct Engine<R> {
295 pub(crate) graph: DependencyGraph,
296 resolver: R,
297 pub config: EvalConfig,
298 workbook_load_limits: crate::engine::WorkbookLoadLimits,
299 clock: Arc<dyn crate::timezone::ClockProvider>,
300 thread_pool: Option<Arc<rayon::ThreadPool>>,
301 pub recalc_epoch: u64,
302 snapshot_id: std::sync::atomic::AtomicU64,
303 topology_epoch: u64,
304 cached_static_schedule: Option<CachedScheduleEntry>,
305 spill_mgr: ShimSpillManager,
306 arrow_sheets: SheetStore,
308 has_edited: bool,
310 overlay_compactions: u64,
312
313 computed_overlay_bytes_estimate: usize,
315 computed_overlay_mirroring_disabled: bool,
316 pub(crate) force_materialize_range_views: bool,
319 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
321 source_cache: Arc<std::sync::RwLock<SourceCache>>,
322 staged_formulas: StagedFormulaMap,
324 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
326 row_visibility_mask_cache: std::sync::RwLock<
328 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
329 >,
330 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
332 active_cancel_flag: Option<Arc<AtomicBool>>,
334
335 action_depth: u32,
340
341 last_virtual_dep_telemetry: VirtualDepTelemetry,
343 virtual_dep_fallback_activations: u64,
344}
345
346pub struct EngineAction<'a, R>
351where
352 R: EvaluationContext,
353{
354 engine: &'a mut Engine<R>,
355 name: String,
356 log: Option<*mut crate::engine::ChangeLog>,
359 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
362 atomic_policy: bool,
364}
365
366impl<'a, R> EngineAction<'a, R>
367where
368 R: EvaluationContext,
369{
370 #[inline]
371 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
372 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
373 let coord = crate::reference::Coord::from_excel(row, col, true, true);
374 crate::reference::CellRef::new(sheet_id, coord)
375 }
376
377 #[inline]
378 pub fn name(&self) -> &str {
379 &self.name
380 }
381
382 #[inline]
383 pub fn set_cell_value(
384 &mut self,
385 sheet: &str,
386 row: u32,
387 col: u32,
388 value: LiteralValue,
389 ) -> Result<(), crate::engine::EditorError> {
390 if self.log.is_some() {
391 let old_value = self.engine.read_cell_value(sheet, row, col);
392 let old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
393 let addr = self.addr_for(sheet, row, col);
394 let Some(log_ptr) = self.log else {
395 return Err(crate::engine::EditorError::TransactionFailed {
396 reason: "action_with_logger: missing ChangeLog".to_string(),
397 });
398 };
399
400 let old_comp = if self.arrow_undo.is_some() {
403 self.engine.read_computed_overlay_cell(sheet, row, col)
404 } else {
405 None
406 };
407
408 let delta_old_sem = if old_formula.is_some() {
409 None
410 } else {
411 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
412 };
413
414 let start_len = unsafe { (&*log_ptr).len() };
415
416 let log = unsafe { &mut *log_ptr };
418 self.engine.edit_with_logger(log, |editor| {
419 editor.set_cell_value(addr, value.clone());
420 });
421 log.patch_last_cell_event_old_state(addr, old_value.clone(), old_formula.clone());
422
423 if let Some(undo_ptr) = self.arrow_undo {
424 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
426 let undo = unsafe { &mut *undo_ptr };
427 self.engine
428 .record_spill_ops_into_arrow_undo(undo, new_events);
429
430 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
432 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
433 let row0 = row.saturating_sub(1);
434 let col0 = col.saturating_sub(1);
435 let delta_new_sem = Some(value.clone());
436 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
437 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
438 }
439 Ok(())
440 } else {
441 self.engine
442 .set_cell_value(sheet, row, col, value)
443 .map_err(crate::engine::EditorError::from)
444 }
445 }
446
447 #[inline]
448 pub fn set_cell_formula(
449 &mut self,
450 sheet: &str,
451 row: u32,
452 col: u32,
453 ast: ASTNode,
454 ) -> Result<(), crate::engine::EditorError> {
455 if self.log.is_some() {
456 let old_value = self.engine.read_cell_value(sheet, row, col);
457 let old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
458 let addr = self.addr_for(sheet, row, col);
459 let Some(log_ptr) = self.log else {
460 return Err(crate::engine::EditorError::TransactionFailed {
461 reason: "action_with_logger: missing ChangeLog".to_string(),
462 });
463 };
464
465 let delta_old = if self.arrow_undo.is_some() {
466 if old_formula.is_some() {
467 None
468 } else {
469 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
470 }
471 } else {
472 None
473 };
474 let start_len = unsafe { (&*log_ptr).len() };
475
476 let log = unsafe { &mut *log_ptr };
478 self.engine.edit_with_logger(log, |editor| {
479 editor.set_cell_formula(addr, ast.clone());
480 });
481 log.patch_last_cell_event_old_state(addr, old_value, old_formula);
482
483 if let Some(undo_ptr) = self.arrow_undo {
484 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
485 let undo = unsafe { &mut *undo_ptr };
486 self.engine
487 .record_spill_ops_into_arrow_undo(undo, new_events);
488 let delta_new: Option<LiteralValue> = None;
489 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
490 let row0 = row.saturating_sub(1);
491 let col0 = col.saturating_sub(1);
492 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
493 }
494 Ok(())
495 } else {
496 self.engine
497 .set_cell_formula(sheet, row, col, ast)
498 .map_err(crate::engine::EditorError::from)
499 }
500 }
501
502 #[inline]
503 pub fn set_row_hidden(
504 &mut self,
505 sheet: &str,
506 row_1based: u32,
507 hidden: bool,
508 source: RowVisibilitySource,
509 ) -> Result<(), crate::engine::EditorError> {
510 if self.log.is_some() {
511 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
512 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
513 let old_hidden = self
514 .engine
515 .row_visibility
516 .get(&sheet_id)
517 .map(|state| state.is_row_hidden(row0, Some(source)))
518 .unwrap_or(false);
519 if old_hidden == hidden {
520 return Ok(());
521 }
522
523 let _ = self
524 .engine
525 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
526
527 let Some(log_ptr) = self.log else {
528 return Err(crate::engine::EditorError::TransactionFailed {
529 reason: "action_with_logger: missing ChangeLog".to_string(),
530 });
531 };
532 unsafe { &mut *log_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
533 sheet_id,
534 row0,
535 source,
536 old_hidden,
537 new_hidden: hidden,
538 });
539
540 Ok(())
541 } else {
542 self.engine
543 .set_row_hidden(sheet, row_1based, hidden, source)
544 }
545 }
546
547 #[inline]
548 pub fn set_rows_hidden(
549 &mut self,
550 sheet: &str,
551 start_row_1based: u32,
552 end_row_1based: u32,
553 hidden: bool,
554 source: RowVisibilitySource,
555 ) -> Result<(), crate::engine::EditorError> {
556 if self.log.is_some() {
557 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
558 let (start_row0, end_row0) =
559 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
560
561 let Some(log_ptr) = self.log else {
562 return Err(crate::engine::EditorError::TransactionFailed {
563 reason: "action_with_logger: missing ChangeLog".to_string(),
564 });
565 };
566 let log = unsafe { &mut *log_ptr };
567
568 for row0 in start_row0..=end_row0 {
569 let old_hidden = self
570 .engine
571 .row_visibility
572 .get(&sheet_id)
573 .map(|state| state.is_row_hidden(row0, Some(source)))
574 .unwrap_or(false);
575 if old_hidden == hidden {
576 continue;
577 }
578
579 let _ = self
580 .engine
581 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
582
583 log.record(crate::engine::ChangeEvent::SetRowVisibility {
584 sheet_id,
585 row0,
586 source,
587 old_hidden,
588 new_hidden: hidden,
589 });
590 }
591
592 Ok(())
593 } else {
594 self.engine
595 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
596 }
597 }
598
599 #[inline]
600 pub fn insert_rows(
601 &mut self,
602 sheet: &str,
603 before: u32,
604 count: u32,
605 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
606 if self.log.is_some() {
607 let Some(log_ptr) = self.log else {
608 return Err(crate::engine::EditorError::TransactionFailed {
609 reason: "action_atomic: missing ChangeLog".to_string(),
610 });
611 };
612
613 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
614 let before0 = before.saturating_sub(1);
615
616 let summary = {
618 let log = unsafe { &mut *log_ptr };
619 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
620 Ok(crate::engine::ShiftSummary::default());
621 self.engine.edit_with_logger(log, |editor| {
622 out = editor.insert_rows(sheet_id, before0, count);
623 });
624 out?
625 };
626
627 self.engine.ensure_arrow_sheet(sheet);
629 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
630 asheet.insert_rows(before0 as usize, count as usize);
631 }
632 self.engine
633 .shift_row_visibility_insert(sheet_id, before0, count);
634 if let Some(undo_ptr) = self.arrow_undo {
635 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
636 }
637 Ok(summary)
638 } else {
639 self.engine.insert_rows(sheet, before, count)
640 }
641 }
642
643 #[inline]
644 pub fn delete_rows(
645 &mut self,
646 sheet: &str,
647 start: u32,
648 count: u32,
649 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
650 if self.atomic_policy {
651 return Err(crate::engine::EditorError::TransactionUnsupported {
652 reason:
653 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
654 .to_string(),
655 });
656 }
657 self.engine.delete_rows(sheet, start, count)
658 }
659
660 #[inline]
661 pub fn insert_columns(
662 &mut self,
663 sheet: &str,
664 before: u32,
665 count: u32,
666 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
667 if self.log.is_some() {
668 let Some(log_ptr) = self.log else {
669 return Err(crate::engine::EditorError::TransactionFailed {
670 reason: "action_atomic: missing ChangeLog".to_string(),
671 });
672 };
673
674 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
675 let before0 = before.saturating_sub(1);
676
677 let summary = {
678 let log = unsafe { &mut *log_ptr };
679 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
680 Ok(crate::engine::ShiftSummary::default());
681 self.engine.edit_with_logger(log, |editor| {
682 out = editor.insert_columns(sheet_id, before0, count);
683 });
684 out?
685 };
686
687 self.engine.ensure_arrow_sheet(sheet);
688 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
689 asheet.insert_columns(before0 as usize, count as usize);
690 }
691 if let Some(undo_ptr) = self.arrow_undo {
692 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
693 }
694 Ok(summary)
695 } else {
696 self.engine.insert_columns(sheet, before, count)
697 }
698 }
699
700 #[inline]
701 pub fn delete_columns(
702 &mut self,
703 sheet: &str,
704 start: u32,
705 count: u32,
706 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
707 if self.atomic_policy {
708 return Err(crate::engine::EditorError::TransactionUnsupported {
709 reason:
710 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
711 .to_string(),
712 });
713 }
714 self.engine.delete_columns(sheet, start, count)
715 }
716
717 #[inline]
722 pub fn action<T>(
723 &mut self,
724 name: impl AsRef<str>,
725 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
726 ) -> Result<T, crate::engine::EditorError> {
727 self.engine.action(name, f)
728 }
729}
730
731struct ActionDepthGuard<'a, R> {
732 engine: *mut Engine<R>,
733 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
734}
735
736impl<'a, R> Drop for ActionDepthGuard<'a, R> {
737 fn drop(&mut self) {
738 unsafe {
741 let e = &mut *self.engine;
742 e.action_depth = e.action_depth.saturating_sub(1);
743 }
744 }
745}
746
747#[derive(Default)]
748struct SourceCache {
749 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
750 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
751}
752
753#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
754struct VisibilityMaskCacheKey {
755 sheet_id: SheetId,
756 start_row0: u32,
757 end_row0: u32,
758 mode: VisibilityMaskMode,
759 version: u64,
760}
761
762struct SourceCacheSession {
763 cache: Arc<std::sync::RwLock<SourceCache>>,
764}
765
766impl Drop for SourceCacheSession {
767 fn drop(&mut self) {
768 if let Ok(mut g) = self.cache.write() {
769 *g = SourceCache::default();
770 }
771 }
772}
773
774#[derive(Debug)]
775pub struct EvalResult {
776 pub computed_vertices: usize,
777 pub cycle_errors: usize,
778 pub elapsed: std::time::Duration,
779}
780
781#[derive(Debug, Clone, Default)]
782pub struct VirtualDepTelemetry {
783 pub candidate_vertices_total: usize,
784 pub vdeps_vertices_total: usize,
785 pub vdeps_edges_total: usize,
786 pub builder_elapsed_ms_total: u128,
787 pub schedule_virtual_passes: usize,
788 pub schedule_static_passes: usize,
789 pub schedule_cache_hits: usize,
790 pub schedule_cache_misses: usize,
791 pub reused_schedule_vertices_total: usize,
792 pub replan_iterations: usize,
793 pub changed_vdeps_total: usize,
794 pub bailout_reason: Option<&'static str>,
795 pub fallback_mode_activations: u64,
796}
797
798#[derive(Debug, Clone, Copy)]
799struct ScheduleBuildMeta {
800 candidate_vertices: usize,
801 vdeps_vertices: usize,
802 vdeps_edges: usize,
803 builder_elapsed_ms: u128,
804 used_virtual_schedule: bool,
805 schedule_cache_hit: bool,
806 schedule_cache_eligible: bool,
807}
808
809#[derive(Debug, Clone)]
810struct CachedScheduleEntry {
811 topology_epoch: u64,
812 candidate_vertices: Vec<VertexId>,
813 schedule: crate::engine::scheduler::Schedule,
814}
815
816type ScheduleBuildOutput = (
817 crate::engine::scheduler::Schedule,
818 FxHashMap<VertexId, Vec<VertexId>>,
819 ScheduleBuildMeta,
820);
821
822#[derive(Debug)]
824pub struct RecalcPlan {
825 schedule: crate::engine::Schedule,
826 has_dynamic_refs: bool,
827}
828
829impl RecalcPlan {
830 pub fn layer_count(&self) -> usize {
831 self.schedule.layers.len()
832 }
833
834 pub fn has_dynamic_refs(&self) -> bool {
835 self.has_dynamic_refs
836 }
837}
838
839#[cfg(test)]
840pub(crate) mod criteria_mask_test_hooks {
841 use std::cell::Cell;
842
843 thread_local! {
844 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
845 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
846 }
847
848 pub fn reset_text_segment_counters() {
849 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
850 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
851 }
852
853 pub fn text_segment_counters() -> (usize, usize) {
854 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
855 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
856 (a, b)
857 }
858
859 pub(crate) fn inc_total() {
860 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
861 }
862 pub(crate) fn inc_all_null() {
863 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
864 }
865}
866
867#[cfg(test)]
868pub(crate) mod visibility_mask_test_hooks {
869 use std::cell::Cell;
870
871 thread_local! {
872 static HITS: Cell<usize> = const { Cell::new(0) };
873 static MISSES: Cell<usize> = const { Cell::new(0) };
874 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
875 }
876
877 pub fn reset() {
878 HITS.with(|c| c.set(0));
879 MISSES.with(|c| c.set(0));
880 EVICTIONS.with(|c| c.set(0));
881 }
882
883 pub fn counters() -> (usize, usize, usize) {
884 let hits = HITS.with(|c| c.get());
885 let misses = MISSES.with(|c| c.get());
886 let evictions = EVICTIONS.with(|c| c.get());
887 (hits, misses, evictions)
888 }
889
890 pub(crate) fn inc_hit() {
891 HITS.with(|c| c.set(c.get() + 1));
892 }
893
894 pub(crate) fn inc_miss() {
895 MISSES.with(|c| c.set(c.get() + 1));
896 }
897
898 pub(crate) fn inc_eviction() {
899 EVICTIONS.with(|c| c.set(c.get() + 1));
900 }
901}
902
903fn compute_criteria_mask(
904 view: &RangeView<'_>,
905 col_in_view: usize,
906 pred: &crate::args::CriteriaPredicate,
907) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
908 use crate::compute_prelude::{boolean, cmp, concat_arrays};
909 use arrow::compute::kernels::comparison::{ilike, nilike};
910 use arrow_array::{
911 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
912 };
913
914 fn apply_numeric_pred(
916 chunk: &Float64Array,
917 pred: &crate::args::CriteriaPredicate,
918 ) -> Option<BooleanArray> {
919 match pred {
920 crate::args::CriteriaPredicate::Gt(n) => {
921 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
922 }
923 crate::args::CriteriaPredicate::Ge(n) => {
924 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
925 }
926 crate::args::CriteriaPredicate::Lt(n) => {
927 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
928 }
929 crate::args::CriteriaPredicate::Le(n) => {
930 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
931 }
932 crate::args::CriteriaPredicate::Eq(v) => match v {
933 formualizer_common::LiteralValue::Number(x) => {
934 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
935 }
936 formualizer_common::LiteralValue::Int(i) => {
937 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
938 }
939 _ => None,
940 },
941 crate::args::CriteriaPredicate::Ne(v) => match v {
942 formualizer_common::LiteralValue::Number(x) => {
943 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
944 }
945 formualizer_common::LiteralValue::Int(i) => {
946 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
947 }
948 _ => None,
949 },
950 _ => None,
951 }
952 }
953
954 let is_numeric_pred = matches!(
956 pred,
957 crate::args::CriteriaPredicate::Gt(_)
958 | crate::args::CriteriaPredicate::Ge(_)
959 | crate::args::CriteriaPredicate::Lt(_)
960 | crate::args::CriteriaPredicate::Le(_)
961 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
962 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
963 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
964 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
965 );
966
967 if is_numeric_pred {
971 let mut bool_parts: Vec<BooleanArray> = Vec::new();
972 for res in view.numbers_slices() {
973 let (_rs, _rl, cols_seg) = res.ok()?;
974 if col_in_view < cols_seg.len() {
975 let chunk = cols_seg[col_in_view].as_ref();
976 let mask = apply_numeric_pred(chunk, pred)?;
977 bool_parts.push(mask);
978 }
979 }
980
981 if bool_parts.is_empty() {
982 return None;
983 } else if bool_parts.len() == 1 {
984 return Some(std::sync::Arc::new(bool_parts.remove(0)));
985 } else {
986 let anys: Vec<&dyn arrow_array::Array> = bool_parts
988 .iter()
989 .map(|a| a as &dyn arrow_array::Array)
990 .collect();
991 let conc: ArrayRef = concat_arrays(&anys).ok()?;
992 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
993 return Some(std::sync::Arc::new(ba));
994 }
995 }
996
997 let (text_kind, text_pat, empty_special) = match pred {
1000 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
1001 (0u8, t.to_lowercase(), t.is_empty())
1002 }
1003 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
1004 (1u8, t.to_lowercase(), false)
1005 }
1006 crate::args::CriteriaPredicate::TextLike {
1007 pattern,
1008 case_insensitive,
1009 } => {
1010 let p = if *case_insensitive {
1011 pattern.to_lowercase()
1012 } else {
1013 pattern.clone()
1014 };
1015 (2u8, p.replace('*', "%").replace('?', "_"), false)
1016 }
1017 _ => return None,
1018 };
1019
1020 let pat = StringArray::new_scalar(text_pat);
1021 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1022
1023 for res in view.iter_row_chunks() {
1024 let cs = res.ok()?;
1025 if cs.row_len == 0 {
1026 continue;
1027 }
1028 #[cfg(test)]
1029 criteria_mask_test_hooks::inc_total();
1030
1031 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
1032 if col_in_view >= slices.len() {
1033 return None;
1034 }
1035
1036 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
1037 let seg = match seg_opt {
1038 Some(s) => s,
1039 None => {
1040 #[cfg(test)]
1041 criteria_mask_test_hooks::inc_all_null();
1042 if text_kind == 0 && empty_special {
1043 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
1045 bb.append_n(cs.row_len, true);
1046 bool_parts.push(bb.finish());
1047 } else {
1048 bool_parts.push(BooleanArray::new_null(cs.row_len));
1050 }
1051 continue;
1052 }
1053 };
1054
1055 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
1056 let mut m = match text_kind {
1057 0 => ilike(seg_sa, &pat).ok()?,
1058 1 => nilike(seg_sa, &pat).ok()?,
1059 2 => ilike(seg_sa, &pat).ok()?,
1060 _ => return None,
1061 };
1062
1063 if text_kind == 0 && empty_special {
1064 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
1066 for i in 0..seg_sa.len() {
1067 bb.append_value(seg_sa.is_null(i));
1068 }
1069 let nulls = bb.finish();
1070 m = boolean::or_kleene(&m, &nulls).ok()?;
1071 }
1072
1073 bool_parts.push(m);
1074 }
1075
1076 if bool_parts.is_empty() {
1077 None
1078 } else if bool_parts.len() == 1 {
1079 Some(std::sync::Arc::new(bool_parts.remove(0)))
1080 } else {
1081 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1082 .iter()
1083 .map(|a| a as &dyn arrow_array::Array)
1084 .collect();
1085 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1086 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1087 Some(std::sync::Arc::new(ba))
1088 }
1089}
1090
1091#[derive(Debug, Clone)]
1092pub struct LayerInfo {
1093 pub vertex_count: usize,
1094 pub parallel_eligible: bool,
1095 pub sample_cells: Vec<String>, }
1097
1098#[derive(Debug, Clone)]
1099pub struct EvalPlan {
1100 pub total_vertices_to_evaluate: usize,
1101 pub layers: Vec<LayerInfo>,
1102 pub cycles_detected: usize,
1103 pub dirty_count: usize,
1104 pub volatile_count: usize,
1105 pub parallel_enabled: bool,
1106 pub estimated_parallel_layers: usize,
1107 pub target_cells: Vec<String>,
1108}
1109
1110impl<R> Engine<R>
1111where
1112 R: EvaluationContext,
1113{
1114 pub fn new(resolver: R, config: EvalConfig) -> Self {
1115 crate::builtins::load_builtins();
1116
1117 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1118 #[cfg(feature = "system-clock")]
1119 {
1120 Arc::new(crate::timezone::SystemClock::new(
1121 crate::timezone::TimeZoneSpec::default(),
1122 ))
1123 }
1124 #[cfg(not(feature = "system-clock"))]
1125 {
1126 Arc::new(crate::timezone::FixedClock::new(
1127 chrono::DateTime::UNIX_EPOCH,
1128 crate::timezone::TimeZoneSpec::Utc,
1129 ))
1130 }
1131 });
1132
1133 let thread_pool = if config.enable_parallel {
1135 let mut builder = ThreadPoolBuilder::new();
1136 if let Some(max_threads) = config.max_threads {
1137 builder = builder.num_threads(max_threads);
1138 }
1139
1140 match builder.build() {
1141 Ok(pool) => Some(Arc::new(pool)),
1142 Err(_) => {
1143 None
1145 }
1146 }
1147 } else {
1148 None
1149 };
1150
1151 let mut engine = Self {
1152 graph: DependencyGraph::new_with_config(config.clone()),
1153 resolver,
1154 config,
1155 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1156 clock,
1157 thread_pool,
1158 recalc_epoch: 0,
1159 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1160 topology_epoch: 0,
1161 cached_static_schedule: None,
1162 spill_mgr: ShimSpillManager::default(),
1163 arrow_sheets: SheetStore::default(),
1164 has_edited: false,
1165 overlay_compactions: 0,
1166 computed_overlay_bytes_estimate: 0,
1167 computed_overlay_mirroring_disabled: false,
1168 force_materialize_range_views: false,
1169 row_bounds_cache: std::sync::RwLock::new(None),
1170 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1171 staged_formulas: std::collections::HashMap::new(),
1172 row_visibility: FxHashMap::default(),
1173 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1174 formula_parse_diagnostics: Vec::new(),
1175 active_cancel_flag: None,
1176 action_depth: 0,
1177 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1178 virtual_dep_fallback_activations: 0,
1179 };
1180 engine.config.arrow_storage_enabled = true;
1182 engine.config.delta_overlay_enabled = true;
1183 engine.config.write_formula_overlay_enabled = true;
1184 let default_sheet = engine.graph.default_sheet_name().to_string();
1185 engine.ensure_arrow_sheet(&default_sheet);
1186 engine
1187 }
1188
1189 pub fn with_thread_pool(
1191 resolver: R,
1192 config: EvalConfig,
1193 thread_pool: Arc<rayon::ThreadPool>,
1194 ) -> Self {
1195 crate::builtins::load_builtins();
1196 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1197 #[cfg(feature = "system-clock")]
1198 {
1199 Arc::new(crate::timezone::SystemClock::new(
1200 crate::timezone::TimeZoneSpec::default(),
1201 ))
1202 }
1203 #[cfg(not(feature = "system-clock"))]
1204 {
1205 Arc::new(crate::timezone::FixedClock::new(
1206 chrono::DateTime::UNIX_EPOCH,
1207 crate::timezone::TimeZoneSpec::Utc,
1208 ))
1209 }
1210 });
1211 let mut engine = Self {
1212 graph: DependencyGraph::new_with_config(config.clone()),
1213 resolver,
1214 config,
1215 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1216 clock,
1217 thread_pool: Some(thread_pool),
1218 recalc_epoch: 0,
1219 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1220 topology_epoch: 0,
1221 cached_static_schedule: None,
1222 spill_mgr: ShimSpillManager::default(),
1223 arrow_sheets: SheetStore::default(),
1224 has_edited: false,
1225 overlay_compactions: 0,
1226 computed_overlay_bytes_estimate: 0,
1227 computed_overlay_mirroring_disabled: false,
1228 force_materialize_range_views: false,
1229 row_bounds_cache: std::sync::RwLock::new(None),
1230 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1231 staged_formulas: std::collections::HashMap::new(),
1232 row_visibility: FxHashMap::default(),
1233 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1234 formula_parse_diagnostics: Vec::new(),
1235 active_cancel_flag: None,
1236 action_depth: 0,
1237 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1238 virtual_dep_fallback_activations: 0,
1239 };
1240 engine.config.arrow_storage_enabled = true;
1242 engine.config.delta_overlay_enabled = true;
1243 engine.config.write_formula_overlay_enabled = true;
1244 let default_sheet = engine.graph.default_sheet_name().to_string();
1245 engine.ensure_arrow_sheet(&default_sheet);
1246 engine
1247 }
1248
1249 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
1250 &self.workbook_load_limits
1251 }
1252
1253 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
1254 self.workbook_load_limits = limits;
1255 }
1256
1257 fn clear_source_cache(&self) {
1258 if let Ok(mut g) = self.source_cache.write() {
1259 *g = SourceCache::default();
1260 }
1261 }
1262
1263 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
1264 &self.last_virtual_dep_telemetry
1265 }
1266
1267 pub fn virtual_dep_fallback_activations(&self) -> u64 {
1268 self.virtual_dep_fallback_activations
1269 }
1270
1271 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
1272 if !self.config.enable_virtual_dep_telemetry {
1273 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
1274 fallback_mode_activations: self.virtual_dep_fallback_activations,
1275 ..VirtualDepTelemetry::default()
1276 };
1277 }
1278 }
1279
1280 fn source_cache_session(&self) -> SourceCacheSession {
1281 self.clear_source_cache();
1282 SourceCacheSession {
1283 cache: self.source_cache.clone(),
1284 }
1285 }
1286
1287 fn resolve_source_scalar_cached(
1288 &self,
1289 name: &str,
1290 version: Option<u64>,
1291 ) -> Result<LiteralValue, ExcelError> {
1292 let key = (name.to_string(), version);
1293 if let Ok(mut g) = self.source_cache.write() {
1294 if let Some(v) = g.scalars.get(&key) {
1295 return Ok(v.clone());
1296 }
1297
1298 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
1299 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1300 ExcelError::new(ExcelErrorKind::Ref)
1301 .with_message(format!("Unresolved source scalar: {name}"))
1302 } else {
1303 err
1304 }
1305 })?;
1306 g.scalars.insert(key, v.clone());
1307 Ok(v)
1308 } else {
1309 self.resolver.resolve_source_scalar(name).map_err(|err| {
1310 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1311 ExcelError::new(ExcelErrorKind::Ref)
1312 .with_message(format!("Unresolved source scalar: {name}"))
1313 } else {
1314 err
1315 }
1316 })
1317 }
1318 }
1319
1320 fn resolve_source_table_cached(
1321 &self,
1322 name: &str,
1323 version: Option<u64>,
1324 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
1325 let key = (name.to_string(), version);
1326 if let Ok(mut g) = self.source_cache.write() {
1327 if let Some(t) = g.tables.get(&key) {
1328 return Ok(t.clone());
1329 }
1330
1331 let t = self.resolver.resolve_source_table(name).map_err(|err| {
1332 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1333 ExcelError::new(ExcelErrorKind::Ref)
1334 .with_message(format!("Unresolved source table: {name}"))
1335 } else {
1336 err
1337 }
1338 })?;
1339 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
1340 g.tables.insert(key, t.clone());
1341 Ok(t)
1342 } else {
1343 self.resolver
1344 .resolve_source_table(name)
1345 .map_err(|err| {
1346 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1347 ExcelError::new(ExcelErrorKind::Ref)
1348 .with_message(format!("Unresolved source table: {name}"))
1349 } else {
1350 err
1351 }
1352 })
1353 .map(Arc::from)
1354 }
1355 }
1356
1357 fn source_table_to_range_view(
1358 &self,
1359 table: &dyn crate::traits::Table,
1360 spec: &Option<formualizer_parse::parser::TableSpecifier>,
1361 ) -> Result<RangeView<'static>, ExcelError> {
1362 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1363
1364 let owned = match spec {
1365 Some(TableSpecifier::Column(c)) => {
1366 let c = c.trim();
1367 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
1368 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1369 "Complex structured references not yet supported".to_string(),
1370 ));
1371 }
1372 table.get_column(c)?.materialise().into_owned()
1373 }
1374 Some(TableSpecifier::ColumnRange(start, end)) => {
1375 let cols = table.columns();
1376 let start = start.trim();
1377 let end = end.trim();
1378 let start_key = start.to_lowercase();
1379 let end_key = end.to_lowercase();
1380 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1381 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1382 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1383 if si > ei {
1384 std::mem::swap(&mut si, &mut ei);
1385 }
1386 let h = table.data_height();
1387 let w = ei - si + 1;
1388 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1389 for (offset, ci) in (si..=ei).enumerate() {
1390 let cname = &cols[ci];
1391 let col_range = table.get_column(cname)?;
1392 let (rh, _) = col_range.dimensions();
1393 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1394 row[offset] = col_range.get(r, 0)?;
1395 }
1396 }
1397 rows
1398 } else {
1399 return Err(ExcelError::new(ExcelErrorKind::Ref)
1400 .with_message("Column range refers to unknown column(s)".to_string()));
1401 }
1402 }
1403 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
1404 | Some(TableSpecifier::Headers) => table
1405 .headers_row()
1406 .map(|r| r.materialise().into_owned())
1407 .unwrap_or_default(),
1408 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
1409 | Some(TableSpecifier::Totals) => table
1410 .totals_row()
1411 .map(|r| r.materialise().into_owned())
1412 .unwrap_or_default(),
1413 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
1414 table
1415 .data_body()
1416 .map(|r| r.materialise().into_owned())
1417 .unwrap_or_default()
1418 }
1419 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
1420 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1421 if let Some(h) = table.headers_row() {
1422 out.extend(h.iter_rows());
1423 }
1424 if let Some(body) = table.data_body() {
1425 out.extend(body.iter_rows());
1426 }
1427 if let Some(tr) = table.totals_row() {
1428 out.extend(tr.iter_rows());
1429 }
1430 out
1431 }
1432 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
1433 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1434 "@ (This Row) requires table-aware context; not yet supported".to_string(),
1435 ));
1436 }
1437 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
1438 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1439 .with_message("Complex structured references not yet supported".to_string()));
1440 }
1441 None => {
1442 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1443 .with_message("Table reference without specifier is unsupported".to_string()));
1444 }
1445 };
1446
1447 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
1448 }
1449
1450 pub fn default_sheet_id(&self) -> SheetId {
1451 self.graph.default_sheet_id()
1452 }
1453
1454 pub fn default_sheet_name(&self) -> &str {
1455 self.graph.default_sheet_name()
1456 }
1457
1458 pub fn set_workbook_seed(&mut self, seed: u64) {
1460 self.config.workbook_seed = seed;
1461 }
1462
1463 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
1465 self.config.volatile_level = level;
1466 }
1467
1468 pub fn set_deterministic_mode(
1470 &mut self,
1471 mode: crate::engine::DeterministicMode,
1472 ) -> Result<(), ExcelError> {
1473 let clock = mode.build_clock()?;
1474 self.config.deterministic_mode = mode;
1475 self.clock = clock;
1476 Ok(())
1477 }
1478
1479 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
1480 self.config.deterministic_mode.validate()
1481 }
1482
1483 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
1484 self.graph.sheet_id(name)
1485 }
1486
1487 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
1488 self.add_sheet(name)
1489 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
1490 }
1491
1492 pub fn sheet_name(&self, id: SheetId) -> &str {
1493 self.graph.sheet_name(id)
1494 }
1495
1496 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
1497 let id = self.graph.add_sheet(name)?;
1498 self.ensure_arrow_sheet(name);
1499 self.mark_topology_edited();
1500 Ok(id)
1501 }
1502
1503 fn ensure_arrow_sheet(&mut self, name: &str) {
1504 if self.arrow_sheets.sheet(name).is_some() {
1505 return;
1506 }
1507 self.arrow_sheets
1508 .sheets
1509 .push(crate::arrow_store::ArrowSheet {
1510 name: std::sync::Arc::<str>::from(name),
1511 columns: Vec::new(),
1512 nrows: 0,
1513 chunk_starts: Vec::new(),
1514 chunk_rows: 32 * 1024,
1515 });
1516 }
1517
1518 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
1519 let name = self.graph.sheet_name(sheet_id).to_string();
1520 self.graph.remove_sheet(sheet_id)?;
1521 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
1522 self.staged_formulas.remove(&name);
1523 if self.row_visibility.remove(&sheet_id).is_some() {
1524 self.invalidate_row_visibility_mask_cache();
1525 }
1526 Ok(())
1527 }
1528
1529 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
1531 if let Some(asheet) = self
1532 .arrow_sheets
1533 .sheets
1534 .iter_mut()
1535 .find(|s| s.name.as_ref() == target_name)
1536 {
1537 asheet.name = std::sync::Arc::<str>::from(new_name);
1538 return true;
1539 }
1540 false
1541 }
1542
1543 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
1544 let old_name = self.graph.sheet_name(sheet_id).to_string();
1545
1546 self.rename_sheet_in_arrow_store(&old_name, new_name);
1549
1550 match self.graph.rename_sheet(sheet_id, new_name) {
1552 Ok(_) => {
1553 self.rename_staged_formula_sheet(&old_name, new_name);
1554 let sheet_vertices: Vec<VertexId> =
1556 self.graph.vertices_in_sheet(sheet_id).collect();
1557 for v_id in sheet_vertices {
1558 self.graph.mark_vertex_dirty(v_id);
1559 }
1560 self.mark_topology_edited();
1561 Ok(())
1562 }
1563 Err(e) => {
1564 self.rename_sheet_in_arrow_store(new_name, &old_name);
1566 Err(e)
1567 }
1568 }
1569 }
1570
1571 pub fn named_ranges_iter(
1572 &self,
1573 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
1574 self.graph.named_ranges_iter()
1575 }
1576
1577 pub fn sheet_named_ranges_iter(
1578 &self,
1579 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
1580 self.graph.sheet_named_ranges_iter()
1581 }
1582
1583 pub fn resolve_name_entry(
1584 &self,
1585 name: &str,
1586 current_sheet: SheetId,
1587 ) -> Option<&crate::engine::named_range::NamedRange> {
1588 self.graph.resolve_name_entry(name, current_sheet)
1589 }
1590
1591 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1592 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
1593
1594 for (name, named) in self.graph.named_ranges_iter() {
1595 out.push(crate::engine::named_range::NamedRangeSnapshot {
1596 name: name.clone(),
1597 scope: NameScope::Workbook,
1598 definition: named.definition.clone(),
1599 });
1600 }
1601
1602 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
1603 out.push(crate::engine::named_range::NamedRangeSnapshot {
1604 name: name.clone(),
1605 scope: NameScope::Sheet(*sheet_id),
1606 definition: named.definition.clone(),
1607 });
1608 }
1609
1610 out.sort_by(|a, b| {
1611 let a_scope = match a.scope {
1612 NameScope::Workbook => (0u8, 0u32),
1613 NameScope::Sheet(id) => (1u8, u32::from(id)),
1614 };
1615 let b_scope = match b.scope {
1616 NameScope::Workbook => (0u8, 0u32),
1617 NameScope::Sheet(id) => (1u8, u32::from(id)),
1618 };
1619 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
1620 });
1621
1622 out
1623 }
1624
1625 pub fn named_ranges_snapshot_for_sheet(
1626 &self,
1627 sheet_id: SheetId,
1628 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
1629 self.named_ranges_snapshot()
1630 .into_iter()
1631 .filter(|entry| match entry.scope {
1632 NameScope::Workbook => true,
1633 NameScope::Sheet(id) => id == sheet_id,
1634 })
1635 .collect()
1636 }
1637
1638 pub fn define_name(
1639 &mut self,
1640 name: &str,
1641 definition: NamedDefinition,
1642 scope: NameScope,
1643 ) -> Result<(), ExcelError> {
1644 self.graph.define_name(name, definition, scope)?;
1645 self.mark_topology_edited();
1646 Ok(())
1647 }
1648
1649 pub fn update_name(
1650 &mut self,
1651 name: &str,
1652 definition: NamedDefinition,
1653 scope: NameScope,
1654 ) -> Result<(), ExcelError> {
1655 self.graph.update_name(name, definition, scope)?;
1656 self.mark_topology_edited();
1657 Ok(())
1658 }
1659
1660 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
1661 self.graph.delete_name(name, scope)?;
1662 self.mark_topology_edited();
1663 Ok(())
1664 }
1665
1666 pub fn define_table(
1667 &mut self,
1668 name: &str,
1669 range: crate::reference::RangeRef,
1670 header_row: bool,
1671 headers: Vec<String>,
1672 totals_row: bool,
1673 ) -> Result<(), ExcelError> {
1674 self.graph
1675 .define_table(name, range, header_row, headers, totals_row)?;
1676 self.mark_topology_edited();
1677 Ok(())
1678 }
1679
1680 pub fn define_source_scalar(
1681 &mut self,
1682 name: &str,
1683 version: Option<u64>,
1684 ) -> Result<(), ExcelError> {
1685 self.graph.define_source_scalar(name, version)?;
1686 self.mark_topology_edited();
1687 Ok(())
1688 }
1689
1690 pub fn define_source_table(
1691 &mut self,
1692 name: &str,
1693 version: Option<u64>,
1694 ) -> Result<(), ExcelError> {
1695 self.graph.define_source_table(name, version)?;
1696 self.mark_topology_edited();
1697 Ok(())
1698 }
1699
1700 pub fn set_source_scalar_version(
1701 &mut self,
1702 name: &str,
1703 version: Option<u64>,
1704 ) -> Result<(), ExcelError> {
1705 self.graph.set_source_scalar_version(name, version)
1706 }
1707
1708 pub fn set_source_table_version(
1709 &mut self,
1710 name: &str,
1711 version: Option<u64>,
1712 ) -> Result<(), ExcelError> {
1713 self.graph.set_source_table_version(name, version)
1714 }
1715
1716 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
1717 self.graph.invalidate_source(name)
1718 }
1719
1720 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
1721 self.graph.get_value(vertex)
1722 }
1723
1724 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
1725 self.graph.get_cell_value(sheet, row, col)
1726 }
1727
1728 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
1729 self.graph.get_vertex_for_cell(cell)
1730 }
1731
1732 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
1733 self.graph.get_evaluation_vertices()
1734 }
1735
1736 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
1737 self.graph.set_first_load_assume_new(enabled);
1738 }
1739
1740 pub fn reset_ensure_touched(&mut self) {
1741 self.graph.reset_ensure_touched();
1742 }
1743
1744 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1745 self.graph.finalize_sheet_index(sheet);
1746 }
1747
1748 pub fn action<T>(
1757 &mut self,
1758 name: impl AsRef<str>,
1759 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1760 ) -> Result<T, crate::engine::EditorError> {
1761 if self.action_depth != 0 {
1762 return Err(crate::engine::EditorError::TransactionFailed {
1763 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
1764 .to_string(),
1765 });
1766 }
1767
1768 self.action_depth = 1;
1769 let engine_ptr: *mut Engine<R> = self;
1770 let _guard = ActionDepthGuard {
1771 engine: engine_ptr,
1772 _marker: std::marker::PhantomData,
1773 };
1774
1775 let mut tx = EngineAction {
1776 engine: self,
1777 name: name.as_ref().to_string(),
1778 log: None,
1779 arrow_undo: None,
1780 atomic_policy: false,
1781 };
1782 f(&mut tx)
1783 }
1784
1785 pub fn action_atomic<T>(
1789 &mut self,
1790 name: impl Into<String>,
1791 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1792 ) -> Result<T, crate::engine::EditorError> {
1793 let (v, _j) = self.action_atomic_journal(name, f)?;
1794 Ok(v)
1795 }
1796
1797 pub fn action_atomic_journal<T>(
1799 &mut self,
1800 name: impl Into<String>,
1801 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1802 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
1803 if self.action_depth != 0 {
1804 return Err(crate::engine::EditorError::TransactionFailed {
1805 reason: "Nested Engine::action calls are not supported (deterministic rule)"
1806 .to_string(),
1807 });
1808 }
1809
1810 self.action_depth = 1;
1811 let engine_ptr: *mut Engine<R> = self;
1812 let _guard = ActionDepthGuard {
1813 engine: engine_ptr,
1814 _marker: std::marker::PhantomData,
1815 };
1816
1817 let name_str = name.into();
1818 let mut log = crate::engine::ChangeLog::new();
1819 let start_len = log.len();
1820 self.action_atomic_impl(&mut log, start_len, name_str, f)
1821 }
1822
1823 fn action_atomic_impl<T>(
1824 &mut self,
1825 log: &mut crate::engine::ChangeLog,
1826 start_len: usize,
1827 name: String,
1828 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1829 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
1830 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
1831 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
1832
1833 let log_ptr: *mut crate::engine::ChangeLog = log;
1834 let mut tx = EngineAction {
1835 engine: self,
1836 name: name.clone(),
1837 log: Some(log_ptr),
1838 arrow_undo: Some(arrow_ptr),
1839 atomic_policy: true,
1840 };
1841
1842 let res = f(&mut tx);
1843
1844 let graph_events: Vec<crate::engine::ChangeEvent> =
1846 unsafe { (&*log_ptr).events() }[start_len..].to_vec();
1847 let graph_batch = crate::engine::GraphUndoBatch {
1848 events: graph_events,
1849 };
1850 let affected_cells = arrow_undo.ops.len();
1851 let journal = crate::engine::ActionJournal {
1852 name,
1853 graph: graph_batch,
1854 arrow: arrow_undo,
1855 affected_cells,
1856 };
1857
1858 match res {
1859 Ok(v) => {
1860 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
1861 self.mark_data_edited();
1862 }
1863 Ok((v, journal))
1864 }
1865 Err(e) => {
1866 if let Err(rb) = self.rollback_from_action_journal(&journal) {
1867 return Err(crate::engine::EditorError::TransactionFailed {
1868 reason: format!(
1869 "Engine::action_atomic rollback failed after error '{e}': {rb}"
1870 ),
1871 });
1872 }
1873 Err(e)
1874 }
1875 }
1876 }
1877
1878 pub fn action_with_logger<T>(
1885 &mut self,
1886 log: &mut crate::engine::ChangeLog,
1887 name: impl AsRef<str>,
1888 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1889 ) -> Result<T, crate::engine::EditorError> {
1890 if self.action_depth != 0 {
1891 return Err(crate::engine::EditorError::TransactionFailed {
1892 reason: "Nested Engine::action calls are not supported (deterministic rule)"
1893 .to_string(),
1894 });
1895 }
1896
1897 self.action_depth = 1;
1898 let engine_ptr: *mut Engine<R> = self;
1899 let _guard = ActionDepthGuard {
1900 engine: engine_ptr,
1901 _marker: std::marker::PhantomData,
1902 };
1903
1904 let start_len = log.len();
1905 let name_str = name.as_ref().to_string();
1906 log.begin_compound(name_str.clone());
1907
1908 let res = self.action_atomic_impl(log, start_len, name_str, f);
1911
1912 match res {
1913 Ok((v, _journal)) => {
1914 log.end_compound();
1915 Ok(v)
1916 }
1917 Err(e) => {
1918 log.end_compound();
1920 log.truncate(start_len);
1921 Err(e)
1922 }
1923 }
1924 }
1925
1926 fn rollback_from_action_journal(
1927 &mut self,
1928 journal: &crate::engine::ActionJournal,
1929 ) -> Result<(), crate::engine::EditorError> {
1930 journal.graph.undo(&mut self.graph)?;
1932 self.apply_inverse_row_visibility_events(&journal.graph.events);
1934 self.apply_arrow_undo_batch(&journal.arrow, true);
1936 Ok(())
1937 }
1938
1939 fn rollback_from_change_events(
1940 &mut self,
1941 events: &[crate::engine::ChangeEvent],
1942 ) -> Result<(), crate::engine::EditorError> {
1943 use crate::engine::ChangeEvent;
1944
1945 {
1947 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
1948 let mut compound_stack: Vec<usize> = Vec::new();
1949 for ev in events.iter().rev() {
1950 match ev {
1951 ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
1952 ChangeEvent::CompoundStart { depth, .. } => {
1953 if compound_stack.last() == Some(depth) {
1954 compound_stack.pop();
1955 }
1956 }
1957 ChangeEvent::SetRowVisibility { .. } => {
1958 }
1960 _ => {
1961 editor.apply_inverse(ev.clone())?;
1962 }
1963 }
1964 }
1965 }
1966
1967 for ev in events.iter().rev() {
1969 self.apply_inverse_row_visibility_event(ev);
1970 }
1971
1972 for ev in events.iter().rev() {
1974 self.mirror_inverse_change_to_arrow(ev);
1975 }
1976
1977 Ok(())
1978 }
1979
1980 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
1981 let sheet_id = self.graph.sheet_id(sheet)?;
1982 let coord = Coord::from_excel(row, col, true, true);
1983 let cell = CellRef::new(sheet_id, coord);
1984 let vid = self.graph.get_vertex_for_cell(&cell)?;
1985 let ast_id = self.graph.get_formula_id(vid)?;
1986 self.graph
1987 .data_store()
1988 .retrieve_ast(ast_id, self.graph.sheet_reg())
1989 }
1990
1991 pub fn edit_with_logger<T>(
1992 &mut self,
1993 log: &mut crate::engine::ChangeLog,
1994 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
1995 ) -> T {
1996 let start_len = log.len();
1998
1999 struct ArrowSpillReader<'a> {
2002 sheets: &'a crate::arrow_store::SheetStore,
2003 }
2004 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
2005 fn read_cell_value(
2006 &self,
2007 sheet: &str,
2008 row: u32,
2009 col: u32,
2010 ) -> Option<formualizer_common::LiteralValue> {
2011 use formualizer_common::LiteralValue;
2012 let asheet = self.sheets.sheet(sheet)?;
2013 let r0 = row.saturating_sub(1) as usize;
2014 let c0 = col.saturating_sub(1) as usize;
2015 let v = asheet.get_cell_value(r0, c0);
2016 if matches!(v, LiteralValue::Empty) {
2017 None
2018 } else {
2019 Some(v)
2020 }
2021 }
2022 }
2023
2024 let ret = {
2025 let spill_reader = ArrowSpillReader {
2026 sheets: &self.arrow_sheets,
2027 };
2028 let mut editor = crate::engine::VertexEditor::with_logger_and_spill_reader(
2029 &mut self.graph,
2030 log,
2031 &spill_reader,
2032 );
2033 f(&mut editor)
2034 };
2035
2036 for ev in &log.events()[start_len..] {
2039 self.mirror_forward_change_to_arrow(ev);
2040 }
2041
2042 ret
2043 }
2044
2045 pub fn undo_logged(
2046 &mut self,
2047 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2048 log: &mut crate::engine::ChangeLog,
2049 ) -> Result<(), crate::engine::EditorError> {
2050 let batch = undo.undo(&mut self.graph, log)?;
2051 for item in batch.iter().rev() {
2052 self.apply_inverse_row_visibility_event(&item.event);
2053 self.apply_inverse_staged_formula_event(&item.event);
2054 }
2055 self.mirror_undo_batch_to_arrow(&batch);
2056 Ok(())
2057 }
2058
2059 pub fn redo_logged(
2060 &mut self,
2061 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2062 log: &mut crate::engine::ChangeLog,
2063 ) -> Result<(), crate::engine::EditorError> {
2064 let batch = undo.redo(&mut self.graph, log)?;
2065 for item in &batch {
2066 self.apply_forward_row_visibility_event(&item.event);
2067 self.apply_forward_staged_formula_event(&item.event);
2068 }
2069 self.mirror_redo_batch_to_arrow(&batch);
2070 Ok(())
2071 }
2072
2073 pub fn undo_action(
2077 &mut self,
2078 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2079 ) -> Result<(), crate::engine::EditorError> {
2080 let Some(journal) = undo.pop_undo_action() else {
2081 return Ok(());
2082 };
2083
2084 journal.graph.undo(&mut self.graph)?;
2085 self.apply_inverse_row_visibility_events(&journal.graph.events);
2086 self.apply_arrow_undo_batch(&journal.arrow, true);
2087
2088 undo.push_redo_action(journal);
2089 Ok(())
2090 }
2091
2092 pub fn redo_action(
2096 &mut self,
2097 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2098 ) -> Result<(), crate::engine::EditorError> {
2099 let Some(journal) = undo.pop_redo_action() else {
2100 return Ok(());
2101 };
2102
2103 journal.graph.redo(&mut self.graph)?;
2104 self.apply_forward_row_visibility_events(&journal.graph.events);
2105 self.apply_arrow_undo_batch(&journal.arrow, false);
2106
2107 undo.push_done_action(journal);
2108 Ok(())
2109 }
2110
2111 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
2112 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
2113 let row = addr.coord.row() + 1;
2115 let col = addr.coord.col() + 1;
2116 (sheet, row, col)
2117 }
2118
2119 fn mirror_undo_batch_to_arrow(
2120 &mut self,
2121 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2122 ) {
2123 for item in batch.iter().rev() {
2125 self.mirror_inverse_change_to_arrow(&item.event);
2126 }
2127 }
2128
2129 fn mirror_redo_batch_to_arrow(
2130 &mut self,
2131 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2132 ) {
2133 for item in batch.iter() {
2135 self.mirror_forward_change_to_arrow(&item.event);
2136 }
2137 }
2138
2139 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2140 use crate::engine::ChangeEvent;
2141 use formualizer_common::LiteralValue;
2142
2143 match ev {
2144 ChangeEvent::SetValue {
2145 addr,
2146 old_value,
2147 old_formula,
2148 ..
2149 } => {
2150 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2151 if old_formula.is_some() {
2152 self.clear_delta_overlay_cell(&sheet, row, col);
2153 } else {
2154 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2155 self.mirror_value_to_overlay(&sheet, row, col, &v);
2156 }
2157 }
2158 ChangeEvent::SetFormula {
2159 addr,
2160 old_value,
2161 old_formula,
2162 ..
2163 } => {
2164 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2165 if old_formula.is_some() {
2166 self.clear_delta_overlay_cell(&sheet, row, col);
2167 } else {
2168 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2169 self.mirror_value_to_overlay(&sheet, row, col, &v);
2170 }
2171 }
2172 ChangeEvent::SpillCommitted { old, new, .. } => {
2173 self.mirror_spill_snapshot(new, true);
2175 if let Some(snap) = old {
2176 self.mirror_spill_snapshot(snap, false);
2177 }
2178 }
2179 ChangeEvent::SpillCleared { old, .. } => {
2180 self.mirror_spill_snapshot(old, false);
2182 }
2183 ChangeEvent::SetRowVisibility { .. } => {
2184 }
2186 _ => {}
2187 }
2188 }
2189
2190 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2191 use crate::engine::ChangeEvent;
2192
2193 match ev {
2194 ChangeEvent::SetValue { addr, new, .. } => {
2195 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2196 self.mirror_value_to_overlay(&sheet, row, col, new);
2197 }
2198 ChangeEvent::SetFormula { addr, .. } => {
2199 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2200 self.clear_delta_overlay_cell(&sheet, row, col);
2201 }
2203 ChangeEvent::SpillCommitted { old, new, .. } => {
2204 if let Some(snap) = old {
2205 self.mirror_spill_snapshot(snap, true);
2206 }
2207 self.mirror_spill_snapshot(new, false);
2208 }
2209 ChangeEvent::SpillCleared { old, .. } => {
2210 self.mirror_spill_snapshot(old, true);
2211 }
2212 ChangeEvent::SetRowVisibility { .. } => {
2213 }
2215 _ => {
2216 }
2218 }
2219 }
2220
2221 fn mirror_spill_snapshot(
2222 &mut self,
2223 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
2224 clear_only: bool,
2225 ) {
2226 use formualizer_common::LiteralValue;
2227
2228 let mut i = 0usize;
2229 for row in &snap.values {
2230 for v in row {
2231 if let Some(cell) = snap.target_cells.get(i) {
2232 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2233 let out = if clear_only {
2234 LiteralValue::Empty
2235 } else {
2236 v.clone()
2237 };
2238 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
2239 }
2240 i += 1;
2241 }
2242 }
2243 if clear_only {
2245 for cell in snap.target_cells.iter().skip(i) {
2246 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2247 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
2248 }
2249 }
2250 }
2251
2252 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2253 self.graph.set_default_sheet_by_name(name);
2254 }
2255
2256 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2257 self.graph.set_default_sheet_by_id(id);
2258 }
2259
2260 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
2261 self.graph.set_sheet_index_mode(mode);
2262 }
2263
2264 fn clear_cached_static_schedule(&mut self) {
2265 self.cached_static_schedule = None;
2266 }
2267
2268 pub fn mark_data_edited(&mut self) {
2271 self.snapshot_id
2272 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2273 self.has_edited = true;
2274 }
2275
2276 pub fn mark_topology_edited(&mut self) {
2278 self.snapshot_id
2279 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2280 self.topology_epoch = self.topology_epoch.wrapping_add(1);
2281 self.clear_cached_static_schedule();
2282 self.has_edited = true;
2283 }
2284
2285 pub fn sheet_store(&self) -> &SheetStore {
2287 &self.arrow_sheets
2288 }
2289
2290 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
2292 &mut self.arrow_sheets
2293 }
2294
2295 pub fn has_staged_formulas(&self) -> bool {
2296 !self.staged_formulas.is_empty()
2297 }
2298
2299 pub fn staged_formula_state_snapshot(&self) -> Vec<(String, u32, u32, String)> {
2300 let mut snapshot = Vec::new();
2301 for (sheet, entries) in &self.staged_formulas {
2302 for (row, col, text) in entries {
2303 snapshot.push((sheet.clone(), *row, *col, text.clone()));
2304 }
2305 }
2306 snapshot.sort_by(|a, b| {
2307 a.0.cmp(&b.0)
2308 .then(a.1.cmp(&b.1))
2309 .then(a.2.cmp(&b.2))
2310 .then(a.3.cmp(&b.3))
2311 });
2312 snapshot
2313 }
2314
2315 pub fn restore_staged_formula_state(&mut self, snapshot: &[(String, u32, u32, String)]) {
2316 self.staged_formulas.clear();
2317 for (sheet, row, col, text) in snapshot {
2318 self.stage_formula_text(sheet, *row, *col, text.clone());
2319 }
2320 }
2321
2322 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
2324 let entries = self.staged_formulas.entry(sheet.to_string()).or_default();
2325 if let Some((_, _, existing)) = entries
2326 .iter_mut()
2327 .find(|(existing_row, existing_col, _)| *existing_row == row && *existing_col == col)
2328 {
2329 *existing = text;
2330 } else {
2331 entries.push((row, col, text));
2332 }
2333 }
2334
2335 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
2336 let mut removed = None;
2337 let mut remove_sheet = false;
2338 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
2339 if let Some(idx) = entries.iter().position(|(existing_row, existing_col, _)| {
2340 *existing_row == row && *existing_col == col
2341 }) {
2342 let (_, _, text) = entries.remove(idx);
2343 removed = Some(text);
2344 }
2345 remove_sheet = entries.is_empty();
2346 }
2347 if remove_sheet {
2348 self.staged_formulas.remove(sheet);
2349 }
2350 removed
2351 }
2352
2353 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
2354 self.staged_formulas.remove(sheet);
2355 }
2356
2357 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
2358 let Some(entries) = self.staged_formulas.remove(old) else {
2359 return;
2360 };
2361 for (row, col, text) in entries {
2362 self.stage_formula_text(new, row, col, text);
2363 }
2364 }
2365
2366 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
2368 self.staged_formulas.get(sheet).and_then(|v| {
2369 v.iter()
2370 .rev()
2371 .find(|(r, c, _)| *r == row && *c == col)
2372 .map(|(_, _, s)| s.clone())
2373 })
2374 }
2375
2376 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
2377 &self.formula_parse_diagnostics
2378 }
2379
2380 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
2381 std::mem::take(&mut self.formula_parse_diagnostics)
2382 }
2383
2384 pub fn clear_formula_parse_diagnostics(&mut self) {
2385 self.formula_parse_diagnostics.clear();
2386 }
2387
2388 pub fn handle_formula_parse_error(
2389 &mut self,
2390 sheet: &str,
2391 row: u32,
2392 col: u32,
2393 formula: &str,
2394 message: String,
2395 ) -> Result<Option<ASTNode>, ExcelError> {
2396 let policy = self.config.formula_parse_policy;
2397
2398 if policy == FormulaParsePolicy::Strict {
2399 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
2400 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
2401 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
2402 )));
2403 }
2404
2405 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
2406 sheet: sheet.to_string(),
2407 row,
2408 col,
2409 formula: formula.to_string(),
2410 message: message.clone(),
2411 policy,
2412 });
2413
2414 match policy {
2415 FormulaParsePolicy::Strict => unreachable!(),
2416 FormulaParsePolicy::KeepCachedValue => Ok(None),
2417 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
2418 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
2419 None,
2420 ))),
2421 FormulaParsePolicy::CoerceToError => {
2422 let err = ExcelError::new(ExcelErrorKind::Error)
2423 .with_message(format!("Malformed formula: {message}"));
2424 Ok(Some(ASTNode::new(
2425 ASTNodeType::Literal(LiteralValue::Error(err)),
2426 None,
2427 )))
2428 }
2429 }
2430 }
2431
2432 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
2434 if self.staged_formulas.is_empty() {
2435 return Ok(());
2436 }
2437 let staged = std::mem::take(&mut self.staged_formulas);
2439 for sheet in staged.keys() {
2440 let _ = self.add_sheet(sheet);
2441 }
2442
2443 let mut prepared: PreparedFormulaBatches = Vec::new();
2445 for (sheet, entries) in staged {
2446 let mut formulas: Vec<ParsedFormulaEntry> = Vec::new();
2447 let mut cache: rustc_hash::FxHashMap<String, ASTNode> =
2448 rustc_hash::FxHashMap::default();
2449 cache.reserve(4096);
2450
2451 for (row, col, txt) in entries {
2452 let key = if txt.starts_with('=') {
2453 txt
2454 } else {
2455 format!("={txt}")
2456 };
2457 let ast = if let Some(p) = cache.get(&key) {
2458 Some(p.clone())
2459 } else {
2460 match formualizer_parse::parser::parse(&key) {
2461 Ok(parsed) => {
2462 cache.insert(key.clone(), parsed.clone());
2463 Some(parsed)
2464 }
2465 Err(e) => {
2466 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
2467 }
2468 }
2469 };
2470
2471 if let Some(ast) = ast {
2472 formulas.push((row, col, ast));
2473 }
2474 }
2475
2476 if !formulas.is_empty() {
2477 prepared.push((sheet, formulas));
2478 }
2479 }
2480
2481 if !prepared.is_empty() {
2482 let mut builder = self.begin_bulk_ingest();
2483 for (sheet, formulas) in prepared {
2484 let sid = builder.add_sheet(&sheet);
2485 builder.add_formulas(sid, formulas.into_iter());
2486 }
2487 let _ = builder.finish();
2488 }
2489 Ok(())
2490 }
2491
2492 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
2494 &mut self,
2495 sheets: I,
2496 ) -> Result<(), formualizer_parse::ExcelError> {
2497 let mut collected: StagedFormulaBatches = Vec::new();
2498 for s in sheets {
2499 if let Some(entries) = self.staged_formulas.remove(s) {
2500 collected.push((s.to_string(), entries));
2501 }
2502 }
2503
2504 if collected.is_empty() {
2505 return Ok(());
2506 }
2507
2508 for (sheet, _) in &collected {
2509 let _ = self.add_sheet(sheet);
2510 }
2511
2512 let mut prepared: PreparedFormulaBatches = Vec::new();
2514 let mut cache: rustc_hash::FxHashMap<String, ASTNode> = rustc_hash::FxHashMap::default();
2515 cache.reserve(4096);
2516
2517 for (sheet, entries) in collected {
2518 let mut formulas: Vec<ParsedFormulaEntry> = Vec::new();
2519 for (row, col, txt) in entries {
2520 let key = if txt.starts_with('=') {
2521 txt
2522 } else {
2523 format!("={txt}")
2524 };
2525 let ast = if let Some(p) = cache.get(&key) {
2526 Some(p.clone())
2527 } else {
2528 match formualizer_parse::parser::parse(&key) {
2529 Ok(parsed) => {
2530 cache.insert(key.clone(), parsed.clone());
2531 Some(parsed)
2532 }
2533 Err(e) => {
2534 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
2535 }
2536 }
2537 };
2538
2539 if let Some(ast) = ast {
2540 formulas.push((row, col, ast));
2541 }
2542 }
2543 if !formulas.is_empty() {
2544 prepared.push((sheet, formulas));
2545 }
2546 }
2547
2548 if !prepared.is_empty() {
2549 let mut builder = self.begin_bulk_ingest();
2550 for (sheet, formulas) in prepared {
2551 let sid = builder.add_sheet(&sheet);
2552 builder.add_formulas(sid, formulas.into_iter());
2553 }
2554 let _ = builder.finish();
2555 }
2556 Ok(())
2557 }
2558
2559 pub fn begin_bulk_ingest_arrow(
2561 &mut self,
2562 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
2563 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
2564 }
2565
2566 pub fn begin_bulk_update_arrow(
2568 &mut self,
2569 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
2570 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
2571 }
2572
2573 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
2574 self.graph.sheet_id(sheet).ok_or(
2575 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2576 name: sheet.to_string(),
2577 reason: "Unknown sheet".to_string(),
2578 },
2579 )
2580 }
2581
2582 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
2583 if row_1based == 0 {
2584 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
2585 }
2586 Ok(row_1based - 1)
2587 }
2588
2589 fn normalize_row_range_1based(
2590 start_row_1based: u32,
2591 end_row_1based: u32,
2592 ) -> Result<(u32, u32), crate::engine::EditorError> {
2593 if start_row_1based == 0 || end_row_1based == 0 {
2594 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
2595 }
2596 if start_row_1based > end_row_1based {
2597 return Err(crate::engine::EditorError::TransactionFailed {
2598 reason: "Row range start is greater than end".to_string(),
2599 });
2600 }
2601 Ok((start_row_1based - 1, end_row_1based - 1))
2602 }
2603
2604 fn invalidate_row_visibility_mask_cache(&self) {
2605 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
2606 cache.clear();
2607 }
2608 }
2609
2610 fn set_row_hidden_by_sheet_id(
2611 &mut self,
2612 sheet_id: SheetId,
2613 row0: u32,
2614 hidden: bool,
2615 source: RowVisibilitySource,
2616 ) -> bool {
2617 let changed = {
2618 let state = self.row_visibility.entry(sheet_id).or_default();
2619 state.set_row_hidden(row0, hidden, source)
2620 };
2621
2622 let remove_entry = self
2623 .row_visibility
2624 .get(&sheet_id)
2625 .map(|state| state.is_empty())
2626 .unwrap_or(false);
2627 if remove_entry {
2628 self.row_visibility.remove(&sheet_id);
2629 }
2630
2631 if changed {
2632 self.invalidate_row_visibility_mask_cache();
2633 }
2634
2635 changed
2636 }
2637
2638 fn set_rows_hidden_by_sheet_id(
2639 &mut self,
2640 sheet_id: SheetId,
2641 start_row0: u32,
2642 end_row0: u32,
2643 hidden: bool,
2644 source: RowVisibilitySource,
2645 ) -> bool {
2646 let changed = {
2647 let state = self.row_visibility.entry(sheet_id).or_default();
2648 state.set_rows_hidden(start_row0, end_row0, hidden, source)
2649 };
2650
2651 let remove_entry = self
2652 .row_visibility
2653 .get(&sheet_id)
2654 .map(|state| state.is_empty())
2655 .unwrap_or(false);
2656 if remove_entry {
2657 self.row_visibility.remove(&sheet_id);
2658 }
2659
2660 if changed {
2661 self.invalidate_row_visibility_mask_cache();
2662 }
2663
2664 changed
2665 }
2666
2667 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
2668 if count == 0 {
2669 return;
2670 }
2671 let mut changed = false;
2672 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
2673 changed = state.insert_rows(before0, count);
2674 state.is_empty()
2675 } else {
2676 false
2677 };
2678 if remove_entry {
2679 self.row_visibility.remove(&sheet_id);
2680 }
2681 if changed {
2682 self.invalidate_row_visibility_mask_cache();
2683 }
2684 }
2685
2686 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
2687 if count == 0 {
2688 return;
2689 }
2690 let mut changed = false;
2691 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
2692 changed = state.delete_rows(start0, count);
2693 state.is_empty()
2694 } else {
2695 false
2696 };
2697 if remove_entry {
2698 self.row_visibility.remove(&sheet_id);
2699 }
2700 if changed {
2701 self.invalidate_row_visibility_mask_cache();
2702 }
2703 }
2704
2705 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
2706 if let crate::engine::ChangeEvent::SetRowVisibility {
2707 sheet_id,
2708 row0,
2709 source,
2710 old_hidden,
2711 ..
2712 } = event
2713 {
2714 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
2715 }
2716 }
2717
2718 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
2719 if let crate::engine::ChangeEvent::SetRowVisibility {
2720 sheet_id,
2721 row0,
2722 source,
2723 new_hidden,
2724 ..
2725 } = event
2726 {
2727 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
2728 }
2729 }
2730
2731 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
2732 for event in events.iter().rev() {
2733 self.apply_inverse_row_visibility_event(event);
2734 }
2735 }
2736
2737 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
2738 for event in events {
2739 self.apply_forward_row_visibility_event(event);
2740 }
2741 }
2742
2743 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
2744 if let crate::engine::ChangeEvent::StagedFormulaStateChanged { before, .. } = event {
2745 self.restore_staged_formula_state(before);
2746 }
2747 }
2748
2749 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
2750 if let crate::engine::ChangeEvent::StagedFormulaStateChanged { after, .. } = event {
2751 self.restore_staged_formula_state(after);
2752 }
2753 }
2754
2755 pub fn set_row_hidden(
2756 &mut self,
2757 sheet: &str,
2758 row_1based: u32,
2759 hidden: bool,
2760 source: RowVisibilitySource,
2761 ) -> Result<(), crate::engine::EditorError> {
2762 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2763 let row0 = Self::normalize_row_1based(row_1based)?;
2764 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
2765 self.mark_data_edited();
2766 }
2767 Ok(())
2768 }
2769
2770 pub fn set_rows_hidden(
2771 &mut self,
2772 sheet: &str,
2773 start_row_1based: u32,
2774 end_row_1based: u32,
2775 hidden: bool,
2776 source: RowVisibilitySource,
2777 ) -> Result<(), crate::engine::EditorError> {
2778 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2779 let (start_row0, end_row0) =
2780 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
2781 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
2782 self.mark_data_edited();
2783 }
2784 Ok(())
2785 }
2786
2787 pub fn is_row_hidden(
2788 &self,
2789 sheet: &str,
2790 row_1based: u32,
2791 source: Option<RowVisibilitySource>,
2792 ) -> Option<bool> {
2793 let sheet_id = self.graph.sheet_id(sheet)?;
2794 let row0 = row_1based.checked_sub(1)?;
2795 Some(
2796 self.row_visibility
2797 .get(&sheet_id)
2798 .map(|state| state.is_row_hidden(row0, source))
2799 .unwrap_or(false),
2800 )
2801 }
2802
2803 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
2804 let sheet_id = self.graph.sheet_id(sheet)?;
2805 Some(
2806 self.row_visibility
2807 .get(&sheet_id)
2808 .map(|state| state.version())
2809 .unwrap_or(0),
2810 )
2811 }
2812
2813 fn build_row_visibility_mask_for_view(
2814 &self,
2815 view: &RangeView<'_>,
2816 mode: VisibilityMaskMode,
2817 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
2818 let sheet_rows = view.sheet().nrows as usize;
2819 if sheet_rows == 0 || view.start_row() >= sheet_rows {
2820 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
2821 }
2822
2823 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
2824 let start_row0 = view.start_row() as u32;
2825 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
2826 let version = self
2827 .row_visibility
2828 .get(&sheet_id)
2829 .map(|state| state.version())
2830 .unwrap_or(0);
2831 let key = VisibilityMaskCacheKey {
2832 sheet_id,
2833 start_row0,
2834 end_row0,
2835 mode,
2836 version,
2837 };
2838
2839 if let Ok(cache) = self.row_visibility_mask_cache.read()
2840 && let Some(mask) = cache.get(&key)
2841 {
2842 #[cfg(test)]
2843 visibility_mask_test_hooks::inc_hit();
2844 return Some(mask.clone());
2845 }
2846
2847 #[cfg(test)]
2848 visibility_mask_test_hooks::inc_miss();
2849
2850 let state = self.row_visibility.get(&sheet_id);
2851 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
2852 for row0 in start_row0..=end_row0 {
2853 let manual_hidden = state
2854 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
2855 .unwrap_or(false);
2856 let filter_hidden = state
2857 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
2858 .unwrap_or(false);
2859
2860 let include = match mode {
2861 VisibilityMaskMode::IncludeAll => true,
2862 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
2863 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
2864 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
2865 !(manual_hidden || filter_hidden)
2866 }
2867 };
2868 out.push(include);
2869 }
2870
2871 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
2872 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
2873 const MAX_CACHE_ENTRIES: usize = 4096;
2874 if cache.len() >= MAX_CACHE_ENTRIES {
2875 cache.clear();
2876 #[cfg(test)]
2877 visibility_mask_test_hooks::inc_eviction();
2878 }
2879 cache.insert(key, mask.clone());
2880 }
2881
2882 Some(mask)
2883 }
2884
2885 pub fn insert_rows(
2887 &mut self,
2888 sheet: &str,
2889 before: u32,
2890 count: u32,
2891 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2892 {
2893 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2894 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2895 let before0 = before.saturating_sub(1);
2896 let summary = {
2897 let mut editor = VertexEditor::new(&mut self.graph);
2898 editor.insert_rows(sheet_id, before0, count)?
2899 };
2900 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2901 let before0 = before0 as usize;
2902 asheet.insert_rows(before0, count as usize);
2903 }
2904 self.shift_row_visibility_insert(sheet_id, before0, count);
2905 self.mark_topology_edited();
2906 Ok(summary)
2907 }
2908
2909 pub fn delete_rows(
2911 &mut self,
2912 sheet: &str,
2913 start: u32,
2914 count: u32,
2915 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2916 {
2917 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2918 let sheet_id = self.ensure_known_sheet_id(sheet)?;
2919 let start0 = start.saturating_sub(1);
2920 let summary = {
2921 let mut editor = VertexEditor::new(&mut self.graph);
2922 editor.delete_rows(sheet_id, start0, count)?
2923 };
2924 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2925 let start0 = start0 as usize;
2926 asheet.delete_rows(start0, count as usize);
2927 }
2928 self.shift_row_visibility_delete(sheet_id, start0, count);
2929 self.mark_topology_edited();
2930 Ok(summary)
2931 }
2932
2933 pub fn insert_columns(
2935 &mut self,
2936 sheet: &str,
2937 before: u32,
2938 count: u32,
2939 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2940 {
2941 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2942 let sheet_id = self.graph.sheet_id(sheet).ok_or(
2943 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2944 name: sheet.to_string(),
2945 reason: "Unknown sheet".to_string(),
2946 },
2947 )?;
2948 let before0 = before.saturating_sub(1);
2949 let summary = {
2950 let mut editor = VertexEditor::new(&mut self.graph);
2951 editor.insert_columns(sheet_id, before0, count)?
2952 };
2953 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2954 let before0 = before0 as usize;
2955 asheet.insert_columns(before0, count as usize);
2956 }
2957 self.mark_topology_edited();
2958 Ok(summary)
2959 }
2960
2961 pub fn delete_columns(
2963 &mut self,
2964 sheet: &str,
2965 start: u32,
2966 count: u32,
2967 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
2968 {
2969 use crate::engine::graph::editor::vertex_editor::VertexEditor;
2970 let sheet_id = self.graph.sheet_id(sheet).ok_or(
2971 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
2972 name: sheet.to_string(),
2973 reason: "Unknown sheet".to_string(),
2974 },
2975 )?;
2976 let start0 = start.saturating_sub(1);
2977 let summary = {
2978 let mut editor = VertexEditor::new(&mut self.graph);
2979 editor.delete_columns(sheet_id, start0, count)?
2980 };
2981 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
2982 let start0 = start0 as usize;
2983 asheet.delete_columns(start0, count as usize);
2984 }
2985 self.mark_topology_edited();
2986 Ok(summary)
2987 }
2988 fn arrow_used_row_bounds(
2990 &self,
2991 sheet: &str,
2992 start_col: u32,
2993 end_col: u32,
2994 ) -> Option<(u32, u32)> {
2995 let a = self.sheet_store().sheet(sheet)?;
2996 if a.columns.is_empty() {
2997 return None;
2998 }
2999 let sc0 = start_col.saturating_sub(1) as usize;
3000 let ec0 = end_col.saturating_sub(1) as usize;
3001 let col_hi = a.columns.len().saturating_sub(1);
3002 if sc0 > col_hi {
3003 return None;
3004 }
3005 let ec0 = ec0.min(col_hi);
3006 let snap = self.data_snapshot_id();
3008 let mut min_r0: Option<usize> = None;
3009 for ci in sc0..=ec0 {
3010 let sheet_id = self.graph.sheet_id(sheet)?;
3011 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
3012 g.as_ref()
3013 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
3014 }) {
3015 let mv = mv as usize;
3016 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
3017 continue;
3018 }
3019 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
3021 if let Ok(mut g) = self.row_bounds_cache.write() {
3022 g.get_or_insert_with(|| RowBoundsCache::new(snap))
3023 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
3024 }
3025 if let Some(m) = min_c {
3026 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
3027 }
3028 }
3029 min_r0?;
3030 let mut max_r0: Option<usize> = None;
3031 for ci in sc0..=ec0 {
3032 let sheet_id = self.graph.sheet_id(sheet)?;
3033 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
3034 g.as_ref()
3035 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
3036 }) {
3037 let mv = mv as usize;
3038 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
3039 continue;
3040 }
3041 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
3042 if let Ok(mut g) = self.row_bounds_cache.write() {
3043 g.get_or_insert_with(|| RowBoundsCache::new(snap))
3044 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
3045 }
3046 if let Some(m) = max_c {
3047 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
3048 }
3049 }
3050 match (min_r0, max_r0) {
3051 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
3052 _ => None,
3053 }
3054 }
3055
3056 fn scan_column_used_bounds(
3057 a: &crate::arrow_store::ArrowSheet,
3058 ci: usize,
3059 ) -> (Option<u32>, Option<u32>) {
3060 let col = &a.columns[ci];
3061
3062 let mut min_r0: Option<u32> = None;
3064 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
3065 let tags = chunk.type_tag.values();
3066 for (off, &t) in tags.iter().enumerate() {
3067 let overlay_non_empty = chunk
3068 .overlay
3069 .get(off)
3070 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3071 .unwrap_or(false)
3072 || chunk
3073 .computed_overlay
3074 .get(off)
3075 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3076 .unwrap_or(false);
3077 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
3078 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
3079 break;
3080 };
3081 let row0 = chunk_start + off;
3082 min_r0 = Some(row0 as u32);
3083 break;
3084 }
3085 }
3086 if min_r0.is_some() {
3087 break;
3088 }
3089 }
3090 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
3091 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
3092 sparse_idxs.sort_unstable();
3093 for chunk_idx in sparse_idxs {
3094 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
3095 continue;
3096 };
3097 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
3098 continue;
3099 };
3100 let tags = chunk.type_tag.values();
3101 for (off, &t) in tags.iter().enumerate() {
3102 let overlay_non_empty = chunk
3103 .overlay
3104 .get(off)
3105 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3106 .unwrap_or(false)
3107 || chunk
3108 .computed_overlay
3109 .get(off)
3110 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3111 .unwrap_or(false);
3112 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
3113 let row0 = chunk_start + off;
3114 min_r0 = Some(row0 as u32);
3115 break;
3116 }
3117 }
3118 if min_r0.is_some() {
3119 break;
3120 }
3121 }
3122 }
3123
3124 let mut max_r0: Option<u32> = None;
3126 if !col.sparse_chunks.is_empty() {
3127 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
3128 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
3129 for chunk_idx in sparse_idxs {
3130 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
3131 continue;
3132 };
3133 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
3134 continue;
3135 };
3136 let tags = chunk.type_tag.values();
3137 for (rev_idx, &t) in tags.iter().enumerate().rev() {
3138 let overlay_non_empty = chunk
3139 .overlay
3140 .get(rev_idx)
3141 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3142 .unwrap_or(false)
3143 || chunk
3144 .computed_overlay
3145 .get(rev_idx)
3146 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3147 .unwrap_or(false);
3148 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
3149 let row0 = chunk_start + rev_idx;
3150 max_r0 = Some(row0 as u32);
3151 break;
3152 }
3153 }
3154 if max_r0.is_some() {
3155 break;
3156 }
3157 }
3158 }
3159 if max_r0.is_none() {
3160 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
3161 let tags = chunk.type_tag.values();
3162 for (rev_idx, &t) in tags.iter().enumerate().rev() {
3163 let overlay_non_empty = chunk
3164 .overlay
3165 .get(rev_idx)
3166 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3167 .unwrap_or(false)
3168 || chunk
3169 .computed_overlay
3170 .get(rev_idx)
3171 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3172 .unwrap_or(false);
3173 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
3174 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
3175 break;
3176 };
3177 let row0 = chunk_start + rev_idx;
3178 max_r0 = Some(row0 as u32);
3179 break;
3180 }
3181 }
3182 if max_r0.is_some() {
3183 break;
3184 }
3185 }
3186 }
3187
3188 (min_r0, max_r0)
3189 }
3190
3191 fn arrow_used_col_bounds(
3193 &self,
3194 sheet: &str,
3195 start_row: u32,
3196 end_row: u32,
3197 ) -> Option<(u32, u32)> {
3198 let a = self.sheet_store().sheet(sheet)?;
3199 if a.columns.is_empty() {
3200 return None;
3201 }
3202 let sr0 = start_row.saturating_sub(1) as usize;
3203 let er0 = end_row.saturating_sub(1) as usize;
3204 if sr0 > er0 {
3205 return None;
3206 }
3207 let mut min_c0: Option<usize> = None;
3210 let mut max_c0: Option<usize> = None;
3211 for (ci, col) in a.columns.iter().enumerate() {
3213 let mut any_in_range = false;
3214
3215 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
3216 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
3217 return false;
3218 };
3219 let chunk_len = chunk.type_tag.len();
3220 if chunk_len == 0 {
3221 return false;
3222 }
3223 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
3224 if sr0 > chunk_end || er0 < chunk_start {
3226 return false;
3227 }
3228 let start_off = sr0.max(chunk_start) - chunk_start;
3229 let end_off = er0.min(chunk_end) - chunk_start;
3230 let tags = chunk.type_tag.values();
3231 for off in start_off..=end_off {
3232 let overlay_non_empty = chunk
3233 .overlay
3234 .get(off)
3235 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3236 .unwrap_or(false)
3237 || chunk
3238 .computed_overlay
3239 .get(off)
3240 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
3241 .unwrap_or(false);
3242 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
3243 return true;
3244 }
3245 }
3246 false
3247 };
3248
3249 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
3250 if scan_chunk(chunk_idx, chunk) {
3251 any_in_range = true;
3252 break;
3253 }
3254 }
3255
3256 if !any_in_range && !col.sparse_chunks.is_empty() {
3257 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
3258 if scan_chunk(chunk_idx, chunk) {
3259 any_in_range = true;
3260 break;
3261 }
3262 }
3263 }
3264
3265 if any_in_range {
3266 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
3267 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
3268 }
3269 }
3270 match (min_c0, max_c0) {
3271 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
3272 _ => None,
3273 }
3274 }
3275
3276 fn formula_row_bounds_for_columns(
3277 &self,
3278 sheet: &str,
3279 start_col: u32,
3280 end_col: u32,
3281 ) -> Option<(u32, u32)> {
3282 let sheet_id = self.graph.sheet_id(sheet)?;
3283 let sc0 = start_col.saturating_sub(1);
3284 let ec0 = end_col.saturating_sub(1);
3285 let mut min_r0: Option<u32> = None;
3286 let mut max_r0: Option<u32> = None;
3287
3288 if let Some(index) = self.graph.sheet_index(sheet_id) {
3289 for vid in index.vertices_in_col_range(sc0, ec0) {
3290 if !matches!(
3291 self.graph.get_vertex_kind(vid),
3292 VertexKind::FormulaScalar | VertexKind::FormulaArray
3293 ) {
3294 continue;
3295 }
3296 let row0 = self.graph.vertex_coord(vid).row();
3297 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
3298 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
3299 }
3300 } else {
3301 for vid in self.graph.vertices_in_sheet(sheet_id) {
3302 if !matches!(
3303 self.graph.get_vertex_kind(vid),
3304 VertexKind::FormulaScalar | VertexKind::FormulaArray
3305 ) {
3306 continue;
3307 }
3308 let coord = self.graph.vertex_coord(vid);
3309 let col0 = coord.col();
3310 if col0 < sc0 || col0 > ec0 {
3311 continue;
3312 }
3313 let row0 = coord.row();
3314 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
3315 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
3316 }
3317 }
3318
3319 match (min_r0, max_r0) {
3320 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
3321 _ => None,
3322 }
3323 }
3324
3325 fn formula_col_bounds_for_rows(
3326 &self,
3327 sheet: &str,
3328 start_row: u32,
3329 end_row: u32,
3330 ) -> Option<(u32, u32)> {
3331 let sheet_id = self.graph.sheet_id(sheet)?;
3332 let sr0 = start_row.saturating_sub(1);
3333 let er0 = end_row.saturating_sub(1);
3334 let mut min_c0: Option<u32> = None;
3335 let mut max_c0: Option<u32> = None;
3336
3337 if let Some(index) = self.graph.sheet_index(sheet_id) {
3338 for vid in index.vertices_in_row_range(sr0, er0) {
3339 if !matches!(
3340 self.graph.get_vertex_kind(vid),
3341 VertexKind::FormulaScalar | VertexKind::FormulaArray
3342 ) {
3343 continue;
3344 }
3345 let col0 = self.graph.vertex_coord(vid).col();
3346 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
3347 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
3348 }
3349 } else {
3350 for vid in self.graph.vertices_in_sheet(sheet_id) {
3351 if !matches!(
3352 self.graph.get_vertex_kind(vid),
3353 VertexKind::FormulaScalar | VertexKind::FormulaArray
3354 ) {
3355 continue;
3356 }
3357 let coord = self.graph.vertex_coord(vid);
3358 let row0 = coord.row();
3359 if row0 < sr0 || row0 > er0 {
3360 continue;
3361 }
3362 let col0 = coord.col();
3363 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
3364 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
3365 }
3366 }
3367
3368 match (min_c0, max_c0) {
3369 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
3370 _ => None,
3371 }
3372 }
3373
3374 fn union_used_bounds(
3375 first: Option<(u32, u32)>,
3376 second: Option<(u32, u32)>,
3377 ) -> Option<(u32, u32)> {
3378 match (first, second) {
3379 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
3380 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
3381 (None, None) => None,
3382 }
3383 }
3384
3385 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
3388 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3389 return;
3390 }
3391 if self.arrow_sheets.sheet(sheet).is_none() {
3392 self.arrow_sheets
3393 .sheets
3394 .push(crate::arrow_store::ArrowSheet {
3395 name: std::sync::Arc::<str>::from(sheet),
3396 columns: Vec::new(),
3397 nrows: 0,
3398 chunk_starts: Vec::new(),
3399 chunk_rows: 32 * 1024,
3400 });
3401 }
3402
3403 let row0 = row.saturating_sub(1) as usize;
3404 let col0 = col.saturating_sub(1) as usize;
3405
3406 let asheet = self
3407 .arrow_sheets
3408 .sheet_mut(sheet)
3409 .expect("ArrowSheet must exist");
3410
3411 let cur_cols = asheet.columns.len();
3412 if col0 >= cur_cols {
3413 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3414 }
3415
3416 if row0 >= asheet.nrows as usize {
3417 if asheet.columns.is_empty() {
3418 asheet.insert_columns(0, 1);
3419 }
3420 asheet.ensure_row_capacity(row0 + 1);
3421 }
3422 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
3423 use crate::arrow_store::OverlayValue;
3424 let ov = match value {
3425 LiteralValue::Empty => OverlayValue::Empty,
3426 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
3427 LiteralValue::Number(n) => OverlayValue::Number(*n),
3428 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
3429 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
3430 LiteralValue::Error(e) => {
3431 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
3432 }
3433 LiteralValue::Date(d) => {
3434 let dt = d.and_hms_opt(0, 0, 0).unwrap();
3435 let serial = crate::builtins::datetime::datetime_to_serial_for(
3436 self.config.date_system,
3437 &dt,
3438 );
3439 OverlayValue::DateTime(serial)
3440 }
3441 LiteralValue::DateTime(dt) => {
3442 let serial = crate::builtins::datetime::datetime_to_serial_for(
3443 self.config.date_system,
3444 dt,
3445 );
3446 OverlayValue::DateTime(serial)
3447 }
3448 LiteralValue::Time(t) => {
3449 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
3450 OverlayValue::DateTime(serial)
3451 }
3452 LiteralValue::Duration(d) => {
3453 let serial = d.num_seconds() as f64 / 86_400.0;
3454 OverlayValue::Duration(serial)
3455 }
3456 LiteralValue::Pending => OverlayValue::Pending,
3457 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
3458 formualizer_common::ExcelErrorKind::Value,
3459 )),
3460 };
3461 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
3462 let _ = ch.overlay.set(in_off, ov);
3463 ch.computed_overlay.remove(in_off)
3468 } else {
3469 return;
3470 };
3471 let abs_threshold = 1024usize;
3473 let frac_den = 50usize;
3474 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
3475 if freed > 0 {
3476 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
3477 }
3478 self.adjust_computed_overlay_bytes(computed_delta);
3479 }
3480 }
3481
3482 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
3487 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3488 return;
3489 }
3490 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
3491 return;
3492 };
3493 let row0 = row.saturating_sub(1) as usize;
3494 let col0 = col.saturating_sub(1) as usize;
3495 if row0 >= asheet.nrows as usize {
3496 return;
3497 }
3498 if col0 >= asheet.columns.len() {
3499 return;
3500 }
3501 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3502 return;
3503 };
3504 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
3505 let _ = ch.overlay.remove(in_off);
3506 }
3507 }
3508
3509 #[inline]
3510 fn literal_to_overlay_value(&self, value: &LiteralValue) -> crate::arrow_store::OverlayValue {
3511 use crate::arrow_store::OverlayValue;
3512 match value {
3513 LiteralValue::Empty => OverlayValue::Empty,
3514 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
3515 LiteralValue::Number(n) => OverlayValue::Number(*n),
3516 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
3517 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
3518 LiteralValue::Error(e) => {
3519 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
3520 }
3521 LiteralValue::Date(d) => {
3522 let dt = d.and_hms_opt(0, 0, 0).unwrap();
3523 let serial =
3524 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, &dt);
3525 OverlayValue::DateTime(serial)
3526 }
3527 LiteralValue::DateTime(dt) => {
3528 let serial =
3529 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, dt);
3530 OverlayValue::DateTime(serial)
3531 }
3532 LiteralValue::Time(t) => {
3533 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
3534 OverlayValue::DateTime(serial)
3535 }
3536 LiteralValue::Duration(d) => {
3537 let serial = d.num_seconds() as f64 / 86_400.0;
3538 OverlayValue::Duration(serial)
3539 }
3540 LiteralValue::Pending => OverlayValue::Pending,
3541 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
3542 formualizer_common::ExcelErrorKind::Value,
3543 )),
3544 }
3545 }
3546
3547 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3550 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3551 return None;
3552 }
3553 let asheet = self.arrow_sheets.sheet(sheet)?;
3554 let row0 = row.saturating_sub(1) as usize;
3555 let col0 = col.saturating_sub(1) as usize;
3556 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
3557 return None;
3558 }
3559 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
3560 let ch = asheet.columns[col0].chunk(ch_idx)?;
3561 ch.overlay.get_scalar(in_off).map(|ov| ov.to_literal())
3562 }
3563
3564 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3567 if !(self.config.arrow_storage_enabled
3568 && self.config.delta_overlay_enabled
3569 && self.config.write_formula_overlay_enabled)
3570 {
3571 return None;
3572 }
3573 let asheet = self.arrow_sheets.sheet(sheet)?;
3574 let row0 = row.saturating_sub(1) as usize;
3575 let col0 = col.saturating_sub(1) as usize;
3576 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
3577 return None;
3578 }
3579 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
3580 let ch = asheet.columns[col0].chunk(ch_idx)?;
3581 ch.computed_overlay
3582 .get_scalar(in_off)
3583 .map(|ov| ov.to_literal())
3584 }
3585
3586 fn set_delta_overlay_cell_raw(
3587 &mut self,
3588 sheet: &str,
3589 row: u32,
3590 col: u32,
3591 value: Option<LiteralValue>,
3592 ) {
3593 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
3594 return;
3595 }
3596
3597 self.ensure_arrow_sheet(sheet);
3598 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
3599 let row0 = row.saturating_sub(1) as usize;
3600 let col0 = col.saturating_sub(1) as usize;
3601 let asheet = self
3602 .arrow_sheets
3603 .sheet_mut(sheet)
3604 .expect("ArrowSheet must exist");
3605
3606 let cur_cols = asheet.columns.len();
3607 if col0 >= cur_cols {
3608 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3609 }
3610 if row0 >= asheet.nrows as usize {
3611 if asheet.columns.is_empty() {
3612 asheet.insert_columns(0, 1);
3613 }
3614 asheet.ensure_row_capacity(row0 + 1);
3615 }
3616
3617 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3618 return;
3619 };
3620 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3621 return;
3622 };
3623
3624 if let Some(ov) = ov_opt {
3625 let _ = ch.overlay.set(in_off, ov);
3626 } else {
3627 let _ = ch.overlay.remove(in_off);
3628 }
3629 }
3630
3631 fn set_computed_overlay_cell_raw(
3632 &mut self,
3633 sheet: &str,
3634 row: u32,
3635 col: u32,
3636 value: Option<LiteralValue>,
3637 ) {
3638 if !(self.config.arrow_storage_enabled
3639 && self.config.delta_overlay_enabled
3640 && self.config.write_formula_overlay_enabled)
3641 {
3642 return;
3643 }
3644
3645 self.ensure_arrow_sheet(sheet);
3646 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
3647 let row0 = row.saturating_sub(1) as usize;
3648 let col0 = col.saturating_sub(1) as usize;
3649 let asheet = self
3650 .arrow_sheets
3651 .sheet_mut(sheet)
3652 .expect("ArrowSheet must exist");
3653
3654 let cur_cols = asheet.columns.len();
3655 if col0 >= cur_cols {
3656 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3657 }
3658 if row0 >= asheet.nrows as usize {
3659 if asheet.columns.is_empty() {
3660 asheet.insert_columns(0, 1);
3661 }
3662 asheet.ensure_row_capacity(row0 + 1);
3663 }
3664
3665 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3666 return;
3667 };
3668 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3669 return;
3670 };
3671
3672 let delta = if let Some(ov) = ov_opt {
3673 ch.computed_overlay.set(in_off, ov)
3674 } else {
3675 ch.computed_overlay.remove(in_off)
3676 };
3677 self.adjust_computed_overlay_bytes(delta);
3678 }
3679
3680 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
3681 use crate::engine::ArrowOp;
3682
3683 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
3684 Box::new(batch.ops.iter().rev())
3685 } else {
3686 Box::new(batch.ops.iter())
3687 };
3688
3689 for op in iter {
3690 match op {
3691 ArrowOp::SetDeltaCell {
3692 sheet_id,
3693 row0,
3694 col0,
3695 old,
3696 new,
3697 } => {
3698 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3699 let v = if undo { old.clone() } else { new.clone() };
3700 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
3701 }
3702 ArrowOp::SetComputedCell {
3703 sheet_id,
3704 row0,
3705 col0,
3706 old,
3707 new,
3708 } => {
3709 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3710 let v = if undo { old.clone() } else { new.clone() };
3711 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
3712 }
3713 ArrowOp::RestoreComputedRect {
3714 sheet_id,
3715 sr0,
3716 sc0,
3717 er0,
3718 ec0,
3719 old,
3720 new,
3721 } => {
3722 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3723 let vals = if undo { old } else { new };
3724 let height = (*er0).saturating_sub(*sr0) as usize + 1;
3725 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
3726 for r in 0..height {
3727 for c in 0..width {
3728 let v = vals
3729 .get(r)
3730 .and_then(|row| row.get(c))
3731 .cloned()
3732 .unwrap_or(LiteralValue::Empty);
3733 self.set_computed_overlay_cell_raw(
3734 &sheet,
3735 *sr0 + 1 + r as u32,
3736 *sc0 + 1 + c as u32,
3737 Some(v),
3738 );
3739 }
3740 }
3741 }
3742 ArrowOp::InsertRows {
3743 sheet_id,
3744 before0,
3745 count,
3746 } => {
3747 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3748 self.ensure_arrow_sheet(&sheet);
3749 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
3750 if undo {
3751 asheet.delete_rows(*before0 as usize, *count as usize);
3752 } else {
3753 asheet.insert_rows(*before0 as usize, *count as usize);
3754 }
3755 }
3756 }
3757 ArrowOp::InsertCols {
3758 sheet_id,
3759 before0,
3760 count,
3761 } => {
3762 let sheet = self.graph.sheet_name(*sheet_id).to_string();
3763 self.ensure_arrow_sheet(&sheet);
3764 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
3765 if undo {
3766 asheet.delete_columns(*before0 as usize, *count as usize);
3767 } else {
3768 asheet.insert_columns(*before0 as usize, *count as usize);
3769 }
3770 }
3771 }
3772 }
3773 }
3774 }
3775
3776 fn record_spill_ops_into_arrow_undo(
3777 &mut self,
3778 undo: &mut crate::engine::ArrowUndoBatch,
3779 events: &[crate::engine::ChangeEvent],
3780 ) {
3781 use crate::engine::ChangeEvent;
3782 use formualizer_common::LiteralValue;
3783
3784 #[allow(clippy::type_complexity)]
3785 let rect_from_snapshot =
3786 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
3787 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
3788 if snap.target_cells.is_empty() {
3789 return None;
3790 }
3791 let sheet_id = snap.target_cells[0].sheet_id;
3792 let sr0 = snap.target_cells[0].coord.row();
3793 let sc0 = snap.target_cells[0].coord.col();
3794 if snap.values.is_empty() || snap.values[0].is_empty() {
3795 return None;
3796 }
3797 let h = snap.values.len() as u32;
3798 let w = snap.values[0].len() as u32;
3799 let er0 = sr0.saturating_add(h.saturating_sub(1));
3800 let ec0 = sc0.saturating_add(w.saturating_sub(1));
3801 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
3802 };
3803
3804 for ev in events {
3805 match ev {
3806 ChangeEvent::SpillCommitted { old, new, .. } => {
3807 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
3808 let old_vals = if let Some(old_snap) = old {
3809 rect_from_snapshot(old_snap)
3810 .map(|(_, _, _, _, _, v)| v)
3811 .unwrap_or_else(|| {
3812 vec![
3813 vec![LiteralValue::Empty; new_vals[0].len()];
3814 new_vals.len()
3815 ]
3816 })
3817 } else {
3818 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
3819 };
3820 undo.record_restore_computed_rect(
3821 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
3822 );
3823 }
3824 }
3825 ChangeEvent::SpillCleared { old, .. } => {
3826 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
3827 let new_vals =
3828 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
3829 undo.record_restore_computed_rect(
3830 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
3831 );
3832 }
3833 }
3834 _ => {}
3835 }
3836 }
3837 }
3838
3839 fn mirror_value_to_computed_overlay(
3844 &mut self,
3845 sheet: &str,
3846 row: u32,
3847 col: u32,
3848 value: &LiteralValue,
3849 ) {
3850 if !(self.config.arrow_storage_enabled
3851 && self.config.delta_overlay_enabled
3852 && self.config.write_formula_overlay_enabled)
3853 {
3854 return;
3855 }
3856 if self.computed_overlay_mirroring_disabled {
3857 return;
3858 }
3859
3860 let ov = self.literal_to_overlay_value(value);
3861 self.write_computed_overlay_value_0based(
3862 sheet,
3863 row.saturating_sub(1),
3864 col.saturating_sub(1),
3865 ov,
3866 );
3867 }
3868
3869 fn write_computed_overlay_value_0based(
3870 &mut self,
3871 sheet: &str,
3872 row0: u32,
3873 col0: u32,
3874 value: OverlayValue,
3875 ) {
3876 if !(self.config.arrow_storage_enabled
3877 && self.config.delta_overlay_enabled
3878 && self.config.write_formula_overlay_enabled)
3879 {
3880 return;
3881 }
3882 if self.computed_overlay_mirroring_disabled {
3883 return;
3884 }
3885
3886 self.ensure_arrow_sheet(sheet);
3887
3888 let row0 = row0 as usize;
3889 let col0 = col0 as usize;
3890 let asheet = self
3891 .arrow_sheets
3892 .sheet_mut(sheet)
3893 .expect("ArrowSheet must exist");
3894
3895 let cur_cols = asheet.columns.len();
3896 if col0 >= cur_cols {
3897 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
3898 }
3899
3900 if row0 >= asheet.nrows as usize {
3901 if asheet.columns.is_empty() {
3902 asheet.insert_columns(0, 1);
3903 }
3904 asheet.ensure_row_capacity(row0 + 1);
3905 }
3906
3907 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
3908 return;
3909 };
3910 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
3911 return;
3912 };
3913
3914 let delta = ch.computed_overlay.set_scalar(in_off, value);
3915 self.adjust_computed_overlay_bytes(delta);
3916
3917 if let Some(cap) = self.config.max_overlay_memory_bytes
3918 && self.computed_overlay_bytes_estimate > cap
3919 {
3920 self.disable_computed_overlay_mirroring_due_to_budget(cap);
3921 }
3922 }
3923
3924 pub(crate) fn plan_computed_write_coalescing(
3925 &self,
3926 buffer: &ComputedWriteBuffer,
3927 ) -> ComputedWriteCoalescingPlan {
3928 self.plan_computed_write_coalescing_from_writes(buffer.writes().iter().cloned())
3929 }
3930
3931 fn plan_owned_computed_write_coalescing(
3932 &self,
3933 writes: Vec<ComputedWrite>,
3934 ) -> ComputedWriteCoalescingPlan {
3935 self.plan_computed_write_coalescing_from_writes(writes)
3936 }
3937
3938 fn plan_computed_write_coalescing_from_writes(
3939 &self,
3940 writes: impl IntoIterator<Item = ComputedWrite>,
3941 ) -> ComputedWriteCoalescingPlan {
3942 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
3943 BTreeMap::new();
3944 let mut input_cells = 0usize;
3945
3946 for write in writes {
3947 match write {
3948 ComputedWrite::Cell {
3949 seq,
3950 sheet_id,
3951 row0,
3952 col0,
3953 value,
3954 } => {
3955 input_cells = input_cells.saturating_add(1);
3956 self.push_computed_write_plan_entry(
3957 &mut groups,
3958 seq,
3959 sheet_id,
3960 row0,
3961 col0,
3962 value,
3963 );
3964 }
3965 ComputedWrite::Rect {
3966 seq,
3967 sheet_id,
3968 sr0,
3969 sc0,
3970 values,
3971 } => {
3972 for (r_off, row) in values.into_iter().enumerate() {
3973 for (c_off, value) in row.into_iter().enumerate() {
3974 input_cells = input_cells.saturating_add(1);
3975 self.push_computed_write_plan_entry(
3976 &mut groups,
3977 seq,
3978 sheet_id,
3979 sr0.saturating_add(r_off as u32),
3980 sc0.saturating_add(c_off as u32),
3981 value,
3982 );
3983 }
3984 }
3985 }
3986 }
3987 }
3988
3989 let mut plan = ComputedWriteCoalescingPlan {
3990 chunks: Vec::with_capacity(groups.len()),
3991 input_cells,
3992 coalesced_cells: 0,
3993 overwritten_cells: 0,
3994 };
3995 for (key, entries) in groups {
3996 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(key, entries);
3997 plan.coalesced_cells = plan
3998 .coalesced_cells
3999 .saturating_add(chunk_plan.entries.len());
4000 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
4001 plan.chunks.push(chunk_plan);
4002 }
4003 debug_assert_eq!(
4004 plan.input_cells,
4005 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
4006 );
4007 plan
4008 }
4009
4010 fn push_computed_write_plan_entry(
4011 &self,
4012 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
4013 seq: u64,
4014 sheet_id: SheetId,
4015 row0: u32,
4016 col0: u32,
4017 value: OverlayValue,
4018 ) {
4019 let (chunk_idx, chunk_start_row0, row_in_chunk) =
4020 self.locate_computed_write_chunk(sheet_id, row0);
4021 let key = ComputedWriteChunkKey {
4022 sheet_id,
4023 col0,
4024 chunk_idx,
4025 chunk_start_row0,
4026 };
4027 groups
4028 .entry(key)
4029 .or_default()
4030 .push(ComputedWriteChunkEntryPlan {
4031 row_in_chunk,
4032 seq,
4033 value,
4034 });
4035 }
4036
4037 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
4038 let sheet_name = self.graph.sheet_name(sheet_id);
4039 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
4040 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
4041 }
4042 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
4043 }
4044
4045 fn locate_row_in_sheet_for_computed_write_plan(
4046 sheet: &crate::arrow_store::ArrowSheet,
4047 row0: usize,
4048 ) -> (usize, u32, usize) {
4049 if row0 < sheet.nrows as usize
4050 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
4051 {
4052 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
4053 return (chunk_idx, chunk_start as u32, row_in_chunk);
4054 }
4055
4056 let chunk_rows = sheet.chunk_rows.max(1);
4057 if sheet.chunk_starts.is_empty() {
4058 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
4059 }
4060
4061 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
4062 let mut chunk_start = sheet.chunk_starts[chunk_idx];
4063 while chunk_start.saturating_add(chunk_rows) <= row0 {
4064 chunk_idx = chunk_idx.saturating_add(1);
4065 chunk_start = chunk_start.saturating_add(chunk_rows);
4066 }
4067 (
4068 chunk_idx,
4069 chunk_start as u32,
4070 row0.saturating_sub(chunk_start),
4071 )
4072 }
4073
4074 fn locate_row_in_empty_sheet_for_computed_write_plan(
4075 row0: usize,
4076 chunk_rows: usize,
4077 ) -> (usize, u32, usize) {
4078 let chunk_rows = chunk_rows.max(1);
4079 let chunk_idx = row0 / chunk_rows;
4080 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
4081 (
4082 chunk_idx,
4083 chunk_start as u32,
4084 row0.saturating_sub(chunk_start),
4085 )
4086 }
4087
4088 #[cfg(test)]
4089 pub(crate) fn debug_plan_computed_write_coalescing(
4090 &self,
4091 buffer: &ComputedWriteBuffer,
4092 ) -> ComputedWriteCoalescingPlan {
4093 self.plan_computed_write_coalescing(buffer)
4094 }
4095
4096 pub(crate) fn flush_computed_write_buffer(
4097 &mut self,
4098 buffer: &mut ComputedWriteBuffer,
4099 ) -> Result<(), ExcelError> {
4100 if buffer.is_empty() {
4101 return Ok(());
4102 }
4103
4104 let plan = self.plan_owned_computed_write_coalescing(buffer.take_writes());
4105 self.flush_computed_write_plan(plan);
4106
4107 Ok(())
4108 }
4109
4110 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
4111 for chunk in plan.chunks {
4112 self.flush_computed_write_chunk_plan(chunk);
4113 }
4114 }
4115
4116 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
4117 match &chunk.shape {
4118 ComputedWriteChunkPlanShape::Point => {
4119 self.flush_computed_write_chunk_plan_as_points(chunk);
4120 }
4121 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
4122 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
4123 }
4124 ComputedWriteChunkPlanShape::DenseRange { .. } => {
4125 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
4126 }
4127 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
4128 if Self::should_emit_computed_run_fragment(*len, *runs) {
4129 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
4130 } else {
4131 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
4132 }
4133 }
4134 }
4135 }
4136
4137 #[inline]
4138 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
4139 runs <= len / 2
4140 }
4141
4142 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
4143 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
4144 for entry in chunk.entries {
4145 let row0 = chunk
4146 .chunk_start_row0
4147 .saturating_add(entry.row_in_chunk as u32);
4148 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
4149 }
4150 }
4151
4152 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
4153 &mut self,
4154 chunk: ComputedWriteChunkPlan,
4155 ) {
4156 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
4157 let sheet_id = chunk.sheet_id;
4158 let col0 = chunk.col0;
4159 let chunk_idx = chunk.chunk_idx;
4160 let chunk_start_row0 = chunk.chunk_start_row0;
4161 let items: Vec<(usize, OverlayValue)> = chunk
4162 .entries
4163 .into_iter()
4164 .map(|entry| (entry.row_in_chunk, entry.value))
4165 .collect();
4166 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
4167 items,
4168 point_estimate,
4169 ) {
4170 Some(Ok(fragment)) => {
4171 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
4172 }
4173 Some(Err(cells)) => {
4174 self.flush_computed_overlay_cells_as_points(
4175 sheet_id,
4176 col0,
4177 chunk_start_row0,
4178 cells,
4179 );
4180 }
4181 None => {}
4182 }
4183 }
4184
4185 #[inline]
4186 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
4187 chunk
4188 .entries
4189 .iter()
4190 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
4191 .fold(0usize, usize::saturating_add)
4192 }
4193
4194 fn flush_computed_overlay_cells_as_points(
4195 &mut self,
4196 sheet_id: SheetId,
4197 col0: u32,
4198 chunk_start_row0: u32,
4199 cells: Vec<(usize, OverlayValue)>,
4200 ) {
4201 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
4202 for (row_in_chunk, value) in cells {
4203 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
4204 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
4205 }
4206 }
4207
4208 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
4209 if chunk.entries.is_empty() {
4210 return;
4211 }
4212 let start = chunk.entries[0].row_in_chunk;
4213 let values: Vec<OverlayValue> =
4214 chunk.entries.into_iter().map(|entry| entry.value).collect();
4215 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
4216 self.apply_computed_overlay_fragment(
4217 chunk.sheet_id,
4218 chunk.col0,
4219 chunk.chunk_idx,
4220 fragment,
4221 );
4222 }
4223 }
4224
4225 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
4226 if chunk.entries.is_empty() {
4227 return;
4228 }
4229 let start = chunk.entries[0].row_in_chunk;
4230 let values: Vec<OverlayValue> =
4231 chunk.entries.into_iter().map(|entry| entry.value).collect();
4232 if let Some(fragment) = OverlayFragment::run_range(start, values) {
4233 self.apply_computed_overlay_fragment(
4234 chunk.sheet_id,
4235 chunk.col0,
4236 chunk.chunk_idx,
4237 fragment,
4238 );
4239 }
4240 }
4241
4242 fn apply_computed_overlay_fragment(
4243 &mut self,
4244 sheet_id: SheetId,
4245 col0: u32,
4246 chunk_idx: usize,
4247 fragment: OverlayFragment,
4248 ) {
4249 if !(self.config.arrow_storage_enabled
4250 && self.config.delta_overlay_enabled
4251 && self.config.write_formula_overlay_enabled)
4252 {
4253 return;
4254 }
4255 if self.computed_overlay_mirroring_disabled {
4256 return;
4257 }
4258
4259 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
4260 self.ensure_arrow_sheet(&sheet_name);
4261
4262 let col0 = col0 as usize;
4263 let asheet = self
4264 .arrow_sheets
4265 .sheet_mut(&sheet_name)
4266 .expect("ArrowSheet must exist");
4267
4268 let cur_cols = asheet.columns.len();
4269 if col0 >= cur_cols {
4270 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
4271 }
4272
4273 let start_row0 = asheet
4274 .chunk_starts
4275 .get(chunk_idx)
4276 .copied()
4277 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
4278 let required_rows =
4279 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
4280 if required_rows > asheet.nrows as usize {
4281 if asheet.columns.is_empty() {
4282 asheet.insert_columns(0, 1);
4283 }
4284 asheet.ensure_row_capacity(required_rows);
4285 }
4286
4287 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
4288 return;
4289 };
4290 let delta = ch.computed_overlay.apply_fragment(fragment);
4291 self.adjust_computed_overlay_bytes(delta);
4292
4293 if let Some(cap) = self.config.max_overlay_memory_bytes
4294 && self.computed_overlay_bytes_estimate > cap
4295 {
4296 self.disable_computed_overlay_mirroring_due_to_budget(cap);
4297 }
4298 }
4299
4300 #[inline]
4301 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
4302 if delta >= 0 {
4303 self.computed_overlay_bytes_estimate = self
4304 .computed_overlay_bytes_estimate
4305 .saturating_add(delta as usize);
4306 } else {
4307 self.computed_overlay_bytes_estimate = self
4308 .computed_overlay_bytes_estimate
4309 .saturating_sub((-delta) as usize);
4310 }
4311 }
4312
4313 fn clear_all_computed_overlays(&mut self) {
4314 let mut freed_total = 0usize;
4315 for sh in self.arrow_sheets.sheets.iter_mut() {
4316 for col in sh.columns.iter_mut() {
4317 for ch in col.chunks.iter_mut() {
4318 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
4319 }
4320 for ch in col.sparse_chunks.values_mut() {
4321 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
4322 }
4323 }
4324 }
4325 self.computed_overlay_bytes_estimate = self
4326 .computed_overlay_bytes_estimate
4327 .saturating_sub(freed_total);
4328 }
4329
4330 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
4331 self.compact_all_computed_overlays();
4334 }
4335
4336 fn compact_all_computed_overlays(&mut self) {
4339 let mut freed_total = 0usize;
4340 for sheet in self.arrow_sheets.sheets.iter_mut() {
4341 for col_idx in 0..sheet.columns.len() {
4342 let num_dense = sheet.columns[col_idx].chunks.len();
4344 for ch_idx in 0..num_dense {
4345 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
4346 }
4347 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
4349 .sparse_chunks
4350 .keys()
4351 .copied()
4352 .collect();
4353 for ch_idx in sparse_keys {
4354 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
4355 }
4356 }
4357 }
4358 self.computed_overlay_bytes_estimate = self
4359 .computed_overlay_bytes_estimate
4360 .saturating_sub(freed_total);
4361 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
4362 }
4363
4364 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
4365 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
4366 }
4367
4368 fn record_vertex_value_to_overlay(
4369 &mut self,
4370 vertex_id: VertexId,
4371 value: &LiteralValue,
4372 computed_writes: Option<&mut ComputedWriteBuffer>,
4373 ) -> Result<(), ExcelError> {
4374 if !(self.config.arrow_storage_enabled
4375 && self.config.delta_overlay_enabled
4376 && self.config.write_formula_overlay_enabled)
4377 {
4378 return Ok(());
4379 }
4380 if self.computed_overlay_mirroring_disabled {
4381 return Ok(());
4382 }
4383 if !matches!(
4384 self.graph.get_vertex_kind(vertex_id),
4385 VertexKind::FormulaScalar | VertexKind::FormulaArray
4386 ) {
4387 return Ok(());
4388 }
4389 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
4390 return Ok(());
4391 };
4392 let ov = self.literal_to_overlay_value(value);
4393 if let Some(buffer) = computed_writes {
4394 buffer.push_cell(cell.sheet_id, cell.coord.row(), cell.coord.col(), ov);
4395 if self.should_flush_computed_write_buffer(buffer) {
4396 self.flush_computed_write_buffer(buffer)?;
4397 }
4398 } else {
4399 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
4400 self.write_computed_overlay_value_0based(
4401 &sheet_name,
4402 cell.coord.row(),
4403 cell.coord.col(),
4404 ov,
4405 );
4406 }
4407 Ok(())
4408 }
4409
4410 #[inline]
4411 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
4412 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
4413 if cap == 0 {
4414 return false;
4415 }
4416 self.computed_overlay_bytes_estimate
4417 .saturating_add(buffer.estimated_bytes())
4418 > cap
4419 })
4420 }
4421
4422 pub fn overlay_memory_usage(&self) -> usize {
4424 self.computed_overlay_bytes_estimate
4425 }
4426
4427 #[cfg(test)]
4428 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
4429 self.overlay_compactions
4430 }
4431
4432 #[cfg(test)]
4433 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
4434 let mut total = 0usize;
4435 for sheet in &self.arrow_sheets.sheets {
4436 for column in &sheet.columns {
4437 for chunk in &column.chunks {
4438 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
4439 }
4440 for chunk in column.sparse_chunks.values() {
4441 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
4442 }
4443 }
4444 }
4445 self.computed_overlay_bytes_estimate = total;
4446 total
4447 }
4448
4449 fn resolve_sheet_locator_for_write(
4450 &mut self,
4451 loc: formualizer_common::SheetLocator<'_>,
4452 current_sheet: &str,
4453 ) -> Result<SheetId, ExcelError> {
4454 Ok(match loc {
4455 formualizer_common::SheetLocator::Id(id) => id,
4456 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
4457 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
4458 })
4459 }
4460
4461 fn resolve_sheet_locator_for_read(
4462 &self,
4463 loc: formualizer_common::SheetLocator<'_>,
4464 current_sheet: &str,
4465 ) -> Result<SheetId, ExcelError> {
4466 match loc {
4467 formualizer_common::SheetLocator::Id(id) => Ok(id),
4468 formualizer_common::SheetLocator::Name(name) => self
4469 .graph
4470 .sheet_id(name.as_ref())
4471 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
4472 formualizer_common::SheetLocator::Current => self
4473 .graph
4474 .sheet_id(current_sheet)
4475 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
4476 }
4477 }
4478
4479 pub fn set_cell_value(
4481 &mut self,
4482 sheet: &str,
4483 row: u32,
4484 col: u32,
4485 value: LiteralValue,
4486 ) -> Result<(), ExcelError> {
4487 self.graph.set_cell_value(sheet, row, col, value.clone())?;
4488 self.mirror_value_to_overlay(sheet, row, col, &value);
4490 self.snapshot_id
4492 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
4493 self.has_edited = true;
4494 Ok(())
4495 }
4496
4497 pub fn set_cell_value_ref(
4498 &mut self,
4499 cell: formualizer_common::SheetCellRef<'_>,
4500 current_sheet: &str,
4501 value: LiteralValue,
4502 ) -> Result<(), ExcelError> {
4503 let owned = cell.into_owned();
4504 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
4505 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
4506 self.set_cell_value(
4507 &sheet_name,
4508 owned.coord.row() + 1,
4509 owned.coord.col() + 1,
4510 value,
4511 )
4512 }
4513
4514 pub fn set_cell_formula_ref(
4515 &mut self,
4516 cell: formualizer_common::SheetCellRef<'_>,
4517 current_sheet: &str,
4518 ast: ASTNode,
4519 ) -> Result<(), ExcelError> {
4520 let owned = cell.into_owned();
4521 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
4522 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
4523 self.set_cell_formula(
4524 &sheet_name,
4525 owned.coord.row() + 1,
4526 owned.coord.col() + 1,
4527 ast,
4528 )
4529 }
4530
4531 pub fn get_cell_value_ref(
4532 &self,
4533 cell: formualizer_common::SheetCellRef<'_>,
4534 current_sheet: &str,
4535 ) -> Result<Option<LiteralValue>, ExcelError> {
4536 let owned = cell.into_owned();
4537 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
4538 let sheet_name = self.graph.sheet_name(sheet_id);
4539 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
4540 }
4541
4542 pub fn resolve_range_view_sheet_ref<'c>(
4543 &'c self,
4544 r: &formualizer_common::SheetRef<'_>,
4545 current_sheet: &str,
4546 ) -> Result<RangeView<'c>, ExcelError> {
4547 use formualizer_common::SheetLocator;
4548
4549 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
4550 match loc {
4551 SheetLocator::Current => Ok(None),
4552 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
4553 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
4554 }
4555 };
4556
4557 let rt = match r {
4558 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
4559 sheet: sheet_to_opt_name(cell.sheet.clone())?,
4560 row: cell.coord.row() + 1,
4561 col: cell.coord.col() + 1,
4562 row_abs: cell.coord.row_abs(),
4563 col_abs: cell.coord.col_abs(),
4564 },
4565 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
4566 sheet: sheet_to_opt_name(range.sheet.clone())?,
4567 start_row: range.start_row.map(|b| b.index + 1),
4568 start_col: range.start_col.map(|b| b.index + 1),
4569 end_row: range.end_row.map(|b| b.index + 1),
4570 end_col: range.end_col.map(|b| b.index + 1),
4571 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
4572 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
4573 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
4574 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
4575 },
4576 };
4577
4578 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
4579 }
4580
4581 pub fn set_cell_formula(
4583 &mut self,
4584 sheet: &str,
4585 row: u32,
4586 col: u32,
4587 ast: ASTNode,
4588 ) -> Result<(), ExcelError> {
4589 let volatile = self.is_ast_volatile_with_provider(&ast);
4590 self.graph
4591 .set_cell_formula_with_volatility(sheet, row, col, ast, volatile)?;
4592
4593 self.clear_delta_overlay_cell(sheet, row, col);
4598
4599 self.mark_topology_edited();
4601 Ok(())
4602 }
4603
4604 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
4606 where
4607 I: IntoIterator<Item = (u32, u32, ASTNode)>,
4608 {
4609 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
4610 let vol_flags: Vec<bool> = collected
4611 .iter()
4612 .map(|(_, _, ast)| self.is_ast_volatile_with_provider(ast))
4613 .collect();
4614 let n = self
4615 .graph
4616 .bulk_set_formulas_with_volatility(sheet, collected, vol_flags)?;
4617 if n > 0 {
4619 self.mark_topology_edited();
4620 }
4621 Ok(n)
4622 }
4623
4624 #[inline]
4625 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
4626 match v {
4627 LiteralValue::Empty => None,
4628 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
4629 other => Some(other),
4630 }
4631 }
4632
4633 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
4635 self.read_cell_value(sheet, row, col)
4636 .and_then(Self::normalize_public_cell_read)
4637 }
4638
4639 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
4641 let asheet = self.sheet_store().sheet(sheet)?;
4642 let r0 = row.saturating_sub(1) as usize;
4643 let c0 = col.saturating_sub(1) as usize;
4644 let v = asheet.get_cell_value(r0, c0);
4645 if matches!(v, LiteralValue::Empty) {
4646 None
4647 } else {
4648 Some(v)
4649 }
4650 }
4651
4652 pub(crate) fn read_range_values(
4654 &self,
4655 sheet: &str,
4656 sr: u32,
4657 sc: u32,
4658 er: u32,
4659 ec: u32,
4660 ) -> RangeView<'_> {
4661 let Some(asheet) = self.sheet_store().sheet(sheet) else {
4662 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
4663 };
4664 if er < sr || ec < sc {
4665 return asheet.range_view(1, 1, 0, 0);
4666 }
4667 let sr0 = sr.saturating_sub(1) as usize;
4668 let sc0 = sc.saturating_sub(1) as usize;
4669 let er0 = er.saturating_sub(1) as usize;
4670 let ec0 = ec.saturating_sub(1) as usize;
4671 asheet.range_view(sr0, sc0, er0, ec0)
4672 }
4673
4674 pub fn get_cell(
4676 &self,
4677 sheet: &str,
4678 row: u32,
4679 col: u32,
4680 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
4681 let v = self.get_cell_value(sheet, row, col);
4682 let sheet_id = self.graph.sheet_id(sheet)?;
4683 let coord = Coord::from_excel(row, col, true, true);
4684 let cell = CellRef::new(sheet_id, coord);
4685 let vid = self.graph.get_vertex_for_cell(&cell)?;
4686 let ast = self.graph.get_formula_id(vid).and_then(|ast_id| {
4687 self.graph
4688 .data_store()
4689 .retrieve_ast(ast_id, self.graph.sheet_reg())
4690 });
4691 Some((ast, v))
4692 }
4693
4694 pub fn begin_batch(&mut self) {
4696 self.graph.begin_batch();
4697 }
4698
4699 pub fn end_batch(&mut self) {
4701 self.graph.end_batch();
4702 }
4703
4704 #[inline]
4707 fn record_cell_if_changed(
4708 delta: &mut DeltaCollector,
4709 cell: &CellRef,
4710 old: &LiteralValue,
4711 new: &LiteralValue,
4712 ) {
4713 if old != new {
4714 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
4715 }
4716 }
4717
4718 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
4719 self.evaluate_vertex_impl(vertex_id, None)
4720 }
4721
4722 fn evaluate_vertex_impl(
4723 &mut self,
4724 vertex_id: VertexId,
4725 delta: Option<&mut DeltaCollector>,
4726 ) -> Result<LiteralValue, ExcelError> {
4727 let mut delta = delta;
4728 if !self.graph.vertex_exists(vertex_id) {
4730 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
4731 .with_message(format!("Vertex not found: {vertex_id:?}")));
4732 }
4733
4734 let kind = self.graph.get_vertex_kind(vertex_id);
4736 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
4737
4738 let ast_id = match kind {
4739 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
4740 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
4741 ast_id
4742 } else {
4743 return Ok(LiteralValue::Number(0.0));
4744 }
4745 }
4746 VertexKind::Empty | VertexKind::Cell => {
4747 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
4748 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
4749 let row = cell_ref.coord.row() + 1;
4750 let col = cell_ref.coord.col() + 1;
4751 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
4752 return Ok(v);
4753 }
4754 }
4755 return Ok(LiteralValue::Number(0.0));
4756 }
4757 VertexKind::NamedScalar => {
4758 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
4759 return Ok(value);
4760 }
4761 VertexKind::NamedArray => {
4762 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
4763 return Ok(value);
4764 }
4765 VertexKind::InfiniteRange
4766 | VertexKind::Range
4767 | VertexKind::External
4768 | VertexKind::Table => {
4769 return Ok(LiteralValue::Number(0.0));
4771 }
4772 };
4773
4774 let sheet_name = self.graph.sheet_name(sheet_id);
4776 let cell_ref = self
4777 .graph
4778 .get_cell_ref(vertex_id)
4779 .expect("cell ref for vertex");
4780 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
4781
4782 let result =
4783 interpreter.evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg());
4784
4785 match result {
4787 Ok(cv) => {
4788 let result_literal = cv.into_literal();
4789 match result_literal {
4790 LiteralValue::Array(rows) => {
4791 self.graph
4793 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
4794 let anchor = self
4796 .graph
4797 .get_cell_ref(vertex_id)
4798 .expect("cell ref for vertex");
4799 let sheet_id = anchor.sheet_id;
4800 let h = rows.len() as u32;
4801 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
4802
4803 let spill_cells = (h as u64).saturating_mul(w as u64);
4805 if spill_cells > self.config.spill.max_spill_cells as u64 {
4806 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
4807 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4808 .with_message("SpillTooLarge")
4809 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4810 expected_rows: h,
4811 expected_cols: w,
4812 });
4813 let spill_val = LiteralValue::Error(spill_err.clone());
4814 if let Some(d) = delta.as_deref_mut() {
4815 let old = self
4816 .read_cell_value(
4817 self.graph.sheet_name(anchor.sheet_id),
4818 anchor.coord.row() + 1,
4819 anchor.coord.col() + 1,
4820 )
4821 .unwrap_or(LiteralValue::Empty);
4822 if old != spill_val {
4823 d.record_cell(
4824 anchor.sheet_id,
4825 anchor.coord.row(),
4826 anchor.coord.col(),
4827 );
4828 }
4829 }
4830 self.graph.update_vertex_value(vertex_id, spill_val.clone());
4831 if self.config.arrow_storage_enabled
4832 && self.config.delta_overlay_enabled
4833 && self.config.write_formula_overlay_enabled
4834 {
4835 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4836 self.mirror_value_to_computed_overlay(
4837 &sheet_name,
4838 anchor.coord.row() + 1,
4839 anchor.coord.col() + 1,
4840 &spill_val,
4841 );
4842 }
4843 return Ok(spill_val);
4844 }
4845 const PACKED_MAX_ROW: u32 = 1_048_575; const PACKED_MAX_COL: u32 = 16_383; let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
4849 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
4850 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
4851 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
4852 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4853 .with_message("Spill exceeds sheet bounds")
4854 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4855 expected_rows: h,
4856 expected_cols: w,
4857 });
4858 let spill_val = LiteralValue::Error(spill_err.clone());
4859 if let Some(d) = delta.as_deref_mut() {
4860 let old = self
4861 .read_cell_value(
4862 self.graph.sheet_name(anchor.sheet_id),
4863 anchor.coord.row() + 1,
4864 anchor.coord.col() + 1,
4865 )
4866 .unwrap_or(LiteralValue::Empty);
4867 if old != spill_val {
4868 d.record_cell(
4869 anchor.sheet_id,
4870 anchor.coord.row(),
4871 anchor.coord.col(),
4872 );
4873 }
4874 }
4875 self.graph.update_vertex_value(vertex_id, spill_val.clone());
4876 if self.config.arrow_storage_enabled
4877 && self.config.delta_overlay_enabled
4878 && self.config.write_formula_overlay_enabled
4879 {
4880 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
4881 self.mirror_value_to_computed_overlay(
4882 &sheet_name,
4883 anchor.coord.row() + 1,
4884 anchor.coord.col() + 1,
4885 &spill_val,
4886 );
4887 }
4888 return Ok(spill_val);
4889 }
4890 let mut targets = Vec::new();
4891 for r in 0..h {
4892 for c in 0..w {
4893 targets.push(self.graph.make_cell_ref_internal(
4894 sheet_id,
4895 anchor.coord.row() + r,
4896 anchor.coord.col() + c,
4897 ));
4898 }
4899 }
4900
4901 match self.spill_mgr.reserve(
4903 vertex_id,
4904 anchor,
4905 SpillShape { rows: h, cols: w },
4906 SpillMeta {
4907 epoch: self.recalc_epoch,
4908 config: self.config.spill,
4909 },
4910 ) {
4911 Ok(()) => {
4912 if let Err(e) = self.commit_spill_and_mirror(
4916 vertex_id,
4917 &targets,
4918 rows.clone(),
4919 delta.as_deref_mut(),
4920 None,
4921 ) {
4922 self.clear_spill_projection_and_mirror(
4924 vertex_id,
4925 delta.as_deref_mut(),
4926 );
4927 if let Some(d) = delta.as_deref_mut() {
4928 let old = self
4929 .read_cell_value(
4930 self.graph.sheet_name(anchor.sheet_id),
4931 anchor.coord.row() + 1,
4932 anchor.coord.col() + 1,
4933 )
4934 .unwrap_or(LiteralValue::Empty);
4935 let new = LiteralValue::Error(e.clone());
4936 if old != new {
4937 d.record_cell(
4938 anchor.sheet_id,
4939 anchor.coord.row(),
4940 anchor.coord.col(),
4941 );
4942 }
4943 }
4944 let err_val = LiteralValue::Error(e.clone());
4945 self.graph.update_vertex_value(vertex_id, err_val.clone());
4946 if self.config.arrow_storage_enabled
4947 && self.config.delta_overlay_enabled
4948 && self.config.write_formula_overlay_enabled
4949 {
4950 let sheet_name =
4951 self.graph.sheet_name(anchor.sheet_id).to_string();
4952 self.mirror_value_to_computed_overlay(
4953 &sheet_name,
4954 anchor.coord.row() + 1,
4955 anchor.coord.col() + 1,
4956 &err_val,
4957 );
4958 }
4959 return Ok(err_val);
4960 }
4961 let top_left = rows
4963 .first()
4964 .and_then(|r| r.first())
4965 .cloned()
4966 .unwrap_or(LiteralValue::Empty);
4967 self.graph.update_vertex_value(vertex_id, top_left.clone());
4968 Ok(top_left)
4969 }
4970 Err(e) => {
4971 self.clear_spill_projection_and_mirror(
4972 vertex_id,
4973 delta.as_deref_mut(),
4974 );
4975 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
4976 .with_message(
4977 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
4978 )
4979 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
4980 expected_rows: h,
4981 expected_cols: w,
4982 });
4983 let spill_val = LiteralValue::Error(spill_err.clone());
4984 if let Some(d) = delta.as_deref_mut() {
4985 let old = self
4986 .read_cell_value(
4987 self.graph.sheet_name(anchor.sheet_id),
4988 anchor.coord.row() + 1,
4989 anchor.coord.col() + 1,
4990 )
4991 .unwrap_or(LiteralValue::Empty);
4992 if old != spill_val {
4993 d.record_cell(
4994 anchor.sheet_id,
4995 anchor.coord.row(),
4996 anchor.coord.col(),
4997 );
4998 }
4999 }
5000 self.graph.update_vertex_value(vertex_id, spill_val.clone());
5001 if self.config.arrow_storage_enabled
5002 && self.config.delta_overlay_enabled
5003 && self.config.write_formula_overlay_enabled
5004 {
5005 let sheet_name =
5006 self.graph.sheet_name(anchor.sheet_id).to_string();
5007 self.mirror_value_to_computed_overlay(
5008 &sheet_name,
5009 anchor.coord.row() + 1,
5010 anchor.coord.col() + 1,
5011 &spill_val,
5012 );
5013 }
5014 Ok(spill_val)
5015 }
5016 }
5017 }
5018 other => {
5019 let spill_cells = self
5021 .graph
5022 .spill_cells_for_anchor(vertex_id)
5023 .map(|cells| cells.to_vec())
5024 .unwrap_or_default();
5025 if let Some(d) = delta.as_deref_mut()
5026 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
5027 {
5028 if spill_cells.is_empty() {
5029 let old = self
5030 .read_cell_value(
5031 self.graph.sheet_name(anchor.sheet_id),
5032 anchor.coord.row() + 1,
5033 anchor.coord.col() + 1,
5034 )
5035 .unwrap_or(LiteralValue::Empty);
5036 if old != other {
5037 d.record_cell(
5038 anchor.sheet_id,
5039 anchor.coord.row(),
5040 anchor.coord.col(),
5041 );
5042 }
5043 } else {
5044 for cell in spill_cells.iter() {
5045 let sheet_name = self.graph.sheet_name(cell.sheet_id);
5046 let old = self
5047 .get_cell_value(
5048 sheet_name,
5049 cell.coord.row() + 1,
5050 cell.coord.col() + 1,
5051 )
5052 .unwrap_or(LiteralValue::Empty);
5053 let new = if cell.sheet_id == anchor.sheet_id
5054 && cell.coord.row() == anchor.coord.row()
5055 && cell.coord.col() == anchor.coord.col()
5056 {
5057 other.clone()
5058 } else {
5059 LiteralValue::Empty
5060 };
5061 Self::record_cell_if_changed(d, cell, &old, &new);
5062 }
5063 }
5064 }
5065 self.graph.clear_spill_region(vertex_id);
5066 if self.config.arrow_storage_enabled
5067 && self.config.delta_overlay_enabled
5068 && self.config.write_formula_overlay_enabled
5069 {
5070 let empty = LiteralValue::Empty;
5071 for cell in spill_cells.iter() {
5072 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
5073 self.mirror_value_to_computed_overlay(
5074 &sheet_name,
5075 cell.coord.row() + 1,
5076 cell.coord.col() + 1,
5077 &empty,
5078 );
5079 }
5080 }
5081 self.graph.update_vertex_value(vertex_id, other.clone());
5082 if self.config.arrow_storage_enabled
5084 && self.config.delta_overlay_enabled
5085 && self.config.write_formula_overlay_enabled
5086 {
5087 let anchor = self
5088 .graph
5089 .get_cell_ref(vertex_id)
5090 .expect("cell ref for vertex");
5091 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
5092 self.mirror_value_to_computed_overlay(
5093 &sheet_name,
5094 anchor.coord.row() + 1,
5095 anchor.coord.col() + 1,
5096 &other,
5097 );
5098 }
5099 Ok(other)
5100 }
5101 }
5102 }
5103 Err(e) => {
5104 let spill_cells = self
5107 .graph
5108 .spill_cells_for_anchor(vertex_id)
5109 .map(|cells| cells.to_vec())
5110 .unwrap_or_default();
5111 let err_val = LiteralValue::Error(e.clone());
5112 if let Some(d) = delta
5113 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
5114 {
5115 if spill_cells.is_empty() {
5116 let old = self
5117 .read_cell_value(
5118 self.graph.sheet_name(anchor.sheet_id),
5119 anchor.coord.row() + 1,
5120 anchor.coord.col() + 1,
5121 )
5122 .unwrap_or(LiteralValue::Empty);
5123 if old != err_val {
5124 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
5125 }
5126 } else {
5127 for cell in spill_cells.iter() {
5128 let sheet_name = self.graph.sheet_name(cell.sheet_id);
5129 let old = self
5130 .get_cell_value(
5131 sheet_name,
5132 cell.coord.row() + 1,
5133 cell.coord.col() + 1,
5134 )
5135 .unwrap_or(LiteralValue::Empty);
5136 let new = if cell.sheet_id == anchor.sheet_id
5137 && cell.coord.row() == anchor.coord.row()
5138 && cell.coord.col() == anchor.coord.col()
5139 {
5140 err_val.clone()
5141 } else {
5142 LiteralValue::Empty
5143 };
5144 Self::record_cell_if_changed(d, cell, &old, &new);
5145 }
5146 }
5147 }
5148 self.graph.clear_spill_region(vertex_id);
5149 if self.config.arrow_storage_enabled
5150 && self.config.delta_overlay_enabled
5151 && self.config.write_formula_overlay_enabled
5152 {
5153 let empty = LiteralValue::Empty;
5154 for cell in spill_cells.iter() {
5155 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
5156 self.mirror_value_to_computed_overlay(
5157 &sheet_name,
5158 cell.coord.row() + 1,
5159 cell.coord.col() + 1,
5160 &empty,
5161 );
5162 }
5163 }
5164 self.graph.update_vertex_value(vertex_id, err_val.clone());
5165 if self.config.arrow_storage_enabled
5166 && self.config.delta_overlay_enabled
5167 && self.config.write_formula_overlay_enabled
5168 {
5169 let anchor = self
5170 .graph
5171 .get_cell_ref(vertex_id)
5172 .expect("cell ref for vertex");
5173 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
5174 self.mirror_value_to_computed_overlay(
5175 &sheet_name,
5176 anchor.coord.row() + 1,
5177 anchor.coord.col() + 1,
5178 &err_val,
5179 );
5180 }
5181 Ok(err_val)
5182 }
5183 }
5184 }
5185
5186 fn evaluate_named_scalar(
5187 &mut self,
5188 vertex_id: VertexId,
5189 sheet_id: SheetId,
5190 ) -> Result<LiteralValue, ExcelError> {
5191 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
5192 ExcelError::new(ExcelErrorKind::Name)
5193 .with_message("Named range metadata missing".to_string())
5194 })?;
5195
5196 match &named_range.definition {
5197 NamedDefinition::Cell(cell_ref) => {
5198 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
5199 let row = cell_ref.coord.row() + 1;
5200 let col = cell_ref.coord.col() + 1;
5201
5202 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
5203 && matches!(
5204 self.graph.get_vertex_kind(dep_vertex),
5205 VertexKind::FormulaScalar | VertexKind::FormulaArray
5206 )
5207 {
5208 let value = self.evaluate_vertex(dep_vertex)?;
5210 self.graph.update_vertex_value(vertex_id, value.clone());
5211 Ok(value)
5212 } else {
5213 let value = self
5214 .get_cell_value(sheet_name, row, col)
5215 .unwrap_or(LiteralValue::Empty);
5216 self.graph.update_vertex_value(vertex_id, value.clone());
5217 Ok(value)
5218 }
5219 }
5220 NamedDefinition::Literal(v) => {
5221 let out = v.clone();
5222 self.graph.update_vertex_value(vertex_id, out.clone());
5223 Ok(out)
5224 }
5225 NamedDefinition::Formula { ast, .. } => {
5226 let context_sheet = match named_range.scope {
5227 NameScope::Sheet(id) => id,
5228 NameScope::Workbook => sheet_id,
5229 };
5230 let sheet_name = self.graph.sheet_name(context_sheet);
5231 let cell_ref = self
5232 .graph
5233 .get_cell_ref(vertex_id)
5234 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
5235 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
5236 match interpreter.evaluate_ast(ast) {
5237 Ok(cv) => {
5238 let value = cv.into_literal();
5239 match value {
5240 LiteralValue::Array(_) => {
5241 let err = ExcelError::new(ExcelErrorKind::NImpl)
5242 .with_message("Array result in scalar named range".to_string());
5243 let err_val = LiteralValue::Error(err.clone());
5244 self.graph.update_vertex_value(vertex_id, err_val.clone());
5245 Ok(err_val)
5246 }
5247 other => {
5248 self.graph.update_vertex_value(vertex_id, other.clone());
5249 Ok(other)
5250 }
5251 }
5252 }
5253 Err(err) => {
5254 let err_val = LiteralValue::Error(err.clone());
5255 self.graph.update_vertex_value(vertex_id, err_val.clone());
5256 Ok(err_val)
5257 }
5258 }
5259 }
5260 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
5261 .with_message("Range-valued name evaluated as scalar".to_string())),
5262 }
5263 }
5264
5265 fn evaluate_named_array(
5266 &mut self,
5267 vertex_id: VertexId,
5268 sheet_id: SheetId,
5269 ) -> Result<LiteralValue, ExcelError> {
5270 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
5271 ExcelError::new(ExcelErrorKind::Name)
5272 .with_message("Named range metadata missing".to_string())
5273 })?;
5274
5275 let out = match &named_range.definition {
5276 NamedDefinition::Range(range_ref) => {
5277 if range_ref.start.sheet_id != range_ref.end.sheet_id {
5278 return Err(ExcelError::new(ExcelErrorKind::Ref)
5279 .with_message("Named range cannot span sheets".to_string()));
5280 }
5281
5282 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
5283 let sr0 = range_ref.start.coord.row();
5284 let sc0 = range_ref.start.coord.col();
5285 let er0 = range_ref.end.coord.row();
5286 let ec0 = range_ref.end.coord.col();
5287 if sr0 > er0 || sc0 > ec0 {
5288 return Err(ExcelError::new(ExcelErrorKind::Ref)
5289 .with_message("Invalid named range bounds".to_string()));
5290 }
5291
5292 let h = (er0 - sr0 + 1) as usize;
5293 let w = (ec0 - sc0 + 1) as usize;
5294 let cell_count = (h as u64).saturating_mul(w as u64);
5295 if cell_count > self.config.spill.max_spill_cells as u64 {
5296 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
5297 "Named range too large to materialize as an array".to_string(),
5298 ));
5299 }
5300
5301 let mut rows = Vec::with_capacity(h);
5302 for r0 in sr0..=er0 {
5303 let mut row = Vec::with_capacity(w);
5304 for c0 in sc0..=ec0 {
5305 let v = self
5306 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
5307 .unwrap_or(LiteralValue::Empty);
5308 row.push(v);
5309 }
5310 rows.push(row);
5311 }
5312 LiteralValue::Array(rows)
5313 }
5314 NamedDefinition::Cell(cell_ref) => {
5315 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
5316 let row = cell_ref.coord.row() + 1;
5317 let col = cell_ref.coord.col() + 1;
5318 let v = self
5319 .get_cell_value(sheet_name, row, col)
5320 .unwrap_or(LiteralValue::Empty);
5321 LiteralValue::Array(vec![vec![v]])
5322 }
5323 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
5324 NamedDefinition::Formula { ast, .. } => {
5325 let context_sheet = match named_range.scope {
5326 NameScope::Sheet(id) => id,
5327 NameScope::Workbook => sheet_id,
5328 };
5329 let sheet_name = self.graph.sheet_name(context_sheet);
5330 let cell_ref = self
5331 .graph
5332 .get_cell_ref(vertex_id)
5333 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
5334 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
5335 match interpreter.evaluate_ast(ast) {
5336 Ok(cv) => {
5337 let v = cv.into_literal();
5338 match v {
5339 LiteralValue::Array(_) => v,
5340 other => LiteralValue::Array(vec![vec![other]]),
5341 }
5342 }
5343 Err(err) => LiteralValue::Error(err),
5344 }
5345 }
5346 };
5347
5348 self.graph.update_vertex_value(vertex_id, out.clone());
5349 Ok(out)
5350 }
5351
5352 pub fn evaluate_until(
5354 &mut self,
5355 targets: &[(&str, u32, u32)],
5356 ) -> Result<EvalResult, ExcelError> {
5357 #[cfg(feature = "tracing")]
5358 let _span_eval = tracing::info_span!("evaluate_until", targets = targets.len()).entered();
5359 let start = crate::instant::FzInstant::now();
5360 let _source_cache = self.source_cache_session();
5361
5362 let mut target_addrs = Vec::new();
5364 for (sheet, row, col) in targets {
5365 let sheet_id = self.graph.sheet_id_mut(sheet);
5368 let coord = Coord::from_excel(*row, *col, true, true);
5369 target_addrs.push(CellRef::new(sheet_id, coord));
5370 }
5371
5372 let mut target_vertex_ids = Vec::new();
5374 for addr in &target_addrs {
5375 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
5376 target_vertex_ids.push(*vertex_id);
5377 }
5378 }
5379
5380 if target_vertex_ids.is_empty() {
5381 return Ok(EvalResult {
5382 computed_vertices: 0,
5383 cycle_errors: 0,
5384 elapsed: start.elapsed(),
5385 });
5386 }
5387
5388 #[cfg(feature = "tracing")]
5390 let _span_sub = tracing::info_span!("demand_subgraph_build").entered();
5391 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
5392 #[cfg(feature = "tracing")]
5393 drop(_span_sub);
5394
5395 if precedents_to_eval.is_empty() {
5396 return Ok(EvalResult {
5397 computed_vertices: 0,
5398 cycle_errors: 0,
5399 elapsed: start.elapsed(),
5400 });
5401 }
5402
5403 let scheduler = Scheduler::new(&self.graph);
5405 #[cfg(feature = "tracing")]
5406 let _span_sched =
5407 tracing::info_span!("schedule_build", vertices = precedents_to_eval.len()).entered();
5408 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
5409 #[cfg(feature = "tracing")]
5410 drop(_span_sched);
5411
5412 let mut cycle_errors = 0;
5414 for cycle in &schedule.cycles {
5415 cycle_errors += 1;
5416 let circ_error = LiteralValue::Error(
5417 ExcelError::new(ExcelErrorKind::Circ)
5418 .with_message("Circular dependency detected".to_string()),
5419 );
5420 for &vertex_id in cycle {
5421 self.graph
5422 .update_vertex_value(vertex_id, circ_error.clone());
5423 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5424 }
5425 }
5426
5427 let mut computed_vertices = 0;
5429 for layer in &schedule.layers {
5430 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5431 computed_vertices += self.evaluate_layer_parallel(layer)?;
5432 } else {
5433 computed_vertices += self.evaluate_layer_sequential(layer)?;
5434 }
5435 }
5436
5437 self.graph.clear_dirty_flags(&precedents_to_eval);
5441
5442 self.graph.redirty_volatiles();
5444
5445 Ok(EvalResult {
5446 computed_vertices,
5447 cycle_errors,
5448 elapsed: start.elapsed(),
5449 })
5450 }
5451
5452 fn evaluate_until_with_delta_collector(
5453 &mut self,
5454 targets: &[(&str, u32, u32)],
5455 delta: &mut DeltaCollector,
5456 ) -> Result<EvalResult, ExcelError> {
5457 #[cfg(feature = "tracing")]
5458 let _span_eval =
5459 tracing::info_span!("evaluate_until_with_delta", targets = targets.len()).entered();
5460 let start = crate::instant::FzInstant::now();
5461 let _source_cache = self.source_cache_session();
5462
5463 let mut target_addrs = Vec::new();
5464 for (sheet, row, col) in targets {
5465 let sheet_id = self.graph.sheet_id_mut(sheet);
5466 let coord = Coord::from_excel(*row, *col, true, true);
5467 target_addrs.push(CellRef::new(sheet_id, coord));
5468 }
5469
5470 let mut target_vertex_ids = Vec::new();
5471 for addr in &target_addrs {
5472 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
5473 target_vertex_ids.push(*vertex_id);
5474 }
5475 }
5476
5477 if target_vertex_ids.is_empty() {
5478 return Ok(EvalResult {
5479 computed_vertices: 0,
5480 cycle_errors: 0,
5481 elapsed: start.elapsed(),
5482 });
5483 }
5484
5485 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
5486
5487 if precedents_to_eval.is_empty() {
5488 return Ok(EvalResult {
5489 computed_vertices: 0,
5490 cycle_errors: 0,
5491 elapsed: start.elapsed(),
5492 });
5493 }
5494
5495 let scheduler = Scheduler::new(&self.graph);
5496 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
5497
5498 let mut cycle_errors = 0;
5499 let circ_error = LiteralValue::Error(
5500 ExcelError::new(ExcelErrorKind::Circ)
5501 .with_message("Circular dependency detected".to_string()),
5502 );
5503 for cycle in &schedule.cycles {
5504 cycle_errors += 1;
5505 for &vertex_id in cycle {
5506 if delta.mode != DeltaMode::Off
5507 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
5508 {
5509 let sheet_name = self.graph.sheet_name(cell.sheet_id);
5510 let old = self
5511 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
5512 .unwrap_or(LiteralValue::Empty);
5513 if old != circ_error {
5514 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
5515 }
5516 }
5517 self.graph
5518 .update_vertex_value(vertex_id, circ_error.clone());
5519 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5520 }
5521 }
5522
5523 let mut computed_vertices = 0;
5524 for layer in &schedule.layers {
5525 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5526 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
5527 } else {
5528 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
5529 }
5530 }
5531
5532 self.graph.clear_dirty_flags(&precedents_to_eval);
5533 self.graph.redirty_volatiles();
5534
5535 Ok(EvalResult {
5536 computed_vertices,
5537 cycle_errors,
5538 elapsed: start.elapsed(),
5539 })
5540 }
5541
5542 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
5544 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
5545 vertices.sort_unstable();
5546 if vertices.is_empty() {
5547 return Ok(RecalcPlan {
5548 schedule: crate::engine::Schedule {
5549 layers: Vec::new(),
5550 cycles: Vec::new(),
5551 },
5552 has_dynamic_refs: false,
5553 });
5554 }
5555
5556 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
5557 let (schedule, _, _) = self.create_evaluation_schedule_uncached(&vertices)?;
5558 Ok(RecalcPlan {
5559 schedule,
5560 has_dynamic_refs,
5561 })
5562 }
5563
5564 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
5566 let _source_cache = self.source_cache_session();
5567 self.validate_deterministic_mode()?;
5568 if self.config.defer_graph_building {
5569 self.build_graph_all()?;
5570 }
5571
5572 let start = crate::instant::FzInstant::now();
5573 let dirty_vertices = self.graph.get_evaluation_vertices();
5574 if dirty_vertices.is_empty() {
5575 return Ok(EvalResult {
5576 computed_vertices: 0,
5577 cycle_errors: 0,
5578 elapsed: start.elapsed(),
5579 });
5580 }
5581
5582 if plan.has_dynamic_refs {
5585 self.virtual_dep_fallback_activations =
5586 self.virtual_dep_fallback_activations.saturating_add(1);
5587 return self.evaluate_all();
5588 }
5589
5590 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
5591 let mut computed_vertices = 0;
5592 let mut cycle_errors = 0;
5593
5594 if !plan.schedule.cycles.is_empty() {
5595 let circ_error = LiteralValue::Error(
5596 ExcelError::new(ExcelErrorKind::Circ)
5597 .with_message("Circular dependency detected".to_string()),
5598 );
5599 for cycle in &plan.schedule.cycles {
5600 if !cycle.iter().any(|v| dirty_set.contains(v)) {
5601 continue;
5602 }
5603 cycle_errors += 1;
5604 for &vertex_id in cycle {
5605 if dirty_set.contains(&vertex_id) {
5606 self.graph
5607 .update_vertex_value(vertex_id, circ_error.clone());
5608 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5609 }
5610 }
5611 }
5612 }
5613
5614 for layer in &plan.schedule.layers {
5615 let work: Vec<VertexId> = layer
5616 .vertices
5617 .iter()
5618 .copied()
5619 .filter(|v| dirty_set.contains(v))
5620 .collect();
5621 if work.is_empty() {
5622 continue;
5623 }
5624 let temp_layer = crate::engine::scheduler::Layer { vertices: work };
5625 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
5626 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
5627 } else {
5628 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
5629 }
5630 }
5631
5632 self.graph.clear_dirty_flags(&dirty_vertices);
5633 self.graph.redirty_volatiles();
5634
5635 Ok(EvalResult {
5636 computed_vertices,
5637 cycle_errors,
5638 elapsed: start.elapsed(),
5639 })
5640 }
5641 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
5643 let _source_cache = self.source_cache_session();
5644 self.validate_deterministic_mode()?;
5645 if self.config.defer_graph_building {
5646 self.build_graph_all()?;
5648 }
5649 self.reset_virtual_dep_telemetry_if_disabled();
5650 #[cfg(feature = "tracing")]
5651 let _span_eval = tracing::info_span!("evaluate_all").entered();
5652 let start = crate::instant::FzInstant::now();
5653 let mut computed_vertices = 0;
5654 let mut cycle_errors = 0;
5655 let mut replan_iterations = 0;
5656 const MAX_REPLAN: usize = 5;
5657 let mut telemetry = self
5658 .config
5659 .enable_virtual_dep_telemetry
5660 .then(|| self.start_virtual_dep_telemetry());
5661
5662 loop {
5663 let to_evaluate = self.graph.get_evaluation_vertices();
5664 if to_evaluate.is_empty() {
5665 if let Some(t) = telemetry.as_mut()
5666 && t.bailout_reason.is_none()
5667 {
5668 t.bailout_reason = Some("no_work");
5669 }
5670 break;
5671 }
5672
5673 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
5674 if let Some(t) = telemetry.as_mut() {
5675 Self::accumulate_schedule_meta(t, &meta);
5676 }
5677
5678 for cycle in &schedule.cycles {
5680 cycle_errors += 1;
5681 let circ_error = LiteralValue::Error(
5682 ExcelError::new(ExcelErrorKind::Circ)
5683 .with_message("Circular dependency detected".to_string()),
5684 );
5685 for &vertex_id in cycle {
5686 self.graph
5687 .update_vertex_value(vertex_id, circ_error.clone());
5688 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5689 }
5690 }
5691
5692 for layer in &schedule.layers {
5694 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5695 computed_vertices += self.evaluate_layer_parallel(layer)?;
5696 } else {
5697 computed_vertices += self.evaluate_layer_sequential(layer)?;
5698 }
5699 }
5700
5701 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
5703 if let Some(t) = telemetry.as_mut() {
5704 t.changed_vdeps_total += changed_vertices.len();
5705 }
5706
5707 self.graph.clear_dirty_flags(&to_evaluate);
5708 for v in &changed_vertices {
5709 self.graph.set_dirty(*v, true);
5710 }
5711
5712 if changed_vertices.is_empty() {
5713 if let Some(t) = telemetry.as_mut() {
5714 t.bailout_reason = Some("converged");
5715 }
5716 break;
5717 }
5718 if replan_iterations >= MAX_REPLAN {
5719 if let Some(t) = telemetry.as_mut() {
5720 t.bailout_reason = Some("max_replan");
5721 }
5722 break;
5723 }
5724
5725 replan_iterations += 1;
5726 }
5727
5728 if let Some(mut t) = telemetry {
5729 t.replan_iterations = replan_iterations;
5730 self.last_virtual_dep_telemetry = t;
5731 }
5732
5733 self.graph.redirty_volatiles();
5735
5736 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
5738
5739 Ok(EvalResult {
5740 computed_vertices,
5741 cycle_errors,
5742 elapsed: start.elapsed(),
5743 })
5744 }
5745
5746 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
5747 let mut collector = DeltaCollector::new(DeltaMode::Cells);
5748 let result = self.evaluate_all_with_delta_collector(&mut collector)?;
5749 Ok((result, collector.finish()))
5750 }
5751
5752 fn evaluate_all_with_delta_collector(
5753 &mut self,
5754 delta: &mut DeltaCollector,
5755 ) -> Result<EvalResult, ExcelError> {
5756 let _source_cache = self.source_cache_session();
5757 if self.config.defer_graph_building {
5758 self.build_graph_all()?;
5759 }
5760 self.reset_virtual_dep_telemetry_if_disabled();
5761 #[cfg(feature = "tracing")]
5762 let _span_eval = tracing::info_span!("evaluate_all_with_delta").entered();
5763 let start = crate::instant::FzInstant::now();
5764 let mut computed_vertices = 0;
5765 let mut cycle_errors = 0;
5766
5767 let mut replan_iterations = 0;
5768 const MAX_REPLAN: usize = 5;
5769 let mut telemetry = self
5770 .config
5771 .enable_virtual_dep_telemetry
5772 .then(|| self.start_virtual_dep_telemetry());
5773
5774 loop {
5775 let to_evaluate = self.graph.get_evaluation_vertices();
5776 if to_evaluate.is_empty() {
5777 if let Some(t) = telemetry.as_mut()
5778 && t.bailout_reason.is_none()
5779 {
5780 t.bailout_reason = Some("no_work");
5781 }
5782 break;
5783 }
5784
5785 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
5786 if let Some(t) = telemetry.as_mut() {
5787 Self::accumulate_schedule_meta(t, &meta);
5788 }
5789
5790 let circ_error = LiteralValue::Error(
5791 ExcelError::new(ExcelErrorKind::Circ)
5792 .with_message("Circular dependency detected".to_string()),
5793 );
5794 for cycle in &schedule.cycles {
5795 cycle_errors += 1;
5796 for &vertex_id in cycle {
5797 if delta.mode != DeltaMode::Off
5798 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
5799 {
5800 let sheet_name = self.graph.sheet_name(cell.sheet_id);
5801 let old = self
5802 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
5803 .unwrap_or(LiteralValue::Empty);
5804 if old != circ_error {
5805 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
5806 }
5807 }
5808 self.graph
5809 .update_vertex_value(vertex_id, circ_error.clone());
5810 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
5811 }
5812 }
5813
5814 for layer in &schedule.layers {
5815 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
5816 computed_vertices += self.evaluate_layer_parallel_with_delta(layer, delta)?;
5817 } else {
5818 computed_vertices += self.evaluate_layer_sequential_with_delta(layer, delta)?;
5819 }
5820 }
5821
5822 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
5823 if let Some(t) = telemetry.as_mut() {
5824 t.changed_vdeps_total += changed_vertices.len();
5825 }
5826 self.graph.clear_dirty_flags(&to_evaluate);
5827 for v in &changed_vertices {
5828 self.graph.set_dirty(*v, true);
5829 }
5830
5831 if changed_vertices.is_empty() {
5832 if let Some(t) = telemetry.as_mut() {
5833 t.bailout_reason = Some("converged");
5834 }
5835 break;
5836 }
5837 if replan_iterations >= MAX_REPLAN {
5838 if let Some(t) = telemetry.as_mut() {
5839 t.bailout_reason = Some("max_replan");
5840 }
5841 break;
5842 }
5843 replan_iterations += 1;
5844 }
5845
5846 if let Some(mut t) = telemetry {
5847 t.replan_iterations = replan_iterations;
5848 self.last_virtual_dep_telemetry = t;
5849 }
5850
5851 self.graph.redirty_volatiles();
5852 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
5853
5854 Ok(EvalResult {
5855 computed_vertices,
5856 cycle_errors,
5857 elapsed: start.elapsed(),
5858 })
5859 }
5860
5861 pub fn evaluate_cell(
5871 &mut self,
5872 sheet: &str,
5873 row: u32,
5874 col: u32,
5875 ) -> Result<Option<LiteralValue>, ExcelError> {
5876 if row == 0 || col == 0 {
5877 return Err(ExcelError::new(ExcelErrorKind::Ref)
5878 .with_message("Row and column must be >= 1".to_string()));
5879 }
5880
5881 if self.config.defer_graph_building {
5882 self.build_graph_for_sheets(std::iter::once(sheet))?;
5883 }
5884
5885 let result = self.evaluate_cells(&[(sheet, row, col)])?;
5886
5887 match result.len() {
5888 0 => Ok(None),
5889 1 => {
5890 let v = result.into_iter().next().unwrap();
5891 Ok(v)
5892 }
5893 _ => unreachable!("evaluate_cells returned unexpected length"),
5894 }
5895 }
5896
5897 pub fn evaluate_cells(
5904 &mut self,
5905 targets: &[(&str, u32, u32)],
5906 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5907 self.validate_deterministic_mode()?;
5908 if targets.is_empty() {
5909 return Ok(Vec::new());
5910 }
5911 if self.config.defer_graph_building {
5912 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5913 for (s, _, _) in targets.iter() {
5914 sheets.insert(*s);
5915 }
5916 self.build_graph_for_sheets(sheets.iter().cloned())?;
5917 }
5918 self.evaluate_until(targets)?;
5919 Ok(targets
5920 .iter()
5921 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5922 .collect())
5923 }
5924
5925 pub fn evaluate_cells_cancellable(
5926 &mut self,
5927 targets: &[(&str, u32, u32)],
5928 cancel_flag: Arc<AtomicBool>,
5929 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5930 self.active_cancel_flag = Some(cancel_flag.clone());
5931 let res = self.evaluate_cells_cancellable_impl(targets, &cancel_flag);
5932 self.active_cancel_flag = None;
5933 res
5934 }
5935
5936 fn evaluate_cells_cancellable_impl(
5937 &mut self,
5938 targets: &[(&str, u32, u32)],
5939 cancel_flag: &AtomicBool,
5940 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
5941 self.validate_deterministic_mode()?;
5942 if targets.is_empty() {
5943 return Ok(Vec::new());
5944 }
5945 if self.config.defer_graph_building {
5946 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5947 for (s, _, _) in targets.iter() {
5948 sheets.insert(*s);
5949 }
5950 self.build_graph_for_sheets(sheets.iter().cloned())?;
5951 }
5952
5953 let a1_targets: Vec<String> = targets
5956 .iter()
5957 .map(|(s, r, c)| {
5958 format!("{}!{}", s, col_letters_from_1based(*c).unwrap()) + &r.to_string()
5959 })
5960 .collect();
5961 let a1_refs: Vec<&str> = a1_targets.iter().map(|s| s.as_str()).collect();
5962
5963 self.evaluate_until_cancellable_impl(&a1_refs, cancel_flag)?;
5964
5965 Ok(targets
5966 .iter()
5967 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5968 .collect())
5969 }
5970
5971 pub fn evaluate_cells_with_delta(
5972 &mut self,
5973 targets: &[(&str, u32, u32)],
5974 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
5975 self.validate_deterministic_mode()?;
5976 if targets.is_empty() {
5977 return Ok((Vec::new(), EvalDelta::default()));
5978 }
5979 if self.config.defer_graph_building {
5980 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
5981 for (s, _, _) in targets.iter() {
5982 sheets.insert(*s);
5983 }
5984 self.build_graph_for_sheets(sheets.iter().cloned())?;
5985 }
5986 let mut collector = DeltaCollector::new(DeltaMode::Cells);
5987 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
5988 let values = targets
5989 .iter()
5990 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
5991 .collect();
5992 Ok((values, collector.finish()))
5993 }
5994
5995 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
5997 if targets.is_empty() {
5998 return Ok(EvalPlan {
5999 total_vertices_to_evaluate: 0,
6000 layers: Vec::new(),
6001 cycles_detected: 0,
6002 dirty_count: 0,
6003 volatile_count: 0,
6004 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
6005 estimated_parallel_layers: 0,
6006 target_cells: Vec::new(),
6007 });
6008 }
6009 if self.config.defer_graph_building && self.has_staged_formulas() {
6010 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6011 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
6012 ));
6013 }
6014
6015 let addresses: Vec<String> = targets
6017 .iter()
6018 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
6019 .collect();
6020
6021 let mut target_addrs = Vec::new();
6023 for (sheet, row, col) in targets {
6024 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
6025 let coord = Coord::from_excel(*row, *col, true, true);
6026 target_addrs.push(CellRef::new(sheet_id, coord));
6027 }
6028 }
6029
6030 let mut target_vertex_ids = Vec::new();
6032 for addr in &target_addrs {
6033 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
6034 target_vertex_ids.push(*vertex_id);
6035 }
6036 }
6037
6038 if target_vertex_ids.is_empty() {
6039 return Ok(EvalPlan {
6040 total_vertices_to_evaluate: 0,
6041 layers: Vec::new(),
6042 cycles_detected: 0,
6043 dirty_count: 0,
6044 volatile_count: 0,
6045 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
6046 estimated_parallel_layers: 0,
6047 target_cells: addresses,
6048 });
6049 }
6050
6051 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
6053
6054 if precedents_to_eval.is_empty() {
6055 return Ok(EvalPlan {
6056 total_vertices_to_evaluate: 0,
6057 layers: Vec::new(),
6058 cycles_detected: 0,
6059 dirty_count: 0,
6060 volatile_count: 0,
6061 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
6062 estimated_parallel_layers: 0,
6063 target_cells: addresses,
6064 });
6065 }
6066
6067 let mut dirty_count = 0;
6069 let mut volatile_count = 0;
6070 for &vertex_id in &precedents_to_eval {
6071 if self.graph.is_dirty(vertex_id) {
6072 dirty_count += 1;
6073 }
6074 if self.graph.is_volatile(vertex_id) {
6075 volatile_count += 1;
6076 }
6077 }
6078
6079 let scheduler = Scheduler::new(&self.graph);
6081 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
6082
6083 let mut layers = Vec::new();
6085 let mut estimated_parallel_layers = 0;
6086 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
6087
6088 for layer in &schedule.layers {
6089 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
6090 if parallel_eligible {
6091 estimated_parallel_layers += 1;
6092 }
6093
6094 let sample_cells: Vec<String> = layer
6096 .vertices
6097 .iter()
6098 .take(5)
6099 .filter_map(|&vertex_id| {
6100 self.graph
6101 .get_cell_ref_for_vertex(vertex_id)
6102 .map(|cell_ref| {
6103 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
6104 format!(
6105 "{}!{}{}",
6106 sheet_name,
6107 Self::col_to_letters(cell_ref.coord.col()),
6108 cell_ref.coord.row() + 1
6109 )
6110 })
6111 })
6112 .collect();
6113
6114 layers.push(LayerInfo {
6115 vertex_count: layer.vertices.len(),
6116 parallel_eligible,
6117 sample_cells,
6118 });
6119 }
6120
6121 Ok(EvalPlan {
6122 total_vertices_to_evaluate: precedents_to_eval.len(),
6123 layers,
6124 cycles_detected: schedule.cycles.len(),
6125 dirty_count,
6126 volatile_count,
6127 parallel_enabled,
6128 estimated_parallel_layers,
6129 target_cells: addresses,
6130 })
6131 }
6132 fn create_evaluation_schedule(
6134 &mut self,
6135 to_evaluate: &[VertexId],
6136 ) -> Result<ScheduleBuildOutput, ExcelError> {
6137 if self.can_use_static_schedule_cache(to_evaluate) {
6138 if let Some(cached) = self.cached_static_schedule.as_ref()
6139 && cached.topology_epoch == self.topology_epoch
6140 && cached.candidate_vertices.as_slice() == to_evaluate
6141 {
6142 let meta = ScheduleBuildMeta {
6143 candidate_vertices: to_evaluate.len(),
6144 vdeps_vertices: 0,
6145 vdeps_edges: 0,
6146 builder_elapsed_ms: 0,
6147 used_virtual_schedule: false,
6148 schedule_cache_hit: true,
6149 schedule_cache_eligible: true,
6150 };
6151 return Ok((cached.schedule.clone(), FxHashMap::default(), meta));
6152 }
6153
6154 let (schedule, vdeps, mut meta) =
6155 self.create_evaluation_schedule_uncached(to_evaluate)?;
6156 meta.schedule_cache_hit = false;
6157 meta.schedule_cache_eligible = true;
6158 if vdeps.is_empty() {
6159 self.cached_static_schedule = Some(CachedScheduleEntry {
6160 topology_epoch: self.topology_epoch,
6161 candidate_vertices: to_evaluate.to_vec(),
6162 schedule: schedule.clone(),
6163 });
6164 }
6165 return Ok((schedule, vdeps, meta));
6166 }
6167
6168 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_uncached(to_evaluate)?;
6169 meta.schedule_cache_hit = false;
6170 meta.schedule_cache_eligible = false;
6171 Ok((schedule, vdeps, meta))
6172 }
6173
6174 fn create_evaluation_schedule_uncached(
6175 &self,
6176 to_evaluate: &[VertexId],
6177 ) -> Result<ScheduleBuildOutput, ExcelError> {
6178 let builder = VirtualDepBuilder::new(self);
6179 let (vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
6180 if self.config.enable_virtual_dep_telemetry {
6181 let build_started = crate::instant::FzInstant::now();
6182 let (vdeps, augmented) = builder.build(to_evaluate);
6183 let builder_elapsed_ms = build_started.elapsed().as_millis();
6184 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
6185 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
6186 } else {
6187 let (vdeps, augmented) = builder.build(to_evaluate);
6188 (vdeps, augmented, 0, 0)
6189 };
6190
6191 let mut final_evaluate = to_evaluate.to_vec();
6192 if !augmented.is_empty() {
6193 final_evaluate.extend(augmented);
6194 final_evaluate.sort_unstable();
6195 final_evaluate.dedup();
6196 }
6197
6198 let use_virtual = !vdeps.is_empty();
6199
6200 let scheduler = Scheduler::new(&self.graph);
6201 let schedule = if use_virtual {
6202 scheduler.create_schedule_with_virtual(&final_evaluate, &vdeps)?
6203 } else {
6204 scheduler.create_schedule(&final_evaluate)?
6205 };
6206
6207 let meta = ScheduleBuildMeta {
6208 candidate_vertices: to_evaluate.len(),
6209 vdeps_vertices: vdeps.len(),
6210 vdeps_edges,
6211 builder_elapsed_ms,
6212 used_virtual_schedule: use_virtual,
6213 schedule_cache_hit: false,
6214 schedule_cache_eligible: false,
6215 };
6216
6217 Ok((schedule, vdeps, meta))
6218 }
6219
6220 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
6221 !to_evaluate.is_empty()
6222 && to_evaluate.iter().copied().all(|v| {
6223 !self.graph.is_dynamic(v) && self.graph.get_range_dependencies(v).is_none()
6224 })
6225 }
6226
6227 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
6228 VirtualDepTelemetry {
6229 fallback_mode_activations: self.virtual_dep_fallback_activations,
6230 ..VirtualDepTelemetry::default()
6231 }
6232 }
6233
6234 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
6235 telemetry.candidate_vertices_total += meta.candidate_vertices;
6236 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
6237 telemetry.vdeps_edges_total += meta.vdeps_edges;
6238 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
6239 if meta.schedule_cache_eligible {
6240 if meta.schedule_cache_hit {
6241 telemetry.schedule_cache_hits += 1;
6242 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
6243 } else {
6244 telemetry.schedule_cache_misses += 1;
6245 }
6246 }
6247 if meta.used_virtual_schedule {
6248 telemetry.schedule_virtual_passes += 1;
6249 } else {
6250 telemetry.schedule_static_passes += 1;
6251 }
6252 }
6253
6254 fn changed_virtual_dep_vertices(
6255 &self,
6256 to_evaluate: &[VertexId],
6257 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
6258 ) -> Vec<VertexId> {
6259 if !to_evaluate
6260 .iter()
6261 .copied()
6262 .any(|v| self.graph.is_dynamic(v))
6263 {
6264 return Vec::new();
6265 }
6266
6267 let builder = VirtualDepBuilder::new(self);
6268 let (new_vdeps, _) = builder.build(to_evaluate);
6269
6270 let mut candidates = FxHashSet::default();
6271 candidates.extend(old_vdeps.keys().copied());
6272 candidates.extend(new_vdeps.keys().copied());
6273
6274 let mut changed = Vec::new();
6275 for v in candidates {
6276 if old_vdeps.get(&v) != new_vdeps.get(&v) {
6277 changed.push(v);
6278 }
6279 }
6280 changed
6281 }
6282
6283 fn build_demand_subgraph(
6286 &self,
6287 target_vertices: &[VertexId],
6288 ) -> (
6289 Vec<VertexId>,
6290 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
6291 ) {
6292 #[cfg(feature = "tracing")]
6293 let _span =
6294 tracing::info_span!("demand_subgraph", targets = target_vertices.len()).entered();
6295 use rustc_hash::{FxHashMap, FxHashSet};
6296
6297 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
6298 let mut visited: FxHashSet<VertexId> = FxHashSet::default();
6299 let mut stack: Vec<VertexId> = Vec::new();
6300 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default(); for &t in target_vertices {
6303 stack.push(t);
6304 }
6305
6306 while let Some(v) = stack.pop() {
6307 if !visited.insert(v) {
6308 continue;
6309 }
6310 if !self.graph.vertex_exists(v) {
6311 continue;
6312 }
6313 match self.graph.get_vertex_kind(v) {
6315 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
6316 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
6317 to_evaluate.insert(v);
6318 }
6319 }
6320 _ => {}
6321 }
6322
6323 if let Some(dependencies) = self.graph.dependencies_slice(v) {
6325 for &dep in dependencies {
6326 if self.graph.vertex_exists(dep) {
6327 match self.graph.get_vertex_kind(dep) {
6328 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
6329 if !visited.contains(&dep) {
6330 stack.push(dep);
6331 }
6332 }
6333 _ => {}
6334 }
6335 }
6336 }
6337 } else {
6338 for dep in self.graph.get_dependencies(v) {
6339 if self.graph.vertex_exists(dep) {
6340 match self.graph.get_vertex_kind(dep) {
6341 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
6342 if !visited.contains(&dep) {
6343 stack.push(dep);
6344 }
6345 }
6346 _ => {}
6347 }
6348 }
6349 }
6350 } let builder = VirtualDepBuilder::new(self);
6352 let (vdeps_map, _) = builder.build(&[v]);
6353 if let Some(deps) = vdeps_map.get(&v) {
6354 for &u in deps {
6355 vdeps.entry(v).or_default().push(u);
6356 if !visited.contains(&u) {
6357 stack.push(u);
6358 }
6359 }
6360 }
6361 }
6362
6363 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
6364 result.sort_unstable();
6365 for deps in vdeps.values_mut() {
6367 deps.sort_unstable();
6368 deps.dedup();
6369 }
6370 (result, vdeps)
6371 }
6372
6373 fn col_to_letters(col: u32) -> String {
6375 col_letters_from_1based(col).expect("column index must be >= 1")
6376 }
6377
6378 pub fn evaluate_all_cancellable(
6380 &mut self,
6381 cancel_flag: Arc<AtomicBool>,
6382 ) -> Result<EvalResult, ExcelError> {
6383 self.active_cancel_flag = Some(cancel_flag.clone());
6384 let res = self.evaluate_all_cancellable_impl(&cancel_flag);
6385 self.active_cancel_flag = None;
6386 res
6387 }
6388
6389 fn evaluate_all_cancellable_impl(
6390 &mut self,
6391 cancel_flag: &AtomicBool,
6392 ) -> Result<EvalResult, ExcelError> {
6393 let _source_cache = self.source_cache_session();
6394 self.validate_deterministic_mode()?;
6395 if self.config.defer_graph_building {
6396 self.build_graph_all()?;
6397 }
6398 self.reset_virtual_dep_telemetry_if_disabled();
6399 let start = crate::instant::FzInstant::now();
6400 let mut computed_vertices = 0;
6401 let mut cycle_errors = 0;
6402
6403 let mut replan_iterations = 0;
6404 const MAX_REPLAN: usize = 5;
6405 let mut telemetry = self
6406 .config
6407 .enable_virtual_dep_telemetry
6408 .then(|| self.start_virtual_dep_telemetry());
6409
6410 loop {
6411 if cancel_flag.load(Ordering::Relaxed) {
6412 if let Some(mut t) = telemetry {
6413 t.bailout_reason = Some("cancelled");
6414 t.replan_iterations = replan_iterations;
6415 self.last_virtual_dep_telemetry = t;
6416 }
6417 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
6418 .with_message("Evaluation cancelled before scheduling".to_string()));
6419 }
6420
6421 let to_evaluate = self.graph.get_evaluation_vertices();
6422 if to_evaluate.is_empty() {
6423 if let Some(t) = telemetry.as_mut()
6424 && t.bailout_reason.is_none()
6425 {
6426 t.bailout_reason = Some("no_work");
6427 }
6428 break;
6429 }
6430
6431 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
6432 if let Some(t) = telemetry.as_mut() {
6433 Self::accumulate_schedule_meta(t, &meta);
6434 }
6435
6436 for cycle in &schedule.cycles {
6438 if cancel_flag.load(Ordering::Relaxed) {
6440 if let Some(mut t) = telemetry {
6441 t.bailout_reason = Some("cancelled");
6442 t.replan_iterations = replan_iterations;
6443 self.last_virtual_dep_telemetry = t;
6444 }
6445 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
6446 .with_message("Evaluation cancelled during cycle handling".to_string()));
6447 }
6448
6449 cycle_errors += 1;
6450 let circ_error = LiteralValue::Error(
6451 ExcelError::new(ExcelErrorKind::Circ)
6452 .with_message("Circular dependency detected".to_string()),
6453 );
6454 for &vertex_id in cycle {
6455 self.graph
6456 .update_vertex_value(vertex_id, circ_error.clone());
6457 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
6458 }
6459 }
6460
6461 for layer in &schedule.layers {
6463 if cancel_flag.load(Ordering::Relaxed) {
6465 if let Some(mut t) = telemetry {
6466 t.bailout_reason = Some("cancelled");
6467 t.replan_iterations = replan_iterations;
6468 self.last_virtual_dep_telemetry = t;
6469 }
6470 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
6471 .with_message("Evaluation cancelled between layers".to_string()));
6472 }
6473
6474 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
6476 computed_vertices +=
6477 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
6478 } else {
6479 computed_vertices +=
6480 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
6481 }
6482 }
6483
6484 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
6485 if let Some(t) = telemetry.as_mut() {
6486 t.changed_vdeps_total += changed_vertices.len();
6487 }
6488 self.graph.clear_dirty_flags(&to_evaluate);
6489 for v in &changed_vertices {
6490 self.graph.set_dirty(*v, true);
6491 }
6492
6493 if changed_vertices.is_empty() {
6494 if let Some(t) = telemetry.as_mut() {
6495 t.bailout_reason = Some("converged");
6496 }
6497 break;
6498 }
6499 if replan_iterations >= MAX_REPLAN {
6500 if let Some(t) = telemetry.as_mut() {
6501 t.bailout_reason = Some("max_replan");
6502 }
6503 break;
6504 }
6505 replan_iterations += 1;
6506 }
6507
6508 if let Some(mut t) = telemetry {
6509 t.replan_iterations = replan_iterations;
6510 self.last_virtual_dep_telemetry = t;
6511 }
6512
6513 self.graph.redirty_volatiles();
6515 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
6516
6517 Ok(EvalResult {
6518 computed_vertices,
6519 cycle_errors,
6520 elapsed: start.elapsed(),
6521 })
6522 }
6523
6524 pub fn evaluate_until_cancellable(
6526 &mut self,
6527 targets: &[&str],
6528 cancel_flag: Arc<AtomicBool>,
6529 ) -> Result<EvalResult, ExcelError> {
6530 self.active_cancel_flag = Some(cancel_flag.clone());
6531 let res = self.evaluate_until_cancellable_impl(targets, &cancel_flag);
6532 self.active_cancel_flag = None;
6533 res
6534 }
6535
6536 fn evaluate_until_cancellable_impl(
6537 &mut self,
6538 targets: &[&str],
6539 cancel_flag: &AtomicBool,
6540 ) -> Result<EvalResult, ExcelError> {
6541 let start = crate::instant::FzInstant::now();
6542
6543 let mut target_addrs = Vec::new();
6545 for target in targets {
6546 let (sheet, row, col) = self.parse_a1_notation(target)?;
6547 let sheet_id = self.graph.sheet_id_mut(&sheet);
6548 let coord = Coord::from_excel(row, col, true, true);
6549 target_addrs.push(CellRef::new(sheet_id, coord));
6550 }
6551
6552 let mut target_vertex_ids = Vec::new();
6554 for addr in &target_addrs {
6555 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
6556 target_vertex_ids.push(*vertex_id);
6557 }
6558 }
6559
6560 if target_vertex_ids.is_empty() {
6561 return Ok(EvalResult {
6562 computed_vertices: 0,
6563 cycle_errors: 0,
6564 elapsed: start.elapsed(),
6565 });
6566 }
6567
6568 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
6570
6571 if precedents_to_eval.is_empty() {
6572 return Ok(EvalResult {
6573 computed_vertices: 0,
6574 cycle_errors: 0,
6575 elapsed: start.elapsed(),
6576 });
6577 }
6578
6579 let scheduler = Scheduler::new(&self.graph);
6581 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
6582
6583 let mut cycle_errors = 0;
6585 for cycle in &schedule.cycles {
6586 if cancel_flag.load(Ordering::Relaxed) {
6588 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
6589 "Demand-driven evaluation cancelled during cycle handling".to_string(),
6590 ));
6591 }
6592
6593 cycle_errors += 1;
6594 let circ_error = LiteralValue::Error(
6595 ExcelError::new(ExcelErrorKind::Circ)
6596 .with_message("Circular dependency detected".to_string()),
6597 );
6598 for &vertex_id in cycle {
6599 self.graph
6600 .update_vertex_value(vertex_id, circ_error.clone());
6601 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
6602 }
6603 }
6604
6605 let mut computed_vertices = 0;
6607 for layer in &schedule.layers {
6608 if cancel_flag.load(Ordering::Relaxed) {
6610 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
6611 "Demand-driven evaluation cancelled between layers".to_string(),
6612 ));
6613 }
6614
6615 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
6617 computed_vertices +=
6618 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
6619 } else {
6620 computed_vertices +=
6621 self.evaluate_layer_sequential_cancellable_demand_driven(layer, cancel_flag)?;
6622 }
6623 }
6624
6625 self.graph.clear_dirty_flags(&precedents_to_eval);
6627
6628 self.graph.redirty_volatiles();
6630
6631 Ok(EvalResult {
6632 computed_vertices,
6633 cycle_errors,
6634 elapsed: start.elapsed(),
6635 })
6636 }
6637
6638 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
6639 let mut parts = address.splitn(2, '!');
6640 let first = parts.next().unwrap_or_default();
6641 let remainder = parts.next();
6642
6643 let (sheet, cell_part) = match remainder {
6644 Some(cell) => (first.to_string(), cell),
6645 None => (self.default_sheet_name().to_string(), first),
6646 };
6647
6648 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
6649 ExcelError::new(ExcelErrorKind::Ref)
6650 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
6651 })?;
6652
6653 Ok((sheet, row, col))
6654 }
6655
6656 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
6658 use formualizer_parse::parser::ASTNodeType;
6659 match &ast.node_type {
6660 ASTNodeType::Function { name, args, .. } => {
6661 if let Some(func) = self
6662 .get_function("", name)
6663 .or_else(|| crate::function_registry::get("", name))
6664 && func.caps().contains(crate::function::FnCaps::VOLATILE)
6665 {
6666 return true;
6667 }
6668 args.iter()
6669 .any(|arg| self.is_ast_volatile_with_provider(arg))
6670 }
6671 ASTNodeType::BinaryOp { left, right, .. } => {
6672 self.is_ast_volatile_with_provider(left)
6673 || self.is_ast_volatile_with_provider(right)
6674 }
6675 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
6676 ASTNodeType::Array(rows) => rows.iter().any(|row| {
6677 row.iter()
6678 .any(|cell| self.is_ast_volatile_with_provider(cell))
6679 }),
6680 _ => false,
6681 }
6682 }
6683
6684 fn find_dirty_precedents(&self, target_vertices: &[VertexId]) -> Vec<VertexId> {
6686 let mut to_evaluate = FxHashSet::default();
6687 let mut visited = FxHashSet::default();
6688 let mut stack = Vec::new();
6689
6690 for &target in target_vertices {
6692 stack.push(target);
6693 }
6694
6695 while let Some(vertex_id) = stack.pop() {
6696 if !visited.insert(vertex_id) {
6697 continue; }
6699
6700 if self.graph.vertex_exists(vertex_id) {
6701 let kind = self.graph.get_vertex_kind(vertex_id);
6703 let needs_eval = match kind {
6704 super::vertex::VertexKind::FormulaScalar
6705 | super::vertex::VertexKind::FormulaArray => {
6706 self.graph.is_dirty(vertex_id) || self.graph.is_volatile(vertex_id)
6707 }
6708 _ => false, };
6710
6711 if needs_eval {
6712 to_evaluate.insert(vertex_id);
6713 }
6714
6715 if let Some(dependencies) = self.graph.dependencies_slice(vertex_id) {
6717 for &dep_id in dependencies {
6718 if !visited.contains(&dep_id) {
6719 stack.push(dep_id);
6720 }
6721 }
6722 } else {
6723 let dependencies = self.graph.get_dependencies(vertex_id);
6724 for dep_id in dependencies {
6725 if !visited.contains(&dep_id) {
6726 stack.push(dep_id);
6727 }
6728 }
6729 }
6730 }
6731 }
6732
6733 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
6734 result.sort_unstable();
6735 result
6736 }
6737
6738 fn evaluate_layer_sequential(
6740 &mut self,
6741 layer: &super::scheduler::Layer,
6742 ) -> Result<usize, ExcelError> {
6743 self.evaluate_layer_sequential_effects(layer)
6744 }
6745
6746 fn update_vertex_value_with_delta(
6747 &mut self,
6748 vertex_id: VertexId,
6749 new_value: LiteralValue,
6750 delta: &mut DeltaCollector,
6751 ) {
6752 if delta.mode != DeltaMode::Off
6753 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
6754 {
6755 let sheet_name = self.graph.sheet_name(cell.sheet_id);
6756 let old = self
6757 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
6758 .unwrap_or(LiteralValue::Empty);
6759 if old != new_value {
6760 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
6761 }
6762 }
6763 self.graph.update_vertex_value(vertex_id, new_value.clone());
6764 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
6765 }
6766
6767 fn evaluate_layer_sequential_with_delta(
6768 &mut self,
6769 layer: &super::scheduler::Layer,
6770 delta: &mut DeltaCollector,
6771 ) -> Result<usize, ExcelError> {
6772 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
6773 }
6774
6775 fn evaluate_layer_sequential_cancellable(
6777 &mut self,
6778 layer: &super::scheduler::Layer,
6779 cancel_flag: &AtomicBool,
6780 ) -> Result<usize, ExcelError> {
6781 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
6782 }
6783
6784 fn evaluate_layer_sequential_cancellable_demand_driven(
6786 &mut self,
6787 layer: &super::scheduler::Layer,
6788 cancel_flag: &AtomicBool,
6789 ) -> Result<usize, ExcelError> {
6790 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
6791 }
6792
6793 fn evaluate_layer_parallel(
6795 &mut self,
6796 layer: &super::scheduler::Layer,
6797 ) -> Result<usize, ExcelError> {
6798 self.evaluate_layer_parallel_effects(layer)
6799 }
6800
6801 fn evaluate_layer_parallel_with_delta(
6802 &mut self,
6803 layer: &super::scheduler::Layer,
6804 delta: &mut DeltaCollector,
6805 ) -> Result<usize, ExcelError> {
6806 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
6807 }
6808
6809 fn evaluate_layer_parallel_cancellable(
6811 &mut self,
6812 layer: &super::scheduler::Layer,
6813 cancel_flag: &AtomicBool,
6814 ) -> Result<usize, ExcelError> {
6815 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
6816 }
6817
6818 fn apply_parallel_vertex_result(
6823 &mut self,
6824 vertex_id: VertexId,
6825 result: LiteralValue,
6826 mut delta: Option<&mut DeltaCollector>,
6827 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6828 ) -> Result<(), ExcelError> {
6829 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
6832 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
6833 && owner != vertex_id
6834 {
6835 return Ok(());
6836 }
6837
6838 let kind = self.graph.get_vertex_kind(vertex_id);
6839
6840 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
6842 if is_formula {
6843 match result {
6844 LiteralValue::Array(rows) => {
6845 self.apply_array_result_from_parallel(
6846 vertex_id,
6847 rows,
6848 delta.as_deref_mut(),
6849 overwritable_formulas,
6850 )?;
6851 }
6852 other => {
6853 self.apply_non_array_result_from_parallel(
6854 vertex_id,
6855 other,
6856 delta.as_deref_mut(),
6857 );
6858 }
6859 }
6860 return Ok(());
6861 }
6862
6863 if let Some(d) = delta {
6865 self.update_vertex_value_with_delta(vertex_id, result, d);
6866 } else {
6867 self.graph.update_vertex_value(vertex_id, result.clone());
6868 self.mirror_vertex_value_to_overlay(vertex_id, &result);
6869 }
6870 Ok(())
6871 }
6872
6873 fn apply_non_array_result_from_parallel(
6874 &mut self,
6875 vertex_id: VertexId,
6876 value: LiteralValue,
6877 delta: Option<&mut DeltaCollector>,
6878 ) {
6879 let spill_cells = self
6882 .graph
6883 .spill_cells_for_anchor(vertex_id)
6884 .map(|cells| cells.to_vec())
6885 .unwrap_or_default();
6886
6887 if let Some(d) = delta
6888 && d.mode != DeltaMode::Off
6889 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
6890 {
6891 if spill_cells.is_empty() {
6892 let old = self
6893 .read_cell_value(
6894 self.graph.sheet_name(anchor.sheet_id),
6895 anchor.coord.row() + 1,
6896 anchor.coord.col() + 1,
6897 )
6898 .unwrap_or(LiteralValue::Empty);
6899 if old != value {
6900 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6901 }
6902 } else {
6903 for cell in spill_cells.iter() {
6904 let sheet_name = self.graph.sheet_name(cell.sheet_id);
6905 let old = self
6906 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
6907 .unwrap_or(LiteralValue::Empty);
6908 let new = if cell.sheet_id == anchor.sheet_id
6909 && cell.coord.row() == anchor.coord.row()
6910 && cell.coord.col() == anchor.coord.col()
6911 {
6912 value.clone()
6913 } else {
6914 LiteralValue::Empty
6915 };
6916 Self::record_cell_if_changed(d, cell, &old, &new);
6917 }
6918 }
6919 }
6920
6921 self.graph.clear_spill_region(vertex_id);
6922
6923 if self.config.arrow_storage_enabled
6924 && self.config.delta_overlay_enabled
6925 && self.config.write_formula_overlay_enabled
6926 {
6927 let empty = LiteralValue::Empty;
6928 for cell in spill_cells.iter() {
6929 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
6930 self.mirror_value_to_computed_overlay(
6931 &sheet_name,
6932 cell.coord.row() + 1,
6933 cell.coord.col() + 1,
6934 &empty,
6935 );
6936 }
6937 }
6938
6939 self.graph.update_vertex_value(vertex_id, value.clone());
6940 self.mirror_vertex_value_to_overlay(vertex_id, &value);
6941 }
6942
6943 fn apply_array_result_from_parallel(
6944 &mut self,
6945 vertex_id: VertexId,
6946 rows: Vec<Vec<LiteralValue>>,
6947 mut delta: Option<&mut DeltaCollector>,
6948 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
6949 ) -> Result<(), ExcelError> {
6950 self.graph
6952 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
6953
6954 let anchor = self
6955 .graph
6956 .get_cell_ref(vertex_id)
6957 .expect("cell ref for vertex");
6958 let sheet_id = anchor.sheet_id;
6959 let h = rows.len() as u32;
6960 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
6961
6962 let spill_cells = (h as u64).saturating_mul(w as u64);
6964 if spill_cells > self.config.spill.max_spill_cells as u64 {
6965 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6966 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
6967 .with_message("SpillTooLarge")
6968 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
6969 expected_rows: h,
6970 expected_cols: w,
6971 });
6972 let spill_val = LiteralValue::Error(spill_err.clone());
6973 if let Some(d) = delta.as_deref_mut()
6974 && d.mode != DeltaMode::Off
6975 {
6976 let old = self
6977 .read_cell_value(
6978 self.graph.sheet_name(anchor.sheet_id),
6979 anchor.coord.row() + 1,
6980 anchor.coord.col() + 1,
6981 )
6982 .unwrap_or(LiteralValue::Empty);
6983 if old != spill_val {
6984 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
6985 }
6986 }
6987 self.graph.update_vertex_value(vertex_id, spill_val.clone());
6988 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
6989 return Ok(());
6990 }
6991
6992 const PACKED_MAX_ROW: u32 = 1_048_575; const PACKED_MAX_COL: u32 = 16_383; let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
6996 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
6997 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
6998 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
6999 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7000 .with_message("Spill exceeds sheet bounds")
7001 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7002 expected_rows: h,
7003 expected_cols: w,
7004 });
7005 let spill_val = LiteralValue::Error(spill_err.clone());
7006 if let Some(d) = delta.as_deref_mut()
7007 && d.mode != DeltaMode::Off
7008 {
7009 let old = self
7010 .read_cell_value(
7011 self.graph.sheet_name(anchor.sheet_id),
7012 anchor.coord.row() + 1,
7013 anchor.coord.col() + 1,
7014 )
7015 .unwrap_or(LiteralValue::Empty);
7016 if old != spill_val {
7017 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7018 }
7019 }
7020 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7021 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
7022 return Ok(());
7023 }
7024
7025 let mut targets = Vec::new();
7026 for r in 0..h {
7027 for c in 0..w {
7028 targets.push(self.graph.make_cell_ref_internal(
7029 sheet_id,
7030 anchor.coord.row() + r,
7031 anchor.coord.col() + c,
7032 ));
7033 }
7034 }
7035
7036 match self.spill_mgr.reserve(
7037 vertex_id,
7038 anchor,
7039 SpillShape { rows: h, cols: w },
7040 SpillMeta {
7041 epoch: self.recalc_epoch,
7042 config: self.config.spill,
7043 },
7044 ) {
7045 Ok(()) => {
7046 if let Err(e) = self.commit_spill_and_mirror(
7047 vertex_id,
7048 &targets,
7049 rows.clone(),
7050 delta.as_deref_mut(),
7051 overwritable_formulas,
7052 ) {
7053 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7054 let err_val = LiteralValue::Error(e.clone());
7055 if let Some(d) = delta.as_deref_mut()
7056 && d.mode != DeltaMode::Off
7057 {
7058 let old = self
7059 .read_cell_value(
7060 self.graph.sheet_name(anchor.sheet_id),
7061 anchor.coord.row() + 1,
7062 anchor.coord.col() + 1,
7063 )
7064 .unwrap_or(LiteralValue::Empty);
7065 if old != err_val {
7066 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7067 }
7068 }
7069 self.graph.update_vertex_value(vertex_id, err_val.clone());
7070 self.mirror_vertex_value_to_overlay(vertex_id, &err_val);
7071 return Ok(());
7072 }
7073
7074 let top_left = rows
7076 .first()
7077 .and_then(|r| r.first())
7078 .cloned()
7079 .unwrap_or(LiteralValue::Empty);
7080 self.graph.update_vertex_value(vertex_id, top_left.clone());
7081 self.mirror_vertex_value_to_overlay(vertex_id, &top_left);
7082 Ok(())
7083 }
7084 Err(e) => {
7085 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7086 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7087 .with_message(e.message.unwrap_or_else(|| "Spill blocked".to_string()))
7088 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7089 expected_rows: h,
7090 expected_cols: w,
7091 });
7092 let spill_val = LiteralValue::Error(spill_err.clone());
7093 if let Some(d) = delta
7094 && d.mode != DeltaMode::Off
7095 {
7096 let old = self
7097 .read_cell_value(
7098 self.graph.sheet_name(anchor.sheet_id),
7099 anchor.coord.row() + 1,
7100 anchor.coord.col() + 1,
7101 )
7102 .unwrap_or(LiteralValue::Empty);
7103 if old != spill_val {
7104 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7105 }
7106 }
7107 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7108 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
7109 Ok(())
7110 }
7111 }
7112 }
7113
7114 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
7116 if !self.graph.vertex_exists(vertex_id) {
7118 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
7119 .with_message(format!("Vertex not found: {vertex_id:?}")));
7120 }
7121
7122 let kind = self.graph.get_vertex_kind(vertex_id);
7124 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
7125
7126 let ast_id = match kind {
7127 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
7128 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
7129 ast_id
7130 } else {
7131 return Ok(LiteralValue::Number(0.0));
7132 }
7133 }
7134 VertexKind::Empty | VertexKind::Cell => {
7135 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
7136 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7137 let row = cell_ref.coord.row() + 1;
7138 let col = cell_ref.coord.col() + 1;
7139 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
7140 return Ok(v);
7141 }
7142 }
7143 return Ok(LiteralValue::Number(0.0));
7144 }
7145 VertexKind::NamedScalar => {
7146 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
7147 ExcelError::new(ExcelErrorKind::Name)
7148 .with_message("Named range metadata missing".to_string())
7149 })?;
7150
7151 return match &named_range.definition {
7152 NamedDefinition::Cell(cell_ref) => {
7153 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7154 Ok(self
7155 .get_cell_value(
7156 sheet_name,
7157 cell_ref.coord.row() + 1,
7158 cell_ref.coord.col() + 1,
7159 )
7160 .unwrap_or(LiteralValue::Empty))
7161 }
7162 NamedDefinition::Literal(v) => Ok(v.clone()),
7163 NamedDefinition::Formula { ast, .. } => {
7164 let context_sheet = match named_range.scope {
7165 NameScope::Sheet(id) => id,
7166 NameScope::Workbook => sheet_id,
7167 };
7168 let sheet_name = self.graph.sheet_name(context_sheet);
7169 let cell_ref = self
7170 .graph
7171 .get_cell_ref(vertex_id)
7172 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
7173 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7174 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
7175 }
7176 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
7177 .with_message("Range-valued name evaluated as scalar".to_string())),
7178 };
7179 }
7180 VertexKind::NamedArray => {
7181 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
7182 ExcelError::new(ExcelErrorKind::Name)
7183 .with_message("Named range metadata missing".to_string())
7184 })?;
7185
7186 return match &named_range.definition {
7187 NamedDefinition::Range(range_ref) => {
7188 if range_ref.start.sheet_id != range_ref.end.sheet_id {
7189 return Err(ExcelError::new(ExcelErrorKind::Ref)
7190 .with_message("Named range cannot span sheets".to_string()));
7191 }
7192 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
7193 let sr0 = range_ref.start.coord.row();
7194 let sc0 = range_ref.start.coord.col();
7195 let er0 = range_ref.end.coord.row();
7196 let ec0 = range_ref.end.coord.col();
7197 if sr0 > er0 || sc0 > ec0 {
7198 return Err(ExcelError::new(ExcelErrorKind::Ref)
7199 .with_message("Invalid named range bounds".to_string()));
7200 }
7201
7202 let h = (er0 - sr0 + 1) as usize;
7203 let w = (ec0 - sc0 + 1) as usize;
7204 let cell_count = (h as u64).saturating_mul(w as u64);
7205 if cell_count > self.config.spill.max_spill_cells as u64 {
7206 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
7207 "Named range too large to materialize as an array".to_string(),
7208 ));
7209 }
7210
7211 let mut rows = Vec::with_capacity(h);
7212 for r0 in sr0..=er0 {
7213 let mut row = Vec::with_capacity(w);
7214 for c0 in sc0..=ec0 {
7215 let v = self
7216 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
7217 .unwrap_or(LiteralValue::Empty);
7218 row.push(v);
7219 }
7220 rows.push(row);
7221 }
7222 Ok(LiteralValue::Array(rows))
7223 }
7224 NamedDefinition::Cell(cell_ref) => {
7225 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7226 let row = cell_ref.coord.row() + 1;
7227 let col = cell_ref.coord.col() + 1;
7228 let v = self
7229 .get_cell_value(sheet_name, row, col)
7230 .unwrap_or(LiteralValue::Empty);
7231 Ok(LiteralValue::Array(vec![vec![v]]))
7232 }
7233 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
7234 NamedDefinition::Formula { ast, .. } => {
7235 let context_sheet = match named_range.scope {
7236 NameScope::Sheet(id) => id,
7237 NameScope::Workbook => sheet_id,
7238 };
7239 let sheet_name = self.graph.sheet_name(context_sheet);
7240 let cell_ref = self
7241 .graph
7242 .get_cell_ref(vertex_id)
7243 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
7244 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7245 match interpreter.evaluate_ast(ast) {
7246 Ok(cv) => {
7247 let v = cv.into_literal();
7248 match v {
7249 LiteralValue::Array(_) => Ok(v),
7250 other => Ok(LiteralValue::Array(vec![vec![other]])),
7251 }
7252 }
7253 Err(err) => Ok(LiteralValue::Error(err)),
7254 }
7255 }
7256 };
7257 }
7258 VertexKind::InfiniteRange
7259 | VertexKind::Range
7260 | VertexKind::External
7261 | VertexKind::Table => {
7262 return Ok(LiteralValue::Number(0.0));
7264 }
7265 };
7266
7267 let sheet_name = self.graph.sheet_name(sheet_id);
7269 let cell_ref = self
7270 .graph
7271 .get_cell_ref(vertex_id)
7272 .expect("cell ref for vertex");
7273 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7274
7275 interpreter
7276 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
7277 .map(|cv| cv.into_literal())
7278 }
7279
7280 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
7282 self.thread_pool.as_ref()
7283 }
7284}
7285
7286#[derive(Default)]
7287struct RowBoundsCache {
7288 snapshot: u64,
7289 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
7291}
7292
7293impl RowBoundsCache {
7294 fn new(snapshot: u64) -> Self {
7295 Self {
7296 snapshot,
7297 map: Default::default(),
7298 }
7299 }
7300 fn get_row_bounds(
7301 &self,
7302 sheet_id: SheetId,
7303 col_idx: usize,
7304 snapshot: u64,
7305 ) -> Option<(Option<u32>, Option<u32>)> {
7306 if self.snapshot != snapshot {
7307 return None;
7308 }
7309 self.map.get(&(sheet_id as u32, col_idx)).copied()
7310 }
7311 fn put_row_bounds(
7312 &mut self,
7313 sheet_id: SheetId,
7314 col_idx: usize,
7315 snapshot: u64,
7316 bounds: (Option<u32>, Option<u32>),
7317 ) {
7318 if self.snapshot != snapshot {
7319 self.snapshot = snapshot;
7320 self.map.clear();
7321 }
7322 self.map.insert((sheet_id as u32, col_idx), bounds);
7323 }
7324}
7325
7326#[derive(Default)]
7328pub struct ShimSpillManager {
7329 region_locks: RegionLockManager,
7330 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
7331}
7332
7333impl ShimSpillManager {
7334 pub(crate) fn reserve(
7335 &mut self,
7336 owner: VertexId,
7337 anchor_cell: CellRef,
7338 shape: SpillShape,
7339 _meta: SpillMeta,
7340 ) -> Result<(), ExcelError> {
7341 let region = crate::engine::spill::Region {
7343 sheet_id: anchor_cell.sheet_id as u32,
7344 row_start: anchor_cell.coord.row(),
7345 row_end: anchor_cell
7346 .coord
7347 .row()
7348 .saturating_add(shape.rows)
7349 .saturating_sub(1),
7350 col_start: anchor_cell.coord.col(),
7351 col_end: anchor_cell
7352 .coord
7353 .col()
7354 .saturating_add(shape.cols)
7355 .saturating_sub(1),
7356 };
7357 match self.region_locks.reserve(region, owner) {
7358 Ok(id) => {
7359 if id != 0 {
7360 self.active_locks.insert(owner, id);
7361 }
7362 Ok(())
7363 }
7364 Err(e) => Err(e),
7365 }
7366 }
7367
7368 pub(crate) fn commit_array_with_value_probe<F>(
7369 &mut self,
7370 graph: &mut DependencyGraph,
7371 anchor_vertex: VertexId,
7372 targets: &[CellRef],
7373 rows: Vec<Vec<LiteralValue>>,
7374 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
7375 mut value_probe: F,
7376 ) -> Result<(), ExcelError>
7377 where
7378 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
7379 {
7380 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
7381
7382 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
7386 anchor_vertex,
7387 targets,
7388 overwritable_formulas,
7389 );
7390 if let Err(e) = plan_res {
7391 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
7392 self.region_locks.release(id);
7393 }
7394 return Err(e);
7395 }
7396
7397 if !graph.value_cache_enabled() {
7398 let (expected_rows, expected_cols) = if targets.is_empty() {
7400 (0u32, 0u32)
7401 } else {
7402 let mut min_r = u32::MAX;
7403 let mut max_r = 0u32;
7404 let mut min_c = u32::MAX;
7405 let mut max_c = 0u32;
7406 for cell in targets {
7407 let r = cell.coord.row();
7408 let c = cell.coord.col();
7409 min_r = min_r.min(r);
7410 max_r = max_r.max(r);
7411 min_c = min_c.min(c);
7412 max_c = max_c.max(c);
7413 }
7414 (
7415 max_r.saturating_sub(min_r).saturating_add(1),
7416 max_c.saturating_sub(min_c).saturating_add(1),
7417 )
7418 };
7419
7420 let anchor_cell = graph
7421 .get_cell_ref(anchor_vertex)
7422 .expect("anchor cell ref for spill commit");
7423
7424 for cell in targets {
7425 if *cell == anchor_cell {
7427 continue;
7428 }
7429 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
7431 continue;
7432 }
7433 if let Some(&vid) = graph.get_vertex_id_for_address(cell)
7435 && vid != anchor_vertex
7436 {
7437 match graph.get_vertex_kind(vid) {
7438 crate::engine::vertex::VertexKind::FormulaScalar
7439 | crate::engine::vertex::VertexKind::FormulaArray => {
7440 continue;
7442 }
7443 _ => {}
7444 }
7445 }
7446
7447 if let Some(v) = value_probe(graph, cell)
7448 && !matches!(v, LiteralValue::Empty)
7449 {
7450 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
7451 self.region_locks.release(id);
7452 }
7453 return Err(ExcelError::new(ExcelErrorKind::Spill)
7454 .with_message("BlockedByValue")
7455 .with_extra(ExcelErrorExtra::Spill {
7456 expected_rows,
7457 expected_cols,
7458 }));
7459 }
7460 }
7461 }
7462
7463 let commit_res = graph.commit_spill_region_atomic_with_fault(
7464 anchor_vertex,
7465 targets.to_vec(),
7466 rows,
7467 None,
7468 );
7469 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
7470 self.region_locks.release(id);
7471 }
7472 commit_res.map(|_| ())
7473 }
7474
7475 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
7477 &mut self,
7478 engine: &mut Engine<R>,
7479 anchor_vertex: VertexId,
7480 targets: &[CellRef],
7481 rows: Vec<Vec<LiteralValue>>,
7482 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
7483 ) -> Result<(), ExcelError> {
7484 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
7486 anchor_vertex,
7487 targets,
7488 overwritable_formulas,
7489 );
7490 if let Err(e) = plan_res {
7491 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
7492 self.region_locks.release(id);
7493 }
7494 return Err(e);
7495 }
7496
7497 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
7498 anchor_vertex,
7499 targets.to_vec(),
7500 rows.clone(),
7501 None,
7502 );
7503 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
7504 self.region_locks.release(id);
7505 }
7506 commit_res.map(|_| ())?;
7507
7508 if engine.config.arrow_storage_enabled
7510 && engine.config.delta_overlay_enabled
7511 && engine.config.write_formula_overlay_enabled
7512 {
7513 for (idx, cell) in targets.iter().enumerate() {
7515 let (r_off, c_off) = {
7516 if rows.is_empty() || rows[0].is_empty() {
7517 (0usize, 0usize)
7518 } else {
7519 let width = rows[0].len();
7520 (idx / width, idx % width)
7521 }
7522 };
7523 let v = rows
7524 .get(r_off)
7525 .and_then(|r| r.get(c_off))
7526 .cloned()
7527 .unwrap_or(LiteralValue::Empty);
7528 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
7529 engine.mirror_value_to_computed_overlay(
7530 &sheet_name,
7531 cell.coord.row() + 1,
7532 cell.coord.col() + 1,
7533 &v,
7534 );
7535 }
7536 }
7537 Ok(())
7538 }
7539}
7540
7541impl<R> Engine<R>
7542where
7543 R: EvaluationContext,
7544{
7545 fn resolve_shared_ref(
7546 &self,
7547 reference: &ReferenceType,
7548 current_sheet: &str,
7549 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
7550 use formualizer_common::{
7551 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
7552 SheetRef as SharedRef,
7553 };
7554
7555 let sr = match reference {
7557 ReferenceType::Cell {
7558 sheet,
7559 row,
7560 col,
7561 row_abs,
7562 col_abs,
7563 } => {
7564 let row0 = row
7565 .checked_sub(1)
7566 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
7567 let col0 = col
7568 .checked_sub(1)
7569 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
7570 let sheet_loc = match sheet.as_deref() {
7571 Some(name) => SheetLocator::from_name(name),
7572 None => SheetLocator::Current,
7573 };
7574 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
7575 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
7576 }
7577 ReferenceType::Range {
7578 sheet,
7579 start_row,
7580 start_col,
7581 end_row,
7582 end_col,
7583 start_row_abs,
7584 start_col_abs,
7585 end_row_abs,
7586 end_col_abs,
7587 } => {
7588 let sheet_loc = match sheet.as_deref() {
7589 Some(name) => SheetLocator::from_name(name),
7590 None => SheetLocator::Current,
7591 };
7592 let sr = start_row
7593 .map(|r| {
7594 r.checked_sub(1)
7595 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
7596 })
7597 .transpose()?;
7598 let sc = start_col
7599 .map(|c| {
7600 c.checked_sub(1)
7601 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
7602 })
7603 .transpose()?;
7604 let er = end_row
7605 .map(|r| {
7606 r.checked_sub(1)
7607 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
7608 })
7609 .transpose()?;
7610 let ec = end_col
7611 .map(|c| {
7612 c.checked_sub(1)
7613 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
7614 })
7615 .transpose()?;
7616 let range = SharedRangeRef::from_parts(
7617 sheet_loc,
7618 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
7619 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
7620 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
7621 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
7622 )
7623 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
7624 SharedRef::Range(range)
7625 }
7626 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
7627 };
7628
7629 let current_id = self
7630 .graph
7631 .sheet_id(current_sheet)
7632 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
7633
7634 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
7635 match loc {
7636 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
7637 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
7638 SheetLocator::Name(name) => {
7639 let n = name.as_ref();
7640 self.graph
7641 .sheet_id(n)
7642 .map(SheetLocator::Id)
7643 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
7644 }
7645 }
7646 };
7647
7648 match sr {
7649 SharedRef::Cell(cell) => {
7650 let owned = cell.into_owned();
7651 let sheet = resolve_loc(owned.sheet)?;
7652 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
7653 }
7654 SharedRef::Range(range) => {
7655 let owned = range.into_owned();
7656 let sheet = resolve_loc(owned.sheet)?;
7657 Ok(SharedRef::Range(SharedRangeRef {
7658 sheet,
7659 start_row: owned.start_row,
7660 start_col: owned.start_col,
7661 end_row: owned.end_row,
7662 end_col: owned.end_col,
7663 }))
7664 }
7665 }
7666 }
7667}
7668
7669impl<R> crate::traits::ReferenceResolver for Engine<R>
7672where
7673 R: EvaluationContext,
7674{
7675 fn resolve_cell_reference(
7676 &self,
7677 sheet: Option<&str>,
7678 row: u32,
7679 col: u32,
7680 ) -> Result<LiteralValue, ExcelError> {
7681 let sheet_name = sheet.unwrap_or_else(|| self.default_sheet_name()); if let Some(v) = self.get_cell_value(sheet_name, row, col) {
7685 Ok(v)
7686 } else {
7687 Ok(LiteralValue::Number(0.0))
7689 }
7690 }
7691}
7692
7693impl<R> crate::traits::RangeResolver for Engine<R>
7694where
7695 R: EvaluationContext,
7696{
7697 fn resolve_range_reference(
7698 &self,
7699 sheet: Option<&str>,
7700 sr: Option<u32>,
7701 sc: Option<u32>,
7702 er: Option<u32>,
7703 ec: Option<u32>,
7704 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
7705 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
7708 }
7709}
7710
7711impl<R> crate::traits::NamedRangeResolver for Engine<R>
7712where
7713 R: EvaluationContext,
7714{
7715 fn resolve_named_range_reference(
7716 &self,
7717 name: &str,
7718 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
7719 self.resolver.resolve_named_range_reference(name)
7720 }
7721}
7722
7723impl<R> crate::traits::TableResolver for Engine<R>
7724where
7725 R: EvaluationContext,
7726{
7727 fn resolve_table_reference(
7728 &self,
7729 tref: &formualizer_parse::parser::TableReference,
7730 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
7731 self.resolver.resolve_table_reference(tref)
7732 }
7733}
7734
7735impl<R> crate::traits::SourceResolver for Engine<R>
7736where
7737 R: EvaluationContext,
7738{
7739 fn source_scalar_version(&self, name: &str) -> Option<u64> {
7740 self.resolver.source_scalar_version(name)
7741 }
7742
7743 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
7744 self.resolver.resolve_source_scalar(name)
7745 }
7746
7747 fn source_table_version(&self, name: &str) -> Option<u64> {
7748 self.resolver.source_table_version(name)
7749 }
7750
7751 fn resolve_source_table(
7752 &self,
7753 name: &str,
7754 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
7755 self.resolver.resolve_source_table(name)
7756 }
7757}
7758
7759impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
7761
7762impl<R> crate::traits::FunctionProvider for Engine<R>
7764where
7765 R: EvaluationContext,
7766{
7767 fn get_function(
7768 &self,
7769 prefix: &str,
7770 name: &str,
7771 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
7772 self.resolver.get_function(prefix, name)
7773 }
7774}
7775
7776impl<R> crate::traits::EvaluationContext for Engine<R>
7778where
7779 R: EvaluationContext,
7780{
7781 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
7782 self.clock.as_ref()
7783 }
7784
7785 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
7786 self.thread_pool.as_ref()
7787 }
7788
7789 fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
7790 self.active_cancel_flag.clone()
7791 }
7792
7793 fn chunk_hint(&self) -> Option<usize> {
7794 let hint =
7796 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
7797 Some(hint.clamp(1024, 1 << 20)) }
7799
7800 fn volatile_level(&self) -> crate::traits::VolatileLevel {
7801 self.config.volatile_level
7802 }
7803
7804 fn workbook_seed(&self) -> u64 {
7805 self.config.workbook_seed
7806 }
7807
7808 fn recalc_epoch(&self) -> u64 {
7809 self.recalc_epoch
7810 }
7811
7812 fn used_rows_for_columns(
7813 &self,
7814 sheet: &str,
7815 start_col: u32,
7816 end_col: u32,
7817 ) -> Option<(u32, u32)> {
7818 let sheet_id = self.graph.sheet_id(sheet)?;
7820 let arrow_bounds = self
7821 .sheet_store()
7822 .sheet(sheet)
7823 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
7824 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
7825 if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
7826 return Some(bounds);
7827 }
7828 let sc0 = start_col.saturating_sub(1);
7829 let ec0 = end_col.saturating_sub(1);
7830 self.graph
7831 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
7832 .map(|(a0, b0)| (a0 + 1, b0 + 1))
7833 }
7834
7835 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
7836 let sheet_id = self.graph.sheet_id(sheet)?;
7838 let arrow_bounds = self
7839 .sheet_store()
7840 .sheet(sheet)
7841 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
7842 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
7843 if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
7844 return Some(bounds);
7845 }
7846 let sr0 = start_row.saturating_sub(1);
7847 let er0 = end_row.saturating_sub(1);
7848 self.graph
7849 .used_col_bounds_for_rows(sheet_id, sr0, er0)
7850 .map(|(a0, b0)| (a0 + 1, b0 + 1))
7851 }
7852
7853 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
7854 let _ = self.graph.sheet_id(sheet)?;
7855 Some((1_048_576, 16_384)) }
7859
7860 fn data_snapshot_id(&self) -> u64 {
7861 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
7862 }
7863
7864 fn backend_caps(&self) -> crate::traits::BackendCaps {
7865 crate::traits::BackendCaps {
7866 streaming: true,
7867 used_region: true,
7868 write: false,
7869 tables: false,
7870 async_stream: false,
7871 }
7872 }
7873
7874 fn date_system(&self) -> crate::engine::DateSystem {
7877 self.config.date_system
7878 }
7879 fn resolve_range_view<'c>(
7881 &'c self,
7882 reference: &ReferenceType,
7883 current_sheet: &str,
7884 ) -> Result<RangeView<'c>, ExcelError> {
7885 match reference {
7886 ReferenceType::External(ext) => {
7887 let name = ext.raw.as_str();
7888 match ext.kind {
7889 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
7890 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
7891 return Err(ExcelError::new(ExcelErrorKind::Name)
7892 .with_message(format!("Undefined name: {name}")));
7893 };
7894 let version = source
7895 .version
7896 .or_else(|| self.resolver.source_scalar_version(name));
7897 let v = self.resolve_source_scalar_cached(name, version)?;
7898 Ok(RangeView::from_owned_rows(
7899 vec![vec![v]],
7900 self.config.date_system,
7901 ))
7902 }
7903 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
7904 let Some(source) = self.graph.resolve_source_table_entry(name) else {
7905 return Err(ExcelError::new(ExcelErrorKind::Name)
7906 .with_message(format!("Undefined table: {name}")));
7907 };
7908 let version = source
7909 .version
7910 .or_else(|| self.resolver.source_table_version(name));
7911 let table = self.resolve_source_table_cached(name, version)?;
7912 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
7913 self.source_table_to_range_view(table.as_ref(), &spec)
7914 }
7915 }
7916 }
7917 ReferenceType::Range { .. } => {
7918 let shared = self.resolve_shared_ref(reference, current_sheet)?;
7919 let formualizer_common::SheetRef::Range(range) = shared else {
7920 return Err(ExcelError::new(ExcelErrorKind::Ref));
7921 };
7922 let sheet_id = match range.sheet {
7923 formualizer_common::SheetLocator::Id(id) => id,
7924 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
7925 };
7926 let sheet_name = self.graph.sheet_name(sheet_id);
7927
7928 let bounded_range = if range.start_row.is_some()
7929 && range.start_col.is_some()
7930 && range.end_row.is_some()
7931 && range.end_col.is_some()
7932 {
7933 Some(RangeRef::try_from_shared(range.as_ref())?)
7934 } else {
7935 None
7936 };
7937
7938 let mut sr = bounded_range
7939 .as_ref()
7940 .map(|r| r.start.coord.row() + 1)
7941 .or_else(|| range.start_row.map(|b| b.index + 1));
7942 let mut sc = bounded_range
7943 .as_ref()
7944 .map(|r| r.start.coord.col() + 1)
7945 .or_else(|| range.start_col.map(|b| b.index + 1));
7946 let mut er = bounded_range
7947 .as_ref()
7948 .map(|r| r.end.coord.row() + 1)
7949 .or_else(|| range.end_row.map(|b| b.index + 1));
7950 let mut ec = bounded_range
7951 .as_ref()
7952 .map(|r| r.end.coord.col() + 1)
7953 .or_else(|| range.end_col.map(|b| b.index + 1));
7954
7955 if sr.is_none() && er.is_none() {
7956 let scv = sc.unwrap_or(1);
7958 let ecv = ec.unwrap_or(scv);
7959 sr = Some(1);
7960 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
7961 er = Some(max_r);
7962 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
7963 er = Some(self.config.max_open_ended_rows);
7964 }
7965 }
7966 if sc.is_none() && ec.is_none() {
7967 let srv = sr.unwrap_or(1);
7969 let erv = er.unwrap_or(srv);
7970 sc = Some(1);
7971 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
7972 ec = Some(max_c);
7973 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
7974 ec = Some(self.config.max_open_ended_cols);
7975 }
7976 }
7977 if sr.is_some() && er.is_none() {
7978 let scv = sc.unwrap_or(1);
7979 let ecv = ec.unwrap_or(scv);
7980 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
7981 er = Some(max_r);
7982 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
7983 er = Some(self.config.max_open_ended_rows);
7984 }
7985 }
7986 if er.is_some() && sr.is_none() {
7987 sr = Some(1);
7989 }
7990 if sc.is_some() && ec.is_none() {
7991 let srv = sr.unwrap_or(1);
7992 let erv = er.unwrap_or(srv);
7993 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
7994 ec = Some(max_c);
7995 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
7996 ec = Some(self.config.max_open_ended_cols);
7997 }
7998 }
7999 if ec.is_some() && sc.is_none() {
8000 sc = Some(1);
8002 }
8003
8004 let sr = sr.unwrap_or(1);
8005 let sc = sc.unwrap_or(1);
8006 let er = er.unwrap_or(sr.saturating_sub(1));
8007 let ec = ec.unwrap_or(sc.saturating_sub(1));
8008
8009 if self.force_materialize_range_views {
8010 if er < sr || ec < sc {
8011 return Ok(RangeView::from_owned_rows(
8012 Vec::new(),
8013 self.config.date_system,
8014 ));
8015 }
8016 let h = (er - sr + 1) as u64;
8017 let w = (ec - sc + 1) as u64;
8018 let cell_count = h.saturating_mul(w);
8019 if cell_count <= self.config.spill.max_spill_cells as u64 {
8020 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
8021 for r in sr..=er {
8022 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
8023 for c in sc..=ec {
8024 rowv.push(
8025 self.get_cell_value(sheet_name, r, c)
8026 .unwrap_or(LiteralValue::Empty),
8027 );
8028 }
8029 rows.push(rowv);
8030 }
8031 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
8032 }
8033 }
8034
8035 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
8036 return Ok(RangeView::from_owned_rows(
8037 Vec::new(),
8038 self.config.date_system,
8039 ));
8040 };
8041
8042 let rv = if er < sr || ec < sc {
8043 asheet.range_view(1, 1, 0, 0)
8044 } else {
8045 let sr0 = sr.saturating_sub(1) as usize;
8046 let sc0 = sc.saturating_sub(1) as usize;
8047 let er0 = er.saturating_sub(1) as usize;
8048 let ec0 = ec.saturating_sub(1) as usize;
8049 asheet.range_view(sr0, sc0, er0, ec0)
8050 };
8051
8052 Ok(rv)
8053 }
8054 ReferenceType::Cell { .. } => {
8055 let shared = self.resolve_shared_ref(reference, current_sheet)?;
8056 let formualizer_common::SheetRef::Cell(cell) = shared else {
8057 return Err(ExcelError::new(ExcelErrorKind::Ref));
8058 };
8059 let addr = CellRef::try_from_shared(cell)?;
8060 let sheet_id = addr.sheet_id;
8061 let sheet_name = self.graph.sheet_name(sheet_id);
8062 let row = addr.coord.row() + 1;
8063 let col = addr.coord.col() + 1;
8064
8065 if self.force_materialize_range_views {
8066 let v = self
8067 .get_cell_value(sheet_name, row, col)
8068 .unwrap_or(LiteralValue::Empty);
8069 return Ok(RangeView::from_owned_rows(
8070 vec![vec![v]],
8071 self.config.date_system,
8072 ));
8073 }
8074
8075 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
8076 let r0 = row.saturating_sub(1) as usize;
8077 let c0 = col.saturating_sub(1) as usize;
8078 let rv = asheet.range_view(r0, c0, r0, c0);
8079 Ok(rv)
8080 } else {
8081 let v = self
8082 .get_cell_value(sheet_name, row, col)
8083 .unwrap_or(LiteralValue::Empty);
8084 Ok(RangeView::from_owned_rows(
8085 vec![vec![v]],
8086 self.config.date_system,
8087 ))
8088 }
8089 }
8090 ReferenceType::NamedRange(name) => {
8091 if let Some(current_id) = self.graph.sheet_id(current_sheet)
8092 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
8093 {
8094 match &named.definition {
8095 NamedDefinition::Cell(cell_ref) => {
8096 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8097 if self.force_materialize_range_views {
8098 let v = self
8099 .get_cell_value(
8100 sheet_name,
8101 cell_ref.coord.row() + 1,
8102 cell_ref.coord.col() + 1,
8103 )
8104 .unwrap_or(LiteralValue::Empty);
8105 return Ok(RangeView::from_owned_rows(
8106 vec![vec![v]],
8107 self.config.date_system,
8108 ));
8109 } else {
8110 let asheet = self
8111 .sheet_store()
8112 .sheet(sheet_name)
8113 .expect("Arrow sheet missing for named cell");
8114 let r0 = cell_ref.coord.row() as usize;
8115 let c0 = cell_ref.coord.col() as usize;
8116 let rv = asheet.range_view(r0, c0, r0, c0);
8117 return Ok(rv);
8118 }
8119 }
8120 NamedDefinition::Range(range_ref) => {
8121 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
8122 let sr = range_ref.start.coord.row() + 1;
8123 let sc = range_ref.start.coord.col() + 1;
8124 let er = range_ref.end.coord.row() + 1;
8125 let ec = range_ref.end.coord.col() + 1;
8126 if self.force_materialize_range_views {
8127 let h = (er.saturating_sub(sr) + 1) as u64;
8128 let w = (ec.saturating_sub(sc) + 1) as u64;
8129 let cell_count = h.saturating_mul(w);
8130 if cell_count <= self.config.spill.max_spill_cells as u64 {
8131 let mut rows: Vec<Vec<LiteralValue>> =
8132 Vec::with_capacity(h as usize);
8133 for r in sr..=er {
8134 let mut rowv: Vec<LiteralValue> =
8135 Vec::with_capacity(w as usize);
8136 for c in sc..=ec {
8137 rowv.push(
8138 self.get_cell_value(sheet_name, r, c)
8139 .unwrap_or(LiteralValue::Empty),
8140 );
8141 }
8142 rows.push(rowv);
8143 }
8144 return Ok(RangeView::from_owned_rows(
8145 rows,
8146 self.config.date_system,
8147 ));
8148 }
8149 }
8150 let asheet = self
8151 .sheet_store()
8152 .sheet(sheet_name)
8153 .expect("Arrow sheet missing for named range");
8154 let sr0 = range_ref.start.coord.row() as usize;
8155 let sc0 = range_ref.start.coord.col() as usize;
8156 let er0 = range_ref.end.coord.row() as usize;
8157 let ec0 = range_ref.end.coord.col() as usize;
8158 let rv = asheet.range_view(sr0, sc0, er0, ec0);
8159 return Ok(rv);
8160 }
8161 NamedDefinition::Literal(v) => {
8162 return Ok(RangeView::from_owned_rows(
8163 vec![vec![v.clone()]],
8164 self.config.date_system,
8165 ));
8166 }
8167 NamedDefinition::Formula { .. } => {
8168 if let Some(value) = self.graph.get_value(named.vertex) {
8169 return Ok(RangeView::from_owned_rows(
8170 vec![vec![value]],
8171 self.config.date_system,
8172 ));
8173 }
8174 }
8175 }
8176 }
8177
8178 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
8179 let version = source
8180 .version
8181 .or_else(|| self.resolver.source_scalar_version(name));
8182 let v = self.resolve_source_scalar_cached(name, version)?;
8183 return Ok(RangeView::from_owned_rows(
8184 vec![vec![v]],
8185 self.config.date_system,
8186 ));
8187 }
8188
8189 let data = self.resolver.resolve_named_range_reference(name)?;
8190 Ok(RangeView::from_owned_rows(data, self.config.date_system))
8191 }
8192 ReferenceType::Table(tref) => {
8193 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
8194 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
8195 let asheet = self
8196 .sheet_store()
8197 .sheet(sheet_name)
8198 .expect("Arrow sheet missing for table reference");
8199
8200 let sr0 = table.range.start.coord.row() as usize;
8201 let sc0 = table.range.start.coord.col() as usize;
8202 let er0 = table.range.end.coord.row() as usize;
8203 let ec0 = table.range.end.coord.col() as usize;
8204
8205 let has_totals = table.totals_row;
8206 let has_headers = table.header_row;
8207 let data_sr = if has_headers {
8208 sr0.saturating_add(1)
8209 } else {
8210 sr0
8211 };
8212 let data_er = if has_totals {
8213 er0.saturating_sub(1)
8214 } else {
8215 er0
8216 };
8217
8218 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
8219 if sr > er || sc > ec {
8220 asheet.range_view(1, 1, 0, 0)
8221 } else {
8222 asheet.range_view(sr, sc, er, ec)
8223 }
8224 };
8225
8226 let av = match &tref.specifier {
8227 None => {
8228 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
8229 "Table reference without specifier is unsupported".to_string(),
8230 ));
8231 }
8232 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
8233 let Some(idx) = table.col_index(col) else {
8234 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
8235 "Column refers to unknown table column".to_string(),
8236 ));
8237 };
8238 let c0 = sc0 + idx;
8239 select(data_sr, c0, data_er, c0)
8240 }
8241 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
8242 start,
8243 end,
8244 )) => {
8245 let Some(si) = table.col_index(start) else {
8246 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
8247 "Column range refers to unknown column(s)".to_string(),
8248 ));
8249 };
8250 let Some(ei) = table.col_index(end) else {
8251 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
8252 "Column range refers to unknown column(s)".to_string(),
8253 ));
8254 };
8255 let (mut a, mut b) = (si, ei);
8256 if a > b {
8257 std::mem::swap(&mut a, &mut b);
8258 }
8259 let c_start = sc0 + a;
8260 let c_end = sc0 + b;
8261 select(data_sr, c_start, data_er, c_end)
8262 }
8263 Some(formualizer_parse::parser::TableSpecifier::All)
8264 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
8265 formualizer_parse::parser::SpecialItem::All,
8266 )) => select(sr0, sc0, er0, ec0),
8267 Some(formualizer_parse::parser::TableSpecifier::Data)
8268 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
8269 formualizer_parse::parser::SpecialItem::Data,
8270 )) => select(data_sr, sc0, data_er, ec0),
8271 Some(formualizer_parse::parser::TableSpecifier::Headers)
8272 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
8273 formualizer_parse::parser::SpecialItem::Headers,
8274 )) => {
8275 if !has_headers {
8276 asheet.range_view(1, 1, 0, 0)
8277 } else {
8278 select(sr0, sc0, sr0, ec0)
8279 }
8280 }
8281 Some(formualizer_parse::parser::TableSpecifier::Totals)
8282 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
8283 formualizer_parse::parser::SpecialItem::Totals,
8284 )) => {
8285 if !has_totals {
8286 asheet.range_view(1, 1, 0, 0)
8287 } else {
8288 select(er0, sc0, er0, ec0)
8289 }
8290 }
8291 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
8292 formualizer_parse::parser::SpecialItem::ThisRow,
8293 )) => {
8294 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
8295 "@ (This Row) requires table-aware context; not yet supported"
8296 .to_string(),
8297 ));
8298 }
8299 Some(formualizer_parse::parser::TableSpecifier::Row(_))
8300 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
8301 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
8302 "Complex structured references not yet supported".to_string(),
8303 ));
8304 }
8305 };
8306
8307 return Ok(av);
8308 }
8309
8310 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
8311 let version = source
8312 .version
8313 .or_else(|| self.resolver.source_table_version(&tref.name));
8314 let table = self.resolve_source_table_cached(&tref.name, version)?;
8315 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
8316 }
8317
8318 let boxed = self.resolve_range_like(&ReferenceType::Table(tref.clone()))?;
8320 let owned = boxed.materialise().into_owned();
8321 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
8322 }
8323 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
8324 Err(ExcelError::new(ExcelErrorKind::NImpl)
8325 .with_message("3D references are not yet supported".to_string()))
8326 }
8327 }
8328 }
8329
8330 fn resolve_cell_reference_value(
8331 &self,
8332 sheet: Option<&str>,
8333 row: u32,
8334 col: u32,
8335 current_sheet: &str,
8336 ) -> Result<LiteralValue, ExcelError> {
8337 let sheet_name = sheet.unwrap_or(current_sheet);
8338 if self.graph.sheet_id(sheet_name).is_none() {
8339 return Err(ExcelError::new(ExcelErrorKind::Ref));
8340 }
8341 Ok(self
8342 .get_cell_value(sheet_name, row, col)
8343 .unwrap_or(LiteralValue::Empty))
8344 }
8345
8346 fn build_criteria_mask(
8347 &self,
8348 view: &RangeView<'_>,
8349 col_in_view: usize,
8350 pred: &crate::args::CriteriaPredicate,
8351 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
8352 if view.dims().1 == 0 {
8353 return None;
8354 }
8355 let sheet_rows = view.sheet().nrows as usize;
8358 if sheet_rows == 0 || view.start_row() >= sheet_rows {
8359 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
8360 }
8361 compute_criteria_mask(view, col_in_view, pred)
8362 }
8363
8364 fn build_row_visibility_mask(
8365 &self,
8366 view: &RangeView<'_>,
8367 mode: VisibilityMaskMode,
8368 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
8369 self.build_row_visibility_mask_for_view(view, mode)
8370 }
8371}
8372
8373impl<R> Engine<R>
8374where
8375 R: EvaluationContext,
8376{
8377 fn clear_spill_projection_and_mirror(
8378 &mut self,
8379 anchor_vertex: VertexId,
8380 delta: Option<&mut DeltaCollector>,
8381 ) {
8382 let spill_cells = self
8383 .graph
8384 .spill_cells_for_anchor(anchor_vertex)
8385 .map(|cells| cells.to_vec())
8386 .unwrap_or_default();
8387 if spill_cells.is_empty() {
8388 return;
8389 }
8390
8391 if let Some(delta) = delta
8392 && delta.mode != DeltaMode::Off
8393 {
8394 let empty = LiteralValue::Empty;
8395 for cell in spill_cells.iter() {
8396 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8397 let old = self
8398 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8399 .unwrap_or(LiteralValue::Empty);
8400 if old != empty {
8401 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8402 }
8403 }
8404 }
8405
8406 self.graph.clear_spill_region(anchor_vertex);
8407
8408 if self.config.arrow_storage_enabled
8409 && self.config.delta_overlay_enabled
8410 && self.config.write_formula_overlay_enabled
8411 {
8412 let empty = LiteralValue::Empty;
8413 for cell in spill_cells.iter() {
8414 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8415 self.mirror_value_to_computed_overlay(
8416 &sheet_name,
8417 cell.coord.row() + 1,
8418 cell.coord.col() + 1,
8419 &empty,
8420 );
8421 }
8422 }
8423 }
8424
8425 fn commit_spill_and_mirror(
8427 &mut self,
8428 anchor_vertex: VertexId,
8429 targets: &[CellRef],
8430 rows: Vec<Vec<LiteralValue>>,
8431 delta: Option<&mut DeltaCollector>,
8432 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
8433 ) -> Result<(), ExcelError> {
8434 let prev_spill_cells = self
8435 .graph
8436 .spill_cells_for_anchor(anchor_vertex)
8437 .map(|cells| cells.to_vec())
8438 .unwrap_or_default();
8439
8440 if let Some(delta) = delta
8441 && delta.mode != DeltaMode::Off
8442 {
8443 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
8444 targets.iter().copied().collect();
8445 let empty = LiteralValue::Empty;
8446
8447 for cell in prev_spill_cells.iter() {
8449 if target_set.contains(cell) {
8450 continue;
8451 }
8452 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8453 let old = self
8454 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8455 .unwrap_or(LiteralValue::Empty);
8456 if old != empty {
8457 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8458 }
8459 }
8460
8461 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
8463 let width = rows[0].len();
8464 for (idx, cell) in targets.iter().enumerate() {
8465 let r_off = idx / width;
8466 let c_off = idx % width;
8467 let new = rows
8468 .get(r_off)
8469 .and_then(|r| r.get(c_off))
8470 .cloned()
8471 .unwrap_or(LiteralValue::Empty);
8472 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8473 let old = self
8474 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8475 .unwrap_or(LiteralValue::Empty);
8476 if old != new {
8477 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8478 }
8479 }
8480 } else {
8481 for cell in targets.iter() {
8483 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8484 let old = self
8485 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8486 .unwrap_or(LiteralValue::Empty);
8487 if !matches!(old, LiteralValue::Empty) {
8488 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8489 }
8490 }
8491 }
8492 }
8493
8494 let arrow_sheets = &self.arrow_sheets;
8497 self.spill_mgr.commit_array_with_value_probe(
8498 &mut self.graph,
8499 anchor_vertex,
8500 targets,
8501 rows.clone(),
8502 overwritable_formulas,
8503 |g, cell| {
8504 let sheet_name = g.sheet_name(cell.sheet_id);
8505 let asheet = arrow_sheets.sheet(sheet_name)?;
8506 let r0 = cell.coord.row() as usize;
8507 let c0 = cell.coord.col() as usize;
8508 let v = asheet.get_cell_value(r0, c0);
8509 if matches!(v, LiteralValue::Empty) {
8510 None
8511 } else {
8512 Some(v)
8513 }
8514 },
8515 )?;
8516
8517 if self.config.arrow_storage_enabled
8518 && self.config.delta_overlay_enabled
8519 && self.config.write_formula_overlay_enabled
8520 {
8521 if !prev_spill_cells.is_empty() {
8522 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
8523 targets.iter().copied().collect();
8524 let empty = LiteralValue::Empty;
8525 for cell in prev_spill_cells.iter() {
8526 if !target_set.contains(cell) {
8527 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8528 self.mirror_value_to_computed_overlay(
8529 &sheet_name,
8530 cell.coord.row() + 1,
8531 cell.coord.col() + 1,
8532 &empty,
8533 );
8534 }
8535 }
8536 }
8537
8538 for (idx, cell) in targets.iter().enumerate() {
8539 if rows.is_empty() || rows[0].is_empty() {
8540 break;
8541 }
8542 let width = rows[0].len();
8543 let r_off = idx / width;
8544 let c_off = idx % width;
8545 let v = rows[r_off][c_off].clone();
8546 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8547 self.mirror_value_to_computed_overlay(
8548 &sheet_name,
8549 cell.coord.row() + 1,
8550 cell.coord.col() + 1,
8551 &v,
8552 );
8553 }
8554 }
8555 Ok(())
8556 }
8557}
8558
8559use crate::engine::effects::Effect;
8564use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
8565
8566impl<R> Engine<R>
8567where
8568 R: EvaluationContext,
8569{
8570 pub(crate) fn plan_vertex_effects(
8577 &mut self,
8578 vertex_id: VertexId,
8579 computed_value: LiteralValue,
8580 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
8581 ) -> Result<Vec<Effect>, ExcelError> {
8582 let kind = self.graph.get_vertex_kind(vertex_id);
8583 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
8584
8585 if !is_formula {
8589 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
8590 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
8591 && owner != vertex_id
8592 {
8593 return Ok(Vec::new());
8594 }
8595 return Ok(vec![Effect::WriteCell {
8597 vertex_id,
8598 value: computed_value,
8599 }]);
8600 }
8601
8602 match computed_value {
8603 LiteralValue::Array(rows) => {
8604 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
8605 }
8606 other => self.plan_scalar_effects(vertex_id, other),
8607 }
8608 }
8609
8610 fn plan_scalar_effects(
8612 &self,
8613 vertex_id: VertexId,
8614 value: LiteralValue,
8615 ) -> Result<Vec<Effect>, ExcelError> {
8616 let has_spill = self
8617 .graph
8618 .spill_cells_for_anchor(vertex_id)
8619 .is_some_and(|c| !c.is_empty());
8620
8621 let mut effects = Vec::new();
8622 if has_spill {
8623 effects.push(Effect::SpillClear {
8624 anchor_vertex: vertex_id,
8625 });
8626 }
8627 effects.push(Effect::WriteCell { vertex_id, value });
8628 Ok(effects)
8629 }
8630
8631 fn plan_array_effects(
8633 &mut self,
8634 vertex_id: VertexId,
8635 rows: Vec<Vec<LiteralValue>>,
8636 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
8637 ) -> Result<Vec<Effect>, ExcelError> {
8638 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
8640
8641 let anchor = self
8642 .graph
8643 .get_cell_ref(vertex_id)
8644 .expect("cell ref for vertex");
8645 let sheet_id = anchor.sheet_id;
8646 let h = rows.len() as u32;
8647 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
8648
8649 let spill_cells = (h as u64).saturating_mul(w as u64);
8651 if spill_cells > self.config.spill.max_spill_cells as u64 {
8652 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
8653 }
8654
8655 const PACKED_MAX_ROW: u32 = 1_048_575;
8657 const PACKED_MAX_COL: u32 = 16_383;
8658 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
8659 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
8660 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
8661 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
8662 }
8663
8664 let mut targets = Vec::new();
8665 for r in 0..h {
8666 for c in 0..w {
8667 targets.push(self.graph.make_cell_ref_internal(
8668 sheet_id,
8669 anchor.coord.row() + r,
8670 anchor.coord.col() + c,
8671 ));
8672 }
8673 }
8674
8675 match self.spill_mgr.reserve(
8677 vertex_id,
8678 anchor,
8679 SpillShape { rows: h, cols: w },
8680 SpillMeta {
8681 epoch: self.recalc_epoch,
8682 config: self.config.spill,
8683 },
8684 ) {
8685 Ok(()) => {
8686 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
8688 vertex_id,
8689 &targets,
8690 overwritable_formulas,
8691 ) {
8692 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
8693 }
8694
8695 if !self.graph.value_cache_enabled() {
8699 let sheet_name = self.graph.sheet_name(sheet_id);
8700 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
8701 for cell in targets.iter() {
8702 if *cell == anchor {
8704 continue;
8705 }
8706 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
8708 continue;
8709 }
8710 if let Some(&vid) = self.graph.get_vertex_id_for_address(cell)
8712 && vid != vertex_id
8713 {
8714 match self.graph.get_vertex_kind(vid) {
8715 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
8716 continue;
8717 }
8718 _ => {}
8719 }
8720 }
8721
8722 let v = asheet.get_cell_value(
8723 cell.coord.row() as usize,
8724 cell.coord.col() as usize,
8725 );
8726 if !matches!(v, LiteralValue::Empty) {
8727 return self.plan_spill_error_effects(
8728 vertex_id,
8729 "BlockedByValue",
8730 h,
8731 w,
8732 );
8733 }
8734 }
8735 }
8736 }
8737
8738 let top_left = rows
8739 .first()
8740 .and_then(|r| r.first())
8741 .cloned()
8742 .unwrap_or(LiteralValue::Empty);
8743
8744 let mut effects = Vec::new();
8745 let has_prev = self
8747 .graph
8748 .spill_cells_for_anchor(vertex_id)
8749 .is_some_and(|c| !c.is_empty());
8750 if has_prev {
8751 effects.push(Effect::SpillClear {
8752 anchor_vertex: vertex_id,
8753 });
8754 }
8755 effects.push(Effect::SpillCommit {
8756 anchor_vertex: vertex_id,
8757 anchor_cell: anchor,
8758 target_cells: targets,
8759 values: rows,
8760 });
8761 effects.push(Effect::WriteCell {
8762 vertex_id,
8763 value: top_left,
8764 });
8765 Ok(effects)
8766 }
8767 Err(e) => {
8768 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
8769 self.plan_spill_error_effects(vertex_id, &msg, h, w)
8770 }
8771 }
8772 }
8773
8774 fn plan_spill_error_effects(
8776 &self,
8777 vertex_id: VertexId,
8778 message: &str,
8779 expected_rows: u32,
8780 expected_cols: u32,
8781 ) -> Result<Vec<Effect>, ExcelError> {
8782 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
8783 .with_message(message)
8784 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
8785 expected_rows,
8786 expected_cols,
8787 });
8788 let spill_val = LiteralValue::Error(spill_err);
8789
8790 let effects = vec![
8791 Effect::SpillClear {
8792 anchor_vertex: vertex_id,
8793 },
8794 Effect::WriteCell {
8795 vertex_id,
8796 value: spill_val,
8797 },
8798 ];
8799 Ok(effects)
8800 }
8801
8802 pub(crate) fn apply_effect(
8804 &mut self,
8805 effect: &Effect,
8806 delta: Option<&mut DeltaCollector>,
8807 log: Option<&mut ChangeLog>,
8808 ) -> Result<(), ExcelError> {
8809 self.apply_effect_with_computed_writes(effect, delta, log, None)
8810 }
8811
8812 fn apply_effect_with_computed_writes(
8813 &mut self,
8814 effect: &Effect,
8815 delta: Option<&mut DeltaCollector>,
8816 log: Option<&mut ChangeLog>,
8817 computed_writes: Option<&mut ComputedWriteBuffer>,
8818 ) -> Result<(), ExcelError> {
8819 match effect {
8820 Effect::WriteCell { vertex_id, value } => {
8821 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
8822 }
8823 Effect::SpillClear { anchor_vertex } => {
8824 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
8825 }
8826 Effect::SpillCommit {
8827 anchor_vertex,
8828 anchor_cell: _,
8829 target_cells,
8830 values,
8831 } => {
8832 self.apply_spill_commit(
8833 *anchor_vertex,
8834 target_cells,
8835 values.clone(),
8836 delta,
8837 log,
8838 computed_writes,
8839 )?;
8840 }
8841 }
8842 Ok(())
8843 }
8844
8845 fn apply_write_cell(
8847 &mut self,
8848 vertex_id: VertexId,
8849 value: &LiteralValue,
8850 delta: Option<&mut DeltaCollector>,
8851 mut computed_writes: Option<&mut ComputedWriteBuffer>,
8852 ) -> Result<(), ExcelError> {
8853 if let Some(d) = delta
8854 && d.mode != DeltaMode::Off
8855 {
8856 if let Some(buffer) = computed_writes.as_deref_mut() {
8857 self.flush_computed_write_buffer(buffer)?;
8858 }
8859 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
8860 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8861 let old = self
8862 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8863 .unwrap_or(LiteralValue::Empty);
8864 if old != *value {
8865 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8866 }
8867 }
8868 }
8869 self.graph.update_vertex_value(vertex_id, value.clone());
8870 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
8871 Ok(())
8872 }
8873
8874 fn apply_spill_clear(
8876 &mut self,
8877 anchor_vertex: VertexId,
8878 delta: Option<&mut DeltaCollector>,
8879 log: Option<&mut ChangeLog>,
8880 computed_writes: Option<&mut ComputedWriteBuffer>,
8881 ) -> Result<(), ExcelError> {
8882 if let Some(buffer) = computed_writes {
8883 self.flush_computed_write_buffer(buffer)?;
8884 }
8885
8886 let spill_cells = self
8887 .graph
8888 .spill_cells_for_anchor(anchor_vertex)
8889 .map(|cells| cells.to_vec())
8890 .unwrap_or_default();
8891 if spill_cells.is_empty() {
8892 return Ok(());
8893 }
8894
8895 let snapshot = if log.is_some() {
8897 self.snapshot_spill_for_anchor(anchor_vertex)
8898 } else {
8899 None
8900 };
8901
8902 if let Some(d) = delta
8904 && d.mode != DeltaMode::Off
8905 {
8906 let empty = LiteralValue::Empty;
8907 for cell in spill_cells.iter() {
8908 let sheet_name = self.graph.sheet_name(cell.sheet_id);
8909 let old = self
8910 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
8911 .unwrap_or(LiteralValue::Empty);
8912 if old != empty {
8913 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
8914 }
8915 }
8916 }
8917
8918 self.graph.clear_spill_region(anchor_vertex);
8919
8920 if self.config.arrow_storage_enabled
8922 && self.config.delta_overlay_enabled
8923 && self.config.write_formula_overlay_enabled
8924 {
8925 let empty = LiteralValue::Empty;
8926 for cell in spill_cells.iter() {
8927 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8928 self.mirror_value_to_computed_overlay(
8929 &sheet_name,
8930 cell.coord.row() + 1,
8931 cell.coord.col() + 1,
8932 &empty,
8933 );
8934 }
8935 }
8936
8937 if let Some(log) = log
8939 && let Some(old) = snapshot
8940 {
8941 log.record(ChangeEvent::SpillCleared {
8942 anchor: anchor_vertex,
8943 old,
8944 });
8945 }
8946 Ok(())
8947 }
8948
8949 fn apply_spill_commit(
8951 &mut self,
8952 anchor_vertex: VertexId,
8953 target_cells: &[CellRef],
8954 values: Vec<Vec<LiteralValue>>,
8955 delta: Option<&mut DeltaCollector>,
8956 log: Option<&mut ChangeLog>,
8957 computed_writes: Option<&mut ComputedWriteBuffer>,
8958 ) -> Result<(), ExcelError> {
8959 if let Some(buffer) = computed_writes {
8960 self.flush_computed_write_buffer(buffer)?;
8961 }
8962
8963 let old_snapshot = if log.is_some() {
8965 self.snapshot_spill_for_anchor(anchor_vertex)
8966 } else {
8967 None
8968 };
8969
8970 self.commit_spill_and_mirror(
8972 anchor_vertex,
8973 target_cells,
8974 values.clone(),
8975 delta,
8976 None, )?;
8978
8979 if let Some(log) = log {
8981 log.record(ChangeEvent::SpillCommitted {
8982 anchor: anchor_vertex,
8983 old: old_snapshot,
8984 new: SpillSnapshot {
8985 target_cells: target_cells.to_vec(),
8986 values,
8987 },
8988 });
8989 }
8990 Ok(())
8991 }
8992
8993 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
8998 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
8999 if cells.is_empty() {
9000 return None;
9001 }
9002
9003 let max = self.config.spill.max_spill_cells as usize;
9004 let mut cells = cells;
9005 if cells.len() > max {
9006 cells.truncate(max);
9007 }
9008
9009 let first = *cells.first().expect("non-empty spill cells");
9010 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
9011 let row0 = first.coord.row();
9012 let col0 = first.coord.col();
9013
9014 let mut max_row = row0;
9015 let mut max_col = col0;
9016 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
9017 for cell in &cells {
9018 max_row = max_row.max(cell.coord.row());
9019 max_col = max_col.max(cell.coord.col());
9020 let v = self
9021 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
9022 .unwrap_or(LiteralValue::Empty);
9023 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
9024 }
9025
9026 let rows = (max_row - row0 + 1) as usize;
9027 let cols = (max_col - col0 + 1) as usize;
9028 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
9029 for r in 0..rows {
9030 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
9031 for c in 0..cols {
9032 row.push(
9033 by_coord
9034 .get(&(row0 + r as u32, col0 + c as u32))
9035 .cloned()
9036 .unwrap_or(LiteralValue::Empty),
9037 );
9038 }
9039 values.push(row);
9040 }
9041
9042 Some(SpillSnapshot {
9043 target_cells: cells,
9044 values,
9045 })
9046 }
9047
9048 fn flush_before_range_dependent_vertex(
9049 &mut self,
9050 vertex_id: VertexId,
9051 computed_writes: &mut ComputedWriteBuffer,
9052 ) -> Result<(), ExcelError> {
9053 if self.graph.get_range_dependencies(vertex_id).is_some() {
9054 self.flush_computed_write_buffer(computed_writes)?;
9055 }
9056 Ok(())
9057 }
9058
9059 fn plan_vertex_effects_with_computed_flush(
9060 &mut self,
9061 vertex_id: VertexId,
9062 computed_value: LiteralValue,
9063 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
9064 computed_writes: &mut ComputedWriteBuffer,
9065 ) -> Result<Vec<Effect>, ExcelError> {
9066 if matches!(&computed_value, LiteralValue::Array(_)) {
9067 self.flush_computed_write_buffer(computed_writes)?;
9068 }
9069 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
9070 }
9071
9072 fn evaluate_small_layer_direct_effects(
9075 &mut self,
9076 layer: &super::scheduler::Layer,
9077 mut delta: Option<&mut DeltaCollector>,
9078 mut log: Option<&mut ChangeLog>,
9079 cancel_flag: Option<&AtomicBool>,
9080 cancel_check_every: usize,
9081 cancel_message: &'static str,
9082 ) -> Result<usize, ExcelError> {
9083 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
9084 if cancel_check_every > 0
9085 && i % cancel_check_every == 0
9086 && cancel_flag.is_some_and(|flag| flag.load(Ordering::Relaxed))
9087 {
9088 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9089 .with_message(cancel_message.to_string()));
9090 }
9091 let value = match self.evaluate_vertex_immutable(vertex_id) {
9092 Ok(v) => v,
9093 Err(e) => LiteralValue::Error(e),
9094 };
9095 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
9096 for effect in &effects {
9097 self.apply_effect_with_computed_writes(
9098 effect,
9099 delta.as_deref_mut(),
9100 log.as_deref_mut(),
9101 None,
9102 )?;
9103 }
9104 }
9105 Ok(layer.vertices.len())
9106 }
9107
9108 fn evaluate_layer_sequential_effects(
9110 &mut self,
9111 layer: &super::scheduler::Layer,
9112 ) -> Result<usize, ExcelError> {
9113 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
9114 return self.evaluate_small_layer_direct_effects(
9115 layer,
9116 None,
9117 None,
9118 None,
9119 0,
9120 "Evaluation cancelled within layer",
9121 );
9122 }
9123
9124 let mut computed_writes = ComputedWriteBuffer::default();
9125 for &vertex_id in &layer.vertices {
9126 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
9127 let value = match self.evaluate_vertex_immutable(vertex_id) {
9128 Ok(v) => v,
9129 Err(e) => LiteralValue::Error(e),
9130 };
9131 let effects = match self.plan_vertex_effects_with_computed_flush(
9132 vertex_id,
9133 value,
9134 None,
9135 &mut computed_writes,
9136 ) {
9137 Ok(effects) => effects,
9138 Err(e) => {
9139 self.flush_computed_write_buffer(&mut computed_writes)?;
9140 return Err(e);
9141 }
9142 };
9143 for effect in &effects {
9144 if let Err(e) = self.apply_effect_with_computed_writes(
9145 effect,
9146 None,
9147 None,
9148 Some(&mut computed_writes),
9149 ) {
9150 self.flush_computed_write_buffer(&mut computed_writes)?;
9151 return Err(e);
9152 }
9153 }
9154 }
9155 self.flush_computed_write_buffer(&mut computed_writes)?;
9156 Ok(layer.vertices.len())
9157 }
9158
9159 fn evaluate_layer_sequential_with_delta_effects(
9161 &mut self,
9162 layer: &super::scheduler::Layer,
9163 delta: &mut DeltaCollector,
9164 ) -> Result<usize, ExcelError> {
9165 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
9166 return self.evaluate_small_layer_direct_effects(
9167 layer,
9168 Some(delta),
9169 None,
9170 None,
9171 0,
9172 "Evaluation cancelled within layer",
9173 );
9174 }
9175
9176 let mut computed_writes = ComputedWriteBuffer::default();
9177 for &vertex_id in &layer.vertices {
9178 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
9179 let value = match self.evaluate_vertex_immutable(vertex_id) {
9180 Ok(v) => v,
9181 Err(e) => LiteralValue::Error(e),
9182 };
9183 let effects = match self.plan_vertex_effects_with_computed_flush(
9184 vertex_id,
9185 value,
9186 None,
9187 &mut computed_writes,
9188 ) {
9189 Ok(effects) => effects,
9190 Err(e) => {
9191 self.flush_computed_write_buffer(&mut computed_writes)?;
9192 return Err(e);
9193 }
9194 };
9195 for effect in &effects {
9196 if let Err(e) = self.apply_effect_with_computed_writes(
9197 effect,
9198 Some(delta),
9199 None,
9200 Some(&mut computed_writes),
9201 ) {
9202 self.flush_computed_write_buffer(&mut computed_writes)?;
9203 return Err(e);
9204 }
9205 }
9206 }
9207 self.flush_computed_write_buffer(&mut computed_writes)?;
9208 Ok(layer.vertices.len())
9209 }
9210
9211 fn evaluate_layer_sequential_cancellable_effects(
9213 &mut self,
9214 layer: &super::scheduler::Layer,
9215 cancel_flag: &AtomicBool,
9216 ) -> Result<usize, ExcelError> {
9217 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
9218 return self.evaluate_small_layer_direct_effects(
9219 layer,
9220 None,
9221 None,
9222 Some(cancel_flag),
9223 256,
9224 "Evaluation cancelled within layer",
9225 );
9226 }
9227
9228 let mut computed_writes = ComputedWriteBuffer::default();
9229 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
9230 if i % 256 == 0 && cancel_flag.load(Ordering::Relaxed) {
9231 self.flush_computed_write_buffer(&mut computed_writes)?;
9232 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9233 .with_message("Evaluation cancelled within layer".to_string()));
9234 }
9235 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
9236 let value = match self.evaluate_vertex_immutable(vertex_id) {
9237 Ok(v) => v,
9238 Err(e) => LiteralValue::Error(e),
9239 };
9240 let effects = match self.plan_vertex_effects_with_computed_flush(
9241 vertex_id,
9242 value,
9243 None,
9244 &mut computed_writes,
9245 ) {
9246 Ok(effects) => effects,
9247 Err(e) => {
9248 self.flush_computed_write_buffer(&mut computed_writes)?;
9249 return Err(e);
9250 }
9251 };
9252 for effect in &effects {
9253 if let Err(e) = self.apply_effect_with_computed_writes(
9254 effect,
9255 None,
9256 None,
9257 Some(&mut computed_writes),
9258 ) {
9259 self.flush_computed_write_buffer(&mut computed_writes)?;
9260 return Err(e);
9261 }
9262 }
9263 }
9264 self.flush_computed_write_buffer(&mut computed_writes)?;
9265 Ok(layer.vertices.len())
9266 }
9267
9268 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
9270 &mut self,
9271 layer: &super::scheduler::Layer,
9272 cancel_flag: &AtomicBool,
9273 ) -> Result<usize, ExcelError> {
9274 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
9275 return self.evaluate_small_layer_direct_effects(
9276 layer,
9277 None,
9278 None,
9279 Some(cancel_flag),
9280 128,
9281 "Demand-driven evaluation cancelled within layer",
9282 );
9283 }
9284
9285 let mut computed_writes = ComputedWriteBuffer::default();
9286 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
9287 if i % 128 == 0 && cancel_flag.load(Ordering::Relaxed) {
9288 self.flush_computed_write_buffer(&mut computed_writes)?;
9289 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9290 .with_message("Demand-driven evaluation cancelled within layer".to_string()));
9291 }
9292 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
9293 let value = match self.evaluate_vertex_immutable(vertex_id) {
9294 Ok(v) => v,
9295 Err(e) => LiteralValue::Error(e),
9296 };
9297 let effects = match self.plan_vertex_effects_with_computed_flush(
9298 vertex_id,
9299 value,
9300 None,
9301 &mut computed_writes,
9302 ) {
9303 Ok(effects) => effects,
9304 Err(e) => {
9305 self.flush_computed_write_buffer(&mut computed_writes)?;
9306 return Err(e);
9307 }
9308 };
9309 for effect in &effects {
9310 if let Err(e) = self.apply_effect_with_computed_writes(
9311 effect,
9312 None,
9313 None,
9314 Some(&mut computed_writes),
9315 ) {
9316 self.flush_computed_write_buffer(&mut computed_writes)?;
9317 return Err(e);
9318 }
9319 }
9320 }
9321 self.flush_computed_write_buffer(&mut computed_writes)?;
9322 Ok(layer.vertices.len())
9323 }
9324
9325 fn evaluate_layer_parallel_effects(
9327 &mut self,
9328 layer: &super::scheduler::Layer,
9329 ) -> Result<usize, ExcelError> {
9330 use rayon::prelude::*;
9331
9332 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
9333
9334 let mut phase1: Vec<VertexId> = Vec::new();
9335 let mut phase2: Vec<VertexId> = Vec::new();
9336 for &vid in &layer.vertices {
9337 if self.graph.get_range_dependencies(vid).is_some() {
9338 phase2.push(vid);
9339 } else {
9340 phase1.push(vid);
9341 }
9342 }
9343
9344 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
9345 let mut applied = 0usize;
9346
9347 for group in [&phase1[..], &phase2[..]] {
9348 if group.is_empty() {
9349 continue;
9350 }
9351 let mut computed_writes = ComputedWriteBuffer::default();
9352
9353 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
9354 thread_pool.install(|| {
9355 group
9356 .par_iter()
9357 .map(
9358 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
9359 Ok(v) => Ok((vertex_id, v)),
9360 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
9361 },
9362 )
9363 .collect()
9364 });
9365
9366 match results {
9367 Ok(vertex_results) => {
9368 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
9371 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
9372 for (vertex_id, result) in vertex_results {
9373 if matches!(result, LiteralValue::Array(_)) {
9374 arrays.push((vertex_id, result));
9375 } else {
9376 others.push((vertex_id, result));
9377 }
9378 }
9379 for (vertex_id, result) in arrays {
9380 let effects = match self.plan_vertex_effects_with_computed_flush(
9381 vertex_id,
9382 result,
9383 Some(&inflight),
9384 &mut computed_writes,
9385 ) {
9386 Ok(effects) => effects,
9387 Err(e) => {
9388 self.flush_computed_write_buffer(&mut computed_writes)?;
9389 return Err(e);
9390 }
9391 };
9392 for effect in &effects {
9393 if let Err(e) = self.apply_effect_with_computed_writes(
9394 effect,
9395 None,
9396 None,
9397 Some(&mut computed_writes),
9398 ) {
9399 self.flush_computed_write_buffer(&mut computed_writes)?;
9400 return Err(e);
9401 }
9402 }
9403 applied = applied.saturating_add(1);
9404 }
9405 self.flush_computed_write_buffer(&mut computed_writes)?;
9407 for (vertex_id, result) in others {
9408 let effects = match self.plan_vertex_effects_with_computed_flush(
9409 vertex_id,
9410 result,
9411 Some(&inflight),
9412 &mut computed_writes,
9413 ) {
9414 Ok(effects) => effects,
9415 Err(e) => {
9416 self.flush_computed_write_buffer(&mut computed_writes)?;
9417 return Err(e);
9418 }
9419 };
9420 for effect in &effects {
9421 if let Err(e) = self.apply_effect_with_computed_writes(
9422 effect,
9423 None,
9424 None,
9425 Some(&mut computed_writes),
9426 ) {
9427 self.flush_computed_write_buffer(&mut computed_writes)?;
9428 return Err(e);
9429 }
9430 }
9431 applied = applied.saturating_add(1);
9432 }
9433 self.flush_computed_write_buffer(&mut computed_writes)?;
9435 }
9436 Err(e) => {
9437 self.flush_computed_write_buffer(&mut computed_writes)?;
9438 return Err(e);
9439 }
9440 }
9441 }
9442
9443 Ok(applied)
9444 }
9445
9446 fn evaluate_layer_parallel_with_delta_effects(
9448 &mut self,
9449 layer: &super::scheduler::Layer,
9450 delta: &mut DeltaCollector,
9451 ) -> Result<usize, ExcelError> {
9452 use rayon::prelude::*;
9453
9454 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
9455
9456 let mut phase1: Vec<VertexId> = Vec::new();
9457 let mut phase2: Vec<VertexId> = Vec::new();
9458 for &vid in &layer.vertices {
9459 if self.graph.get_range_dependencies(vid).is_some() {
9460 phase2.push(vid);
9461 } else {
9462 phase1.push(vid);
9463 }
9464 }
9465
9466 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
9467 let mut applied = 0usize;
9468
9469 for group in [&phase1[..], &phase2[..]] {
9470 if group.is_empty() {
9471 continue;
9472 }
9473 let mut computed_writes = ComputedWriteBuffer::default();
9474 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
9475 thread_pool.install(|| {
9476 group
9477 .par_iter()
9478 .map(
9479 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
9480 Ok(v) => Ok((vertex_id, v)),
9481 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
9482 },
9483 )
9484 .collect()
9485 });
9486
9487 match results {
9488 Ok(vertex_results) => {
9489 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
9490 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
9491 for (vertex_id, result) in vertex_results {
9492 if matches!(result, LiteralValue::Array(_)) {
9493 arrays.push((vertex_id, result));
9494 } else {
9495 others.push((vertex_id, result));
9496 }
9497 }
9498 for (vertex_id, result) in arrays {
9499 let effects = match self.plan_vertex_effects_with_computed_flush(
9500 vertex_id,
9501 result,
9502 Some(&inflight),
9503 &mut computed_writes,
9504 ) {
9505 Ok(effects) => effects,
9506 Err(e) => {
9507 self.flush_computed_write_buffer(&mut computed_writes)?;
9508 return Err(e);
9509 }
9510 };
9511 for effect in &effects {
9512 if let Err(e) = self.apply_effect_with_computed_writes(
9513 effect,
9514 Some(delta),
9515 None,
9516 Some(&mut computed_writes),
9517 ) {
9518 self.flush_computed_write_buffer(&mut computed_writes)?;
9519 return Err(e);
9520 }
9521 }
9522 applied = applied.saturating_add(1);
9523 }
9524 self.flush_computed_write_buffer(&mut computed_writes)?;
9525 for (vertex_id, result) in others {
9526 let effects = match self.plan_vertex_effects_with_computed_flush(
9527 vertex_id,
9528 result,
9529 Some(&inflight),
9530 &mut computed_writes,
9531 ) {
9532 Ok(effects) => effects,
9533 Err(e) => {
9534 self.flush_computed_write_buffer(&mut computed_writes)?;
9535 return Err(e);
9536 }
9537 };
9538 for effect in &effects {
9539 if let Err(e) = self.apply_effect_with_computed_writes(
9540 effect,
9541 Some(delta),
9542 None,
9543 Some(&mut computed_writes),
9544 ) {
9545 self.flush_computed_write_buffer(&mut computed_writes)?;
9546 return Err(e);
9547 }
9548 }
9549 applied = applied.saturating_add(1);
9550 }
9551 self.flush_computed_write_buffer(&mut computed_writes)?;
9552 }
9553 Err(e) => {
9554 self.flush_computed_write_buffer(&mut computed_writes)?;
9555 return Err(e);
9556 }
9557 }
9558 }
9559
9560 Ok(applied)
9561 }
9562
9563 fn evaluate_layer_parallel_cancellable_effects(
9565 &mut self,
9566 layer: &super::scheduler::Layer,
9567 cancel_flag: &AtomicBool,
9568 ) -> Result<usize, ExcelError> {
9569 use rayon::prelude::*;
9570
9571 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
9572
9573 if cancel_flag.load(Ordering::Relaxed) {
9574 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9575 .with_message("Parallel evaluation cancelled before starting".to_string()));
9576 }
9577
9578 let mut phase1: Vec<VertexId> = Vec::new();
9579 let mut phase2: Vec<VertexId> = Vec::new();
9580 for &vid in &layer.vertices {
9581 if self.graph.get_range_dependencies(vid).is_some() {
9582 phase2.push(vid);
9583 } else {
9584 phase1.push(vid);
9585 }
9586 }
9587
9588 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
9589 let mut applied = 0usize;
9590
9591 for group in [&phase1[..], &phase2[..]] {
9592 if group.is_empty() {
9593 continue;
9594 }
9595 let mut computed_writes = ComputedWriteBuffer::default();
9596
9597 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
9598 thread_pool.install(|| {
9599 group
9600 .par_iter()
9601 .map(|&vertex_id| {
9602 if cancel_flag.load(Ordering::Relaxed) {
9603 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9604 .with_message(
9605 "Parallel evaluation cancelled during execution"
9606 .to_string(),
9607 ));
9608 }
9609 match self.evaluate_vertex_immutable(vertex_id) {
9610 Ok(v) => Ok((vertex_id, v)),
9611 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
9612 }
9613 })
9614 .collect()
9615 });
9616
9617 match results {
9618 Ok(vertex_results) => {
9619 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
9620 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
9621 for (vertex_id, result) in vertex_results {
9622 if matches!(result, LiteralValue::Array(_)) {
9623 arrays.push((vertex_id, result));
9624 } else {
9625 others.push((vertex_id, result));
9626 }
9627 }
9628 for (vertex_id, result) in arrays {
9629 let effects = match self.plan_vertex_effects_with_computed_flush(
9630 vertex_id,
9631 result,
9632 Some(&inflight),
9633 &mut computed_writes,
9634 ) {
9635 Ok(effects) => effects,
9636 Err(e) => {
9637 self.flush_computed_write_buffer(&mut computed_writes)?;
9638 return Err(e);
9639 }
9640 };
9641 for effect in &effects {
9642 if let Err(e) = self.apply_effect_with_computed_writes(
9643 effect,
9644 None,
9645 None,
9646 Some(&mut computed_writes),
9647 ) {
9648 self.flush_computed_write_buffer(&mut computed_writes)?;
9649 return Err(e);
9650 }
9651 }
9652 applied = applied.saturating_add(1);
9653 }
9654 self.flush_computed_write_buffer(&mut computed_writes)?;
9655 for (vertex_id, result) in others {
9656 let effects = match self.plan_vertex_effects_with_computed_flush(
9657 vertex_id,
9658 result,
9659 Some(&inflight),
9660 &mut computed_writes,
9661 ) {
9662 Ok(effects) => effects,
9663 Err(e) => {
9664 self.flush_computed_write_buffer(&mut computed_writes)?;
9665 return Err(e);
9666 }
9667 };
9668 for effect in &effects {
9669 if let Err(e) = self.apply_effect_with_computed_writes(
9670 effect,
9671 None,
9672 None,
9673 Some(&mut computed_writes),
9674 ) {
9675 self.flush_computed_write_buffer(&mut computed_writes)?;
9676 return Err(e);
9677 }
9678 }
9679 applied = applied.saturating_add(1);
9680 }
9681 self.flush_computed_write_buffer(&mut computed_writes)?;
9682 }
9683 Err(e) => {
9684 self.flush_computed_write_buffer(&mut computed_writes)?;
9685 return Err(e);
9686 }
9687 }
9688 }
9689
9690 Ok(applied)
9691 }
9692
9693 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
9700 let _source_cache = self.source_cache_session();
9701 self.validate_deterministic_mode()?;
9702 if self.config.defer_graph_building {
9703 self.build_graph_all()?;
9704 }
9705 self.reset_virtual_dep_telemetry_if_disabled();
9706 let start = crate::instant::FzInstant::now();
9707 let mut computed_vertices = 0;
9708 let mut cycle_errors = 0;
9709
9710 let mut replan_iterations = 0;
9711 const MAX_REPLAN: usize = 5;
9712 let mut telemetry = self
9713 .config
9714 .enable_virtual_dep_telemetry
9715 .then(|| self.start_virtual_dep_telemetry());
9716
9717 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
9718
9719 loop {
9720 let to_evaluate = self.graph.get_evaluation_vertices();
9721 if to_evaluate.is_empty() {
9722 if let Some(t) = telemetry.as_mut()
9723 && t.bailout_reason.is_none()
9724 {
9725 t.bailout_reason = Some("no_work");
9726 }
9727 break;
9728 }
9729
9730 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9731 if let Some(t) = telemetry.as_mut() {
9732 Self::accumulate_schedule_meta(t, &meta);
9733 }
9734
9735 let circ_error = LiteralValue::Error(
9737 ExcelError::new(ExcelErrorKind::Circ)
9738 .with_message("Circular dependency detected".to_string()),
9739 );
9740 for cycle in &schedule.cycles {
9741 cycle_errors += 1;
9742 for &vertex_id in cycle {
9743 self.graph
9744 .update_vertex_value(vertex_id, circ_error.clone());
9745 self.mirror_vertex_value_to_overlay(vertex_id, &circ_error);
9746 }
9747 }
9748
9749 for layer in &schedule.layers {
9751 computed_vertices += self.evaluate_layer_logged(layer, log)?;
9752 }
9753
9754 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9755 if let Some(t) = telemetry.as_mut() {
9756 t.changed_vdeps_total += changed_vertices.len();
9757 }
9758 self.graph.clear_dirty_flags(&to_evaluate);
9759 for v in &changed_vertices {
9760 self.graph.set_dirty(*v, true);
9761 }
9762
9763 if changed_vertices.is_empty() {
9764 if let Some(t) = telemetry.as_mut() {
9765 t.bailout_reason = Some("converged");
9766 }
9767 break;
9768 }
9769 if replan_iterations >= MAX_REPLAN {
9770 if let Some(t) = telemetry.as_mut() {
9771 t.bailout_reason = Some("max_replan");
9772 }
9773 break;
9774 }
9775 replan_iterations += 1;
9776 }
9777
9778 if let Some(mut t) = telemetry {
9779 t.replan_iterations = replan_iterations;
9780 self.last_virtual_dep_telemetry = t;
9781 }
9782
9783 log.end_compound();
9784
9785 self.graph.redirty_volatiles();
9786 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9787
9788 Ok(EvalResult {
9789 computed_vertices,
9790 cycle_errors,
9791 elapsed: start.elapsed(),
9792 })
9793 }
9794
9795 fn evaluate_layer_logged(
9797 &mut self,
9798 layer: &super::scheduler::Layer,
9799 log: &mut ChangeLog,
9800 ) -> Result<usize, ExcelError> {
9801 let mut computed_writes = ComputedWriteBuffer::default();
9802 for &vertex_id in &layer.vertices {
9803 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
9804 let value = match self.evaluate_vertex_immutable(vertex_id) {
9805 Ok(v) => v,
9806 Err(e) => LiteralValue::Error(e),
9807 };
9808 let effects = match self.plan_vertex_effects_with_computed_flush(
9809 vertex_id,
9810 value,
9811 None,
9812 &mut computed_writes,
9813 ) {
9814 Ok(effects) => effects,
9815 Err(e) => {
9816 self.flush_computed_write_buffer(&mut computed_writes)?;
9817 return Err(e);
9818 }
9819 };
9820 for effect in &effects {
9821 if let Err(e) = self.apply_effect_with_computed_writes(
9822 effect,
9823 None,
9824 Some(log),
9825 Some(&mut computed_writes),
9826 ) {
9827 self.flush_computed_write_buffer(&mut computed_writes)?;
9828 return Err(e);
9829 }
9830 }
9831 }
9832 self.flush_computed_write_buffer(&mut computed_writes)?;
9833 Ok(layer.vertices.len())
9834 }
9835}