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