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 = self
1754 .sheet_reg
1755 .resolve_locator(&range.sheet, *sheet_id)
1756 .unwrap_or(*sheet_id);
1757 range_sheet == *sheet_id
1758 && range
1759 .start_row
1760 .is_none_or(|bound| target_row >= bound.index)
1761 && range.end_row.is_none_or(|bound| target_row <= bound.index)
1762 && range
1763 .start_col
1764 .is_none_or(|bound| target_col >= bound.index)
1765 && range.end_col.is_none_or(|bound| target_col <= bound.index)
1766 }) {
1767 dependencies.insert((1, target.as_u64()));
1768 }
1769 added_edges = added_edges.checked_add(dependencies.len()).ok_or_else(|| {
1770 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
1771 })?;
1772 }
1773 let stats = self.baseline_stats();
1774 Ok(crate::engine::resource_ledger::GraphAdmission {
1775 final_vertices: stats
1776 .graph_vertex_count
1777 .checked_add(new_cells.len())
1778 .ok_or_else(|| {
1779 ExcelError::new(ExcelErrorKind::NImpl)
1780 .with_message("graph vertex count overflow")
1781 })?,
1782 final_edges: stats
1783 .graph_edge_count
1784 .checked_sub(removed_edges)
1785 .and_then(|count| count.checked_add(added_edges))
1786 .ok_or_else(|| {
1787 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
1788 })?,
1789 materialization_cells: plans.len() as u64,
1790 added_vertices: new_cells.len(),
1791 added_edges,
1792 })
1793 }
1794
1795 pub(crate) fn vertices_in_region(
1796 &self,
1797 sheet_id: SheetId,
1798 start_row0: u32,
1799 end_row0: u32,
1800 start_col0: u32,
1801 end_col0: u32,
1802 ) -> Vec<VertexId> {
1803 self.sheet_indexes
1804 .get(&sheet_id)
1805 .map_or_else(Vec::new, |index| {
1806 index.vertices_in_rect(start_row0, end_row0, start_col0, end_col0)
1807 })
1808 }
1809
1810 #[cfg(test)]
1811 pub(crate) fn reset_sheet_index_query_stats(&self) {
1812 for index in self.sheet_indexes.values() {
1813 index.reset_query_stats();
1814 }
1815 }
1816
1817 #[cfg(test)]
1818 pub(crate) fn sheet_index_query_stats(
1819 &self,
1820 ) -> crate::engine::sheet_index::SheetIndexQueryStats {
1821 self.sheet_indexes.values().fold(
1822 crate::engine::sheet_index::SheetIndexQueryStats::default(),
1823 |mut total, index| {
1824 let stats = index.query_stats();
1825 total.coordinate_nodes_visited = total
1826 .coordinate_nodes_visited
1827 .saturating_add(stats.coordinate_nodes_visited);
1828 total.values_visited = total.values_visited.saturating_add(stats.values_visited);
1829 total
1830 },
1831 )
1832 }
1833
1834 pub fn set_cell_value(
1836 &mut self,
1837 sheet: &str,
1838 row: u32,
1839 col: u32,
1840 value: LiteralValue,
1841 ) -> Result<OperationSummary, ExcelError> {
1842 let value = normalize_stored_literal(value);
1843 let sheet_id = self.sheet_id_mut(sheet);
1844 let budgets = self.self_admission_budgets();
1845 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
1846 let usage = self.preview_value_mutation(sheet_id, row, col)?;
1847 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
1848 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
1849 }
1850 let coord = Coord::from_excel(row, col, true, true);
1852 let addr = CellRef::new(sheet_id, coord);
1853 let mut created_placeholders = Vec::new();
1854
1855 let vertex_id = if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
1856 let is_formula = matches!(
1858 self.store.kind(existing_id),
1859 VertexKind::FormulaScalar | VertexKind::FormulaArray
1860 );
1861
1862 if is_formula {
1863 self.remove_dependent_edges(existing_id);
1864 self.detach_vertex_from_names(existing_id);
1865 self.clear_pending_name_references(existing_id);
1866 self.vertex_formulas.remove(&existing_id);
1867 }
1868
1869 self.store.set_kind(existing_id, VertexKind::Cell);
1871 if self.value_cache_enabled {
1872 let value_ref = self.data_store.store_value(value);
1873 self.vertex_values.insert(existing_id, value_ref);
1874 } else {
1875 self.vertex_values.remove(&existing_id);
1877 }
1878 existing_id
1879 } else {
1880 created_placeholders.push(addr);
1882 let packed_coord = AbsCoord::from_excel(row, col);
1883 let vertex_id = self.store.allocate(packed_coord, sheet_id, 0x01); self.edges.add_vertex(packed_coord, vertex_id.0);
1887
1888 self.sheet_index_mut(sheet_id)
1890 .add_vertex(packed_coord, vertex_id);
1891
1892 self.store.set_kind(vertex_id, VertexKind::Cell);
1893 if self.value_cache_enabled {
1894 let value_ref = self.data_store.store_value(value);
1895 self.vertex_values.insert(vertex_id, value_ref);
1896 }
1897 self.cell_to_vertex.insert(addr, vertex_id);
1898 vertex_id
1899 };
1900
1901 self.ref_error_vertices.remove(&vertex_id);
1903
1904 Ok(OperationSummary {
1905 affected_vertices: self.mark_dirty(vertex_id),
1906 created_placeholders,
1907 })
1908 }
1909
1910 pub fn reserve_cells(&mut self, additional: usize) {
1912 self.store.reserve(additional);
1913 if self.value_cache_enabled {
1914 self.vertex_values.reserve(additional);
1915 }
1916 self.cell_to_vertex.reserve(additional);
1917 }
1919
1920 pub fn set_cell_value_bulk_untracked(
1922 &mut self,
1923 sheet: &str,
1924 row: u32,
1925 col: u32,
1926 value: LiteralValue,
1927 ) -> Result<(), ExcelError> {
1928 let value = normalize_stored_literal(value);
1929 let sheet_id = self.sheet_id_mut(sheet);
1930 let budgets = self.self_admission_budgets();
1931 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
1932 let usage = self.preview_value_mutation(sheet_id, row, col)?;
1933 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
1934 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
1935 }
1936 let coord = Coord::from_excel(row, col, true, true);
1937 let addr = CellRef::new(sheet_id, coord);
1938 if let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
1939 if matches!(
1941 self.store.kind(existing_id),
1942 VertexKind::FormulaScalar | VertexKind::FormulaArray
1943 ) {
1944 self.remove_dependent_edges(existing_id);
1945 self.detach_vertex_from_names(existing_id);
1946 self.clear_pending_name_references(existing_id);
1947 self.vertex_formulas.remove(&existing_id);
1948 }
1949 if self.value_cache_enabled {
1950 let value_ref = self.data_store.store_value(value);
1951 self.vertex_values.insert(existing_id, value_ref);
1952 } else {
1953 self.vertex_values.remove(&existing_id);
1954 }
1955 self.store.set_kind(existing_id, VertexKind::Cell);
1956 self.ref_error_vertices.remove(&existing_id);
1957 return Ok(());
1958 }
1959 let packed_coord = AbsCoord::from_excel(row, col);
1960 let vertex_id = self.store.allocate(packed_coord, sheet_id, 0x00); self.edges.add_vertex(packed_coord, vertex_id.0);
1962 self.sheet_index_mut(sheet_id)
1963 .add_vertex(packed_coord, vertex_id);
1964 self.store.set_kind(vertex_id, VertexKind::Cell);
1965 self.ref_error_vertices.remove(&vertex_id);
1966 if self.value_cache_enabled {
1967 let value_ref = self.data_store.store_value(value);
1968 self.vertex_values.insert(vertex_id, value_ref);
1969 }
1970 self.cell_to_vertex.insert(addr, vertex_id);
1971 Ok(())
1972 }
1973
1974 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I) -> Result<(), ExcelError>
1976 where
1977 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
1978 {
1979 use crate::instant::FzInstant as Instant;
1980 let t0 = Instant::now();
1981 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
1983 if collected.is_empty() {
1984 return Ok(());
1985 }
1986 let sheet_id = self.sheet_id_mut(sheet);
1987 let budgets = self.self_admission_budgets();
1988 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
1989 let coordinates = collected
1990 .iter()
1991 .map(|(row, col, _)| (*row, *col))
1992 .collect::<Vec<_>>();
1993 let usage = self.preview_value_mutations(sheet_id, &coordinates)?;
1994 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
1995 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
1996 }
1997 self.reserve_cells(collected.len());
1998 let t_reserve = Instant::now();
1999 let mut new_vertices: Vec<(AbsCoord, u32)> = Vec::with_capacity(collected.len());
2000 let mut index_items: Vec<(AbsCoord, VertexId)> = Vec::with_capacity(collected.len());
2001 let mut new_value_coords: Vec<(AbsCoord, VertexId)> = Vec::with_capacity(collected.len());
2003 let mut new_value_literals: Vec<LiteralValue> = Vec::with_capacity(collected.len());
2004 let assume_new = self.first_load_assume_new
2006 && self
2007 .sheet_id(sheet)
2008 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
2009 .unwrap_or(false);
2010
2011 for (row, col, value) in collected {
2012 let value = normalize_stored_literal(value);
2013 let coord = Coord::from_excel(row, col, true, true);
2014 let addr = CellRef::new(sheet_id, coord);
2015 if !assume_new && let Some(&existing_id) = self.cell_to_vertex.get(&addr) {
2016 if matches!(
2017 self.store.kind(existing_id),
2018 VertexKind::FormulaScalar | VertexKind::FormulaArray
2019 ) {
2020 self.remove_dependent_edges(existing_id);
2021 self.detach_vertex_from_names(existing_id);
2022 self.clear_pending_name_references(existing_id);
2023 self.vertex_formulas.remove(&existing_id);
2024 }
2025 if self.value_cache_enabled {
2026 let value_ref = self.data_store.store_value(value);
2027 self.vertex_values.insert(existing_id, value_ref);
2028 } else {
2029 self.vertex_values.remove(&existing_id);
2030 }
2031 self.store.set_kind(existing_id, VertexKind::Cell);
2032 continue;
2033 }
2034 let packed = AbsCoord::from_excel(row, col);
2035 let vertex_id = self.store.allocate(packed, sheet_id, 0x00);
2036 self.store.set_kind(vertex_id, VertexKind::Cell);
2037 new_value_coords.push((packed, vertex_id));
2039 new_value_literals.push(value);
2040 self.cell_to_vertex.insert(addr, vertex_id);
2041 new_vertices.push((packed, vertex_id.0));
2042 index_items.push((packed, vertex_id));
2043 }
2044 if self.value_cache_enabled && !new_value_literals.is_empty() {
2046 let vrefs = self.data_store.store_values_batch(new_value_literals);
2047 debug_assert_eq!(vrefs.len(), new_value_coords.len());
2048 for (i, (_pc, vid)) in new_value_coords.iter().enumerate() {
2049 self.vertex_values.insert(*vid, vrefs[i]);
2050 }
2051 }
2052 let t_after_alloc = Instant::now();
2053 if !new_vertices.is_empty() {
2054 let t_edges_start = Instant::now();
2055 self.edges.add_vertices_batch(&new_vertices);
2056 let t_edges_done = Instant::now();
2057
2058 match self.config.sheet_index_mode {
2059 crate::engine::SheetIndexMode::Eager => {
2060 self.sheet_index_mut(sheet_id)
2061 .add_vertices_batch(&index_items);
2062 }
2063 crate::engine::SheetIndexMode::Lazy => {
2064 }
2066 crate::engine::SheetIndexMode::FastBatch => {
2067 self.sheet_index_mut(sheet_id)
2069 .add_vertices_batch(&index_items);
2070 }
2071 }
2072 let t_index_done = Instant::now();
2073 }
2074 Ok(())
2075 }
2076
2077 pub fn set_cell_formula(
2079 &mut self,
2080 sheet: &str,
2081 row: u32,
2082 col: u32,
2083 ast: ASTNode,
2084 ) -> Result<OperationSummary, ExcelError> {
2085 self.set_cell_formula_with_volatility(sheet, row, col, ast, false)
2086 }
2087
2088 pub fn set_cell_formula_with_volatility(
2091 &mut self,
2092 sheet: &str,
2093 row: u32,
2094 col: u32,
2095 ast: ASTNode,
2096 _volatile: bool,
2097 ) -> Result<OperationSummary, ExcelError> {
2098 let sheet_id = self.sheet_id_mut(sheet);
2099 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2100 let provider = RegistryFunctionProvider;
2101 let ingested = {
2102 let mut pipeline = self.ingest_pipeline(&provider);
2103 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
2104 };
2105 self.set_cell_formula_with_plan(
2106 sheet,
2107 row,
2108 col,
2109 ingested.ast_id,
2110 &ingested.dep_plan,
2111 ingested.dep_plan.volatile,
2112 ingested.dep_plan.dynamic,
2113 )
2114 }
2115
2116 pub(crate) fn set_cell_formula_with_plan(
2117 &mut self,
2118 sheet: &str,
2119 row: u32,
2120 col: u32,
2121 ast_id: AstNodeId,
2122 plan: &DependencyPlanRow,
2123 volatile: bool,
2124 dynamic: bool,
2125 ) -> Result<OperationSummary, ExcelError> {
2126 let dbg = std::env::var("FZ_DEBUG_LOAD")
2127 .ok()
2128 .is_some_and(|v| v != "0");
2129 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
2130 .ok()
2131 .and_then(|s| s.parse().ok())
2132 .unwrap_or(0);
2133 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
2134 .ok()
2135 .and_then(|s| s.parse().ok())
2136 .unwrap_or(0);
2137 let t0 = if dbg {
2138 Some(crate::instant::FzInstant::now())
2139 } else {
2140 None
2141 };
2142 let sheet_id = self.sheet_id_mut(sheet);
2143 let budgets = self.self_admission_budgets();
2144 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2145 let usage = self.preview_formula_mutations(&[(sheet_id, row, col, plan.clone())])?;
2146 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2147 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2148 }
2149 let coord = Coord::from_excel(row, col, true, true);
2150 let addr = CellRef::new(sheet_id, coord);
2151
2152 let t_dep0 = if dbg {
2153 Some(crate::instant::FzInstant::now())
2154 } else {
2155 None
2156 };
2157 let mut created_placeholders = Vec::new();
2158 let mut new_dependencies = Vec::with_capacity(plan.direct_cell_deps.len());
2159 for dep in &plan.direct_cell_deps {
2160 let dep_vid = self.get_or_create_vertex(dep, &mut created_placeholders);
2161 if !new_dependencies.contains(&dep_vid) {
2162 new_dependencies.push(dep_vid);
2163 }
2164 }
2165 let mut named_dependencies = Vec::new();
2166 let mut unresolved_names = Vec::new();
2167 for name in plan
2168 .resolved_named_refs
2169 .iter()
2170 .chain(plan.named_refs.iter())
2171 {
2172 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
2173 if !new_dependencies.contains(&named.vertex) {
2174 new_dependencies.push(named.vertex);
2175 }
2176 if !named_dependencies.contains(&named.vertex) {
2177 named_dependencies.push(named.vertex);
2178 }
2179 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
2180 if !new_dependencies.contains(&source.vertex) {
2181 new_dependencies.push(source.vertex);
2182 }
2183 } else {
2184 unresolved_names.push(name.clone());
2185 }
2186 }
2187 for source_name in &plan.source_refs {
2188 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
2189 if !new_dependencies.contains(&source.vertex) {
2190 new_dependencies.push(source.vertex);
2191 }
2192 } else if let Some(source) = self.resolve_source_table_entry(source_name)
2193 && !new_dependencies.contains(&source.vertex)
2194 {
2195 new_dependencies.push(source.vertex);
2196 }
2197 }
2198 for table_name in &plan.table_refs {
2199 if let Some(table) = self.resolve_table_entry(table_name) {
2200 if !new_dependencies.contains(&table.vertex) {
2201 new_dependencies.push(table.vertex);
2202 }
2203 } else if let Some(source) = self.resolve_source_table_entry(table_name)
2204 && !new_dependencies.contains(&source.vertex)
2205 {
2206 new_dependencies.push(source.vertex);
2207 }
2208 }
2209 if let (true, Some(t)) = (dbg, t_dep0) {
2210 let elapsed = t.elapsed().as_millis();
2211 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
2212 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
2213 if (dep_ms_thresh == 0 && sample_n == 0 && row.is_multiple_of(1000)) || do_log {
2214 eprintln!(
2215 "[fz][dep] {}!{} planned: deps={}, ranges={}, placeholders={}, names={} in {} ms",
2216 self.sheet_name(sheet_id),
2217 crate::reference::Coord::from_excel(row, col, true, true),
2218 new_dependencies.len(),
2219 plan.range_deps.len(),
2220 created_placeholders.len(),
2221 named_dependencies.len(),
2222 elapsed
2223 );
2224 }
2225 }
2226
2227 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
2229
2230 self.ref_error_vertices.remove(&addr_vertex_id);
2232
2233 if new_dependencies.contains(&addr_vertex_id) && !self.config.cycle.allows_self_dependency()
2248 {
2249 return Err(ExcelError::new(ExcelErrorKind::Circ)
2250 .with_message("Self-reference detected".to_string()));
2251 }
2252
2253 for &name_vertex in &named_dependencies {
2254 let mut visited = FxHashSet::default();
2255 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
2256 return Err(ExcelError::new(ExcelErrorKind::Circ)
2257 .with_message("Circular reference through named range".to_string()));
2258 }
2259 }
2260
2261 self.remove_dependent_edges(addr_vertex_id);
2263 self.detach_vertex_from_names(addr_vertex_id);
2264 self.clear_pending_name_references(addr_vertex_id);
2265
2266 self.store
2268 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
2269 self.vertex_formulas.insert(addr_vertex_id, ast_id);
2270 self.store.set_dirty(addr_vertex_id, true);
2271
2272 self.vertex_values.remove(&addr_vertex_id);
2274
2275 self.mark_volatile(addr_vertex_id, volatile);
2276 self.store.set_dynamic(addr_vertex_id, dynamic);
2277
2278 if !named_dependencies.is_empty() {
2279 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
2280 }
2281 for unresolved_name in &unresolved_names {
2282 self.record_pending_name_reference(sheet_id, unresolved_name, addr_vertex_id);
2283 }
2284
2285 if let (true, Some(t)) = (dbg, t0) {
2286 let elapsed = t.elapsed().as_millis();
2287 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
2288 if log_set {
2289 eprintln!(
2290 "[fz][set] {}!{} total {} ms",
2291 self.sheet_name(sheet_id),
2292 crate::reference::Coord::from_excel(row, col, true, true),
2293 elapsed
2294 );
2295 }
2296 }
2297
2298 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
2300 self.add_range_dependent_edges(addr_vertex_id, &plan.range_deps, sheet_id);
2301
2302 Ok(OperationSummary {
2303 affected_vertices: self.mark_dirty(addr_vertex_id),
2304 created_placeholders,
2305 })
2306 }
2307
2308 pub(crate) fn rewrite_structured_references_for_cell(
2309 &self,
2310 ast: &mut ASTNode,
2311 cell: CellRef,
2312 ) -> Result<bool, ExcelError> {
2313 self.rewrite_structured_references_node(ast, cell)
2314 }
2315
2316 fn rewrite_structured_references_node(
2317 &self,
2318 node: &mut ASTNode,
2319 cell: CellRef,
2320 ) -> Result<bool, ExcelError> {
2321 match &mut node.node_type {
2322 ASTNodeType::Reference { reference, .. } => {
2323 self.rewrite_structured_reference(reference, cell)
2324 }
2325 ASTNodeType::UnaryOp { expr, .. } => {
2326 self.rewrite_structured_references_node(expr, cell)
2327 }
2328 ASTNodeType::BinaryOp { left, right, .. } => {
2329 let left_rewritten = self.rewrite_structured_references_node(left, cell)?;
2330 let right_rewritten = self.rewrite_structured_references_node(right, cell)?;
2331 Ok(left_rewritten || right_rewritten)
2332 }
2333 ASTNodeType::Function { args, .. } => {
2334 let mut rewritten = false;
2335 for a in args.iter_mut() {
2336 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2337 }
2338 Ok(rewritten)
2339 }
2340 ASTNodeType::Call { callee, args } => {
2341 let mut rewritten = self.rewrite_structured_references_node(callee, cell)?;
2342 for a in args.iter_mut() {
2343 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2344 }
2345 Ok(rewritten)
2346 }
2347 ASTNodeType::Array(rows) => {
2348 let mut rewritten = false;
2349 for r in rows.iter_mut() {
2350 for item in r.iter_mut() {
2351 rewritten |= self.rewrite_structured_references_node(item, cell)?;
2352 }
2353 }
2354 Ok(rewritten)
2355 }
2356 ASTNodeType::Literal(_) | ASTNodeType::Omitted => Ok(false),
2357 }
2358 }
2359
2360 fn rewrite_structured_reference(
2361 &self,
2362 reference: &mut ReferenceType,
2363 cell: CellRef,
2364 ) -> Result<bool, ExcelError> {
2365 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
2366
2367 let ReferenceType::Table(tref) = reference else {
2368 return Ok(false);
2369 };
2370
2371 if !tref.name.is_empty() {
2373 return Ok(false);
2374 }
2375
2376 let col_name = match &tref.specifier {
2377 Some(TableSpecifier::Combination(parts)) => {
2378 let mut saw_this_row = false;
2379 let mut col: Option<&str> = None;
2380 for p in parts {
2381 match p.as_ref() {
2382 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
2383 saw_this_row = true;
2384 }
2385 TableSpecifier::Column(c) => {
2386 if col.is_some() {
2387 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2388 "This-row structured reference with multiple columns is not supported"
2389 .to_string(),
2390 ));
2391 }
2392 col = Some(c.as_str());
2393 }
2394 other => {
2395 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2396 format!(
2397 "Unsupported this-row structured reference component: {other}"
2398 ),
2399 ));
2400 }
2401 }
2402 }
2403 if !saw_this_row {
2404 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2405 "Unnamed structured reference requires a this-row selector".to_string(),
2406 ));
2407 }
2408 col.ok_or_else(|| {
2409 ExcelError::new(ExcelErrorKind::NImpl).with_message(
2410 "This-row structured reference missing column selector".to_string(),
2411 )
2412 })?
2413 }
2414 _ => {
2415 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2416 "Unnamed structured reference form is not supported".to_string(),
2417 ));
2418 }
2419 };
2420
2421 let Some(table) = self.find_table_containing_cell(cell) else {
2422 return Err(ExcelError::new(ExcelErrorKind::Name)
2423 .with_message("This-row structured reference used outside a table".to_string()));
2424 };
2425
2426 let row0 = cell.coord.row();
2427 let col0 = cell.coord.col();
2428 let sr0 = table.range.start.coord.row();
2429 let sc0 = table.range.start.coord.col();
2430 let er0 = table.range.end.coord.row();
2431 let ec0 = table.range.end.coord.col();
2432
2433 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2434 return Err(ExcelError::new(ExcelErrorKind::Name)
2435 .with_message("This-row structured reference used outside a table".to_string()));
2436 }
2437
2438 if table.header_row && row0 == sr0 {
2439 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
2440 "This-row structured references are not valid in the table header row".to_string(),
2441 ));
2442 }
2443
2444 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
2445 if row0 < data_start {
2446 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
2447 "This-row structured references require a data/totals row context".to_string(),
2448 ));
2449 }
2450
2451 let Some(idx) = table.col_index(col_name) else {
2452 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
2453 "Unknown table column in this-row reference: {col_name}"
2454 )));
2455 };
2456 let target_col0 = sc0 + (idx as u32);
2457 let target_row = row0 + 1;
2458 let target_col = target_col0 + 1;
2459
2460 *reference = ReferenceType::Cell {
2461 sheet: None,
2462 row: target_row,
2463 col: target_col,
2464 row_abs: true,
2465 col_abs: true,
2466 };
2467
2468 Ok(true)
2469 }
2470
2471 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
2472 let row0 = cell.coord.row();
2473 let col0 = cell.coord.col();
2474
2475 let mut best: Option<&tables::TableEntry> = None;
2476 let mut best_area: u64 = u64::MAX;
2477 let mut best_name: &str = "";
2478
2479 for t in self.tables.values() {
2480 if t.sheet_id() != cell.sheet_id {
2481 continue;
2482 }
2483 let sr0 = t.range.start.coord.row();
2484 let sc0 = t.range.start.coord.col();
2485 let er0 = t.range.end.coord.row();
2486 let ec0 = t.range.end.coord.col();
2487 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2488 continue;
2489 }
2490
2491 let h = (er0 - sr0 + 1) as u64;
2492 let w = (ec0 - sc0 + 1) as u64;
2493 let area = h.saturating_mul(w);
2494 let name = t.name.as_str();
2495 let better = match best {
2496 None => true,
2497 Some(_) => area < best_area || (area == best_area && name < best_name),
2498 };
2499 if better {
2500 best = Some(t);
2501 best_area = area;
2502 best_name = name;
2503 }
2504 }
2505
2506 best
2507 }
2508
2509 #[allow(clippy::type_complexity)]
2510 pub(crate) fn fp8_parity_extract_dependencies_with_pending_names(
2511 &mut self,
2512 ast: &ASTNode,
2513 current_sheet_id: SheetId,
2514 ) -> Result<
2515 (
2516 Vec<VertexId>,
2517 Vec<SharedRangeRef<'static>>,
2518 Vec<CellRef>,
2519 Vec<VertexId>,
2520 Vec<String>,
2521 ),
2522 ExcelError,
2523 > {
2524 self.extract_dependencies_with_pending_names(ast, current_sheet_id)
2525 }
2526
2527 pub(crate) fn fp8_parity_is_ast_volatile(&self, ast: &ASTNode) -> bool {
2528 self.is_ast_volatile(ast)
2529 }
2530
2531 pub fn set_cell_value_ref(
2532 &mut self,
2533 cell: formualizer_common::SheetCellRef<'_>,
2534 value: LiteralValue,
2535 ) -> Result<OperationSummary, ExcelError> {
2536 let owned = cell.into_owned();
2537 let sheet_id = match owned.sheet {
2538 formualizer_common::SheetLocator::Id(id) => id,
2539 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
2540 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2541 };
2542 let sheet_name = self.sheet_name(sheet_id).to_string();
2543 self.set_cell_value(
2544 &sheet_name,
2545 owned.coord.row() + 1,
2546 owned.coord.col() + 1,
2547 value,
2548 )
2549 }
2550
2551 pub fn set_cell_formula_ref(
2552 &mut self,
2553 cell: formualizer_common::SheetCellRef<'_>,
2554 ast: ASTNode,
2555 ) -> Result<OperationSummary, ExcelError> {
2556 let owned = cell.into_owned();
2557 let sheet_id = match owned.sheet {
2558 formualizer_common::SheetLocator::Id(id) => id,
2559 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
2560 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2561 };
2562 let sheet_name = self.sheet_name(sheet_id).to_string();
2563 self.set_cell_formula(
2564 &sheet_name,
2565 owned.coord.row() + 1,
2566 owned.coord.col() + 1,
2567 ast,
2568 )
2569 }
2570
2571 pub fn get_cell_value_ref(
2572 &self,
2573 cell: formualizer_common::SheetCellRef<'_>,
2574 ) -> Option<LiteralValue> {
2575 let owned = cell.into_owned();
2576 let sheet_id = match owned.sheet {
2577 formualizer_common::SheetLocator::Id(id) => id,
2578 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
2579 formualizer_common::SheetLocator::Current => self.default_sheet_id,
2580 };
2581 let sheet_name = self.sheet_name(sheet_id);
2582 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
2583 }
2584
2585 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
2587 if !self.value_cache_enabled {
2588 #[cfg(debug_assertions)]
2589 {
2590 self.graph_value_read_attempts
2591 .fetch_add(1, Ordering::Relaxed);
2592 }
2593 return None;
2594 }
2595 let sheet_id = self.sheet_reg.get_id(sheet)?;
2596 let coord = Coord::from_excel(row, col, true, true);
2597 let addr = CellRef::new(sheet_id, coord);
2598
2599 self.get_vertex_id_for_address(&addr)
2600 .and_then(|&vertex_id| {
2601 self.vertex_values
2603 .get(&vertex_id)
2604 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
2605 })
2606 }
2607
2608 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
2610 self.mark_dirty_many(&[vertex_id])
2611 }
2612
2613 pub(crate) fn mark_dirty_many(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
2633 if self.deferred_dirty_depth > 0 {
2634 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
2635 return vertex_ids.to_vec();
2636 }
2637 let mut affected = FxHashSet::default();
2638 let mut to_visit = Vec::new();
2639 let mut visited_for_propagation = FxHashSet::default();
2640
2641 for &vertex_id in vertex_ids {
2642 let is_formula = matches!(
2646 self.store.kind(vertex_id),
2647 VertexKind::FormulaScalar
2648 | VertexKind::FormulaArray
2649 | VertexKind::NamedScalar
2650 | VertexKind::NamedArray
2651 );
2652
2653 if is_formula {
2654 to_visit.push(vertex_id);
2655 } else {
2656 affected.insert(vertex_id);
2658 }
2659
2660 {
2662 if let Some(dependents) = self.dependents_slice(vertex_id) {
2664 to_visit.extend(dependents.iter().copied());
2665 } else {
2666 let dependents = self.get_dependents(vertex_id);
2667 to_visit.extend(dependents);
2668 }
2669
2670 if let Some(name_set) = self.cell_to_name_dependents.get(&vertex_id) {
2671 for &name_vertex in name_set {
2672 to_visit.push(name_vertex);
2673 }
2674 }
2675
2676 to_visit.extend(self.collect_range_dependents_for_vertex(vertex_id));
2677 }
2678 }
2679
2680 while let Some(id) = to_visit.pop() {
2681 if !visited_for_propagation.insert(id) {
2682 continue; }
2684 self.dirty_propagation_visits += 1;
2685 affected.insert(id);
2686
2687 self.store.set_dirty(id, true);
2689
2690 if let Some(dependents) = self.dependents_slice(id) {
2692 to_visit.extend(dependents.iter().copied());
2693 } else {
2694 let dependents = self.get_dependents(id);
2695 to_visit.extend(dependents);
2696 }
2697 to_visit.extend(self.collect_range_dependents_for_vertex(id));
2698 }
2699
2700 self.formula_dirty.legacy_extend(affected.iter().copied());
2702
2703 affected.into_iter().collect()
2705 }
2706
2707 pub(crate) fn dirty_propagation_visits(&self) -> u64 {
2710 self.dirty_propagation_visits
2711 }
2712
2713 pub fn begin_deferred_dirty(&mut self) {
2734 self.edges.begin_batch();
2735 self.deferred_dirty_depth += 1;
2736 }
2737
2738 pub fn end_deferred_dirty(&mut self) -> Vec<VertexId> {
2743 debug_assert!(
2744 self.deferred_dirty_depth > 0,
2745 "end_deferred_dirty without matching begin_deferred_dirty"
2746 );
2747 self.edges.end_batch();
2748 self.deferred_dirty_depth = self.deferred_dirty_depth.saturating_sub(1);
2749 if self.deferred_dirty_depth > 0 {
2750 return Vec::new();
2751 }
2752 let pending = std::mem::take(&mut self.deferred_dirty_pending);
2753 if pending.is_empty() {
2754 return Vec::new();
2755 }
2756 let live: Vec<VertexId> = pending
2757 .into_iter()
2758 .filter(|&id| self.vertex_exists(id))
2759 .collect();
2760 self.mark_dirty_many(&live)
2761 }
2762
2763 pub fn deferred_dirty_active(&self) -> bool {
2766 self.deferred_dirty_depth > 0
2767 }
2768
2769 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
2771 let mut combined = FxHashSet::default();
2772 combined.extend(self.formula_dirty.legacy_iter().copied());
2773 combined.extend(&self.volatile_vertices);
2774
2775 let mut result: Vec<VertexId> = combined
2776 .into_iter()
2777 .filter(|&id| {
2778 self.store.vertex_exists_active(id)
2781 && matches!(
2782 self.store.kind(id),
2783 VertexKind::FormulaScalar
2784 | VertexKind::FormulaArray
2785 | VertexKind::NamedScalar
2786 | VertexKind::NamedArray
2787 )
2788 })
2789 .collect();
2790 result.sort_unstable();
2791 result
2792 }
2793
2794 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
2796 for &vertex_id in vertices {
2797 self.store.set_dirty(vertex_id, false);
2798 self.formula_dirty.legacy_remove(&vertex_id);
2799 }
2800 }
2801
2802 pub fn clear_volatile_flags(&mut self) {
2804 self.volatile_vertices.clear();
2805 }
2806
2807 pub(crate) fn redirty_volatiles(&mut self) {
2812 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
2813 let _ = self.mark_dirty_many(&volatile_ids);
2814 }
2815
2816 pub(crate) fn redirty_iterative_members(&mut self, members: &[VertexId]) {
2829 let live: Vec<VertexId> = members
2830 .iter()
2831 .copied()
2832 .filter(|&id| self.vertex_exists(id))
2833 .collect();
2834 let _ = self.mark_dirty_many(&live);
2835 }
2836
2837 fn get_or_create_vertex(
2838 &mut self,
2839 addr: &CellRef,
2840 created_placeholders: &mut Vec<CellRef>,
2841 ) -> VertexId {
2842 if let Some(&vertex_id) = self.cell_to_vertex.get(addr) {
2843 return vertex_id;
2844 }
2845
2846 if self.first_load_assume_new {
2851 let packed = Self::packed_cell_key(
2852 addr.sheet_id,
2853 AbsCoord::new(addr.coord.row(), addr.coord.col()),
2854 );
2855 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
2856 self.cell_to_vertex.insert(*addr, existing);
2857 return existing;
2858 }
2859 }
2860
2861 created_placeholders.push(*addr);
2862 let packed_coord = AbsCoord::new(addr.coord.row(), addr.coord.col());
2863 let vertex_id = self.store.allocate(packed_coord, addr.sheet_id, 0x00);
2864
2865 self.edges.add_vertex(packed_coord, vertex_id.0);
2867
2868 self.sheet_index_mut(addr.sheet_id)
2870 .add_vertex(packed_coord, vertex_id);
2871
2872 self.store.set_kind(vertex_id, VertexKind::Empty);
2873 self.cell_to_vertex.insert(*addr, vertex_id);
2874 vertex_id
2875 }
2876
2877 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
2878 self.edges.begin_batch();
2880
2881 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
2884 if self.pk_order.is_some()
2885 && let Some(mut pk) = self.pk_order.take()
2886 {
2887 pk.ensure_nodes(std::iter::once(dependent));
2888 pk.ensure_nodes(dependencies.iter().copied());
2889 {
2890 let adapter = GraphAdapter { g: self };
2891 for &dep_id in dependencies {
2892 match pk.try_add_edge(&adapter, dep_id, dependent) {
2893 Ok(_) => {}
2894 Err(_cycle) => {
2895 if self.config.pk_reject_cycle_edges {
2896 skip_deps.insert(dep_id);
2897 } else {
2898 pk.rebuild_full(&adapter);
2899 }
2900 }
2901 }
2902 }
2903 } self.pk_order = Some(pk);
2905 }
2906
2907 for &dep_id in dependencies {
2909 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
2910 continue;
2911 }
2912 self.edges.add_edge(dependent, dep_id);
2913 #[cfg(test)]
2914 {
2915 if let Ok(mut g) = self.instr.lock() {
2916 g.edges_added += 1;
2917 }
2918 }
2919 }
2920
2921 self.edges.end_batch();
2922 }
2923
2924 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
2926 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
2928 if self.pk_order.is_some()
2929 && let Some(mut pk) = self.pk_order.take()
2930 {
2931 pk.ensure_nodes(std::iter::once(dependent));
2932 pk.ensure_nodes(dependencies.iter().copied());
2933 {
2934 let adapter = GraphAdapter { g: self };
2935 for &dep_id in dependencies {
2936 match pk.try_add_edge(&adapter, dep_id, dependent) {
2937 Ok(_) => {}
2938 Err(_cycle) => {
2939 if self.config.pk_reject_cycle_edges {
2940 skip_deps.insert(dep_id);
2941 } else {
2942 pk.rebuild_full(&adapter);
2943 }
2944 }
2945 }
2946 }
2947 }
2948 self.pk_order = Some(pk);
2949 }
2950
2951 for &dep_id in dependencies {
2952 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
2953 continue;
2954 }
2955 self.edges.add_edge(dependent, dep_id);
2956 #[cfg(test)]
2957 {
2958 if let Ok(mut g) = self.instr.lock() {
2959 g.edges_added += 1;
2960 }
2961 }
2962 }
2963 }
2964
2965 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
2967 where
2968 I: IntoIterator<Item = (u32, u32, ASTNode)>,
2969 {
2970 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
2971 if collected.is_empty() {
2972 return Ok(0);
2973 }
2974 let vol_flags: Vec<bool> = collected
2975 .iter()
2976 .map(|(_, _, ast)| self.is_ast_volatile(ast))
2977 .collect();
2978 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
2979 }
2980
2981 pub fn bulk_set_formulas_with_volatility(
2982 &mut self,
2983 sheet: &str,
2984 collected: Vec<(u32, u32, ASTNode)>,
2985 _vol_flags: Vec<bool>,
2986 ) -> Result<usize, ExcelError> {
2987 let sheet_id = self.sheet_id_mut(sheet);
2988 if collected.is_empty() {
2989 return Ok(0);
2990 }
2991 let provider = RegistryFunctionProvider;
2992 let ingested = {
2993 let mut pipeline = self.ingest_pipeline(&provider);
2994 let inputs = collected.into_iter().map(|(row, col, ast)| {
2995 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2996 (FormulaAstInput::Tree(ast), placement, None)
2997 });
2998 pipeline.ingest_batch(inputs)?
2999 };
3000 let planned = ingested
3001 .into_iter()
3002 .map(|formula| {
3003 (
3004 formula.placement.coord.row() + 1,
3005 formula.placement.coord.col() + 1,
3006 formula.ast_id,
3007 formula.dep_plan,
3008 )
3009 })
3010 .collect();
3011 self.bulk_set_formulas_with_plans(sheet, planned)
3012 }
3013
3014 pub(crate) fn bulk_set_formulas_with_plans(
3015 &mut self,
3016 sheet: &str,
3017 planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
3018 ) -> Result<usize, ExcelError> {
3019 let sheet_id = self.sheet_id_mut(sheet);
3020 if planned.is_empty() {
3021 return Ok(0);
3022 }
3023 let budgets = self.self_admission_budgets();
3024 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3025 let admission_plans = planned
3026 .iter()
3027 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone()))
3028 .collect::<Vec<_>>();
3029 let usage = self.preview_formula_mutations(&admission_plans)?;
3030 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
3031 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3032 }
3033 let mut created_placeholders: Vec<CellRef> = Vec::new();
3034 let mut target_vids: Vec<VertexId> = Vec::with_capacity(planned.len());
3035 for (row, col, _, _) in &planned {
3036 let addr = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
3037 target_vids.push(self.get_or_create_vertex(&addr, &mut created_placeholders));
3038 }
3039 for (_, _, _, plan) in &planned {
3044 for cell in &plan.direct_cell_deps {
3045 self.get_or_create_vertex(cell, &mut created_placeholders);
3046 }
3047 }
3048
3049 for (i, &tvid) in target_vids.iter().enumerate() {
3050 if self.vertex_formulas.contains_key(&tvid) {
3051 self.remove_dependent_edges(tvid);
3052 }
3053 self.detach_vertex_from_names(tvid);
3054 self.clear_pending_name_references(tvid);
3055 self.store.set_kind(tvid, VertexKind::FormulaScalar);
3056 self.store.set_dirty(tvid, true);
3057 self.vertex_values.remove(&tvid);
3058 self.vertex_formulas.insert(tvid, planned[i].2);
3059 self.mark_volatile(tvid, planned[i].3.volatile);
3060 self.store.set_dynamic(tvid, planned[i].3.dynamic);
3061 }
3062 self.formula_dirty
3063 .legacy_extend(target_vids.iter().copied());
3064
3065 self.edges.begin_batch();
3066 for (i, tvid) in target_vids.iter().copied().enumerate() {
3067 let plan = &planned[i].3;
3068 let mut deps: Vec<VertexId> = Vec::new();
3069 for cell in &plan.direct_cell_deps {
3070 let dep_vid = self.get_or_create_vertex(cell, &mut created_placeholders);
3071 if !deps.contains(&dep_vid) {
3072 deps.push(dep_vid);
3073 }
3074 }
3075
3076 let mut name_vertices = Vec::new();
3077 for name in plan
3078 .resolved_named_refs
3079 .iter()
3080 .chain(plan.named_refs.iter())
3081 {
3082 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
3083 if !deps.contains(&named.vertex) {
3084 deps.push(named.vertex);
3085 }
3086 if !name_vertices.contains(&named.vertex) {
3087 name_vertices.push(named.vertex);
3088 }
3089 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
3090 if !deps.contains(&source.vertex) {
3091 deps.push(source.vertex);
3092 }
3093 } else {
3094 self.record_pending_name_reference(sheet_id, name, tvid);
3095 }
3096 }
3097 for source_name in &plan.source_refs {
3098 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
3099 if !deps.contains(&source.vertex) {
3100 deps.push(source.vertex);
3101 }
3102 } else if let Some(source) = self.resolve_source_table_entry(source_name)
3103 && !deps.contains(&source.vertex)
3104 {
3105 deps.push(source.vertex);
3106 }
3107 }
3108 for table_name in &plan.table_refs {
3109 if let Some(table) = self.resolve_table_entry(table_name) {
3110 if !deps.contains(&table.vertex) {
3111 deps.push(table.vertex);
3112 }
3113 } else if let Some(source) = self.resolve_source_table_entry(table_name)
3114 && !deps.contains(&source.vertex)
3115 {
3116 deps.push(source.vertex);
3117 }
3118 }
3119 if !name_vertices.is_empty() {
3120 self.attach_vertex_to_names(tvid, &name_vertices);
3121 }
3122 if !deps.is_empty() {
3123 self.add_dependent_edges_nobatch(tvid, &deps);
3124 }
3125 self.add_range_dependent_edges(tvid, &plan.range_deps, sheet_id);
3126 }
3127 self.edges.end_batch();
3128
3129 Ok(planned.len())
3130 }
3131
3132 pub fn add_dependency_edge(
3134 &mut self,
3135 dependent: VertexId,
3136 dependency: VertexId,
3137 ) -> Result<(), ExcelError> {
3138 if dependent == dependency {
3139 return Ok(());
3140 }
3141 let budgets = self.self_admission_budgets();
3142 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3143 let stats = self.baseline_stats();
3144 let added = usize::from(!self.get_dependencies(dependent).contains(&dependency));
3145 crate::engine::resource_ledger::preflight_graph_admission(
3146 &budgets,
3147 crate::engine::resource_ledger::GraphAdmission {
3148 final_vertices: stats.graph_vertex_count,
3149 final_edges: stats.graph_edge_count.checked_add(added).ok_or_else(|| {
3150 ExcelError::new(ExcelErrorKind::NImpl)
3151 .with_message("graph edge count overflow")
3152 })?,
3153 materialization_cells: 0,
3154 added_vertices: 0,
3155 added_edges: added,
3156 },
3157 None,
3158 )
3159 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3160 }
3161 if self.pk_order.is_some()
3163 && let Some(mut pk) = self.pk_order.take()
3164 {
3165 pk.ensure_nodes(std::iter::once(dependent));
3166 pk.ensure_nodes(std::iter::once(dependency));
3167 let adapter = GraphAdapter { g: self };
3168 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
3169 pk.rebuild_full(&adapter);
3171 }
3172 self.pk_order = Some(pk);
3173 }
3174 self.edges.add_edge(dependent, dependency);
3175 self.store.set_dirty(dependent, true);
3176 self.formula_dirty.legacy_insert(dependent);
3177 Ok(())
3178 }
3179
3180 fn remove_dependent_edges(&mut self, vertex: VertexId) {
3181 let dependencies = self.edges.out_edges(vertex);
3183
3184 self.edges.begin_batch();
3185 if self.pk_order.is_some()
3186 && let Some(mut pk) = self.pk_order.take()
3187 {
3188 for dep in &dependencies {
3189 pk.remove_edge(*dep, vertex);
3190 }
3191 self.pk_order = Some(pk);
3192 }
3193 for dep in dependencies {
3194 self.edges.remove_edge(vertex, dep);
3195 }
3196 self.edges.end_batch();
3197
3198 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
3200 let old_sheet_id = self.store.sheet_id(vertex);
3201
3202 for range in &old_ranges {
3203 let sheet_id = self
3205 .sheet_reg
3206 .resolve_locator(&range.sheet, old_sheet_id)
3207 .unwrap_or(old_sheet_id);
3208 let s_row = range.start_row.map(|b| b.index);
3209 let e_row = range.end_row.map(|b| b.index);
3210 let s_col = range.start_col.map(|b| b.index);
3211 let e_col = range.end_col.map(|b| b.index);
3212
3213 let mut keys_to_clean = FxHashSet::default();
3214
3215 let col_stripes = (s_row.is_none() && e_row.is_none())
3216 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
3217 let row_stripes = (s_col.is_none() && e_col.is_none())
3218 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
3219
3220 if col_stripes && !row_stripes {
3221 let sc = s_col.unwrap_or(0);
3222 let ec = e_col.unwrap_or(sc);
3223 for col in sc..=ec {
3224 keys_to_clean.insert(StripeKey {
3225 sheet_id,
3226 stripe_type: StripeType::Column,
3227 index: col,
3228 });
3229 }
3230 } else if row_stripes && !col_stripes {
3231 let sr = s_row.unwrap_or(0);
3232 let er = e_row.unwrap_or(sr);
3233 for row in sr..=er {
3234 keys_to_clean.insert(StripeKey {
3235 sheet_id,
3236 stripe_type: StripeType::Row,
3237 index: row,
3238 });
3239 }
3240 } else {
3241 let start_row = s_row.unwrap_or(0);
3242 let start_col = s_col.unwrap_or(0);
3243 let end_row = e_row.unwrap_or(start_row);
3244 let end_col = e_col.unwrap_or(start_col);
3245
3246 let height = end_row.saturating_sub(start_row) + 1;
3247 let width = end_col.saturating_sub(start_col) + 1;
3248
3249 if self.config.enable_block_stripes && height > 1 && width > 1 {
3250 let start_block_row = start_row / BLOCK_H;
3251 let end_block_row = end_row / BLOCK_H;
3252 let start_block_col = start_col / BLOCK_W;
3253 let end_block_col = end_col / BLOCK_W;
3254
3255 for block_row in start_block_row..=end_block_row {
3256 for block_col in start_block_col..=end_block_col {
3257 keys_to_clean.insert(StripeKey {
3258 sheet_id,
3259 stripe_type: StripeType::Block,
3260 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
3261 });
3262 }
3263 }
3264 } else if height > width {
3265 for col in start_col..=end_col {
3266 keys_to_clean.insert(StripeKey {
3267 sheet_id,
3268 stripe_type: StripeType::Column,
3269 index: col,
3270 });
3271 }
3272 } else {
3273 for row in start_row..=end_row {
3274 keys_to_clean.insert(StripeKey {
3275 sheet_id,
3276 stripe_type: StripeType::Row,
3277 index: row,
3278 });
3279 }
3280 }
3281 }
3282
3283 for key in keys_to_clean {
3284 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
3285 dependents.remove(&vertex);
3286 if dependents.is_empty() {
3287 self.stripe_to_dependents.remove(&key);
3288 #[cfg(test)]
3289 {
3290 if let Ok(mut g) = self.instr.lock() {
3291 g.stripe_removes += 1;
3292 }
3293 }
3294 }
3295 }
3296 }
3297 }
3298 }
3299 }
3300
3301 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
3307 if !self.value_cache_enabled {
3308 match self.store.kind(vertex_id) {
3310 VertexKind::Cell
3311 | VertexKind::FormulaScalar
3312 | VertexKind::FormulaArray
3313 | VertexKind::Empty => {
3314 self.vertex_values.remove(&vertex_id);
3315 return;
3316 }
3317 _ => {
3318 }
3320 }
3321 }
3322 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
3323 self.vertex_values.insert(vertex_id, value_ref);
3324 }
3325
3326 pub fn plan_spill_region(
3328 &self,
3329 anchor: VertexId,
3330 target_cells: &[CellRef],
3331 ) -> Result<(), ExcelError> {
3332 self.plan_spill_region_allowing_formula_overwrite(anchor, target_cells, None)
3333 }
3334
3335 pub(crate) fn plan_spill_region_allowing_formula_overwrite(
3340 &self,
3341 anchor: VertexId,
3342 target_cells: &[CellRef],
3343 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
3344 ) -> Result<(), ExcelError> {
3345 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
3346 let (expected_rows, expected_cols) = if target_cells.is_empty() {
3348 (0u32, 0u32)
3349 } else {
3350 let mut min_r = u32::MAX;
3351 let mut max_r = 0u32;
3352 let mut min_c = u32::MAX;
3353 let mut max_c = 0u32;
3354 for cell in target_cells {
3355 let r = cell.coord.row();
3356 let c = cell.coord.col();
3357 if r < min_r {
3358 min_r = r;
3359 }
3360 if r > max_r {
3361 max_r = r;
3362 }
3363 if c < min_c {
3364 min_c = c;
3365 }
3366 if c > max_c {
3367 max_c = c;
3368 }
3369 }
3370 (
3371 max_r.saturating_sub(min_r).saturating_add(1),
3372 max_c.saturating_sub(min_c).saturating_add(1),
3373 )
3374 };
3375 for cell in target_cells {
3377 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
3379 Some(&existing_anchor) if existing_anchor == anchor => true,
3380 Some(_other) => {
3381 return Err(ExcelError::new(ExcelErrorKind::Spill)
3382 .with_message("BlockedBySpill")
3383 .with_extra(ExcelErrorExtra::Spill {
3384 expected_rows,
3385 expected_cols,
3386 }));
3387 }
3388 None => false,
3389 };
3390
3391 if owned_by_anchor {
3392 continue;
3393 }
3394
3395 if let Some(&vid) = self.cell_to_vertex.get(cell)
3397 && vid != anchor
3398 {
3399 match self.store.kind(vid) {
3401 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
3402 if let Some(allow) = overwritable_formulas
3403 && allow.contains(&vid)
3404 {
3405 continue;
3406 }
3407 return Err(ExcelError::new(ExcelErrorKind::Spill)
3408 .with_message("BlockedByFormula")
3409 .with_extra(ExcelErrorExtra::Spill {
3410 expected_rows,
3411 expected_cols,
3412 }));
3413 }
3414 _ => {
3415 if let Some(vref) = self.vertex_values.get(&vid) {
3417 let v = self.data_store.retrieve_value(*vref);
3418 if !matches!(v, LiteralValue::Empty) {
3419 return Err(ExcelError::new(ExcelErrorKind::Spill)
3420 .with_message("BlockedByValue")
3421 .with_extra(ExcelErrorExtra::Spill {
3422 expected_rows,
3423 expected_cols,
3424 }));
3425 }
3426 }
3427 }
3428 }
3429 }
3430 }
3431 Ok(())
3432 }
3433
3434 pub fn commit_spill_region_atomic_with_fault(
3441 &mut self,
3442 anchor: VertexId,
3443 target_cells: Vec<CellRef>,
3444 values: Vec<Vec<LiteralValue>>,
3445 fault_after_ops: Option<usize>,
3446 ) -> Result<(), ExcelError> {
3447 let budgets = self.self_admission_budgets();
3448 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3449 let admission = self.preview_spill_materialization(&target_cells)?;
3450 crate::engine::resource_ledger::preflight_graph_admission(&budgets, admission, None)
3451 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3452 }
3453
3454 let anchor_cell = self
3458 .get_cell_ref(anchor)
3459 .expect("anchor cell ref for spill commit");
3460 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
3461 let anchor_row = anchor_cell.coord.row();
3462 let anchor_col = anchor_cell.coord.col();
3463
3464 let prev_cells = self
3466 .spill_anchor_to_cells
3467 .get(&anchor)
3468 .cloned()
3469 .unwrap_or_default();
3470 let new_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
3473 target_cells.iter().copied().collect();
3474 let prev_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
3475 prev_cells.iter().copied().collect();
3476
3477 #[derive(Clone)]
3479 struct Op {
3480 sheet: String,
3481 row: u32,
3482 col: u32,
3483 new_value: LiteralValue,
3484 }
3485 let mut ops: Vec<Op> = Vec::new();
3486
3487 for cell in prev_cells.iter() {
3489 if !new_set.contains(cell) {
3490 let sheet = self.sheet_name(cell.sheet_id).to_string();
3491 ops.push(Op {
3492 sheet,
3493 row: cell.coord.row(),
3494 col: cell.coord.col(),
3495 new_value: LiteralValue::Empty,
3496 });
3497 }
3498 }
3499
3500 if !target_cells.is_empty() {
3502 let first = target_cells.first().copied().unwrap();
3503 let row0 = first.coord.row();
3504 let col0 = first.coord.col();
3505 let sheet = self.sheet_name(first.sheet_id).to_string();
3506 for (r_off, row_vals) in values.iter().enumerate() {
3507 for (c_off, v) in row_vals.iter().enumerate() {
3508 ops.push(Op {
3509 sheet: sheet.clone(),
3510 row: row0 + r_off as u32,
3511 col: col0 + c_off as u32,
3512 new_value: v.clone(),
3513 });
3514 }
3515 }
3516 }
3517
3518 #[derive(Clone)]
3520 struct OldVal {
3521 present: bool,
3522 value: LiteralValue,
3523 }
3524 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
3525
3526 for op in &ops {
3528 let old = self
3530 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
3531 .unwrap_or(LiteralValue::Empty);
3532 let present = true; old_values.push((
3534 (op.sheet.clone(), op.row, op.col),
3535 OldVal {
3536 present,
3537 value: old,
3538 },
3539 ));
3540 }
3541
3542 for (applied, op) in ops.iter().enumerate() {
3544 if let Some(n) = fault_after_ops
3545 && applied == n
3546 {
3547 for idx in (0..applied).rev() {
3548 let ((ref sheet, row, col), ref old) = old_values[idx];
3549 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
3550 self.update_vertex_value(anchor, old.value.clone());
3551 } else {
3552 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
3553 }
3554 }
3555 return Err(ExcelError::new(ExcelErrorKind::Error)
3556 .with_message("Injected persistence fault during spill commit"));
3557 }
3558 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
3559 self.update_vertex_value(anchor, op.new_value.clone());
3560 } else {
3561 let _ =
3562 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
3563 }
3564 }
3565
3566 for cell in prev_cells.iter() {
3569 if !new_set.contains(cell) {
3570 self.spill_cell_to_anchor.remove(cell);
3571 let remove_sheet = self
3572 .spill_cells_by_sheet
3573 .get_mut(&cell.sheet_id)
3574 .is_some_and(|sheet| {
3575 sheet.remove(&(cell.coord.row(), cell.coord.col()));
3576 sheet.is_empty()
3577 });
3578 if remove_sheet {
3579 self.spill_cells_by_sheet.remove(&cell.sheet_id);
3580 }
3581 }
3582 }
3583 for cell in &target_cells {
3585 self.spill_cell_to_anchor.insert(*cell, anchor);
3586 self.spill_cells_by_sheet
3587 .entry(cell.sheet_id)
3588 .or_default()
3589 .insert((cell.coord.row(), cell.coord.col()), anchor);
3590 }
3591 self.spill_anchor_to_cells.insert(anchor, target_cells);
3592 Ok(())
3593 }
3594
3595 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
3596 self.spill_anchor_to_cells
3597 .get(&anchor)
3598 .map(|v| v.as_slice())
3599 }
3600
3601 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
3602 self.spill_anchor_to_cells.contains_key(&anchor)
3603 }
3604
3605 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
3606 self.spill_cell_to_anchor.get(&cell).copied()
3607 }
3608
3609 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
3610 (
3611 self.spill_anchor_to_cells.len(),
3612 self.spill_cell_to_anchor.len(),
3613 )
3614 }
3615
3616 pub fn clear_spill_region(&mut self, anchor: VertexId) {
3618 let _ = self.clear_spill_region_bulk(anchor);
3619 }
3620
3621 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
3630 let anchor_cell = self.get_cell_ref(anchor);
3631 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
3632 return Vec::new();
3633 };
3634
3635 for cell in cells.iter() {
3637 self.spill_cell_to_anchor.remove(cell);
3638 let remove_sheet = self
3639 .spill_cells_by_sheet
3640 .get_mut(&cell.sheet_id)
3641 .is_some_and(|sheet| {
3642 sheet.remove(&(cell.coord.row(), cell.coord.col()));
3643 sheet.is_empty()
3644 });
3645 if remove_sheet {
3646 self.spill_cells_by_sheet.remove(&cell.sheet_id);
3647 }
3648 }
3649
3650 let empty_ref = if self.value_cache_enabled {
3652 Some(self.data_store.store_value(LiteralValue::Empty))
3653 } else {
3654 None
3655 };
3656
3657 let mut changed_vertices: Vec<VertexId> = Vec::new();
3659 for cell in cells.iter().copied() {
3660 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
3661 if is_anchor {
3662 continue;
3663 }
3664 let Some(&vid) = self.cell_to_vertex.get(&cell) else {
3665 continue;
3666 };
3667 if self.vertex_formulas.remove(&vid).is_some() {
3669 self.remove_dependent_edges(vid);
3672 }
3673 self.store.set_kind(vid, VertexKind::Cell);
3674 if let Some(er) = empty_ref {
3675 self.vertex_values.insert(vid, er);
3676 } else {
3677 self.vertex_values.remove(&vid);
3678 }
3679 self.store.set_dirty(vid, false);
3680 self.formula_dirty.legacy_remove(&vid);
3681 changed_vertices.push(vid);
3682 }
3683
3684 if !changed_vertices.is_empty() {
3686 self.mark_dirty_many_value_cells(&changed_vertices);
3687 }
3688
3689 cells
3690 }
3691
3692 fn mark_dirty_many_value_cells(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
3693 if vertex_ids.is_empty() {
3694 return Vec::new();
3695 }
3696
3697 if self.deferred_dirty_depth > 0 {
3705 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
3706 return vertex_ids.to_vec();
3707 }
3708
3709 if self.edges.delta_size() > 0 {
3714 self.edges.rebuild();
3715 }
3716
3717 let mut affected: FxHashSet<VertexId> = FxHashSet::default();
3718 let mut to_visit: Vec<VertexId> = Vec::new();
3719 let mut visited_for_propagation: FxHashSet<VertexId> = FxHashSet::default();
3720
3721 for &src in vertex_ids {
3723 affected.insert(src);
3724 }
3725
3726 for &src in vertex_ids {
3728 to_visit.extend(self.edges.in_edges(src));
3729 if let Some(name_set) = self.cell_to_name_dependents.get(&src) {
3730 for &name_vertex in name_set {
3731 to_visit.push(name_vertex);
3732 }
3733 }
3734 }
3735
3736 let mut bounds_by_sheet: FxHashMap<SheetId, (u32, u32, u32, u32)> = FxHashMap::default();
3738 for &src in vertex_ids {
3739 let view = self.store.view(src);
3740 let sid = view.sheet_id();
3741 let r = view.row();
3742 let c = view.col();
3743 bounds_by_sheet
3744 .entry(sid)
3745 .and_modify(|b| {
3746 b.0 = b.0.min(r);
3747 b.1 = b.1.max(r);
3748 b.2 = b.2.min(c);
3749 b.3 = b.3.max(c);
3750 })
3751 .or_insert((r, r, c, c));
3752 }
3753
3754 for (sid, (sr, er, sc, ec)) in bounds_by_sheet {
3755 to_visit.extend(self.collect_range_dependents_for_rect(sid, sr, sc, er, ec));
3756 }
3757
3758 while let Some(id) = to_visit.pop() {
3759 if !visited_for_propagation.insert(id) {
3760 continue;
3761 }
3762 self.dirty_propagation_visits += 1;
3763 affected.insert(id);
3764 self.store.set_dirty(id, true);
3765 to_visit.extend(self.edges.in_edges(id));
3766 to_visit.extend(self.collect_range_dependents_for_vertex(id));
3767 }
3768
3769 self.formula_dirty.legacy_extend(affected.iter().copied());
3770 affected.into_iter().collect()
3771 }
3772
3773 fn collect_range_dependents_for_vertex(&self, vertex_id: VertexId) -> Vec<VertexId> {
3774 match self.store.kind(vertex_id) {
3775 VertexKind::Cell
3776 | VertexKind::Empty
3777 | VertexKind::FormulaScalar
3778 | VertexKind::FormulaArray => {
3779 let view = self.store.view(vertex_id);
3780 self.collect_range_dependents_for_rect(
3781 view.sheet_id(),
3782 view.row(),
3783 view.col(),
3784 view.row(),
3785 view.col(),
3786 )
3787 }
3788 _ => Vec::new(),
3789 }
3790 }
3791
3792 fn collect_range_dependents_for_rect(
3793 &self,
3794 sheet_id: SheetId,
3795 start_row: u32,
3796 start_col: u32,
3797 end_row: u32,
3798 end_col: u32,
3799 ) -> Vec<VertexId> {
3800 if self.stripe_to_dependents.is_empty() {
3801 return Vec::new();
3802 }
3803 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
3804
3805 for col in start_col..=end_col {
3806 let key = StripeKey {
3807 sheet_id,
3808 stripe_type: StripeType::Column,
3809 index: col,
3810 };
3811 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3812 candidates.extend(deps);
3813 }
3814 }
3815 for row in start_row..=end_row {
3816 let key = StripeKey {
3817 sheet_id,
3818 stripe_type: StripeType::Row,
3819 index: row,
3820 };
3821 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3822 candidates.extend(deps);
3823 }
3824 }
3825 if self.config.enable_block_stripes {
3826 let br0 = start_row / BLOCK_H;
3827 let br1 = end_row / BLOCK_H;
3828 let bc0 = start_col / BLOCK_W;
3829 let bc1 = end_col / BLOCK_W;
3830 for br in br0..=br1 {
3831 for bc in bc0..=bc1 {
3832 let key = StripeKey {
3833 sheet_id,
3834 stripe_type: StripeType::Block,
3835 index: block_index(br * BLOCK_H, bc * BLOCK_W),
3836 };
3837 if let Some(deps) = self.stripe_to_dependents.get(&key) {
3838 candidates.extend(deps);
3839 }
3840 }
3841 }
3842 }
3843
3844 let mut out: Vec<VertexId> = Vec::new();
3846 for dep_id in candidates {
3847 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
3848 continue;
3849 };
3850 let mut hit = false;
3851 for range in ranges {
3852 let range_sheet_id = self
3855 .sheet_reg
3856 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dep_id))
3857 .unwrap_or(sheet_id);
3858 if range_sheet_id != sheet_id {
3859 continue;
3860 }
3861 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
3862 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
3863 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
3864 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
3865 let overlap =
3866 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
3867 if overlap {
3868 hit = true;
3869 break;
3870 }
3871 }
3872 if hit {
3873 out.push(dep_id);
3874 }
3875 }
3876 out
3877 }
3878
3879 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
3881 if vertex_id.0 < FIRST_NORMAL_VERTEX {
3882 return false;
3883 }
3884 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
3885 index < self.store.len()
3886 }
3887
3888 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
3890 self.store.kind(vertex_id)
3891 }
3892
3893 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
3895 self.store.sheet_id(vertex_id)
3896 }
3897
3898 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
3899 self.vertex_formulas.get(&vertex_id).copied()
3900 }
3901
3902 pub(crate) fn formula_vertices(&self) -> Vec<VertexId> {
3903 let mut vertices = self.vertex_formulas.keys().copied().collect::<Vec<_>>();
3904 vertices.sort_unstable();
3905 vertices
3906 }
3907
3908 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
3909 let ast_id = self.get_formula_id(vertex_id)?;
3910 Some((ast_id, self.is_volatile(vertex_id)))
3911 }
3912
3913 pub fn get_formula_node(&self, vertex_id: VertexId) -> Option<&super::arena::AstNodeData> {
3914 let ast_id = self.get_formula_id(vertex_id)?;
3915 self.data_store.get_node(ast_id)
3916 }
3917
3918 pub fn get_formula_node_and_volatile(
3919 &self,
3920 vertex_id: VertexId,
3921 ) -> Option<(&super::arena::AstNodeData, bool)> {
3922 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
3923 let node = self.data_store.get_node(ast_id)?;
3924 Some((node, vol))
3925 }
3926
3927 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
3931 let ast_id = self.get_formula_id(vertex_id)?;
3932 self.data_store.retrieve_ast(ast_id, &self.sheet_reg)
3933 }
3934
3935 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
3937 if !self.value_cache_enabled {
3938 match self.store.kind(vertex_id) {
3941 VertexKind::Cell
3942 | VertexKind::FormulaScalar
3943 | VertexKind::FormulaArray
3944 | VertexKind::Empty => {
3945 #[cfg(debug_assertions)]
3946 {
3947 self.graph_value_read_attempts
3948 .fetch_add(1, Ordering::Relaxed);
3949 }
3950 return None;
3951 }
3952 _ => {
3953 }
3955 }
3956 }
3957 self.vertex_values
3958 .get(&vertex_id)
3959 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
3960 }
3961
3962 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
3964 let packed_coord = self.store.coord(vertex_id);
3965 let sheet_id = self.store.sheet_id(vertex_id);
3966 let coord = Coord::new(packed_coord.row(), packed_coord.col(), true, true);
3967 Some(CellRef::new(sheet_id, coord))
3968 }
3969
3970 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
3972 let coord = Coord::new(row, col, true, true);
3973 CellRef::new(sheet_id, coord)
3974 }
3975
3976 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
3978 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
3979 let coord = Coord::from_excel(row, col, true, true);
3980 CellRef::new(sheet_id, coord)
3981 }
3982
3983 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
3985 self.store.is_dirty(vertex_id)
3986 }
3987
3988 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
3990 self.store.is_volatile(vertex_id)
3991 }
3992
3993 pub(crate) fn is_dynamic(&self, vertex_id: VertexId) -> bool {
3994 self.store.is_dynamic(vertex_id)
3995 }
3996
3997 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<&VertexId> {
3999 self.cell_to_vertex.get(addr)
4000 }
4001
4002 #[cfg(test)]
4003 pub fn cell_to_vertex(
4004 &self,
4005 ) -> &std::collections::HashMap<CellRef, VertexId, CoordBuildHasher> {
4006 &self.cell_to_vertex
4007 }
4008
4009 #[inline]
4013 pub(crate) fn dependencies_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
4014 self.edges.out_edges_ref(vertex_id)
4015 }
4016
4017 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
4019 self.edges.out_edges(vertex_id)
4020 }
4021
4022 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
4024 if let Some(deps) = self.dependencies_slice(vertex_id) {
4025 deps.contains(&vertex_id)
4026 } else {
4027 self.edges.out_edges(vertex_id).contains(&vertex_id)
4028 }
4029 }
4030
4031 #[inline]
4035 pub(crate) fn dependents_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
4036 self.edges.in_edges_ref(vertex_id)
4037 }
4038
4039 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
4045 self.edges.in_edges_merged(vertex_id)
4046 }
4047
4048 pub(crate) fn visit_direct_dependents_bounded(
4052 &self,
4053 vertex_id: VertexId,
4054 remaining_work: &mut u64,
4055 visitor: &mut dyn FnMut(VertexId) -> bool,
4056 ) -> bool {
4057 self.edges
4058 .visit_in_edges_bounded(vertex_id, remaining_work, visitor)
4059 }
4060
4061 #[doc(hidden)]
4065 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
4066 let coord = self.store.coord(id);
4067 let sheet_id = self.store.sheet_id(id);
4068 let kind = self.store.kind(id);
4069 let flags = self.store.flags(id);
4070
4071 let value_ref = self.vertex_values.get(&id).copied();
4073 let formula_ref = self.vertex_formulas.get(&id).copied();
4074
4075 let out_edges = self.get_dependencies(id);
4077
4078 crate::engine::VertexSnapshot {
4079 coord,
4080 sheet_id,
4081 kind,
4082 flags,
4083 value_ref,
4084 formula_ref,
4085 out_edges,
4086 }
4087 }
4088
4089 #[doc(hidden)]
4091 pub fn remove_all_edges(&mut self, id: VertexId) {
4092 self.edges.begin_batch();
4094
4095 self.remove_dependent_edges(id);
4097
4098 let dependents = self.get_dependents(id);
4101 if self.pk_order.is_some()
4102 && let Some(mut pk) = self.pk_order.take()
4103 {
4104 for dependent in &dependents {
4105 pk.remove_edge(id, *dependent);
4106 }
4107 self.pk_order = Some(pk);
4108 }
4109 for dependent in dependents {
4110 self.edges.remove_edge(dependent, id);
4111 }
4112
4113 self.edges.end_batch();
4115 }
4116
4117 #[doc(hidden)]
4119 pub fn mark_as_ref_error(&mut self, id: VertexId) {
4120 if !self.value_cache_enabled {
4121 match self.store.kind(id) {
4122 VertexKind::Cell
4123 | VertexKind::FormulaScalar
4124 | VertexKind::FormulaArray
4125 | VertexKind::Empty => {
4126 self.ref_error_vertices.insert(id);
4127 self.vertex_values.remove(&id);
4130 let _ = self.mark_dirty(id);
4131 return;
4132 }
4133 _ => {
4134 }
4136 }
4137 }
4138 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
4139 let value_ref = self.data_store.store_value(error);
4140 self.vertex_values.insert(id, value_ref);
4141 let _ = self.mark_dirty(id);
4142 }
4143
4144 pub fn is_ref_error(&self, id: VertexId) -> bool {
4146 if !self.value_cache_enabled {
4147 match self.store.kind(id) {
4148 VertexKind::Cell
4149 | VertexKind::FormulaScalar
4150 | VertexKind::FormulaArray
4151 | VertexKind::Empty => {
4152 return self.ref_error_vertices.contains(&id);
4153 }
4154 _ => {
4155 }
4157 }
4158 }
4159 if let Some(value_ref) = self.vertex_values.get(&id) {
4160 let value = self.data_store.retrieve_value(*value_ref);
4161 if let LiteralValue::Error(err) = value {
4162 return err.kind == ExcelErrorKind::Ref;
4163 }
4164 }
4165 false
4166 }
4167
4168 #[doc(hidden)]
4170 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
4171 let dependents = self.get_dependents(id);
4172 for dep_id in dependents {
4173 self.store.set_dirty(dep_id, true);
4174 self.formula_dirty.legacy_insert(dep_id);
4175 }
4176 }
4177
4178 #[doc(hidden)]
4180 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
4181 self.store.set_volatile(id, volatile);
4182 if volatile {
4183 self.volatile_vertices.insert(id);
4184 } else {
4185 self.volatile_vertices.remove(&id);
4186 }
4187 }
4188
4189 #[doc(hidden)]
4191 pub fn set_coord(&mut self, id: VertexId, coord: AbsCoord) {
4192 self.store.set_coord(id, coord);
4193 }
4194
4195 #[doc(hidden)]
4197 pub fn update_edge_coord(&mut self, id: VertexId, coord: AbsCoord) {
4198 self.edges.update_coord(id, coord);
4199 }
4200
4201 #[doc(hidden)]
4203 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
4204 self.store.mark_deleted(id, deleted);
4205 }
4206
4207 #[doc(hidden)]
4209 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
4210 self.store.set_kind(id, kind);
4211 }
4212
4213 #[doc(hidden)]
4215 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
4216 self.store.set_dirty(id, dirty);
4217 if dirty {
4218 self.formula_dirty.legacy_insert(id);
4219 } else {
4220 self.formula_dirty.legacy_remove(&id);
4221 }
4222 }
4223
4224 #[cfg(test)]
4226 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
4227 self.store.kind(id)
4228 }
4229
4230 #[cfg(test)]
4232 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
4233 self.store.flags(id)
4234 }
4235
4236 #[cfg(test)]
4238 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
4239 self.store.is_deleted(id)
4240 }
4241
4242 #[doc(hidden)]
4244 pub fn rebuild_edges(&mut self) {
4245 self.edges.rebuild();
4246 }
4247
4248 pub fn flush_pending_edge_deltas(&mut self) {
4254 self.edges.rebuild();
4255 }
4256
4257 #[doc(hidden)]
4259 pub fn edges_delta_size(&self) -> usize {
4260 self.edges.delta_size()
4261 }
4262
4263 #[doc(hidden)]
4266 pub fn edges_rebuild_count(&self) -> u64 {
4267 self.edges.rebuild_count()
4268 }
4269
4270 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
4272 self.cell_to_vertex.get(addr).copied()
4273 }
4274
4275 pub fn get_coord(&self, id: VertexId) -> AbsCoord {
4277 self.store.coord(id)
4278 }
4279
4280 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
4282 self.store.sheet_id(id)
4283 }
4284
4285 pub fn vertices_in_sheet(&self, sheet_id: SheetId) -> impl Iterator<Item = VertexId> + '_ {
4287 self.store
4288 .all_vertices()
4289 .filter(move |&id| self.vertex_exists(id) && self.store.sheet_id(id) == sheet_id)
4290 }
4291
4292 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
4294 self.vertex_formulas.contains_key(&id)
4295 }
4296
4297 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
4299 self.vertex_formulas.keys().copied()
4300 }
4301
4302 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
4304 let sheet_id = self.store.sheet_id(id);
4306
4307 let (new_dependencies, new_range_dependencies, _, named_dependencies, unresolved_names) =
4309 self.extract_dependencies_with_pending_names(&ast, sheet_id)?;
4310
4311 let old_kind = self.store.kind(id);
4312
4313 self.remove_dependent_edges(id);
4315 self.detach_vertex_from_names(id);
4316 self.clear_pending_name_references(id);
4317
4318 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
4320 self.vertex_formulas.insert(id, ast_id);
4321
4322 self.add_dependent_edges(id, &new_dependencies);
4324 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
4325
4326 if !named_dependencies.is_empty() {
4327 self.attach_vertex_to_names(id, &named_dependencies);
4328 }
4329 for unresolved_name in &unresolved_names {
4330 self.record_pending_name_reference(sheet_id, unresolved_name, id);
4331 }
4332
4333 self.ref_error_vertices.remove(&id);
4336 self.vertex_values.remove(&id);
4337
4338 self.store.set_kind(
4340 id,
4341 if old_kind == VertexKind::FormulaArray {
4342 VertexKind::FormulaArray
4343 } else {
4344 VertexKind::FormulaScalar
4345 },
4346 );
4347
4348 Ok(())
4349 }
4350
4351 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
4353 self.store.set_dirty(vertex_id, true);
4354 self.formula_dirty.legacy_insert(vertex_id);
4355 }
4356
4357 pub fn mark_vertices_dirty_batch(&mut self, vertices: &[VertexId]) {
4359 self.formula_dirty.legacy_reserve(vertices.len());
4360 for &vertex_id in vertices {
4361 self.store.set_dirty(vertex_id, true);
4362 }
4363 self.formula_dirty.legacy_extend(vertices.iter().copied());
4364 }
4365
4366 pub fn update_cell_mapping(
4368 &mut self,
4369 id: VertexId,
4370 old_addr: Option<CellRef>,
4371 new_addr: CellRef,
4372 ) {
4373 if let Some(old) = old_addr {
4375 self.cell_to_vertex.remove(&old);
4376 }
4377 self.cell_to_vertex.insert(new_addr, id);
4379 }
4380
4381 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
4383 self.cell_to_vertex.remove(addr);
4384 }
4385
4386 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
4388 let coord = self.store.coord(id);
4389 let sheet_id = self.store.sheet_id(id);
4390 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
4392 if self.cell_to_vertex.get(&cell_ref) == Some(&id) {
4394 Some(cell_ref)
4395 } else {
4396 None
4397 }
4398 }
4399
4400 pub(crate) fn rebuild_formula_dependencies(&mut self, vertex_id: VertexId, ast: &ASTNode) {
4406 let sheet_id = self.store.sheet_id(vertex_id);
4407
4408 self.remove_dependent_edges(vertex_id);
4410 self.detach_vertex_from_names(vertex_id);
4411 self.clear_pending_name_references(vertex_id);
4412
4413 let (
4414 new_dependencies,
4415 new_range_dependencies,
4416 _created_placeholders,
4417 named_dependencies,
4418 unresolved_names,
4419 ) = match self.extract_dependencies_with_pending_names(ast, sheet_id) {
4420 Ok(v) => v,
4421 Err(_) => {
4422 self.mark_as_ref_error(vertex_id);
4423 return;
4424 }
4425 };
4426
4427 if new_dependencies.contains(&vertex_id) && !self.config.cycle.allows_self_dependency() {
4430 self.mark_as_ref_error(vertex_id);
4431 return;
4432 }
4433
4434 for &name_vertex in &named_dependencies {
4435 let mut visited = FxHashSet::default();
4436 if self.name_depends_on_vertex(name_vertex, vertex_id, &mut visited) {
4437 self.mark_as_ref_error(vertex_id);
4438 return;
4439 }
4440 }
4441
4442 self.ref_error_vertices.remove(&vertex_id);
4444 self.vertex_values.remove(&vertex_id);
4445
4446 if !named_dependencies.is_empty() {
4447 self.attach_vertex_to_names(vertex_id, &named_dependencies);
4448 }
4449 for unresolved_name in &unresolved_names {
4450 self.record_pending_name_reference(sheet_id, unresolved_name, vertex_id);
4451 }
4452
4453 self.add_dependent_edges(vertex_id, &new_dependencies);
4454 self.add_range_dependent_edges(vertex_id, &new_range_dependencies, sheet_id);
4455 let _ = self.mark_dirty(vertex_id);
4456 }
4457}
4458
4459