1use crate::SheetId;
2use crate::engine::TombstoneRegistry;
3use crate::engine::named_range::{NameScope, NamedDefinition, NamedRange};
4use crate::engine::sheet_registry::SheetRegistry;
5use crate::formula_plane::authority::FormulaAuthority;
6use formualizer_common::{
7 CoordBuildHasher, ExcelError, ExcelErrorKind, LiteralValue, PackedSheetCell,
8};
9use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
10use rustc_hash::{FxHashMap, FxHashSet};
11
12#[cfg(debug_assertions)]
13use std::sync::atomic::{AtomicU64, Ordering};
14
15#[cfg(test)]
16#[derive(Debug, Default, Clone)]
17pub struct GraphInstrumentation {
18 pub edges_added: u64,
19 pub stripe_inserts: u64,
20 pub stripe_removes: u64,
21 pub dependents_scan_fallback_calls: u64,
22 pub dependents_scan_vertices_scanned: u64,
23}
24
25mod ast_utils;
26pub mod editor;
27mod formula_analysis;
28#[cfg(test)]
29mod formula_analysis_legacy_tests;
30mod formula_dirty;
31mod names;
32pub(crate) mod prepared_legacy_graph;
33mod range_deps;
34pub(crate) use range_deps::{StructuralEdit, StructuralOccupancy};
35
36mod sheets;
37pub mod snapshot;
38mod sources;
39mod tables;
40pub(crate) use tables::TableEntry;
41
42use super::addr::{GridAddr, SymbolAddr, VertexAddr};
43use super::arena::{AstNodeId, DataStore, ValueRef};
44use super::delta_edges::CsrMutableEdges;
45use super::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
46use super::sheet_index::SheetIndex;
47use super::vertex::{VertexId, VertexKind};
48use super::vertex_store::{FIRST_NORMAL_VERTEX, VertexStore};
49use crate::engine::topo::{
50 GraphAdapter,
51 pk::{DynamicTopo, PkConfig},
52};
53use crate::reference::{CellRef, Coord, SharedRangeRef, SharedRef, SharedSheetLocator};
54use formualizer_common::Coord as AbsCoord;
55use formula_dirty::FormulaDirtyState;
56pub(crate) use formula_dirty::{
57 FormulaDirtyEventSnapshot, FormulaDirtyLease, FormulaDirtyStats, FormulaDirtySublease,
58 WholeSpanDirtyReason,
59};
60struct RegistryFunctionProvider;
63
64impl crate::traits::FunctionProvider for RegistryFunctionProvider {
65 fn planning_semantic_revision(&self) -> Option<u64> {
66 Some(0)
67 }
68
69 fn get_function(
70 &self,
71 ns: &str,
72 name: &str,
73 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
74 crate::function_registry::get(ns, name)
75 }
76
77 fn get_function_for_planning(
78 &self,
79 ns: &str,
80 name: &str,
81 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
82 crate::function_registry::get_for_planning(ns, name)
83 }
84}
85
86#[inline]
87fn normalize_stored_literal(value: LiteralValue) -> LiteralValue {
88 match value {
89 LiteralValue::Int(i) => LiteralValue::Number(i as f64),
91 other => other,
92 }
93}
94
95pub use editor::change_log::{ChangeEvent, ChangeLog};
96
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub enum DependencyRef {
102 Cell(VertexId),
104 Range {
106 sheet: String,
107 start_row: u32,
108 start_col: u32,
109 end_row: u32, end_col: u32, },
112 WholeColumn { sheet: String, col: u32 },
114 WholeRow { sheet: String, row: u32 },
116}
117
118#[derive(Debug, Clone, Hash, PartialEq, Eq)]
120pub struct StripeKey {
121 pub sheet_id: SheetId,
122 pub stripe_type: StripeType,
123 pub index: u32, }
125
126#[derive(Debug, Clone, Hash, PartialEq, Eq)]
127pub enum StripeType {
128 Row,
129 Column,
130 Block, }
132
133const BLOCK_H: u32 = 256;
135const BLOCK_W: u32 = 256;
136
137pub fn block_index(row: u32, col: u32) -> u32 {
138 (row / BLOCK_H) << 16 | (col / BLOCK_W)
139}
140
141#[derive(Debug, Clone)]
144pub struct OperationSummary {
145 pub affected_vertices: Vec<VertexId>,
147 pub created_placeholders: Vec<CellRef>,
149}
150
151#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
156pub struct GraphBaselineStats {
157 pub graph_vertex_count: usize,
158 pub graph_formula_vertex_count: usize,
159 pub graph_edge_count: usize,
160 pub dirty_vertex_count: usize,
161 pub evaluation_vertex_count: usize,
162 pub formula_ast_root_count: usize,
163 pub formula_ast_node_count: usize,
164}
165
166#[derive(Debug)]
168pub struct DependencyGraph {
169 store: VertexStore,
171
172 edges: CsrMutableEdges,
174
175 data_store: DataStore,
177 vertex_values: FxHashMap<VertexId, ValueRef>,
178 vertex_formulas: FxHashMap<VertexId, AstNodeId>,
179
180 value_cache_enabled: bool,
185
186 #[cfg(debug_assertions)]
189 graph_value_read_attempts: AtomicU64,
190
191 cell_to_vertex: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
195 load_packed_to_vertex: std::collections::HashMap<PackedSheetCell, VertexId, CoordBuildHasher>,
196
197 formula_dirty: FormulaDirtyState,
200 volatile_vertices: FxHashSet<VertexId>,
201
202 dirty_propagation_visits: u64,
207
208 deferred_dirty_depth: u32,
214 deferred_dirty_pending: Vec<VertexId>,
216
217 ref_error_vertices: FxHashSet<VertexId>,
223
224 formula_to_range_deps: FxHashMap<VertexId, Vec<SharedRangeRef<'static>>>,
227
228 stripe_to_dependents: FxHashMap<StripeKey, FxHashSet<VertexId>>,
231
232 sheet_indexes: FxHashMap<SheetId, SheetIndex>,
235
236 sheet_reg: SheetRegistry,
238 default_sheet_id: SheetId,
239
240 named_ranges: FxHashMap<String, NamedRange>,
243
244 named_ranges_lookup: FxHashMap<String, String>,
249
250 sheet_named_ranges: FxHashMap<(SheetId, String), NamedRange>,
252
253 sheet_named_ranges_lookup: FxHashMap<(SheetId, String), String>,
258
259 vertex_to_names: FxHashMap<VertexId, Vec<VertexId>>,
261
262 name_vertex_lookup: FxHashMap<VertexId, (NameScope, String)>,
264
265 pending_name_links: FxHashMap<String, FxHashSet<(SheetId, VertexId)>>,
270
271 vertex_to_pending_names: FxHashMap<VertexId, FxHashSet<String>>,
274
275 tables: FxHashMap<String, tables::TableEntry>,
277 tables_lookup: FxHashMap<String, String>,
279 table_vertex_lookup: FxHashMap<VertexId, String>,
280
281 source_scalars: FxHashMap<String, sources::SourceScalarEntry>,
283 source_tables: FxHashMap<String, sources::SourceTableEntry>,
284 source_vertex_lookup: FxHashMap<VertexId, String>,
285
286 symbol_vertex_seq: u32,
292
293 cell_to_name_dependents: FxHashMap<VertexId, FxHashSet<VertexId>>,
295 name_to_cell_dependencies: FxHashMap<VertexId, Vec<VertexId>>,
297
298 config: super::EvalConfig,
300 topology_revision: u64,
302 symbol_revision: u64,
304
305 formula_authority: FormulaAuthority,
307
308 pk_order: Option<DynamicTopo<VertexId>>,
310
311 spill_anchor_to_cells: FxHashMap<VertexId, Vec<CellRef>>,
315 spill_cell_to_anchor: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
316 spill_cells_by_sheet: FxHashMap<SheetId, std::collections::BTreeMap<(u32, u32), VertexId>>,
317
318 admission_budget_override: Option<crate::engine::EvaluationBudgets>,
320
321 first_load_assume_new: bool,
323 ensure_touched_sheets: FxHashSet<SheetId>,
324
325 pub tombstone_registry: TombstoneRegistry,
327
328 #[cfg(test)]
329 instr: std::sync::Mutex<GraphInstrumentation>,
330 #[cfg(test)]
331 prepared_legacy_graph_failure_for_test: bool,
332}
333
334impl Default for DependencyGraph {
335 fn default() -> Self {
336 Self::new()
337 }
338}
339
340impl DependencyGraph {
341 pub fn range_expansion_limit(&self) -> usize {
343 self.config.range_expansion_limit
344 }
345
346 pub fn get_config(&self) -> &super::EvalConfig {
347 &self.config
348 }
349
350 pub(crate) fn formula_authority(&self) -> &FormulaAuthority {
351 &self.formula_authority
352 }
353
354 pub(crate) fn formula_authority_mut(&mut self) -> &mut FormulaAuthority {
355 &mut self.formula_authority
356 }
357
358 pub(crate) fn mark_formula_region_dirty(
359 &mut self,
360 region: crate::formula_plane::region_index::Region,
361 ) {
362 self.formula_dirty.record_region(region);
363 }
364
365 pub(crate) fn mark_formula_span_region_dirty(
366 &mut self,
367 span_ref: crate::formula_plane::runtime::FormulaSpanRef,
368 region: crate::formula_plane::region_index::Region,
369 ) {
370 self.formula_dirty.record_span_region(span_ref, region);
371 }
372
373 pub(crate) fn mark_formula_spans_dirty(
374 &mut self,
375 spans: impl IntoIterator<Item = crate::formula_plane::runtime::FormulaSpanRef>,
376 reason: WholeSpanDirtyReason,
377 ) {
378 self.formula_dirty.record_whole_spans(spans, reason);
379 }
380
381 pub(crate) fn mark_all_formula_spans_dirty(&mut self, reason: WholeSpanDirtyReason) {
382 let spans = self.formula_authority.active_span_refs();
383 self.formula_dirty.record_whole_spans(spans, reason);
384 }
385
386 pub(crate) fn lease_formula_dirty(&mut self) -> FormulaDirtyLease {
387 self.formula_dirty.lease()
388 }
389
390 pub(crate) fn extend_formula_dirty_lease(
391 &mut self,
392 lease: FormulaDirtyLease,
393 ) -> Option<FormulaDirtyLease> {
394 self.formula_dirty.extend(lease)
395 }
396
397 pub(crate) fn ack_formula_dirty(&mut self, lease: FormulaDirtyLease) -> bool {
398 self.formula_dirty.ack(lease)
399 }
400
401 pub(crate) fn ack_formula_dirty_sublease(&mut self, sublease: FormulaDirtySublease) -> bool {
402 self.formula_dirty.ack_sublease(sublease)
403 }
404
405 pub(crate) fn release_formula_dirty_lease(&mut self, lease: FormulaDirtyLease) -> bool {
406 self.formula_dirty.release(lease)
407 }
408
409 pub(crate) fn pending_formula_dirty_regions(
410 &self,
411 ) -> impl Iterator<Item = crate::formula_plane::region_index::Region> + '_ {
412 self.formula_dirty.pending_regions()
413 }
414
415 pub(crate) fn pending_formula_dirty_span_regions(
416 &self,
417 ) -> impl Iterator<
418 Item = (
419 crate::formula_plane::runtime::FormulaSpanRef,
420 crate::formula_plane::region_index::Region,
421 ),
422 > + '_ {
423 self.formula_dirty.pending_span_regions()
424 }
425
426 pub(crate) fn pending_formula_dirty_whole_spans(
427 &self,
428 ) -> impl Iterator<Item = crate::formula_plane::runtime::FormulaSpanRef> + '_ {
429 self.formula_dirty.pending_whole_spans()
430 }
431
432 pub(crate) fn pending_formula_dirty_event_count(&self) -> usize {
433 self.formula_dirty.pending_event_count()
434 }
435
436 pub(crate) fn formula_dirty_stats(&self) -> FormulaDirtyStats {
437 self.formula_dirty.stats()
438 }
439
440 pub(crate) fn clear_formula_vertex_dirty(&mut self, vertex_id: VertexId) {
441 self.store.set_dirty(vertex_id, false);
442 self.formula_dirty.legacy_remove(&vertex_id);
443 }
444
445 pub fn baseline_stats(&self) -> GraphBaselineStats {
447 let data_stats = self.data_store.memory_usage();
448 GraphBaselineStats {
449 graph_vertex_count: self.store.len(),
450 graph_formula_vertex_count: self.vertex_formulas.len(),
451 graph_edge_count: self.edges.num_edges_exact(),
452 dirty_vertex_count: self.formula_dirty.legacy_len(),
453 evaluation_vertex_count: self.get_evaluation_vertices().len(),
454 formula_ast_root_count: self.vertex_formulas.len(),
455 formula_ast_node_count: data_stats.total_ast_nodes,
456 }
457 }
458
459 #[inline]
460 pub(crate) fn value_cache_enabled(&self) -> bool {
461 self.value_cache_enabled
462 }
463
464 #[cfg(test)]
468 pub fn debug_graph_value_read_attempts(&self) -> u64 {
469 #[cfg(debug_assertions)]
470 {
471 self.graph_value_read_attempts.load(Ordering::Relaxed)
472 }
473 #[cfg(not(debug_assertions))]
474 {
475 0
476 }
477 }
478
479 pub fn plan_dependencies<'a, I>(
481 &mut self,
482 items: I,
483 policy: &formualizer_parse::parser::CollectPolicy,
484 volatile: Option<&[bool]>,
485 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
486 where
487 I: IntoIterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
488 {
489 crate::engine::plan::build_dependency_plan(
490 &mut self.sheet_reg,
491 items.into_iter(),
492 policy,
493 volatile,
494 )
495 }
496
497 pub fn plan_dependencies_mixed<'a, I>(
498 &mut self,
499 items: I,
500 policy: &formualizer_parse::parser::CollectPolicy,
501 volatile: Option<&[bool]>,
502 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
503 where
504 I: IntoIterator<
505 Item = (
506 &'a str,
507 u32,
508 u32,
509 crate::engine::plan::DependencyPlanAst<'a>,
510 ),
511 >,
512 {
513 crate::engine::plan::build_dependency_plan_mixed(
514 &mut self.sheet_reg,
515 &self.data_store,
516 items.into_iter(),
517 policy,
518 volatile,
519 )
520 }
521
522 pub fn ensure_vertices_batch(
525 &mut self,
526 coords: &[(SheetId, AbsCoord)],
527 ) -> Vec<(VertexAddr, u32)> {
528 self.ensure_vertices_batch_ordered(coords).1
529 }
530
531 pub fn ensure_vertices_batch_packed_ordered(
535 &mut self,
536 packed_cells: &[PackedSheetCell],
537 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
538 #[cfg(feature = "perf_instrumentation")]
539 use crate::instant::FzInstant as PerfInstant;
540 use rustc_hash::FxHashMap;
541
542 #[cfg(feature = "perf_instrumentation")]
543 let debug = std::env::var("FZ_DEBUG_LOAD")
544 .ok()
545 .is_some_and(|v| v != "0");
546 #[cfg(feature = "perf_instrumentation")]
547 let t0 = PerfInstant::now();
548
549 let mut ordered: Vec<Option<VertexId>> = vec![None; packed_cells.len()];
550 if packed_cells.is_empty() {
551 return (Vec::new(), Vec::new());
552 }
553
554 let first_sid = packed_cells[0].sheet_id();
555 let single_sheet = packed_cells.iter().all(|cell| cell.sheet_id() == first_sid);
556 let mut add_batch: Vec<(VertexAddr, u32)> = Vec::new();
557
558 #[cfg(feature = "perf_instrumentation")]
559 let mut packed_hits = 0usize;
560 #[cfg(feature = "perf_instrumentation")]
561 let mut generic_hits = 0usize;
562 #[cfg(feature = "perf_instrumentation")]
563 let mut missing = 0usize;
564 #[cfg(feature = "perf_instrumentation")]
565 let mut t_packed_lookup_us = 0u128;
566 #[cfg(feature = "perf_instrumentation")]
567 let mut t_generic_lookup_us = 0u128;
568 #[cfg(feature = "perf_instrumentation")]
569 let mut t_alloc_us = 0u128;
570 #[cfg(feature = "perf_instrumentation")]
571 let mut t_map_insert_us = 0u128;
572 #[cfg(feature = "perf_instrumentation")]
573 let mut t_index_insert_us = 0u128;
574 #[cfg(feature = "perf_instrumentation")]
575 let mut t_edge_register_us = 0u128;
576
577 if single_sheet {
578 let sid = first_sid;
579 let mut missing_items: Vec<(usize, PackedSheetCell)> =
580 Vec::with_capacity(packed_cells.len());
581
582 for (idx, packed) in packed_cells.iter().copied().enumerate() {
583 #[cfg(feature = "perf_instrumentation")]
584 let tl0 = PerfInstant::now();
585 if self.first_load_assume_new
586 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
587 {
588 ordered[idx] = Some(existing);
589 #[cfg(feature = "perf_instrumentation")]
590 {
591 packed_hits += 1;
592 t_packed_lookup_us += tl0.elapsed().as_micros();
593 }
594 continue;
595 }
596 #[cfg(feature = "perf_instrumentation")]
597 {
598 t_packed_lookup_us += tl0.elapsed().as_micros();
599 }
600
601 let pc = AbsCoord::new(packed.row0(), packed.col0());
602 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
603 #[cfg(feature = "perf_instrumentation")]
604 let tg0 = PerfInstant::now();
605 if let Some(&existing) = self.cell_to_vertex.get(&addr) {
606 ordered[idx] = Some(existing);
607 if self.first_load_assume_new {
608 self.load_packed_to_vertex.insert(packed, existing);
609 }
610 #[cfg(feature = "perf_instrumentation")]
611 {
612 generic_hits += 1;
613 }
614 } else {
615 missing_items.push((idx, packed));
616 #[cfg(feature = "perf_instrumentation")]
617 {
618 missing += 1;
619 }
620 }
621 #[cfg(feature = "perf_instrumentation")]
622 {
623 t_generic_lookup_us += tg0.elapsed().as_micros();
624 }
625 }
626
627 if !missing_items.is_empty() {
628 self.ensure_touched_sheets.insert(sid);
629
630 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(missing_items.len());
631 for (_, packed) in &missing_items {
632 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
633 }
634
635 #[cfg(feature = "perf_instrumentation")]
636 let ta0 = PerfInstant::now();
637 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
638 #[cfg(feature = "perf_instrumentation")]
639 {
640 t_alloc_us += ta0.elapsed().as_micros();
641 }
642 add_batch.reserve(missing_items.len());
643
644 match self.config.sheet_index_mode {
645 crate::engine::SheetIndexMode::Eager
646 | crate::engine::SheetIndexMode::FastBatch => {
647 for ((input_idx, packed), vid) in
648 missing_items.into_iter().zip(vids.into_iter())
649 {
650 let pc = AbsCoord::new(packed.row0(), packed.col0());
651 ordered[input_idx] = Some(vid);
652 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
653
654 #[cfg(feature = "perf_instrumentation")]
655 let tm0 = PerfInstant::now();
656 if self.first_load_assume_new {
657 self.load_packed_to_vertex.insert(packed, vid);
658 } else {
659 let addr =
660 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
661 self.cell_to_vertex.insert(addr, vid);
662 }
663 #[cfg(feature = "perf_instrumentation")]
664 {
665 t_map_insert_us += tm0.elapsed().as_micros();
666 }
667
668 #[cfg(feature = "perf_instrumentation")]
669 let ti0 = PerfInstant::now();
670 self.sheet_index_mut(sid)
671 .add_vertex(GridAddr::from_coord(pc), vid);
672 #[cfg(feature = "perf_instrumentation")]
673 {
674 t_index_insert_us += ti0.elapsed().as_micros();
675 }
676 }
677 }
678 crate::engine::SheetIndexMode::Lazy => {
679 for ((input_idx, packed), vid) in
680 missing_items.into_iter().zip(vids.into_iter())
681 {
682 let pc = AbsCoord::new(packed.row0(), packed.col0());
683 ordered[input_idx] = Some(vid);
684 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
685
686 #[cfg(feature = "perf_instrumentation")]
687 let tm0 = PerfInstant::now();
688 if self.first_load_assume_new {
689 self.load_packed_to_vertex.insert(packed, vid);
690 } else {
691 let addr =
692 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
693 self.cell_to_vertex.insert(addr, vid);
694 }
695 #[cfg(feature = "perf_instrumentation")]
696 {
697 t_map_insert_us += tm0.elapsed().as_micros();
698 }
699 }
700 }
701 }
702 }
703 } else {
704 let mut grouped: FxHashMap<SheetId, Vec<(usize, PackedSheetCell)>> =
705 FxHashMap::default();
706
707 for (idx, packed) in packed_cells.iter().copied().enumerate() {
708 #[cfg(feature = "perf_instrumentation")]
709 let tl0 = PerfInstant::now();
710 if self.first_load_assume_new
711 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
712 {
713 ordered[idx] = Some(existing);
714 #[cfg(feature = "perf_instrumentation")]
715 {
716 packed_hits += 1;
717 t_packed_lookup_us += tl0.elapsed().as_micros();
718 }
719 continue;
720 }
721 #[cfg(feature = "perf_instrumentation")]
722 {
723 t_packed_lookup_us += tl0.elapsed().as_micros();
724 }
725
726 let sid = packed.sheet_id();
727 let pc = AbsCoord::new(packed.row0(), packed.col0());
728 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
729 #[cfg(feature = "perf_instrumentation")]
730 let tg0 = PerfInstant::now();
731 if let Some(&existing) = self.cell_to_vertex.get(&addr) {
732 ordered[idx] = Some(existing);
733 if self.first_load_assume_new {
734 self.load_packed_to_vertex.insert(packed, existing);
735 }
736 #[cfg(feature = "perf_instrumentation")]
737 {
738 generic_hits += 1;
739 }
740 } else {
741 grouped.entry(sid).or_default().push((idx, packed));
742 #[cfg(feature = "perf_instrumentation")]
743 {
744 missing += 1;
745 }
746 }
747 #[cfg(feature = "perf_instrumentation")]
748 {
749 t_generic_lookup_us += tg0.elapsed().as_micros();
750 }
751 }
752
753 for (sid, items) in grouped {
754 if items.is_empty() {
755 continue;
756 }
757 self.ensure_touched_sheets.insert(sid);
758
759 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(items.len());
760 for (_, packed) in &items {
761 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
762 }
763
764 #[cfg(feature = "perf_instrumentation")]
765 let ta0 = PerfInstant::now();
766 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
767 #[cfg(feature = "perf_instrumentation")]
768 {
769 t_alloc_us += ta0.elapsed().as_micros();
770 }
771
772 for ((input_idx, packed), vid) in items.into_iter().zip(vids.into_iter()) {
773 let pc = AbsCoord::new(packed.row0(), packed.col0());
774 ordered[input_idx] = Some(vid);
775 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
776
777 #[cfg(feature = "perf_instrumentation")]
778 let tm0 = PerfInstant::now();
779 if self.first_load_assume_new {
780 self.load_packed_to_vertex.insert(packed, vid);
781 } else {
782 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
783 self.cell_to_vertex.insert(addr, vid);
784 }
785 #[cfg(feature = "perf_instrumentation")]
786 {
787 t_map_insert_us += tm0.elapsed().as_micros();
788 }
789
790 match self.config.sheet_index_mode {
791 crate::engine::SheetIndexMode::Eager
792 | crate::engine::SheetIndexMode::FastBatch => {
793 #[cfg(feature = "perf_instrumentation")]
794 let ti0 = PerfInstant::now();
795 self.sheet_index_mut(sid)
796 .add_vertex(GridAddr::from_coord(pc), vid);
797 #[cfg(feature = "perf_instrumentation")]
798 {
799 t_index_insert_us += ti0.elapsed().as_micros();
800 }
801 }
802 crate::engine::SheetIndexMode::Lazy => {
803 }
805 }
806 }
807 }
808 }
809
810 if !add_batch.is_empty() {
811 #[cfg(feature = "perf_instrumentation")]
812 let te0 = PerfInstant::now();
813 self.edges.add_vertices_batch(&add_batch);
814 #[cfg(feature = "perf_instrumentation")]
815 {
816 t_edge_register_us += te0.elapsed().as_micros();
817 }
818 }
819
820 #[cfg(feature = "perf_instrumentation")]
821 if debug {
822 eprintln!(
823 "[fz][ensure] cells={} single_sheet={} packed_hits={} generic_hits={} missing={} packed_lookup={}us generic_lookup={}us alloc={}us map_insert={}us index_insert={}us edge_register={}us total={}ms",
824 packed_cells.len(),
825 single_sheet,
826 packed_hits,
827 generic_hits,
828 missing,
829 t_packed_lookup_us,
830 t_generic_lookup_us,
831 t_alloc_us,
832 t_map_insert_us,
833 t_index_insert_us,
834 t_edge_register_us,
835 t0.elapsed().as_millis(),
836 );
837 }
838
839 let ordered = ordered
840 .into_iter()
841 .map(|vid| vid.expect("ensure_vertices_batch_packed_ordered must resolve every coord"))
842 .collect();
843 (ordered, add_batch)
844 }
845
846 pub fn ensure_vertices_batch_ordered(
849 &mut self,
850 coords: &[(SheetId, AbsCoord)],
851 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
852 let mut packed: Vec<PackedSheetCell> = Vec::with_capacity(coords.len());
853 for &(sid, coord) in coords {
854 packed.push(Self::packed_cell_key(sid, coord));
855 }
856 self.ensure_vertices_batch_packed_ordered(&packed)
857 }
858
859 #[inline]
860 fn packed_cell_key(sheet_id: SheetId, coord: AbsCoord) -> PackedSheetCell {
861 PackedSheetCell::try_new(sheet_id, coord.row(), coord.col())
862 .expect("graph coordinate must fit PackedSheetCell")
863 }
864
865 fn flush_load_packed_mappings(&mut self) {
866 if self.load_packed_to_vertex.is_empty() {
867 return;
868 }
869 let debug = std::env::var("FZ_DEBUG_LOAD")
870 .ok()
871 .is_some_and(|v| v != "0");
872 let t0 = crate::instant::FzInstant::now();
873 let count = self.load_packed_to_vertex.len();
874 self.cell_to_vertex.reserve(count);
875 for (&packed, &vid) in &self.load_packed_to_vertex {
876 let coord = AbsCoord::new(packed.row0(), packed.col0());
877 let addr = CellRef::new(
878 packed.sheet_id(),
879 Coord::new(coord.row(), coord.col(), true, true),
880 );
881 self.cell_to_vertex.insert(addr, vid);
882 }
883 self.load_packed_to_vertex.clear();
884 if debug {
885 eprintln!(
886 "[fz][load] flush_load_packed_mappings: {} entries in {:.1} ms",
887 count,
888 t0.elapsed().as_secs_f64() * 1000.0,
889 );
890 }
891 }
892
893 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
895 if self.first_load_assume_new && !enabled {
896 self.flush_load_packed_mappings();
897 } else if enabled {
898 self.load_packed_to_vertex.clear();
899 }
900 self.first_load_assume_new = enabled;
901 }
902
903 #[doc(hidden)]
904 pub fn first_load_assume_new(&self) -> bool {
905 self.first_load_assume_new
906 }
907
908 pub fn reset_ensure_touched(&mut self) {
910 self.ensure_touched_sheets.clear();
911 }
912
913 pub fn store_ast(&mut self, ast: &formualizer_parse::parser::ASTNode) -> AstNodeId {
915 self.data_store.store_ast(ast, &self.sheet_reg)
916 }
917
918 pub fn store_asts_batch<'a, I>(&mut self, asts: I) -> Vec<AstNodeId>
920 where
921 I: IntoIterator<Item = &'a formualizer_parse::parser::ASTNode>,
922 {
923 self.data_store.store_asts_batch(asts, &self.sheet_reg)
924 }
925
926 pub fn reserve_formula_metadata(&mut self, additional: usize) {
928 self.vertex_formulas.reserve(additional);
929 self.formula_dirty.legacy_reserve(additional);
930 self.volatile_vertices.reserve(additional);
931 }
932
933 pub fn vid_for_sid_pc(&self, sid: SheetId, pc: AbsCoord) -> Option<VertexId> {
935 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
936 self.cell_to_vertex.get(&addr).copied()
937 }
938
939 pub fn vid_for_plan_idx(
941 &self,
942 plan: &crate::engine::plan::DependencyPlan,
943 idx: u32,
944 ) -> Option<VertexId> {
945 let (sid, pc) = plan.global_cells.get(idx as usize).copied()?;
946 self.vid_for_sid_pc(sid, pc)
947 }
948 pub fn assign_formula_vertex(
950 &mut self,
951 vid: VertexId,
952 ast_id: AstNodeId,
953 volatile: bool,
954 dynamic: bool,
955 ) {
956 if self.vertex_formulas.contains_key(&vid) {
957 self.remove_dependent_edges(vid);
958 }
959 self.store
960 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
961 self.vertex_values.remove(&vid);
962 self.vertex_formulas.insert(vid, ast_id);
963 self.mark_volatile(vid, volatile);
964 self.store.set_dynamic(vid, dynamic);
965
966 self.mark_vertex_dirty(vid);
968 }
969
970 pub fn assign_formula_vertex_load_fast(
973 &mut self,
974 vid: VertexId,
975 ast_id: AstNodeId,
976 volatile: bool,
977 dynamic: bool,
978 ) {
979 debug_assert!(
980 !self.vertex_formulas.contains_key(&vid),
981 "load-fast formula assignment expects fresh/non-formula vertices"
982 );
983 self.store
984 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
985 self.vertex_values.remove(&vid);
986 self.vertex_formulas.insert(vid, ast_id);
987 self.mark_volatile(vid, volatile);
988 self.store.set_dynamic(vid, dynamic);
989 }
990
991 pub fn add_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
993 self.add_dependent_edges_nobatch(dependent, dependencies);
994 }
995
996 pub fn iter_vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
998 self.store.all_vertices()
999 }
1000
1001 pub fn vertex_addr(&self, vid: VertexId) -> VertexAddr {
1004 self.store.addr(vid)
1005 }
1006
1007 pub fn vertex_grid_addr(&self, vid: VertexId) -> Option<GridAddr> {
1009 self.store.grid_addr(vid)
1010 }
1011
1012 pub fn vertex_count(&self) -> usize {
1014 self.store.len()
1015 }
1016
1017 pub fn build_edges_from_adjacency(
1019 &mut self,
1020 adjacency: Vec<(u32, Vec<u32>)>,
1021 coords: Vec<VertexAddr>,
1022 vertex_ids: Vec<u32>,
1023 ) {
1024 let adjacency = self.edges.adjacency_with_carried_forward_edges(adjacency);
1028 self.edges
1029 .build_from_adjacency(adjacency, coords, vertex_ids);
1030 }
1031 pub fn used_row_bounds_for_columns(
1033 &self,
1034 sheet_id: SheetId,
1035 start_col: u32,
1036 end_col: u32,
1037 ) -> Option<(u32, u32)> {
1038 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1040 && !index.is_empty()
1041 {
1042 let mut min_r: Option<u32> = None;
1043 let mut max_r: Option<u32> = None;
1044 for vid in index.vertices_in_col_range(start_col, end_col) {
1045 let Some(r) = self.store.grid_addr(vid).map(|addr| addr.row()) else {
1046 continue;
1047 };
1048 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1049 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1050 }
1051 return match (min_r, max_r) {
1052 (Some(a), Some(b)) => Some((a, b)),
1053 _ => None,
1054 };
1055 }
1056 let mut min_r: Option<u32> = None;
1058 let mut max_r: Option<u32> = None;
1059 for cref in self.cell_to_vertex.keys() {
1060 if cref.sheet_id == sheet_id {
1061 let c = cref.coord.col();
1062 if c >= start_col && c <= end_col {
1063 let r = cref.coord.row();
1064 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1065 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1066 }
1067 }
1068 }
1069 for packed in self.load_packed_to_vertex.keys() {
1070 if packed.sheet_id() == sheet_id {
1071 let c = packed.col0();
1072 if c >= start_col && c <= end_col {
1073 let r = packed.row0();
1074 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1075 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1076 }
1077 }
1078 }
1079 match (min_r, max_r) {
1080 (Some(a), Some(b)) => Some((a, b)),
1081 _ => None,
1082 }
1083 }
1084
1085 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1087 let Some(sheet_id) = self.sheet_reg.get_id(sheet) else {
1088 return;
1089 };
1090 self.rebuild_sheet_index(sheet_id);
1091 }
1092
1093 fn rebuild_sheet_index(&mut self, sheet_id: SheetId) {
1094 let mut idx = SheetIndex::new();
1095 let mut batch: Vec<(GridAddr, VertexId)> =
1096 Vec::with_capacity(self.cell_to_vertex.len() + self.load_packed_to_vertex.len());
1097 for (cref, vid) in &self.cell_to_vertex {
1098 if cref.sheet_id == sheet_id {
1099 batch.push((GridAddr::new(cref.coord.row(), cref.coord.col()), *vid));
1100 }
1101 }
1102 for (&packed, &vid) in &self.load_packed_to_vertex {
1103 if packed.sheet_id() != sheet_id {
1104 continue;
1105 }
1106 let coord = GridAddr::new(packed.row0(), packed.col0());
1107 let addr = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
1108 if self.cell_to_vertex.contains_key(&addr) {
1109 continue;
1110 }
1111 batch.push((coord, vid));
1112 }
1113 idx.add_vertices_batch(&batch);
1114 self.sheet_indexes.insert(sheet_id, idx);
1115 }
1116
1117 pub(crate) fn prepare_sheet_index_for_query(&mut self, sheet_id: SheetId) {
1121 if self.config.sheet_index_mode == crate::engine::SheetIndexMode::Lazy {
1122 self.rebuild_sheet_index(sheet_id);
1123 }
1124 }
1125
1126 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
1127 self.config.sheet_index_mode = mode;
1128 }
1129
1130 pub(crate) fn set_evaluation_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
1131 self.config.evaluation_budgets = budgets;
1132 }
1133
1134 pub fn used_col_bounds_for_rows(
1136 &self,
1137 sheet_id: SheetId,
1138 start_row: u32,
1139 end_row: u32,
1140 ) -> Option<(u32, u32)> {
1141 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1142 && !index.is_empty()
1143 {
1144 let mut min_c: Option<u32> = None;
1145 let mut max_c: Option<u32> = None;
1146 for vid in index.vertices_in_row_range(start_row, end_row) {
1147 let Some(c) = self.store.grid_addr(vid).map(|addr| addr.col()) else {
1148 continue;
1149 };
1150 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1151 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1152 }
1153 return match (min_c, max_c) {
1154 (Some(a), Some(b)) => Some((a, b)),
1155 _ => None,
1156 };
1157 }
1158 let mut min_c: Option<u32> = None;
1160 let mut max_c: Option<u32> = None;
1161 for cref in self.cell_to_vertex.keys() {
1162 if cref.sheet_id == sheet_id {
1163 let r = cref.coord.row();
1164 if r >= start_row && r <= end_row {
1165 let c = cref.coord.col();
1166 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1167 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1168 }
1169 }
1170 }
1171 for packed in self.load_packed_to_vertex.keys() {
1172 if packed.sheet_id() == sheet_id {
1173 let r = packed.row0();
1174 if r >= start_row && r <= end_row {
1175 let c = packed.col0();
1176 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1177 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1178 }
1179 }
1180 }
1181 match (min_c, max_c) {
1182 (Some(a), Some(b)) => Some((a, b)),
1183 _ => None,
1184 }
1185 }
1186
1187 pub fn sheet_has_formulas(&self, sheet_id: SheetId) -> bool {
1189 for &vid in self.vertex_formulas.keys() {
1191 if self.store.sheet_id(vid) == sheet_id {
1192 return true;
1193 }
1194 }
1195 false
1196 }
1197 pub fn new() -> Self {
1198 Self::new_with_config(super::EvalConfig::default())
1199 }
1200
1201 pub fn new_with_config(config: super::EvalConfig) -> Self {
1202 let mut sheet_reg = SheetRegistry::new();
1203 let default_sheet_id = sheet_reg.id_for(&config.default_sheet_name);
1204
1205 let mut g = Self {
1206 store: VertexStore::new(),
1207 edges: CsrMutableEdges::new(),
1208 data_store: DataStore::new(),
1209 vertex_values: FxHashMap::default(),
1210 vertex_formulas: FxHashMap::default(),
1211 value_cache_enabled: false,
1214 #[cfg(debug_assertions)]
1215 graph_value_read_attempts: AtomicU64::new(0),
1216 cell_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1217 load_packed_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1218 formula_dirty: FormulaDirtyState::default(),
1219 dirty_propagation_visits: 0,
1220 deferred_dirty_depth: 0,
1221 deferred_dirty_pending: Vec::new(),
1222 volatile_vertices: FxHashSet::default(),
1223 ref_error_vertices: FxHashSet::default(),
1224 formula_to_range_deps: FxHashMap::default(),
1225 stripe_to_dependents: FxHashMap::default(),
1226 sheet_indexes: FxHashMap::default(),
1227 sheet_reg,
1228 default_sheet_id,
1229 named_ranges: FxHashMap::default(),
1230 named_ranges_lookup: FxHashMap::default(),
1231 sheet_named_ranges: FxHashMap::default(),
1232 sheet_named_ranges_lookup: FxHashMap::default(),
1233 vertex_to_names: FxHashMap::default(),
1234 name_vertex_lookup: FxHashMap::default(),
1235 pending_name_links: FxHashMap::default(),
1236 vertex_to_pending_names: FxHashMap::default(),
1237 tables: FxHashMap::default(),
1238 tables_lookup: FxHashMap::default(),
1239 table_vertex_lookup: FxHashMap::default(),
1240 source_scalars: FxHashMap::default(),
1241 source_tables: FxHashMap::default(),
1242 source_vertex_lookup: FxHashMap::default(),
1243 symbol_vertex_seq: 0,
1244 cell_to_name_dependents: FxHashMap::default(),
1245 name_to_cell_dependencies: FxHashMap::default(),
1246 config: config.clone(),
1247 topology_revision: 0,
1248 symbol_revision: 0,
1249 formula_authority: FormulaAuthority::default(),
1250 pk_order: None,
1251 spill_anchor_to_cells: FxHashMap::default(),
1252 spill_cell_to_anchor: std::collections::HashMap::with_hasher(CoordBuildHasher),
1253 spill_cells_by_sheet: FxHashMap::default(),
1254 admission_budget_override: None,
1255 first_load_assume_new: false,
1256 ensure_touched_sheets: FxHashSet::default(),
1257 tombstone_registry: TombstoneRegistry::default(),
1258 #[cfg(test)]
1259 instr: std::sync::Mutex::new(GraphInstrumentation::default()),
1260 #[cfg(test)]
1261 prepared_legacy_graph_failure_for_test: false,
1262 };
1263
1264 if config.use_dynamic_topo {
1265 let nodes = g
1267 .store
1268 .all_vertices()
1269 .filter(|&id| g.store.vertex_exists_active(id));
1270 let mut pk = DynamicTopo::new(
1271 nodes,
1272 PkConfig {
1273 visit_budget: config.pk_visit_budget,
1274 compaction_interval_ops: config.pk_compaction_interval_ops,
1275 },
1276 );
1277 let adapter = GraphAdapter { g: &g };
1279 pk.rebuild_full(&adapter);
1280 g.pk_order = Some(pk);
1281 }
1282
1283 g
1284 }
1285
1286 pub(crate) fn pk_layers_for(&self, subset: &[VertexId]) -> Option<Vec<crate::engine::Layer>> {
1288 let pk = self.pk_order.as_ref()?;
1289 let adapter = crate::engine::topo::GraphAdapter { g: self };
1290 let layers = pk.layers_for(&adapter, subset, self.config.max_layer_width);
1291 Some(
1292 layers
1293 .into_iter()
1294 .map(|vs| crate::engine::Layer { vertices: vs })
1295 .collect(),
1296 )
1297 }
1298
1299 #[inline]
1300 pub(crate) fn dynamic_topo_enabled(&self) -> bool {
1301 self.pk_order.is_some()
1302 }
1303
1304 #[cfg(test)]
1305 pub fn reset_instr(&mut self) {
1306 if let Ok(mut g) = self.instr.lock() {
1307 *g = GraphInstrumentation::default();
1308 }
1309 }
1310
1311 #[cfg(test)]
1312 pub fn instr(&self) -> GraphInstrumentation {
1313 self.instr.lock().map(|g| g.clone()).unwrap_or_default()
1314 }
1315
1316 pub fn begin_batch(&mut self) {
1318 self.edges.begin_batch();
1319 }
1320
1321 pub fn end_batch(&mut self) {
1323 self.edges.end_batch();
1324 }
1325
1326 pub fn default_sheet_id(&self) -> SheetId {
1327 self.default_sheet_id
1328 }
1329
1330 pub fn default_sheet_name(&self) -> &str {
1331 self.sheet_reg.name(self.default_sheet_id)
1332 }
1333
1334 pub fn set_default_sheet_by_name(&mut self, name: &str) {
1335 self.default_sheet_id = self.sheet_id_mut(name);
1336 }
1337
1338 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
1339 self.default_sheet_id = id;
1340 }
1341
1342 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
1344 self.sheet_reg.id_for(name)
1345 }
1346
1347 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
1348 self.sheet_reg.get_id(name)
1349 }
1350
1351 fn resolve_existing_sheet_id(&self, name: &str) -> Result<SheetId, ExcelError> {
1353 self.sheet_id(name).ok_or_else(|| {
1354 ExcelError::new(ExcelErrorKind::Ref).with_message(format!("Sheet not found: {name}"))
1355 })
1356 }
1357
1358 pub fn sheet_name(&self, id: SheetId) -> &str {
1360 self.sheet_reg.name(id)
1361 }
1362
1363 pub fn sheet_reg(&self) -> &SheetRegistry {
1365 &self.sheet_reg
1366 }
1367
1368 pub(crate) fn data_store(&self) -> &DataStore {
1369 &self.data_store
1370 }
1371
1372 pub(crate) fn make_ingest_pipeline<'a>(
1373 &'a mut self,
1374 function_provider: &'a dyn crate::traits::FunctionProvider,
1375 policy: formualizer_parse::parser::CollectPolicy,
1376 ) -> crate::engine::ingest_pipeline::IngestPipeline<'a> {
1377 use crate::engine::ingest_pipeline::{
1378 NameRegistryView, NamedEntryRef, NamedTarget, SourceEntryRef, SourceRegistryView,
1379 TableEntrySnapshot, TableRegistryView,
1380 };
1381
1382 let DependencyGraph {
1383 data_store,
1384 sheet_reg,
1385 named_ranges,
1386 named_ranges_lookup,
1387 sheet_named_ranges,
1388 sheet_named_ranges_lookup,
1389 tables,
1390 tables_lookup,
1391 source_scalars,
1392 source_tables,
1393 config,
1394 ..
1395 } = self;
1396
1397 let case_sensitive_names = config.case_sensitive_names;
1398 let names = NameRegistryView::new(move |name, current_sheet| {
1399 let found = if case_sensitive_names {
1400 sheet_named_ranges
1401 .get(&(current_sheet, name.to_string()))
1402 .or_else(|| named_ranges.get(name))
1403 } else {
1404 let key = name.to_lowercase();
1405 sheet_named_ranges_lookup
1406 .get(&(current_sheet, key.clone()))
1407 .and_then(|canon| sheet_named_ranges.get(&(current_sheet, canon.clone())))
1408 .or_else(|| {
1409 named_ranges_lookup
1410 .get(&key)
1411 .and_then(|canon| named_ranges.get(canon))
1412 })
1413 };
1414 found.map(|entry| NamedEntryRef {
1415 vertex: entry.vertex,
1416 target: match &entry.definition {
1417 crate::engine::named_range::NamedDefinition::Cell(cell) => {
1418 NamedTarget::Cell(*cell)
1419 }
1420 crate::engine::named_range::NamedDefinition::Range(range) => {
1421 NamedTarget::Range(*range)
1422 }
1423 crate::engine::named_range::NamedDefinition::Literal(_)
1424 | crate::engine::named_range::NamedDefinition::Formula { .. } => {
1425 NamedTarget::Other
1426 }
1427 },
1428 })
1429 });
1430
1431 let case_sensitive_tables = config.case_sensitive_tables;
1432 let tables_ref = &*tables;
1433 let tables_lookup_ref = &*tables_lookup;
1434 let snapshot_table = |entry: &tables::TableEntry| TableEntrySnapshot {
1435 name: entry.name.clone(),
1436 range: entry.range,
1437 header_row: entry.header_row,
1438 headers: entry.headers.clone(),
1439 vertex: entry.vertex,
1440 };
1441 let tables_view = TableRegistryView::new(
1442 move |name| {
1443 if case_sensitive_tables {
1444 tables_ref.get(name).map(snapshot_table)
1445 } else {
1446 let key = name.to_lowercase();
1447 tables_lookup_ref
1448 .get(&key)
1449 .and_then(|canon| tables_ref.get(canon))
1450 .map(snapshot_table)
1451 }
1452 },
1453 move |cell| {
1454 let row0 = cell.coord.row();
1455 let col0 = cell.coord.col();
1456 let mut best: Option<&tables::TableEntry> = None;
1457 let mut best_area = u64::MAX;
1458 let mut best_name = "";
1459 for table in tables_ref.values() {
1460 if table.sheet_id() != cell.sheet_id {
1461 continue;
1462 }
1463 let sr0 = table.range.start.coord.row();
1464 let sc0 = table.range.start.coord.col();
1465 let er0 = table.range.end.coord.row();
1466 let ec0 = table.range.end.coord.col();
1467 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
1468 continue;
1469 }
1470 let area = ((er0 - sr0 + 1) as u64).saturating_mul((ec0 - sc0 + 1) as u64);
1471 let name = table.name.as_str();
1472 if best.is_none() || area < best_area || (area == best_area && name < best_name)
1473 {
1474 best = Some(table);
1475 best_area = area;
1476 best_name = name;
1477 }
1478 }
1479 best.map(snapshot_table)
1480 },
1481 );
1482
1483 let sources = SourceRegistryView::new(
1484 move |name| {
1485 source_scalars.get(name).map(|entry| SourceEntryRef {
1486 vertex: entry.vertex,
1487 })
1488 },
1489 move |name| {
1490 source_tables.get(name).map(|entry| SourceEntryRef {
1491 vertex: entry.vertex,
1492 })
1493 },
1494 );
1495
1496 crate::engine::ingest_pipeline::IngestPipeline::new(
1497 data_store,
1498 sheet_reg,
1499 names,
1500 tables_view,
1501 sources,
1502 function_provider,
1503 policy,
1504 )
1505 }
1506
1507 pub fn to_a1(&self, cell_ref: CellRef) -> String {
1509 format!("{}!{}", self.sheet_name(cell_ref.sheet_id), cell_ref.coord)
1510 }
1511
1512 pub(crate) fn vertex_len(&self) -> usize {
1513 self.store.len()
1514 }
1515
1516 pub(crate) fn topology_revision(&self) -> u64 {
1517 self.topology_revision
1518 }
1519
1520 pub(crate) fn bump_topology_revision(&mut self) {
1521 self.topology_revision = self.topology_revision.wrapping_add(1);
1522 }
1523
1524 pub(crate) fn symbol_revision(&self) -> u64 {
1525 self.symbol_revision
1526 }
1527
1528 pub(crate) fn bump_symbol_revision(&mut self) {
1529 self.symbol_revision = self.symbol_revision.wrapping_add(1);
1530 }
1531
1532 pub(crate) fn authority_revisions(&self) -> (u64, u64, u64) {
1533 (
1534 self.formula_authority.plane.epoch().0,
1535 self.formula_authority.indexes_epoch(),
1536 self.formula_authority.indexed_plane_epoch(),
1537 )
1538 }
1539
1540 pub(crate) fn formula_range_dependencies(
1541 &self,
1542 vertex: VertexId,
1543 ) -> Option<&[SharedRangeRef<'static>]> {
1544 self.formula_to_range_deps.get(&vertex).map(Vec::as_slice)
1545 }
1546
1547 pub(crate) fn spill_anchors_in_region(
1548 &self,
1549 sheet_id: SheetId,
1550 start_row0: u32,
1551 start_col0: u32,
1552 end_row0: u32,
1553 end_col0: u32,
1554 ) -> Vec<VertexId> {
1555 let mut anchors = self
1556 .spill_cells_by_sheet
1557 .get(&sheet_id)
1558 .into_iter()
1559 .flat_map(|cells| cells.range((start_row0, 0)..=(end_row0, u32::MAX)))
1560 .filter_map(|(&(row, col), anchor)| {
1561 (row <= end_row0 && col >= start_col0 && col <= end_col0).then_some(*anchor)
1562 })
1563 .collect::<Vec<_>>();
1564 anchors.sort_unstable();
1565 anchors.dedup();
1566 anchors
1567 }
1568
1569 pub fn sheet_index_mut(&mut self, sheet_id: SheetId) -> &mut SheetIndex {
1572 self.sheet_indexes.entry(sheet_id).or_default()
1573 }
1574
1575 pub fn sheet_index(&self, sheet_id: SheetId) -> Option<&SheetIndex> {
1577 self.sheet_indexes.get(&sheet_id)
1578 }
1579
1580 pub(crate) fn sheet_index_vertex_count(&self, sheet_id: SheetId) -> usize {
1581 self.sheet_indexes.get(&sheet_id).map_or(0, SheetIndex::len)
1582 }
1583
1584 pub(crate) fn set_admission_budget_override(
1585 &mut self,
1586 budgets: Option<crate::engine::EvaluationBudgets>,
1587 ) -> Option<crate::engine::EvaluationBudgets> {
1588 std::mem::replace(&mut self.admission_budget_override, budgets)
1589 }
1590
1591 fn self_admission_budgets(&self) -> crate::engine::EvaluationBudgets {
1592 self.admission_budget_override
1593 .clone()
1594 .unwrap_or_else(|| self.config.resolved_evaluation_budgets())
1595 }
1596
1597 fn preview_spill_materialization(
1598 &self,
1599 target_cells: &[CellRef],
1600 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
1601 let unique = target_cells.iter().copied().collect::<FxHashSet<_>>();
1602 let added_vertices = unique
1603 .iter()
1604 .filter(|cell| !self.cell_to_vertex.contains_key(cell))
1605 .count();
1606 let stats = self.baseline_stats();
1607 Ok(crate::engine::resource_ledger::GraphAdmission {
1608 final_vertices: stats
1609 .graph_vertex_count
1610 .checked_add(added_vertices)
1611 .ok_or_else(|| {
1612 ExcelError::new(ExcelErrorKind::NImpl)
1613 .with_message("spill vertex count overflow")
1614 })?,
1615 final_edges: stats.graph_edge_count,
1616 materialization_cells: unique.len() as u64,
1617 added_vertices,
1618 added_edges: 0,
1619 })
1620 }
1621
1622 pub(crate) fn preview_value_mutation(
1623 &self,
1624 sheet_id: SheetId,
1625 row: u32,
1626 col: u32,
1627 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
1628 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
1629 let existing = self.cell_to_vertex.get(&cell).copied();
1630 let stats = self.baseline_stats();
1631 let removed_edges = existing.map_or(0, |vertex| self.get_dependencies(vertex).len());
1632 Ok(crate::engine::resource_ledger::GraphAdmission {
1633 final_vertices: stats
1634 .graph_vertex_count
1635 .checked_add(usize::from(existing.is_none()))
1636 .ok_or_else(|| {
1637 ExcelError::new(ExcelErrorKind::NImpl)
1638 .with_message("graph vertex count overflow")
1639 })?,
1640 final_edges: stats
1641 .graph_edge_count
1642 .checked_sub(removed_edges)
1643 .ok_or_else(|| {
1644 ExcelError::new(ExcelErrorKind::NImpl)
1645 .with_message("graph edge count underflow")
1646 })?,
1647 materialization_cells: 0,
1648 added_vertices: usize::from(existing.is_none()),
1649 added_edges: 0,
1650 })
1651 }
1652
1653 pub(crate) fn preview_value_mutations(
1654 &self,
1655 sheet_id: SheetId,
1656 cells: &[(u32, u32)],
1657 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
1658 let mut targets = std::collections::BTreeSet::new();
1659 let mut added_vertices = 0usize;
1660 let mut removed_edges = 0usize;
1661 for (row, col) in cells {
1662 let packed = PackedSheetCell::try_from_excel_1based(sheet_id, *row, *col)
1663 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
1664 if !targets.insert(packed) {
1665 continue;
1666 }
1667 let reference = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
1668 if let Some(vertex) = self.cell_to_vertex.get(&reference).copied() {
1669 removed_edges = removed_edges
1670 .checked_add(self.get_dependencies(vertex).len())
1671 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?;
1672 } else {
1673 added_vertices = added_vertices
1674 .checked_add(1)
1675 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?;
1676 }
1677 }
1678 let stats = self.baseline_stats();
1679 Ok(crate::engine::resource_ledger::GraphAdmission {
1680 final_vertices: stats
1681 .graph_vertex_count
1682 .checked_add(added_vertices)
1683 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
1684 final_edges: stats
1685 .graph_edge_count
1686 .checked_sub(removed_edges)
1687 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
1688 materialization_cells: 0,
1689 added_vertices,
1690 added_edges: 0,
1691 })
1692 }
1693
1694 pub(crate) fn preview_formula_mutations(
1695 &self,
1696 plans: &[(SheetId, u32, u32, DependencyPlanRow)],
1697 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
1698 let mut new_cells = std::collections::BTreeSet::new();
1699 let mut removed_edges = 0usize;
1700 let mut added_edges = 0usize;
1701 for (sheet_id, row, col, plan) in plans {
1702 let target = PackedSheetCell::try_from_excel_1based(*sheet_id, *row, *col)
1703 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
1704 let target_ref = CellRef::new(*sheet_id, Coord::from_excel(*row, *col, true, true));
1705 if let Some(vertex) = self.cell_to_vertex.get(&target_ref).copied() {
1706 removed_edges = removed_edges
1707 .checked_add(self.get_dependencies(vertex).len())
1708 .ok_or_else(|| {
1709 ExcelError::new(ExcelErrorKind::NImpl)
1710 .with_message("graph edge count overflow")
1711 })?;
1712 } else {
1713 new_cells.insert(target);
1714 }
1715
1716 let mut dependencies = std::collections::BTreeSet::new();
1717 for dependency in &plan.direct_cell_deps {
1718 let packed = PackedSheetCell::try_new(
1719 dependency.sheet_id,
1720 dependency.coord.row(),
1721 dependency.coord.col(),
1722 )
1723 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
1724 let reference = CellRef::new(dependency.sheet_id, dependency.coord);
1725 if let Some(vertex) = self.cell_to_vertex.get(&reference).copied() {
1726 dependencies.insert((0u8, u64::from(vertex.0)));
1727 } else {
1728 new_cells.insert(packed);
1729 dependencies.insert((1u8, packed.as_u64()));
1730 }
1731 }
1732 for name in plan.resolved_named_refs.iter().chain(&plan.named_refs) {
1733 if let Some(entry) = self.resolve_name_entry(name, *sheet_id) {
1734 dependencies.insert((0, u64::from(entry.vertex.0)));
1735 } else if let Some(entry) = self.resolve_source_scalar_entry(name) {
1736 dependencies.insert((0, u64::from(entry.vertex.0)));
1737 }
1738 }
1739 for name in &plan.source_refs {
1740 if let Some(vertex) = self
1741 .resolve_source_scalar_entry(name)
1742 .map(|entry| entry.vertex)
1743 .or_else(|| {
1744 self.resolve_source_table_entry(name)
1745 .map(|entry| entry.vertex)
1746 })
1747 {
1748 dependencies.insert((0, u64::from(vertex.0)));
1749 }
1750 }
1751 for name in &plan.table_refs {
1752 if let Some(vertex) = self
1753 .resolve_table_entry(name)
1754 .map(|entry| entry.vertex)
1755 .or_else(|| {
1756 self.resolve_source_table_entry(name)
1757 .map(|entry| entry.vertex)
1758 })
1759 {
1760 dependencies.insert((0, u64::from(vertex.0)));
1761 }
1762 }
1763 let target_row = target.row0();
1764 let target_col = target.col0();
1765 if plan.range_deps.iter().any(|range| {
1766 let range_sheet = self
1768 .sheet_reg
1769 .resolve_locator(&range.sheet, *sheet_id)
1770 .unwrap_or(*sheet_id);
1771 range_sheet == *sheet_id
1772 && range
1773 .start_row
1774 .is_none_or(|bound| target_row >= bound.index)
1775 && range.end_row.is_none_or(|bound| target_row <= bound.index)
1776 && range
1777 .start_col
1778 .is_none_or(|bound| target_col >= bound.index)
1779 && range.end_col.is_none_or(|bound| target_col <= bound.index)
1780 }) {
1781 dependencies.insert((1, target.as_u64()));
1782 }
1783 added_edges = added_edges.checked_add(dependencies.len()).ok_or_else(|| {
1784 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
1785 })?;
1786 }
1787 let stats = self.baseline_stats();
1788 Ok(crate::engine::resource_ledger::GraphAdmission {
1789 final_vertices: stats
1790 .graph_vertex_count
1791 .checked_add(new_cells.len())
1792 .ok_or_else(|| {
1793 ExcelError::new(ExcelErrorKind::NImpl)
1794 .with_message("graph vertex count overflow")
1795 })?,
1796 final_edges: stats
1797 .graph_edge_count
1798 .checked_sub(removed_edges)
1799 .and_then(|count| count.checked_add(added_edges))
1800 .ok_or_else(|| {
1801 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
1802 })?,
1803 materialization_cells: plans.len() as u64,
1804 added_vertices: new_cells.len(),
1805 added_edges,
1806 })
1807 }
1808
1809 pub(crate) fn vertices_in_region(
1810 &self,
1811 sheet_id: SheetId,
1812 start_row0: u32,
1813 end_row0: u32,
1814 start_col0: u32,
1815 end_col0: u32,
1816 ) -> Vec<VertexId> {
1817 self.sheet_indexes
1818 .get(&sheet_id)
1819 .map_or_else(Vec::new, |index| {
1820 index.vertices_in_rect(start_row0, end_row0, start_col0, end_col0)
1821 })
1822 }
1823
1824 #[cfg(test)]
1825 pub(crate) fn reset_sheet_index_query_stats(&self) {
1826 for index in self.sheet_indexes.values() {
1827 index.reset_query_stats();
1828 }
1829 }
1830
1831 #[cfg(test)]
1832 pub(crate) fn sheet_index_query_stats(
1833 &self,
1834 ) -> crate::engine::sheet_index::SheetIndexQueryStats {
1835 self.sheet_indexes.values().fold(
1836 crate::engine::sheet_index::SheetIndexQueryStats::default(),
1837 |mut total, index| {
1838 let stats = index.query_stats();
1839 total.coordinate_nodes_visited = total
1840 .coordinate_nodes_visited
1841 .saturating_add(stats.coordinate_nodes_visited);
1842 total.values_visited = total.values_visited.saturating_add(stats.values_visited);
1843 total
1844 },
1845 )
1846 }
1847
1848 pub fn set_cell_value(
1850 &mut self,
1851 sheet: &str,
1852 row: u32,
1853 col: u32,
1854 value: LiteralValue,
1855 ) -> Result<OperationSummary, ExcelError> {
1856 let value = normalize_stored_literal(value);
1857 let sheet_id = self.sheet_id_mut(sheet);
1858 let budgets = self.self_admission_budgets();
1859 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
1860 let usage = self.preview_value_mutation(sheet_id, row, col)?;
1861 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
1862 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
1863 }
1864 let coord = Coord::from_excel(row, col, true, true);
1866 let addr = CellRef::new(sheet_id, coord);
1867 let mut created_placeholders = Vec::new();
1868
1869 let vertex_id = if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
1870 let is_formula = matches!(
1872 self.store.kind(existing_id),
1873 VertexKind::FormulaScalar | VertexKind::FormulaArray
1874 );
1875
1876 if is_formula {
1877 self.remove_dependent_edges(existing_id);
1878 self.detach_vertex_from_names(existing_id);
1879 self.clear_pending_name_references(existing_id);
1880 self.vertex_formulas.remove(&existing_id);
1881 }
1882
1883 self.store.set_kind(existing_id, VertexKind::Cell);
1885 if self.value_cache_enabled {
1886 let value_ref = self.data_store.store_value(value);
1887 self.vertex_values.insert(existing_id, value_ref);
1888 } else {
1889 self.vertex_values.remove(&existing_id);
1891 }
1892 existing_id
1893 } else {
1894 created_placeholders.push(addr);
1896 let position = GridAddr::from_coord(AbsCoord::from_excel(row, col));
1897 let vertex_id = self
1898 .store
1899 .allocate(VertexAddr::grid(position), sheet_id, 0x01); self.edges
1903 .add_vertex(VertexAddr::grid(position), vertex_id.0);
1904
1905 self.sheet_index_mut(sheet_id)
1907 .add_vertex(position, vertex_id);
1908
1909 self.store.set_kind(vertex_id, VertexKind::Cell);
1910 if self.value_cache_enabled {
1911 let value_ref = self.data_store.store_value(value);
1912 self.vertex_values.insert(vertex_id, value_ref);
1913 }
1914 self.cell_to_vertex.insert(addr, vertex_id);
1915 vertex_id
1916 };
1917
1918 self.ref_error_vertices.remove(&vertex_id);
1920
1921 Ok(OperationSummary {
1922 affected_vertices: self.mark_dirty(vertex_id),
1923 created_placeholders,
1924 })
1925 }
1926
1927 pub fn reserve_cells(&mut self, additional: usize) {
1929 self.store.reserve(additional);
1930 if self.value_cache_enabled {
1931 self.vertex_values.reserve(additional);
1932 }
1933 self.cell_to_vertex.reserve(additional);
1934 }
1936
1937 pub fn set_cell_value_bulk_untracked(
1939 &mut self,
1940 sheet: &str,
1941 row: u32,
1942 col: u32,
1943 value: LiteralValue,
1944 ) -> Result<(), ExcelError> {
1945 let value = normalize_stored_literal(value);
1946 let sheet_id = self.sheet_id_mut(sheet);
1947 let budgets = self.self_admission_budgets();
1948 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
1949 let usage = self.preview_value_mutation(sheet_id, row, col)?;
1950 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
1951 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
1952 }
1953 let coord = Coord::from_excel(row, col, true, true);
1954 let addr = CellRef::new(sheet_id, coord);
1955 if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
1956 if matches!(
1958 self.store.kind(existing_id),
1959 VertexKind::FormulaScalar | VertexKind::FormulaArray
1960 ) {
1961 self.remove_dependent_edges(existing_id);
1962 self.detach_vertex_from_names(existing_id);
1963 self.clear_pending_name_references(existing_id);
1964 self.vertex_formulas.remove(&existing_id);
1965 }
1966 if self.value_cache_enabled {
1967 let value_ref = self.data_store.store_value(value);
1968 self.vertex_values.insert(existing_id, value_ref);
1969 } else {
1970 self.vertex_values.remove(&existing_id);
1971 }
1972 self.store.set_kind(existing_id, VertexKind::Cell);
1973 self.ref_error_vertices.remove(&existing_id);
1974 return Ok(());
1975 }
1976 let position = GridAddr::from_coord(AbsCoord::from_excel(row, col));
1977 let vertex_id = self
1978 .store
1979 .allocate(VertexAddr::grid(position), sheet_id, 0x00); self.edges
1981 .add_vertex(VertexAddr::grid(position), vertex_id.0);
1982 self.sheet_index_mut(sheet_id)
1983 .add_vertex(position, vertex_id);
1984 self.store.set_kind(vertex_id, VertexKind::Cell);
1985 self.ref_error_vertices.remove(&vertex_id);
1986 if self.value_cache_enabled {
1987 let value_ref = self.data_store.store_value(value);
1988 self.vertex_values.insert(vertex_id, value_ref);
1989 }
1990 self.cell_to_vertex.insert(addr, vertex_id);
1991 Ok(())
1992 }
1993
1994 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I) -> Result<(), ExcelError>
1996 where
1997 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
1998 {
1999 use crate::instant::FzInstant as Instant;
2000 let t0 = Instant::now();
2001 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
2003 if collected.is_empty() {
2004 return Ok(());
2005 }
2006 let sheet_id = self.sheet_id_mut(sheet);
2007 let budgets = self.self_admission_budgets();
2008 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2009 let coordinates = collected
2010 .iter()
2011 .map(|(row, col, _)| (*row, *col))
2012 .collect::<Vec<_>>();
2013 let usage = self.preview_value_mutations(sheet_id, &coordinates)?;
2014 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2015 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2016 }
2017 self.reserve_cells(collected.len());
2018 let t_reserve = Instant::now();
2019 let mut new_vertices: Vec<(VertexAddr, u32)> = Vec::with_capacity(collected.len());
2020 let mut index_items: Vec<(GridAddr, VertexId)> = Vec::with_capacity(collected.len());
2021 let mut new_value_coords: Vec<(GridAddr, VertexId)> = Vec::with_capacity(collected.len());
2023 let mut new_value_literals: Vec<LiteralValue> = Vec::with_capacity(collected.len());
2024 let assume_new = self.first_load_assume_new
2026 && self
2027 .sheet_id(sheet)
2028 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
2029 .unwrap_or(false);
2030
2031 for (row, col, value) in collected {
2032 let value = normalize_stored_literal(value);
2033 let coord = Coord::from_excel(row, col, true, true);
2034 let addr = CellRef::new(sheet_id, coord);
2035 if !assume_new && let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
2036 if matches!(
2037 self.store.kind(existing_id),
2038 VertexKind::FormulaScalar | VertexKind::FormulaArray
2039 ) {
2040 self.remove_dependent_edges(existing_id);
2041 self.detach_vertex_from_names(existing_id);
2042 self.clear_pending_name_references(existing_id);
2043 self.vertex_formulas.remove(&existing_id);
2044 }
2045 if self.value_cache_enabled {
2046 let value_ref = self.data_store.store_value(value);
2047 self.vertex_values.insert(existing_id, value_ref);
2048 } else {
2049 self.vertex_values.remove(&existing_id);
2050 }
2051 self.store.set_kind(existing_id, VertexKind::Cell);
2052 continue;
2053 }
2054 let packed = GridAddr::from_coord(AbsCoord::from_excel(row, col));
2055 let vertex_id = self
2056 .store
2057 .allocate(VertexAddr::grid(packed), sheet_id, 0x00);
2058 self.store.set_kind(vertex_id, VertexKind::Cell);
2059 new_value_coords.push((packed, vertex_id));
2061 new_value_literals.push(value);
2062 self.cell_to_vertex.insert(addr, vertex_id);
2063 new_vertices.push((VertexAddr::grid(packed), vertex_id.0));
2064 index_items.push((packed, vertex_id));
2065 }
2066 if self.value_cache_enabled && !new_value_literals.is_empty() {
2068 let vrefs = self.data_store.store_values_batch(new_value_literals);
2069 debug_assert_eq!(vrefs.len(), new_value_coords.len());
2070 for (i, (_pc, vid)) in new_value_coords.iter().enumerate() {
2071 self.vertex_values.insert(*vid, vrefs[i]);
2072 }
2073 }
2074 let t_after_alloc = Instant::now();
2075 if !new_vertices.is_empty() {
2076 let t_edges_start = Instant::now();
2077 self.edges.add_vertices_batch(&new_vertices);
2078 let t_edges_done = Instant::now();
2079
2080 match self.config.sheet_index_mode {
2081 crate::engine::SheetIndexMode::Eager => {
2082 self.sheet_index_mut(sheet_id)
2083 .add_vertices_batch(&index_items);
2084 }
2085 crate::engine::SheetIndexMode::Lazy => {
2086 }
2088 crate::engine::SheetIndexMode::FastBatch => {
2089 self.sheet_index_mut(sheet_id)
2091 .add_vertices_batch(&index_items);
2092 }
2093 }
2094 let t_index_done = Instant::now();
2095 }
2096 Ok(())
2097 }
2098
2099 pub fn set_cell_formula(
2101 &mut self,
2102 sheet: &str,
2103 row: u32,
2104 col: u32,
2105 ast: ASTNode,
2106 ) -> Result<OperationSummary, ExcelError> {
2107 self.set_cell_formula_with_volatility(sheet, row, col, ast, false)
2108 }
2109
2110 pub fn set_cell_formula_with_volatility(
2113 &mut self,
2114 sheet: &str,
2115 row: u32,
2116 col: u32,
2117 ast: ASTNode,
2118 _volatile: bool,
2119 ) -> Result<OperationSummary, ExcelError> {
2120 let sheet_id = self.sheet_id_mut(sheet);
2121 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2122 let provider = RegistryFunctionProvider;
2123 let ingested = {
2124 let mut pipeline = self.ingest_pipeline(&provider);
2125 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
2126 };
2127 self.set_cell_formula_with_plan(
2128 sheet,
2129 row,
2130 col,
2131 ingested.ast_id,
2132 &ingested.dep_plan,
2133 ingested.dep_plan.volatile,
2134 ingested.dep_plan.dynamic,
2135 )
2136 }
2137
2138 pub(crate) fn set_cell_formula_with_plan(
2139 &mut self,
2140 sheet: &str,
2141 row: u32,
2142 col: u32,
2143 ast_id: AstNodeId,
2144 plan: &DependencyPlanRow,
2145 volatile: bool,
2146 dynamic: bool,
2147 ) -> Result<OperationSummary, ExcelError> {
2148 let dbg = std::env::var("FZ_DEBUG_LOAD")
2149 .ok()
2150 .is_some_and(|v| v != "0");
2151 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
2152 .ok()
2153 .and_then(|s| s.parse().ok())
2154 .unwrap_or(0);
2155 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
2156 .ok()
2157 .and_then(|s| s.parse().ok())
2158 .unwrap_or(0);
2159 let t0 = if dbg {
2160 Some(crate::instant::FzInstant::now())
2161 } else {
2162 None
2163 };
2164 let sheet_id = self.sheet_id_mut(sheet);
2165 let budgets = self.self_admission_budgets();
2166 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2167 let usage = self.preview_formula_mutations(&[(sheet_id, row, col, plan.clone())])?;
2168 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2169 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2170 }
2171 let coord = Coord::from_excel(row, col, true, true);
2172 let addr = CellRef::new(sheet_id, coord);
2173
2174 let t_dep0 = if dbg {
2175 Some(crate::instant::FzInstant::now())
2176 } else {
2177 None
2178 };
2179 let mut created_placeholders = Vec::new();
2180 let mut new_dependencies = Vec::with_capacity(plan.direct_cell_deps.len());
2181 for dep in &plan.direct_cell_deps {
2182 let dep_vid = self.get_or_create_vertex(dep, &mut created_placeholders);
2183 if !new_dependencies.contains(&dep_vid) {
2184 new_dependencies.push(dep_vid);
2185 }
2186 }
2187 let mut named_dependencies = Vec::new();
2188 let mut unresolved_names = Vec::new();
2189 for name in plan
2190 .resolved_named_refs
2191 .iter()
2192 .chain(plan.named_refs.iter())
2193 {
2194 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
2195 if !new_dependencies.contains(&named.vertex) {
2196 new_dependencies.push(named.vertex);
2197 }
2198 if !named_dependencies.contains(&named.vertex) {
2199 named_dependencies.push(named.vertex);
2200 }
2201 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
2202 if !new_dependencies.contains(&source.vertex) {
2203 new_dependencies.push(source.vertex);
2204 }
2205 } else {
2206 unresolved_names.push(name.clone());
2207 }
2208 }
2209 for source_name in &plan.source_refs {
2210 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
2211 if !new_dependencies.contains(&source.vertex) {
2212 new_dependencies.push(source.vertex);
2213 }
2214 } else if let Some(source) = self.resolve_source_table_entry(source_name)
2215 && !new_dependencies.contains(&source.vertex)
2216 {
2217 new_dependencies.push(source.vertex);
2218 }
2219 }
2220 for table_name in &plan.table_refs {
2221 if let Some(table) = self.resolve_table_entry(table_name) {
2222 if !new_dependencies.contains(&table.vertex) {
2223 new_dependencies.push(table.vertex);
2224 }
2225 } else if let Some(source) = self.resolve_source_table_entry(table_name)
2226 && !new_dependencies.contains(&source.vertex)
2227 {
2228 new_dependencies.push(source.vertex);
2229 }
2230 }
2231 if let (true, Some(t)) = (dbg, t_dep0) {
2232 let elapsed = t.elapsed().as_millis();
2233 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
2234 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
2235 if (dep_ms_thresh == 0 && sample_n == 0 && row.is_multiple_of(1000)) || do_log {
2236 eprintln!(
2237 "[fz][dep] {}!{} planned: deps={}, ranges={}, placeholders={}, names={} in {} ms",
2238 self.sheet_name(sheet_id),
2239 crate::reference::Coord::from_excel(row, col, true, true),
2240 new_dependencies.len(),
2241 plan.range_deps.len(),
2242 created_placeholders.len(),
2243 named_dependencies.len(),
2244 elapsed
2245 );
2246 }
2247 }
2248
2249 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
2251
2252 self.ref_error_vertices.remove(&addr_vertex_id);
2254
2255 if new_dependencies.contains(&addr_vertex_id) && !self.config.cycle.allows_self_dependency()
2270 {
2271 return Err(ExcelError::new(ExcelErrorKind::Circ)
2272 .with_message("Self-reference detected".to_string()));
2273 }
2274
2275 for &name_vertex in &named_dependencies {
2276 let mut visited = FxHashSet::default();
2277 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
2278 return Err(ExcelError::new(ExcelErrorKind::Circ)
2279 .with_message("Circular reference through named range".to_string()));
2280 }
2281 }
2282
2283 self.remove_dependent_edges(addr_vertex_id);
2285 self.detach_vertex_from_names(addr_vertex_id);
2286 self.clear_pending_name_references(addr_vertex_id);
2287
2288 self.store
2290 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
2291 self.vertex_formulas.insert(addr_vertex_id, ast_id);
2292 self.store.set_dirty(addr_vertex_id, true);
2293
2294 self.vertex_values.remove(&addr_vertex_id);
2296
2297 self.mark_volatile(addr_vertex_id, volatile);
2298 self.store.set_dynamic(addr_vertex_id, dynamic);
2299
2300 if !named_dependencies.is_empty() {
2301 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
2302 }
2303 for unresolved_name in &unresolved_names {
2304 self.record_pending_name_reference(sheet_id, unresolved_name, addr_vertex_id);
2305 }
2306
2307 if let (true, Some(t)) = (dbg, t0) {
2308 let elapsed = t.elapsed().as_millis();
2309 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
2310 if log_set {
2311 eprintln!(
2312 "[fz][set] {}!{} total {} ms",
2313 self.sheet_name(sheet_id),
2314 crate::reference::Coord::from_excel(row, col, true, true),
2315 elapsed
2316 );
2317 }
2318 }
2319
2320 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
2322 self.add_range_dependent_edges(addr_vertex_id, &plan.range_deps, sheet_id);
2323
2324 Ok(OperationSummary {
2325 affected_vertices: self.mark_dirty(addr_vertex_id),
2326 created_placeholders,
2327 })
2328 }
2329
2330 pub(crate) fn rewrite_structured_references_for_cell(
2331 &self,
2332 ast: &mut ASTNode,
2333 cell: CellRef,
2334 ) -> Result<bool, ExcelError> {
2335 self.rewrite_structured_references_node(ast, cell)
2336 }
2337
2338 fn rewrite_structured_references_node(
2339 &self,
2340 node: &mut ASTNode,
2341 cell: CellRef,
2342 ) -> Result<bool, ExcelError> {
2343 match &mut node.node_type {
2344 ASTNodeType::Reference { reference, .. } => {
2345 self.rewrite_structured_reference(reference, cell)
2346 }
2347 ASTNodeType::UnaryOp { expr, .. } => {
2348 self.rewrite_structured_references_node(expr, cell)
2349 }
2350 ASTNodeType::BinaryOp { left, right, .. } => {
2351 let left_rewritten = self.rewrite_structured_references_node(left, cell)?;
2352 let right_rewritten = self.rewrite_structured_references_node(right, cell)?;
2353 Ok(left_rewritten || right_rewritten)
2354 }
2355 ASTNodeType::Function { args, .. } => {
2356 let mut rewritten = false;
2357 for a in args.iter_mut() {
2358 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2359 }
2360 Ok(rewritten)
2361 }
2362 ASTNodeType::Call { callee, args } => {
2363 let mut rewritten = self.rewrite_structured_references_node(callee, cell)?;
2364 for a in args.iter_mut() {
2365 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2366 }
2367 Ok(rewritten)
2368 }
2369 ASTNodeType::Array(rows) => {
2370 let mut rewritten = false;
2371 for r in rows.iter_mut() {
2372 for item in r.iter_mut() {
2373 rewritten |= self.rewrite_structured_references_node(item, cell)?;
2374 }
2375 }
2376 Ok(rewritten)
2377 }
2378 ASTNodeType::Literal(_) | ASTNodeType::Omitted => Ok(false),
2379 }
2380 }
2381
2382 fn rewrite_structured_reference(
2383 &self,
2384 reference: &mut ReferenceType,
2385 cell: CellRef,
2386 ) -> Result<bool, ExcelError> {
2387 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
2388
2389 let ReferenceType::Table(tref) = reference else {
2390 return Ok(false);
2391 };
2392
2393 if !tref.name.is_empty() {
2395 return Ok(false);
2396 }
2397
2398 let col_name = match &tref.specifier {
2399 Some(TableSpecifier::Combination(parts)) => {
2400 let mut saw_this_row = false;
2401 let mut col: Option<&str> = None;
2402 for p in parts {
2403 match p.as_ref() {
2404 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
2405 saw_this_row = true;
2406 }
2407 TableSpecifier::Column(c) => {
2408 if col.is_some() {
2409 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2410 "This-row structured reference with multiple columns is not supported"
2411 .to_string(),
2412 ));
2413 }
2414 col = Some(c.as_str());
2415 }
2416 other => {
2417 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2418 format!(
2419 "Unsupported this-row structured reference component: {other}"
2420 ),
2421 ));
2422 }
2423 }
2424 }
2425 if !saw_this_row {
2426 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2427 "Unnamed structured reference requires a this-row selector".to_string(),
2428 ));
2429 }
2430 col.ok_or_else(|| {
2431 ExcelError::new(ExcelErrorKind::NImpl).with_message(
2432 "This-row structured reference missing column selector".to_string(),
2433 )
2434 })?
2435 }
2436 _ => {
2437 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2438 "Unnamed structured reference form is not supported".to_string(),
2439 ));
2440 }
2441 };
2442
2443 let Some(table) = self.find_table_containing_cell(cell) else {
2444 return Err(ExcelError::new(ExcelErrorKind::Name)
2445 .with_message("This-row structured reference used outside a table".to_string()));
2446 };
2447
2448 let row0 = cell.coord.row();
2449 let col0 = cell.coord.col();
2450 let sr0 = table.range.start.coord.row();
2451 let sc0 = table.range.start.coord.col();
2452 let er0 = table.range.end.coord.row();
2453 let ec0 = table.range.end.coord.col();
2454
2455 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2456 return Err(ExcelError::new(ExcelErrorKind::Name)
2457 .with_message("This-row structured reference used outside a table".to_string()));
2458 }
2459
2460 if table.header_row && row0 == sr0 {
2461 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
2462 "This-row structured references are not valid in the table header row".to_string(),
2463 ));
2464 }
2465
2466 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
2467 if row0 < data_start {
2468 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
2469 "This-row structured references require a data/totals row context".to_string(),
2470 ));
2471 }
2472
2473 let Some(idx) = table.col_index(col_name) else {
2474 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
2475 "Unknown table column in this-row reference: {col_name}"
2476 )));
2477 };
2478 let target_col0 = sc0 + (idx as u32);
2479 let target_row = row0 + 1;
2480 let target_col = target_col0 + 1;
2481
2482 *reference = ReferenceType::Cell {
2483 sheet: None,
2484 row: target_row,
2485 col: target_col,
2486 row_abs: true,
2487 col_abs: true,
2488 };
2489
2490 Ok(true)
2491 }
2492
2493 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
2494 let row0 = cell.coord.row();
2495 let col0 = cell.coord.col();
2496
2497 let mut best: Option<&tables::TableEntry> = None;
2498 let mut best_area: u64 = u64::MAX;
2499 let mut best_name: &str = "";
2500
2501 for t in self.tables.values() {
2502 if t.sheet_id() != cell.sheet_id {
2503 continue;
2504 }
2505 let sr0 = t.range.start.coord.row();
2506 let sc0 = t.range.start.coord.col();
2507 let er0 = t.range.end.coord.row();
2508 let ec0 = t.range.end.coord.col();
2509 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2510 continue;
2511 }
2512
2513 let h = (er0 - sr0 + 1) as u64;
2514 let w = (ec0 - sc0 + 1) as u64;
2515 let area = h.saturating_mul(w);
2516 let name = t.name.as_str();
2517 let better = match best {
2518 None => true,
2519 Some(_) => area < best_area || (area == best_area && name < best_name),
2520 };
2521 if better {
2522 best = Some(t);
2523 best_area = area;
2524 best_name = name;
2525 }
2526 }
2527
2528 best
2529 }
2530
2531 #[allow(clippy::type_complexity)]
2532 pub(crate) fn fp8_parity_extract_dependencies_with_pending_names(
2533 &mut self,
2534 ast: &ASTNode,
2535 current_sheet_id: SheetId,
2536 ) -> Result<
2537 (
2538 Vec<VertexId>,
2539 Vec<SharedRangeRef<'static>>,
2540 Vec<CellRef>,
2541 Vec<VertexId>,
2542 Vec<String>,
2543 ),
2544 ExcelError,
2545 > {
2546 self.extract_dependencies_with_pending_names(ast, current_sheet_id)
2547 }
2548
2549 pub(crate) fn fp8_parity_is_ast_volatile(&self, ast: &ASTNode) -> bool {
2550 self.is_ast_volatile(ast)
2551 }
2552
2553 pub fn set_cell_value_ref(
2554 &mut self,
2555 cell: formualizer_common::SheetCellRef<'_>,
2556 value: LiteralValue,
2557 ) -> Result<OperationSummary, ExcelError> {
2558 let owned = cell.into_owned();
2559 let sheet_id = match owned.sheet {
2560 formualizer_common::SheetLocator::Id(id) => id,
2561 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
2562 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2563 };
2564 let sheet_name = self.sheet_name(sheet_id).to_string();
2565 self.set_cell_value(
2566 &sheet_name,
2567 owned.coord.row() + 1,
2568 owned.coord.col() + 1,
2569 value,
2570 )
2571 }
2572
2573 pub fn set_cell_formula_ref(
2574 &mut self,
2575 cell: formualizer_common::SheetCellRef<'_>,
2576 ast: ASTNode,
2577 ) -> Result<OperationSummary, ExcelError> {
2578 let owned = cell.into_owned();
2579 let sheet_id = match owned.sheet {
2580 formualizer_common::SheetLocator::Id(id) => id,
2581 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
2582 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2583 };
2584 let sheet_name = self.sheet_name(sheet_id).to_string();
2585 self.set_cell_formula(
2586 &sheet_name,
2587 owned.coord.row() + 1,
2588 owned.coord.col() + 1,
2589 ast,
2590 )
2591 }
2592
2593 pub fn get_cell_value_ref(
2594 &self,
2595 cell: formualizer_common::SheetCellRef<'_>,
2596 ) -> Option<LiteralValue> {
2597 let owned = cell.into_owned();
2598 let sheet_id = match owned.sheet {
2599 formualizer_common::SheetLocator::Id(id) => id,
2600 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
2601 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2602 };
2603 let sheet_name = self.sheet_name(sheet_id);
2604 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
2605 }
2606
2607 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
2609 if !self.value_cache_enabled {
2610 #[cfg(debug_assertions)]
2611 {
2612 self.graph_value_read_attempts
2613 .fetch_add(1, Ordering::Relaxed);
2614 }
2615 return None;
2616 }
2617 let sheet_id = self.sheet_reg.get_id(sheet)?;
2618 let coord = Coord::from_excel(row, col, true, true);
2619 let addr = CellRef::new(sheet_id, coord);
2620
2621 self.get_vertex_id_for_address(&addr)
2622 .and_then(|&vertex_id| {
2623 self.vertex_values
2625 .get(&vertex_id)
2626 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
2627 })
2628 }
2629
2630 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
2632 self.mark_dirty_many(&[vertex_id])
2633 }
2634
2635 pub(crate) fn mark_dirty_many(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
2655 if self.deferred_dirty_depth > 0 {
2656 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
2657 return vertex_ids.to_vec();
2658 }
2659 let mut affected = FxHashSet::default();
2660 let mut to_visit = Vec::new();
2661 let mut visited_for_propagation = FxHashSet::default();
2662
2663 for &vertex_id in vertex_ids {
2664 let is_formula = matches!(
2668 self.store.kind(vertex_id),
2669 VertexKind::FormulaScalar
2670 | VertexKind::FormulaArray
2671 | VertexKind::NamedScalar
2672 | VertexKind::NamedArray
2673 );
2674
2675 if is_formula {
2676 to_visit.push(vertex_id);
2677 } else {
2678 affected.insert(vertex_id);
2680 }
2681
2682 {
2684 if let Some(dependents) = self.dependents_slice(vertex_id) {
2686 to_visit.extend(dependents.iter().copied());
2687 } else {
2688 let dependents = self.get_dependents(vertex_id);
2689 to_visit.extend(dependents);
2690 }
2691
2692 if let Some(name_set) = self.cell_to_name_dependents.get(&vertex_id) {
2693 for &name_vertex in name_set {
2694 to_visit.push(name_vertex);
2695 }
2696 }
2697
2698 to_visit.extend(self.collect_range_dependents_for_vertex(vertex_id));
2699 }
2700 }
2701
2702 while let Some(id) = to_visit.pop() {
2703 if !visited_for_propagation.insert(id) {
2704 continue; }
2706 self.dirty_propagation_visits += 1;
2707 affected.insert(id);
2708
2709 self.store.set_dirty(id, true);
2711
2712 if let Some(dependents) = self.dependents_slice(id) {
2714 to_visit.extend(dependents.iter().copied());
2715 } else {
2716 let dependents = self.get_dependents(id);
2717 to_visit.extend(dependents);
2718 }
2719 to_visit.extend(self.collect_range_dependents_for_vertex(id));
2720 }
2721
2722 self.formula_dirty.legacy_extend(affected.iter().copied());
2724
2725 affected.into_iter().collect()
2727 }
2728
2729 pub(crate) fn dirty_propagation_visits(&self) -> u64 {
2732 self.dirty_propagation_visits
2733 }
2734
2735 pub fn begin_deferred_dirty(&mut self) {
2756 self.edges.begin_batch();
2757 self.deferred_dirty_depth += 1;
2758 }
2759
2760 pub fn end_deferred_dirty(&mut self) -> Vec<VertexId> {
2765 debug_assert!(
2766 self.deferred_dirty_depth > 0,
2767 "end_deferred_dirty without matching begin_deferred_dirty"
2768 );
2769 self.edges.end_batch();
2770 self.deferred_dirty_depth = self.deferred_dirty_depth.saturating_sub(1);
2771 if self.deferred_dirty_depth > 0 {
2772 return Vec::new();
2773 }
2774 let pending = std::mem::take(&mut self.deferred_dirty_pending);
2775 if pending.is_empty() {
2776 return Vec::new();
2777 }
2778 let live: Vec<VertexId> = pending
2779 .into_iter()
2780 .filter(|&id| self.vertex_exists(id))
2781 .collect();
2782 self.mark_dirty_many(&live)
2783 }
2784
2785 pub fn deferred_dirty_active(&self) -> bool {
2788 self.deferred_dirty_depth > 0
2789 }
2790
2791 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
2793 let mut combined = FxHashSet::default();
2794 combined.extend(self.formula_dirty.legacy_iter().copied());
2795 combined.extend(&self.volatile_vertices);
2796
2797 let mut result: Vec<VertexId> = combined
2798 .into_iter()
2799 .filter(|&id| {
2800 self.store.vertex_exists_active(id)
2803 && matches!(
2804 self.store.kind(id),
2805 VertexKind::FormulaScalar
2806 | VertexKind::FormulaArray
2807 | VertexKind::NamedScalar
2808 | VertexKind::NamedArray
2809 )
2810 })
2811 .collect();
2812 result.sort_unstable();
2813 result
2814 }
2815
2816 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
2818 for &vertex_id in vertices {
2819 self.store.set_dirty(vertex_id, false);
2820 self.formula_dirty.legacy_remove(&vertex_id);
2821 }
2822 }
2823
2824 pub fn clear_volatile_flags(&mut self) {
2826 self.volatile_vertices.clear();
2827 }
2828
2829 pub(crate) fn redirty_volatiles(&mut self) {
2834 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
2835 let _ = self.mark_dirty_many(&volatile_ids);
2836 }
2837
2838 pub(crate) fn redirty_iterative_members(&mut self, members: &[VertexId]) {
2851 let live: Vec<VertexId> = members
2852 .iter()
2853 .copied()
2854 .filter(|&id| self.vertex_exists(id))
2855 .collect();
2856 let _ = self.mark_dirty_many(&live);
2857 }
2858
2859 fn get_or_create_vertex(
2860 &mut self,
2861 addr: &CellRef,
2862 created_placeholders: &mut Vec<CellRef>,
2863 ) -> VertexId {
2864 if let Some(&vertex_id) = self.cell_to_vertex.get(addr) {
2865 return vertex_id;
2866 }
2867
2868 if self.first_load_assume_new {
2873 let packed = Self::packed_cell_key(
2874 addr.sheet_id,
2875 AbsCoord::new(addr.coord.row(), addr.coord.col()),
2876 );
2877 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
2878 self.cell_to_vertex.insert(*addr, existing);
2879 return existing;
2880 }
2881 }
2882
2883 created_placeholders.push(*addr);
2884 let position = GridAddr::new(addr.coord.row(), addr.coord.col());
2885 let vertex_id = self
2886 .store
2887 .allocate(VertexAddr::grid(position), addr.sheet_id, 0x00);
2888
2889 self.edges
2891 .add_vertex(VertexAddr::grid(position), vertex_id.0);
2892
2893 self.sheet_index_mut(addr.sheet_id)
2895 .add_vertex(position, vertex_id);
2896
2897 self.store.set_kind(vertex_id, VertexKind::Empty);
2898 self.cell_to_vertex.insert(*addr, vertex_id);
2899 vertex_id
2900 }
2901
2902 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
2903 self.edges.begin_batch();
2905
2906 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
2909 if self.pk_order.is_some()
2910 && let Some(mut pk) = self.pk_order.take()
2911 {
2912 pk.ensure_nodes(std::iter::once(dependent));
2913 pk.ensure_nodes(dependencies.iter().copied());
2914 {
2915 let adapter = GraphAdapter { g: self };
2916 for &dep_id in dependencies {
2917 match pk.try_add_edge(&adapter, dep_id, dependent) {
2918 Ok(_) => {}
2919 Err(_cycle) => {
2920 if self.config.pk_reject_cycle_edges {
2921 skip_deps.insert(dep_id);
2922 } else {
2923 pk.rebuild_full(&adapter);
2924 }
2925 }
2926 }
2927 }
2928 } self.pk_order = Some(pk);
2930 }
2931
2932 for &dep_id in dependencies {
2934 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
2935 continue;
2936 }
2937 self.edges.add_edge(dependent, dep_id);
2938 #[cfg(test)]
2939 {
2940 if let Ok(mut g) = self.instr.lock() {
2941 g.edges_added += 1;
2942 }
2943 }
2944 }
2945
2946 self.edges.end_batch();
2947 }
2948
2949 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
2951 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
2953 if self.pk_order.is_some()
2954 && let Some(mut pk) = self.pk_order.take()
2955 {
2956 pk.ensure_nodes(std::iter::once(dependent));
2957 pk.ensure_nodes(dependencies.iter().copied());
2958 {
2959 let adapter = GraphAdapter { g: self };
2960 for &dep_id in dependencies {
2961 match pk.try_add_edge(&adapter, dep_id, dependent) {
2962 Ok(_) => {}
2963 Err(_cycle) => {
2964 if self.config.pk_reject_cycle_edges {
2965 skip_deps.insert(dep_id);
2966 } else {
2967 pk.rebuild_full(&adapter);
2968 }
2969 }
2970 }
2971 }
2972 }
2973 self.pk_order = Some(pk);
2974 }
2975
2976 for &dep_id in dependencies {
2977 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
2978 continue;
2979 }
2980 self.edges.add_edge(dependent, dep_id);
2981 #[cfg(test)]
2982 {
2983 if let Ok(mut g) = self.instr.lock() {
2984 g.edges_added += 1;
2985 }
2986 }
2987 }
2988 }
2989
2990 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
2992 where
2993 I: IntoIterator<Item = (u32, u32, ASTNode)>,
2994 {
2995 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
2996 if collected.is_empty() {
2997 return Ok(0);
2998 }
2999 let vol_flags: Vec<bool> = collected
3000 .iter()
3001 .map(|(_, _, ast)| self.is_ast_volatile(ast))
3002 .collect();
3003 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
3004 }
3005
3006 pub fn bulk_set_formulas_with_volatility(
3007 &mut self,
3008 sheet: &str,
3009 collected: Vec<(u32, u32, ASTNode)>,
3010 _vol_flags: Vec<bool>,
3011 ) -> Result<usize, ExcelError> {
3012 let sheet_id = self.sheet_id_mut(sheet);
3013 if collected.is_empty() {
3014 return Ok(0);
3015 }
3016 let provider = RegistryFunctionProvider;
3017 let ingested = {
3018 let mut pipeline = self.ingest_pipeline(&provider);
3019 let inputs = collected.into_iter().map(|(row, col, ast)| {
3020 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
3021 (FormulaAstInput::Tree(ast), placement, None)
3022 });
3023 pipeline.ingest_batch(inputs)?
3024 };
3025 let planned = ingested
3026 .into_iter()
3027 .map(|formula| {
3028 (
3029 formula.placement.coord.row() + 1,
3030 formula.placement.coord.col() + 1,
3031 formula.ast_id,
3032 formula.dep_plan,
3033 )
3034 })
3035 .collect();
3036 self.bulk_set_formulas_with_plans(sheet, planned)
3037 }
3038
3039 pub(crate) fn bulk_set_formulas_with_plans(
3040 &mut self,
3041 sheet: &str,
3042 planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
3043 ) -> Result<usize, ExcelError> {
3044 let sheet_id = self.sheet_id_mut(sheet);
3045 if planned.is_empty() {
3046 return Ok(0);
3047 }
3048 let budgets = self.self_admission_budgets();
3049 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3050 let admission_plans = planned
3051 .iter()
3052 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone()))
3053 .collect::<Vec<_>>();
3054 let usage = self.preview_formula_mutations(&admission_plans)?;
3055 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
3056 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3057 }
3058 let mut created_placeholders: Vec<CellRef> = Vec::new();
3059 let mut target_vids: Vec<VertexId> = Vec::with_capacity(planned.len());
3060 for (row, col, _, _) in &planned {
3061 let addr = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
3062 target_vids.push(self.get_or_create_vertex(&addr, &mut created_placeholders));
3063 }
3064 for (_, _, _, plan) in &planned {
3069 for cell in &plan.direct_cell_deps {
3070 self.get_or_create_vertex(cell, &mut created_placeholders);
3071 }
3072 }
3073
3074 for (i, &tvid) in target_vids.iter().enumerate() {
3075 if self.vertex_formulas.contains_key(&tvid) {
3076 self.remove_dependent_edges(tvid);
3077 }
3078 self.detach_vertex_from_names(tvid);
3079 self.clear_pending_name_references(tvid);
3080 self.store.set_kind(tvid, VertexKind::FormulaScalar);
3081 self.store.set_dirty(tvid, true);
3082 self.vertex_values.remove(&tvid);
3083 self.vertex_formulas.insert(tvid, planned[i].2);
3084 self.mark_volatile(tvid, planned[i].3.volatile);
3085 self.store.set_dynamic(tvid, planned[i].3.dynamic);
3086 }
3087 self.formula_dirty
3088 .legacy_extend(target_vids.iter().copied());
3089
3090 self.edges.begin_batch();
3091 for (i, tvid) in target_vids.iter().copied().enumerate() {
3092 let plan = &planned[i].3;
3093 let mut deps: Vec<VertexId> = Vec::new();
3094 for cell in &plan.direct_cell_deps {
3095 let dep_vid = self.get_or_create_vertex(cell, &mut created_placeholders);
3096 if !deps.contains(&dep_vid) {
3097 deps.push(dep_vid);
3098 }
3099 }
3100
3101 let mut name_vertices = Vec::new();
3102 for name in plan
3103 .resolved_named_refs
3104 .iter()
3105 .chain(plan.named_refs.iter())
3106 {
3107 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
3108 if !deps.contains(&named.vertex) {
3109 deps.push(named.vertex);
3110 }
3111 if !name_vertices.contains(&named.vertex) {
3112 name_vertices.push(named.vertex);
3113 }
3114 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
3115 if !deps.contains(&source.vertex) {
3116 deps.push(source.vertex);
3117 }
3118 } else {
3119 self.record_pending_name_reference(sheet_id, name, tvid);
3120 }
3121 }
3122 for source_name in &plan.source_refs {
3123 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
3124 if !deps.contains(&source.vertex) {
3125 deps.push(source.vertex);
3126 }
3127 } else if let Some(source) = self.resolve_source_table_entry(source_name)
3128 && !deps.contains(&source.vertex)
3129 {
3130 deps.push(source.vertex);
3131 }
3132 }
3133 for table_name in &plan.table_refs {
3134 if let Some(table) = self.resolve_table_entry(table_name) {
3135 if !deps.contains(&table.vertex) {
3136 deps.push(table.vertex);
3137 }
3138 } else if let Some(source) = self.resolve_source_table_entry(table_name)
3139 && !deps.contains(&source.vertex)
3140 {
3141 deps.push(source.vertex);
3142 }
3143 }
3144 if !name_vertices.is_empty() {
3145 self.attach_vertex_to_names(tvid, &name_vertices);
3146 }
3147 if !deps.is_empty() {
3148 self.add_dependent_edges_nobatch(tvid, &deps);
3149 }
3150 self.add_range_dependent_edges(tvid, &plan.range_deps, sheet_id);
3151 }
3152 self.edges.end_batch();
3153
3154 Ok(planned.len())
3155 }
3156
3157 pub fn add_dependency_edge(
3159 &mut self,
3160 dependent: VertexId,
3161 dependency: VertexId,
3162 ) -> Result<(), ExcelError> {
3163 if dependent == dependency {
3164 return Ok(());
3165 }
3166 let budgets = self.self_admission_budgets();
3167 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3168 let stats = self.baseline_stats();
3169 let added = usize::from(!self.get_dependencies(dependent).contains(&dependency));
3170 crate::engine::resource_ledger::preflight_graph_admission(
3171 &budgets,
3172 crate::engine::resource_ledger::GraphAdmission {
3173 final_vertices: stats.graph_vertex_count,
3174 final_edges: stats.graph_edge_count.checked_add(added).ok_or_else(|| {
3175 ExcelError::new(ExcelErrorKind::NImpl)
3176 .with_message("graph edge count overflow")
3177 })?,
3178 materialization_cells: 0,
3179 added_vertices: 0,
3180 added_edges: added,
3181 },
3182 None,
3183 )
3184 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3185 }
3186 if self.pk_order.is_some()
3188 && let Some(mut pk) = self.pk_order.take()
3189 {
3190 pk.ensure_nodes(std::iter::once(dependent));
3191 pk.ensure_nodes(std::iter::once(dependency));
3192 let adapter = GraphAdapter { g: self };
3193 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
3194 pk.rebuild_full(&adapter);
3196 }
3197 self.pk_order = Some(pk);
3198 }
3199 self.edges.add_edge(dependent, dependency);
3200 self.store.set_dirty(dependent, true);
3201 self.formula_dirty.legacy_insert(dependent);
3202 Ok(())
3203 }
3204
3205 fn remove_dependent_edges(&mut self, vertex: VertexId) {
3206 let dependencies = self.edges.out_edges(vertex);
3208
3209 self.edges.begin_batch();
3210 if self.pk_order.is_some()
3211 && let Some(mut pk) = self.pk_order.take()
3212 {
3213 for dep in &dependencies {
3214 pk.remove_edge(*dep, vertex);
3215 }
3216 self.pk_order = Some(pk);
3217 }
3218 for dep in dependencies {
3219 self.edges.remove_edge(vertex, dep);
3220 }
3221 self.edges.end_batch();
3222
3223 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
3225 let old_sheet_id = self.store.sheet_id(vertex);
3226
3227 for range in &old_ranges {
3228 let sheet_id = self
3230 .sheet_reg
3231 .resolve_locator(&range.sheet, old_sheet_id)
3232 .unwrap_or(old_sheet_id);
3233 let s_row = range.start_row.map(|b| b.index);
3234 let e_row = range.end_row.map(|b| b.index);
3235 let s_col = range.start_col.map(|b| b.index);
3236 let e_col = range.end_col.map(|b| b.index);
3237
3238 let mut keys_to_clean = FxHashSet::default();
3239
3240 let col_stripes = (s_row.is_none() && e_row.is_none())
3241 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
3242 let row_stripes = (s_col.is_none() && e_col.is_none())
3243 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
3244
3245 if col_stripes && !row_stripes {
3246 let sc = s_col.unwrap_or(0);
3247 let ec = e_col.unwrap_or(sc);
3248 for col in sc..=ec {
3249 keys_to_clean.insert(StripeKey {
3250 sheet_id,
3251 stripe_type: StripeType::Column,
3252 index: col,
3253 });
3254 }
3255 } else if row_stripes && !col_stripes {
3256 let sr = s_row.unwrap_or(0);
3257 let er = e_row.unwrap_or(sr);
3258 for row in sr..=er {
3259 keys_to_clean.insert(StripeKey {
3260 sheet_id,
3261 stripe_type: StripeType::Row,
3262 index: row,
3263 });
3264 }
3265 } else {
3266 let start_row = s_row.unwrap_or(0);
3267 let start_col = s_col.unwrap_or(0);
3268 let end_row = e_row.unwrap_or(start_row);
3269 let end_col = e_col.unwrap_or(start_col);
3270
3271 let height = end_row.saturating_sub(start_row) + 1;
3272 let width = end_col.saturating_sub(start_col) + 1;
3273
3274 if self.config.enable_block_stripes && height > 1 && width > 1 {
3275 let start_block_row = start_row / BLOCK_H;
3276 let end_block_row = end_row / BLOCK_H;
3277 let start_block_col = start_col / BLOCK_W;
3278 let end_block_col = end_col / BLOCK_W;
3279
3280 for block_row in start_block_row..=end_block_row {
3281 for block_col in start_block_col..=end_block_col {
3282 keys_to_clean.insert(StripeKey {
3283 sheet_id,
3284 stripe_type: StripeType::Block,
3285 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
3286 });
3287 }
3288 }
3289 } else if height > width {
3290 for col in start_col..=end_col {
3291 keys_to_clean.insert(StripeKey {
3292 sheet_id,
3293 stripe_type: StripeType::Column,
3294 index: col,
3295 });
3296 }
3297 } else {
3298 for row in start_row..=end_row {
3299 keys_to_clean.insert(StripeKey {
3300 sheet_id,
3301 stripe_type: StripeType::Row,
3302 index: row,
3303 });
3304 }
3305 }
3306 }
3307
3308 for key in keys_to_clean {
3309 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
3310 dependents.remove(&vertex);
3311 if dependents.is_empty() {
3312 self.stripe_to_dependents.remove(&key);
3313 #[cfg(test)]
3314 {
3315 if let Ok(mut g) = self.instr.lock() {
3316 g.stripe_removes += 1;
3317 }
3318 }
3319 }
3320 }
3321 }
3322 }
3323 }
3324 }
3325
3326 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
3332 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
3333 self.vertex_values.remove(&vertex_id);
3336 return;
3337 }
3338 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
3339 self.vertex_values.insert(vertex_id, value_ref);
3340 }
3341
3342 pub fn plan_spill_region(
3344 &self,
3345 anchor: VertexId,
3346 target_cells: &[CellRef],
3347 ) -> Result<(), ExcelError> {
3348 self.plan_spill_region_allowing_formula_overwrite(anchor, target_cells, None)
3349 }
3350
3351 pub(crate) fn plan_spill_region_allowing_formula_overwrite(
3356 &self,
3357 anchor: VertexId,
3358 target_cells: &[CellRef],
3359 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
3360 ) -> Result<(), ExcelError> {
3361 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
3362 let (expected_rows, expected_cols) = if target_cells.is_empty() {
3364 (0u32, 0u32)
3365 } else {
3366 let mut min_r = u32::MAX;
3367 let mut max_r = 0u32;
3368 let mut min_c = u32::MAX;
3369 let mut max_c = 0u32;
3370 for cell in target_cells {
3371 let r = cell.coord.row();
3372 let c = cell.coord.col();
3373 if r < min_r {
3374 min_r = r;
3375 }
3376 if r > max_r {
3377 max_r = r;
3378 }
3379 if c < min_c {
3380 min_c = c;
3381 }
3382 if c > max_c {
3383 max_c = c;
3384 }
3385 }
3386 (
3387 max_r.saturating_sub(min_r).saturating_add(1),
3388 max_c.saturating_sub(min_c).saturating_add(1),
3389 )
3390 };
3391 for cell in target_cells {
3393 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
3395 Some(&existing_anchor) if existing_anchor == anchor => true,
3396 Some(_other) => {
3397 return Err(ExcelError::new(ExcelErrorKind::Spill)
3398 .with_message("BlockedBySpill")
3399 .with_extra(ExcelErrorExtra::Spill {
3400 expected_rows,
3401 expected_cols,
3402 }));
3403 }
3404 None => false,
3405 };
3406
3407 if owned_by_anchor {
3408 continue;
3409 }
3410
3411 if let Some(&vid) = self.cell_to_vertex.get(cell)
3413 && vid != anchor
3414 {
3415 match self.store.kind(vid) {
3417 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
3418 if let Some(allow) = overwritable_formulas
3419 && allow.contains(&vid)
3420 {
3421 continue;
3422 }
3423 return Err(ExcelError::new(ExcelErrorKind::Spill)
3424 .with_message("BlockedByFormula")
3425 .with_extra(ExcelErrorExtra::Spill {
3426 expected_rows,
3427 expected_cols,
3428 }));
3429 }
3430 _ => {
3431 if let Some(vref) = self.vertex_values.get(&vid) {
3433 let v = self.data_store.retrieve_value(*vref);
3434 if !matches!(v, LiteralValue::Empty) {
3435 return Err(ExcelError::new(ExcelErrorKind::Spill)
3436 .with_message("BlockedByValue")
3437 .with_extra(ExcelErrorExtra::Spill {
3438 expected_rows,
3439 expected_cols,
3440 }));
3441 }
3442 }
3443 }
3444 }
3445 }
3446 }
3447 Ok(())
3448 }
3449
3450 pub fn commit_spill_region_atomic_with_fault(
3457 &mut self,
3458 anchor: VertexId,
3459 target_cells: Vec<CellRef>,
3460 values: Vec<Vec<LiteralValue>>,
3461 fault_after_ops: Option<usize>,
3462 ) -> Result<(), ExcelError> {
3463 let budgets = self.self_admission_budgets();
3464 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3465 let admission = self.preview_spill_materialization(&target_cells)?;
3466 crate::engine::resource_ledger::preflight_graph_admission(&budgets, admission, None)
3467 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3468 }
3469
3470 let anchor_cell = self
3474 .get_cell_ref(anchor)
3475 .expect("anchor cell ref for spill commit");
3476 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
3477 let anchor_row = anchor_cell.coord.row();
3478 let anchor_col = anchor_cell.coord.col();
3479
3480 let prev_cells = self
3482 .spill_anchor_to_cells
3483 .get(&anchor)
3484 .cloned()
3485 .unwrap_or_default();
3486 let new_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
3489 target_cells.iter().copied().collect();
3490 let prev_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
3491 prev_cells.iter().copied().collect();
3492
3493 #[derive(Clone)]
3495 struct Op {
3496 sheet: String,
3497 row: u32,
3498 col: u32,
3499 new_value: LiteralValue,
3500 }
3501 let mut ops: Vec<Op> = Vec::new();
3502
3503 for cell in prev_cells.iter() {
3505 if !new_set.contains(cell) {
3506 let sheet = self.sheet_name(cell.sheet_id).to_string();
3507 ops.push(Op {
3508 sheet,
3509 row: cell.coord.row(),
3510 col: cell.coord.col(),
3511 new_value: LiteralValue::Empty,
3512 });
3513 }
3514 }
3515
3516 if !target_cells.is_empty() {
3518 let first = target_cells.first().copied().unwrap();
3519 let row0 = first.coord.row();
3520 let col0 = first.coord.col();
3521 let sheet = self.sheet_name(first.sheet_id).to_string();
3522 for (r_off, row_vals) in values.iter().enumerate() {
3523 for (c_off, v) in row_vals.iter().enumerate() {
3524 ops.push(Op {
3525 sheet: sheet.clone(),
3526 row: row0 + r_off as u32,
3527 col: col0 + c_off as u32,
3528 new_value: v.clone(),
3529 });
3530 }
3531 }
3532 }
3533
3534 #[derive(Clone)]
3536 struct OldVal {
3537 present: bool,
3538 value: LiteralValue,
3539 }
3540 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
3541
3542 for op in &ops {
3544 let old = self
3546 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
3547 .unwrap_or(LiteralValue::Empty);
3548 let present = true; old_values.push((
3550 (op.sheet.clone(), op.row, op.col),
3551 OldVal {
3552 present,
3553 value: old,
3554 },
3555 ));
3556 }
3557
3558 for (applied, op) in ops.iter().enumerate() {
3560 if let Some(n) = fault_after_ops
3561 && applied == n
3562 {
3563 for idx in (0..applied).rev() {
3564 let ((ref sheet, row, col), ref old) = old_values[idx];
3565 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
3566 self.update_vertex_value(anchor, old.value.clone());
3567 } else {
3568 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
3569 }
3570 }
3571 return Err(ExcelError::new(ExcelErrorKind::Error)
3572 .with_message("Injected persistence fault during spill commit"));
3573 }
3574 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
3575 self.update_vertex_value(anchor, op.new_value.clone());
3576 } else {
3577 let _ =
3578 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
3579 }
3580 }
3581
3582 for cell in prev_cells.iter() {
3585 if !new_set.contains(cell) {
3586 self.spill_cell_to_anchor.remove(cell);
3587 let remove_sheet = self
3588 .spill_cells_by_sheet
3589 .get_mut(&cell.sheet_id)
3590 .is_some_and(|sheet| {
3591 sheet.remove(&(cell.coord.row(), cell.coord.col()));
3592 sheet.is_empty()
3593 });
3594 if remove_sheet {
3595 self.spill_cells_by_sheet.remove(&cell.sheet_id);
3596 }
3597 }
3598 }
3599 for cell in &target_cells {
3601 self.spill_cell_to_anchor.insert(*cell, anchor);
3602 self.spill_cells_by_sheet
3603 .entry(cell.sheet_id)
3604 .or_default()
3605 .insert((cell.coord.row(), cell.coord.col()), anchor);
3606 }
3607 self.spill_anchor_to_cells.insert(anchor, target_cells);
3608 Ok(())
3609 }
3610
3611 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
3612 self.spill_anchor_to_cells
3613 .get(&anchor)
3614 .map(|v| v.as_slice())
3615 }
3616
3617 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
3618 self.spill_anchor_to_cells.contains_key(&anchor)
3619 }
3620
3621 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
3622 self.spill_cell_to_anchor.get(&cell).copied()
3623 }
3624
3625 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
3626 (
3627 self.spill_anchor_to_cells.len(),
3628 self.spill_cell_to_anchor.len(),
3629 )
3630 }
3631
3632 pub fn clear_spill_region(&mut self, anchor: VertexId) {
3634 let _ = self.clear_spill_region_bulk(anchor);
3635 }
3636
3637 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
3646 let anchor_cell = self.get_cell_ref(anchor);
3647 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
3648 return Vec::new();
3649 };
3650
3651 for cell in cells.iter() {
3653 self.spill_cell_to_anchor.remove(cell);
3654 let remove_sheet = self
3655 .spill_cells_by_sheet
3656 .get_mut(&cell.sheet_id)
3657 .is_some_and(|sheet| {
3658 sheet.remove(&(cell.coord.row(), cell.coord.col()));
3659 sheet.is_empty()
3660 });
3661 if remove_sheet {
3662 self.spill_cells_by_sheet.remove(&cell.sheet_id);
3663 }
3664 }
3665
3666 let empty_ref = if self.value_cache_enabled {
3668 Some(self.data_store.store_value(LiteralValue::Empty))
3669 } else {
3670 None
3671 };
3672
3673 let mut changed_vertices: Vec<VertexId> = Vec::new();
3675 for cell in cells.iter().copied() {
3676 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
3677 if is_anchor {
3678 continue;
3679 }
3680 let Some(&vid) = self.cell_to_vertex.get(&cell) else {
3681 continue;
3682 };
3683 if self.vertex_formulas.remove(&vid).is_some() {
3685 self.remove_dependent_edges(vid);
3688 }
3689 self.store.set_kind(vid, VertexKind::Cell);
3690 if let Some(er) = empty_ref {
3691 self.vertex_values.insert(vid, er);
3692 } else {
3693 self.vertex_values.remove(&vid);
3694 }
3695 self.store.set_dirty(vid, false);
3696 self.formula_dirty.legacy_remove(&vid);
3697 changed_vertices.push(vid);
3698 }
3699
3700 if !changed_vertices.is_empty() {
3702 self.mark_dirty_many_value_cells(&changed_vertices);
3703 }
3704
3705 cells
3706 }
3707
3708 fn mark_dirty_many_value_cells(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
3709 if vertex_ids.is_empty() {
3710 return Vec::new();
3711 }
3712
3713 if self.deferred_dirty_depth > 0 {
3721 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
3722 return vertex_ids.to_vec();
3723 }
3724
3725 if self.edges.delta_size() > 0 {
3730 self.edges.rebuild();
3731 }
3732
3733 let mut affected: FxHashSet<VertexId> = FxHashSet::default();
3734 let mut to_visit: Vec<VertexId> = Vec::new();
3735 let mut visited_for_propagation: FxHashSet<VertexId> = FxHashSet::default();
3736
3737 for &src in vertex_ids {
3739 affected.insert(src);
3740 }
3741
3742 for &src in vertex_ids {
3744 to_visit.extend(self.edges.in_edges(src));
3745 if let Some(name_set) = self.cell_to_name_dependents.get(&src) {
3746 for &name_vertex in name_set {
3747 to_visit.push(name_vertex);
3748 }
3749 }
3750 }
3751
3752 let mut bounds_by_sheet: FxHashMap<SheetId, (u32, u32, u32, u32)> = FxHashMap::default();
3754 for &src in vertex_ids {
3755 let view = self.store.view(src);
3756 let sid = view.sheet_id();
3757 let r = view.row();
3758 let c = view.col();
3759 bounds_by_sheet
3760 .entry(sid)
3761 .and_modify(|b| {
3762 b.0 = b.0.min(r);
3763 b.1 = b.1.max(r);
3764 b.2 = b.2.min(c);
3765 b.3 = b.3.max(c);
3766 })
3767 .or_insert((r, r, c, c));
3768 }
3769
3770 for (sid, (sr, er, sc, ec)) in bounds_by_sheet {
3771 to_visit.extend(self.collect_range_dependents_for_rect(sid, sr, sc, er, ec));
3772 }
3773
3774 while let Some(id) = to_visit.pop() {
3775 if !visited_for_propagation.insert(id) {
3776 continue;
3777 }
3778 self.dirty_propagation_visits += 1;
3779 affected.insert(id);
3780 self.store.set_dirty(id, true);
3781 to_visit.extend(self.edges.in_edges(id));
3782 to_visit.extend(self.collect_range_dependents_for_vertex(id));
3783 }
3784
3785 self.formula_dirty.legacy_extend(affected.iter().copied());
3786 affected.into_iter().collect()
3787 }
3788
3789 fn collect_range_dependents_for_vertex(&self, vertex_id: VertexId) -> Vec<VertexId> {
3790 let Some(position) = self.store.grid_addr(vertex_id) else {
3792 return Vec::new();
3793 };
3794 self.collect_range_dependents_for_rect(
3795 self.store.sheet_id(vertex_id),
3796 position.row(),
3797 position.col(),
3798 position.row(),
3799 position.col(),
3800 )
3801 }
3802
3803 fn collect_range_dependents_for_rect(
3804 &self,
3805 sheet_id: SheetId,
3806 start_row: u32,
3807 start_col: u32,
3808 end_row: u32,
3809 end_col: u32,
3810 ) -> Vec<VertexId> {
3811 if self.stripe_to_dependents.is_empty() {
3812 return Vec::new();
3813 }
3814 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
3815
3816 for col in start_col..=end_col {
3817 let key = StripeKey {
3818 sheet_id,
3819 stripe_type: StripeType::Column,
3820 index: col,
3821 };
3822 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3823 candidates.extend(deps);
3824 }
3825 }
3826 for row in start_row..=end_row {
3827 let key = StripeKey {
3828 sheet_id,
3829 stripe_type: StripeType::Row,
3830 index: row,
3831 };
3832 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3833 candidates.extend(deps);
3834 }
3835 }
3836 if self.config.enable_block_stripes {
3837 let br0 = start_row / BLOCK_H;
3838 let br1 = end_row / BLOCK_H;
3839 let bc0 = start_col / BLOCK_W;
3840 let bc1 = end_col / BLOCK_W;
3841 for br in br0..=br1 {
3842 for bc in bc0..=bc1 {
3843 let key = StripeKey {
3844 sheet_id,
3845 stripe_type: StripeType::Block,
3846 index: block_index(br * BLOCK_H, bc * BLOCK_W),
3847 };
3848 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3849 candidates.extend(deps);
3850 }
3851 }
3852 }
3853 }
3854
3855 let mut out: Vec<VertexId> = Vec::new();
3857 for dep_id in candidates {
3858 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
3859 continue;
3860 };
3861 let mut hit = false;
3862 for range in ranges {
3863 let range_sheet_id = self
3866 .sheet_reg
3867 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dep_id))
3868 .unwrap_or(sheet_id);
3869 if range_sheet_id != sheet_id {
3870 continue;
3871 }
3872 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
3873 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
3874 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
3875 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
3876 let overlap =
3877 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
3878 if overlap {
3879 hit = true;
3880 break;
3881 }
3882 }
3883 if hit {
3884 out.push(dep_id);
3885 }
3886 }
3887 out
3888 }
3889
3890 pub(crate) fn is_live_formula_vertex(&self, vertex_id: VertexId) -> bool {
3894 self.store.vertex_exists_active(vertex_id) && self.get_formula_id(vertex_id).is_some()
3895 }
3896
3897 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
3899 if vertex_id.0 < FIRST_NORMAL_VERTEX {
3900 return false;
3901 }
3902 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
3903 index < self.store.len()
3904 }
3905
3906 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
3908 self.store.kind(vertex_id)
3909 }
3910
3911 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
3913 self.store.sheet_id(vertex_id)
3914 }
3915
3916 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
3917 self.vertex_formulas.get(&vertex_id).copied()
3918 }
3919
3920 pub(crate) fn formula_vertices(&self) -> Vec<VertexId> {
3921 let mut vertices = self.vertex_formulas.keys().copied().collect::<Vec<_>>();
3922 vertices.sort_unstable();
3923 vertices
3924 }
3925
3926 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
3927 let ast_id = self.get_formula_id(vertex_id)?;
3928 Some((ast_id, self.is_volatile(vertex_id)))
3929 }
3930
3931 pub fn get_formula_node(&self, vertex_id: VertexId) -> Option<&super::arena::AstNodeData> {
3932 let ast_id = self.get_formula_id(vertex_id)?;
3933 self.data_store.get_node(ast_id)
3934 }
3935
3936 pub fn get_formula_node_and_volatile(
3937 &self,
3938 vertex_id: VertexId,
3939 ) -> Option<(&super::arena::AstNodeData, bool)> {
3940 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
3941 let node = self.data_store.get_node(ast_id)?;
3942 Some((node, vol))
3943 }
3944
3945 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
3949 let ast_id = self.get_formula_id(vertex_id)?;
3950 self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
3951 }
3952
3953 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
3955 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
3956 #[cfg(debug_assertions)]
3959 {
3960 self.graph_value_read_attempts
3961 .fetch_add(1, Ordering::Relaxed);
3962 }
3963 return None;
3964 }
3965 self.vertex_values
3966 .get(&vertex_id)
3967 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
3968 }
3969
3970 #[inline]
3977 fn is_grid_backed(&self, vertex_id: VertexId) -> bool {
3978 self.store.grid_addr(vertex_id).is_some()
3979 }
3980
3981 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
3986 let grid = self.store.grid_addr(vertex_id)?;
3987 let sheet_id = self.store.sheet_id(vertex_id);
3988 let coord = Coord::new(grid.row(), grid.col(), true, true);
3989 Some(CellRef::new(sheet_id, coord))
3990 }
3991
3992 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
3994 let coord = Coord::new(row, col, true, true);
3995 CellRef::new(sheet_id, coord)
3996 }
3997
3998 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
4000 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
4001 let coord = Coord::from_excel(row, col, true, true);
4002 CellRef::new(sheet_id, coord)
4003 }
4004
4005 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
4007 self.store.is_dirty(vertex_id)
4008 }
4009
4010 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
4012 self.store.is_volatile(vertex_id)
4013 }
4014
4015 pub(crate) fn is_dynamic(&self, vertex_id: VertexId) -> bool {
4016 self.store.is_dynamic(vertex_id)
4017 }
4018
4019 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<&VertexId> {
4021 self.cell_to_vertex.get(addr)
4022 }
4023
4024 #[cfg(test)]
4025 pub fn cell_to_vertex(
4026 &self,
4027 ) -> &std::collections::HashMap<CellRef, VertexId, CoordBuildHasher> {
4028 &self.cell_to_vertex
4029 }
4030
4031 #[inline]
4035 pub(crate) fn dependencies_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
4036 self.edges.out_edges_ref(vertex_id)
4037 }
4038
4039 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
4041 self.edges.out_edges(vertex_id)
4042 }
4043
4044 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
4046 if let Some(deps) = self.dependencies_slice(vertex_id) {
4047 deps.contains(&vertex_id)
4048 } else {
4049 self.edges.out_edges(vertex_id).contains(&vertex_id)
4050 }
4051 }
4052
4053 #[inline]
4057 pub(crate) fn dependents_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
4058 self.edges.in_edges_ref(vertex_id)
4059 }
4060
4061 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
4067 self.edges.in_edges_merged(vertex_id)
4068 }
4069
4070 pub(crate) fn visit_direct_dependents_bounded(
4074 &self,
4075 vertex_id: VertexId,
4076 remaining_work: &mut u64,
4077 visitor: &mut dyn FnMut(VertexId) -> bool,
4078 ) -> bool {
4079 self.edges
4080 .visit_in_edges_bounded(vertex_id, remaining_work, visitor)
4081 }
4082
4083 #[doc(hidden)]
4087 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
4088 let coord = self.store.grid_addr(id).unwrap_or_default();
4089 let sheet_id = self.store.sheet_id(id);
4090 let kind = self.store.kind(id);
4091 let flags = self.store.flags(id);
4092
4093 let value_ref = self.vertex_values.get(&id).copied();
4095 let formula_ref = self.vertex_formulas.get(&id).copied();
4096
4097 let out_edges = self.get_dependencies(id);
4099
4100 crate::engine::VertexSnapshot {
4101 coord,
4102 sheet_id,
4103 kind,
4104 flags,
4105 value_ref,
4106 formula_ref,
4107 out_edges,
4108 }
4109 }
4110
4111 #[doc(hidden)]
4113 pub fn remove_all_edges(&mut self, id: VertexId) {
4114 self.edges.begin_batch();
4116
4117 self.remove_dependent_edges(id);
4119
4120 let dependents = self.get_dependents(id);
4123 if self.pk_order.is_some()
4124 && let Some(mut pk) = self.pk_order.take()
4125 {
4126 for dependent in &dependents {
4127 pk.remove_edge(id, *dependent);
4128 }
4129 self.pk_order = Some(pk);
4130 }
4131 for dependent in dependents {
4132 self.edges.remove_edge(dependent, id);
4133 }
4134
4135 self.edges.end_batch();
4137 }
4138
4139 #[doc(hidden)]
4141 pub fn mark_as_ref_error(&mut self, id: VertexId) {
4142 if !self.value_cache_enabled && self.is_grid_backed(id) {
4143 self.ref_error_vertices.insert(id);
4144 self.vertex_values.remove(&id);
4147 let _ = self.mark_dirty(id);
4148 return;
4149 }
4150 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
4151 let value_ref = self.data_store.store_value(error);
4152 self.vertex_values.insert(id, value_ref);
4153 let _ = self.mark_dirty(id);
4154 }
4155
4156 pub fn is_ref_error(&self, id: VertexId) -> bool {
4158 if !self.value_cache_enabled && self.is_grid_backed(id) {
4159 return self.ref_error_vertices.contains(&id);
4160 }
4161 if let Some(value_ref) = self.vertex_values.get(&id) {
4162 let value = self.data_store.retrieve_value(*value_ref);
4163 if let LiteralValue::Error(err) = value {
4164 return err.kind == ExcelErrorKind::Ref;
4165 }
4166 }
4167 false
4168 }
4169
4170 #[doc(hidden)]
4172 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
4173 let dependents = self.get_dependents(id);
4174 for dep_id in dependents {
4175 self.store.set_dirty(dep_id, true);
4176 self.formula_dirty.legacy_insert(dep_id);
4177 }
4178 }
4179
4180 #[doc(hidden)]
4182 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
4183 self.store.set_volatile(id, volatile);
4184 if volatile {
4185 self.volatile_vertices.insert(id);
4186 } else {
4187 self.volatile_vertices.remove(&id);
4188 }
4189 }
4190
4191 #[doc(hidden)]
4196 pub fn set_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
4197 self.store.set_addr(id, VertexAddr::grid(coord));
4198 }
4199
4200 #[doc(hidden)]
4202 pub fn update_edge_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
4203 self.edges.update_addr(id, VertexAddr::grid(coord));
4204 }
4205
4206 #[doc(hidden)]
4208 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
4209 self.store.mark_deleted(id, deleted);
4210 }
4211
4212 #[doc(hidden)]
4214 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
4215 self.store.set_kind(id, kind);
4216 }
4217
4218 #[doc(hidden)]
4220 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
4221 self.store.set_dirty(id, dirty);
4222 if dirty {
4223 self.formula_dirty.legacy_insert(id);
4224 } else {
4225 self.formula_dirty.legacy_remove(&id);
4226 }
4227 }
4228
4229 #[cfg(test)]
4231 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
4232 self.store.kind(id)
4233 }
4234
4235 #[cfg(test)]
4237 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
4238 self.store.flags(id)
4239 }
4240
4241 #[cfg(test)]
4243 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
4244 self.store.is_deleted(id)
4245 }
4246
4247 #[doc(hidden)]
4249 pub fn rebuild_edges(&mut self) {
4250 self.edges.rebuild();
4251 }
4252
4253 pub fn flush_pending_edge_deltas(&mut self) {
4259 self.edges.rebuild();
4260 }
4261
4262 #[doc(hidden)]
4264 pub fn edges_delta_size(&self) -> usize {
4265 self.edges.delta_size()
4266 }
4267
4268 #[doc(hidden)]
4271 pub fn edges_rebuild_count(&self) -> u64 {
4272 self.edges.rebuild_count()
4273 }
4274
4275 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
4277 self.cell_to_vertex.get(addr).copied()
4278 }
4279
4280 pub fn get_grid_addr(&self, id: VertexId) -> Option<GridAddr> {
4285 self.store.grid_addr(id)
4286 }
4287
4288 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
4290 self.store.sheet_id(id)
4291 }
4292
4293 pub fn grid_vertices_in_sheet(
4300 &self,
4301 sheet_id: SheetId,
4302 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
4303 self.store.all_vertices().filter_map(move |id| {
4304 if !self.vertex_exists(id) || self.store.sheet_id(id) != sheet_id {
4305 return None;
4306 }
4307 self.store.grid_addr(id).map(|addr| (id, addr))
4308 })
4309 }
4310
4311 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
4313 self.vertex_formulas.contains_key(&id)
4314 }
4315
4316 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
4318 self.vertex_formulas.keys().copied()
4319 }
4320
4321 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
4323 let sheet_id = self.store.sheet_id(id);
4325
4326 let (new_dependencies, new_range_dependencies, _, named_dependencies, unresolved_names) =
4328 self.extract_dependencies_with_pending_names(&ast, sheet_id)?;
4329
4330 let old_kind = self.store.kind(id);
4331
4332 self.remove_dependent_edges(id);
4334 self.detach_vertex_from_names(id);
4335 self.clear_pending_name_references(id);
4336
4337 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
4339 self.vertex_formulas.insert(id, ast_id);
4340
4341 self.add_dependent_edges(id, &new_dependencies);
4343 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
4344
4345 if !named_dependencies.is_empty() {
4346 self.attach_vertex_to_names(id, &named_dependencies);
4347 }
4348 for unresolved_name in &unresolved_names {
4349 self.record_pending_name_reference(sheet_id, unresolved_name, id);
4350 }
4351
4352 self.ref_error_vertices.remove(&id);
4355 self.vertex_values.remove(&id);
4356
4357 self.store.set_kind(
4359 id,
4360 if old_kind == VertexKind::FormulaArray {
4361 VertexKind::FormulaArray
4362 } else {
4363 VertexKind::FormulaScalar
4364 },
4365 );
4366
4367 Ok(())
4368 }
4369
4370 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
4372 self.store.set_dirty(vertex_id, true);
4373 self.formula_dirty.legacy_insert(vertex_id);
4374 }
4375
4376 pub fn mark_vertices_dirty_batch(&mut self, vertices: &[VertexId]) {
4378 self.formula_dirty.legacy_reserve(vertices.len());
4379 for &vertex_id in vertices {
4380 self.store.set_dirty(vertex_id, true);
4381 }
4382 self.formula_dirty.legacy_extend(vertices.iter().copied());
4383 }
4384
4385 pub fn update_cell_mapping(
4387 &mut self,
4388 id: VertexId,
4389 old_addr: Option<CellRef>,
4390 new_addr: CellRef,
4391 ) {
4392 if let Some(old) = old_addr {
4394 self.cell_to_vertex.remove(&old);
4395 }
4396 self.cell_to_vertex.insert(new_addr, id);
4398 }
4399
4400 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
4402 self.cell_to_vertex.remove(addr);
4403 }
4404
4405 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
4407 let coord = self.store.grid_addr(id)?;
4408 let sheet_id = self.store.sheet_id(id);
4409 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
4411 if self.cell_to_vertex.get(&cell_ref) == Some(&id) {
4413 Some(cell_ref)
4414 } else {
4415 None
4416 }
4417 }
4418
4419 pub(crate) fn rebuild_formula_dependencies(&mut self, vertex_id: VertexId, ast: &ASTNode) {
4425 let sheet_id = self.store.sheet_id(vertex_id);
4426
4427 self.remove_dependent_edges(vertex_id);
4429 self.detach_vertex_from_names(vertex_id);
4430 self.clear_pending_name_references(vertex_id);
4431
4432 let (
4433 new_dependencies,
4434 new_range_dependencies,
4435 _created_placeholders,
4436 named_dependencies,
4437 unresolved_names,
4438 ) = match self.extract_dependencies_with_pending_names(ast, sheet_id) {
4439 Ok(v) => v,
4440 Err(_) => {
4441 self.mark_as_ref_error(vertex_id);
4442 return;
4443 }
4444 };
4445
4446 if new_dependencies.contains(&vertex_id) && !self.config.cycle.allows_self_dependency() {
4449 self.mark_as_ref_error(vertex_id);
4450 return;
4451 }
4452
4453 for &name_vertex in &named_dependencies {
4454 let mut visited = FxHashSet::default();
4455 if self.name_depends_on_vertex(name_vertex, vertex_id, &mut visited) {
4456 self.mark_as_ref_error(vertex_id);
4457 return;
4458 }
4459 }
4460
4461 self.ref_error_vertices.remove(&vertex_id);
4463 self.vertex_values.remove(&vertex_id);
4464
4465 if !named_dependencies.is_empty() {
4466 self.attach_vertex_to_names(vertex_id, &named_dependencies);
4467 }
4468 for unresolved_name in &unresolved_names {
4469 self.record_pending_name_reference(sheet_id, unresolved_name, vertex_id);
4470 }
4471
4472 self.add_dependent_edges(vertex_id, &new_dependencies);
4473 self.add_range_dependent_edges(vertex_id, &new_range_dependencies, sheet_id);
4474 let _ = self.mark_dirty(vertex_id);
4475 }
4476}
4477
4478