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