1use crate::SheetId;
2use crate::engine::TombstoneRegistry;
3use crate::engine::named_range::{NameScope, NamedDefinition, NamedRange};
4use crate::engine::sheet_registry::SheetRegistry;
5use formualizer_common::{
6 CoordBuildHasher, ExcelError, ExcelErrorKind, LiteralValue, PackedSheetCell,
7};
8use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
9use rustc_hash::{FxHashMap, FxHashSet};
10
11#[cfg(debug_assertions)]
12use std::sync::atomic::{AtomicU64, Ordering};
13
14#[cfg(test)]
15#[derive(Debug, Default, Clone)]
16pub struct GraphInstrumentation {
17 pub edges_added: u64,
18 pub stripe_inserts: u64,
19 pub stripe_removes: u64,
20 pub dependents_scan_fallback_calls: u64,
21 pub dependents_scan_vertices_scanned: u64,
22}
23
24mod ast_utils;
25pub(crate) mod authority_host;
26pub mod editor;
27mod extent_record;
28mod formula_analysis;
29#[cfg(test)]
30mod formula_analysis_legacy_tests;
31mod formula_dirty;
32mod names;
33pub(crate) mod prepared_legacy_graph;
34mod range_deps;
35pub(crate) use range_deps::{StructuralEdit, StructuralOccupancy};
36
37mod sheets;
38pub mod snapshot;
39mod sources;
40mod structural_runs;
41mod tables;
42pub(crate) mod virtual_members;
43
44pub(crate) use structural_runs::ShiftedRun;
45pub(crate) use tables::TableEntry;
46
47use super::addr::{GridAddr, SymbolAddr, VertexAddr};
48use super::arena::{AstNodeId, DataStore, ValueRef};
49#[cfg(any(test, feature = "legacy_oracle"))]
50use super::delta_edges::CsrMutableEdges;
51use super::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
52use super::sheet_index::SheetIndex;
53use super::vertex::{VertexId, VertexKind};
54use super::vertex_store::{FIRST_NORMAL_VERTEX, VertexStore};
55#[cfg(any(test, feature = "legacy_oracle"))]
56use crate::engine::topo::{
57 GraphAdapter,
58 pk::{DynamicTopo, PkConfig},
59};
60use crate::reference::{CellRef, Coord, SharedRangeRef, SharedRef, SharedSheetLocator};
61use formualizer_common::Coord as AbsCoord;
62use formula_dirty::FormulaDirtyState;
63
64fn ast_calls_subtotal(ast: &ASTNode) -> bool {
66 match &ast.node_type {
67 ASTNodeType::Function { name, args } => {
68 super::arena::ast::subtotal_call_bit(name) != 0 || args.iter().any(ast_calls_subtotal)
69 }
70 ASTNodeType::BinaryOp { left, right, .. } => {
71 ast_calls_subtotal(left) || ast_calls_subtotal(right)
72 }
73 ASTNodeType::UnaryOp { expr, .. } => ast_calls_subtotal(expr),
74 ASTNodeType::Array(rows) => rows.iter().flatten().any(ast_calls_subtotal),
75 ASTNodeType::Call { callee, args } => {
76 ast_calls_subtotal(callee) || args.iter().any(ast_calls_subtotal)
77 }
78 _ => false,
79 }
80}
81struct RegistryFunctionProvider;
84
85impl crate::traits::FunctionProvider for RegistryFunctionProvider {
86 fn planning_semantic_revision(&self) -> Option<u64> {
87 Some(0)
88 }
89
90 fn get_function(
91 &self,
92 ns: &str,
93 name: &str,
94 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
95 crate::function_registry::get(ns, name)
96 }
97
98 fn get_function_for_planning(
99 &self,
100 ns: &str,
101 name: &str,
102 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
103 crate::function_registry::get_for_planning(ns, name)
104 }
105}
106
107#[inline]
108fn normalize_stored_literal(value: LiteralValue) -> LiteralValue {
109 match value {
110 LiteralValue::Int(i) => LiteralValue::Number(i as f64),
112 other => other,
113 }
114}
115
116pub use editor::change_log::{ChangeEvent, ChangeLog};
117
118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub enum DependencyRef {
123 Cell(VertexId),
125 Range {
127 sheet: String,
128 start_row: u32,
129 start_col: u32,
130 end_row: u32, end_col: u32, },
133 WholeColumn { sheet: String, col: u32 },
135 WholeRow { sheet: String, row: u32 },
137}
138
139#[derive(Debug, Clone, Hash, PartialEq, Eq)]
141pub struct StripeKey {
142 pub sheet_id: SheetId,
143 pub stripe_type: StripeType,
144 pub index: u32, }
146
147#[derive(Debug, Clone, Hash, PartialEq, Eq)]
148pub enum StripeType {
149 Row,
150 Column,
151 Block, }
153
154const BLOCK_H: u32 = 256;
156const BLOCK_W: u32 = 256;
157
158pub fn block_index(row: u32, col: u32) -> u32 {
159 (row / BLOCK_H) << 16 | (col / BLOCK_W)
160}
161
162#[derive(Debug, Clone)]
165pub struct OperationSummary {
166 pub affected_vertices: Vec<VertexId>,
168 pub created_placeholders: Vec<CellRef>,
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
177pub struct GraphBaselineStats {
178 pub graph_vertex_count: usize,
179 pub graph_formula_vertex_count: usize,
180 pub graph_edge_count: usize,
181 pub dirty_vertex_count: usize,
182 pub evaluation_vertex_count: usize,
183 pub formula_ast_root_count: usize,
184 pub formula_ast_node_count: usize,
185}
186
187#[derive(Clone, Copy, Debug, PartialEq, Eq)]
189pub(crate) enum FormulaRef {
190 Own(AstNodeId),
192 Member {
196 template: AstNodeId,
197 anchor: (u32, u32),
198 },
199}
200
201impl FormulaRef {
202 #[inline]
205 pub(crate) fn root(self) -> AstNodeId {
206 match self {
207 FormulaRef::Own(id) | FormulaRef::Member { template: id, .. } => id,
208 }
209 }
210
211 #[inline]
213 pub(crate) fn of_ingested(ast_id: AstNodeId, member_anchor: Option<(u32, u32)>) -> Self {
214 match member_anchor {
215 None => FormulaRef::Own(ast_id),
216 Some(anchor) => FormulaRef::Member {
217 template: ast_id,
218 anchor,
219 },
220 }
221 }
222
223 #[inline]
225 pub(crate) fn own(self) -> Option<AstNodeId> {
226 match self {
227 FormulaRef::Own(id) => Some(id),
228 FormulaRef::Member { .. } => None,
229 }
230 }
231}
232
233#[derive(Debug, Default)]
240pub(crate) struct FormulaMap {
241 map: FxHashMap<VertexId, FormulaRef>,
242 virt: virtual_members::VirtualMembers,
243 touched: Vec<VertexId>,
244}
245
246impl FormulaMap {
247 #[inline]
249 pub(crate) fn get(&self, vertex: &VertexId) -> Option<FormulaRef> {
250 match self.map.get(vertex) {
251 Some(&f) => Some(f),
252 None => self.virt.by_vertex(*vertex).map(|m| m.formula),
253 }
254 }
255
256 #[inline]
257 pub(crate) fn contains_key(&self, vertex: &VertexId) -> bool {
258 self.map.contains_key(vertex) || self.virt.contains_vertex(*vertex)
259 }
260
261 #[inline]
263 pub(crate) fn len(&self) -> usize {
264 self.map.len() + self.virt.len()
265 }
266
267 pub(crate) fn iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
270 self.map
271 .iter()
272 .map(|(&v, &f)| (v, f))
273 .chain(self.virt.iter().map(|m| (m.vertex, m.formula)))
274 }
275
276 pub(crate) fn keys(&self) -> impl Iterator<Item = VertexId> + '_ {
277 self.iter().map(|(v, _)| v)
278 }
279
280 pub(crate) fn roots(&self) -> impl Iterator<Item = AstNodeId> + '_ {
284 self.map
285 .values()
286 .map(|f| f.root())
287 .chain(self.virt.runs().map(|r| r.template))
288 }
289
290 pub(crate) fn map_iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
291 self.map.iter().map(|(&v, &f)| (v, f))
292 }
293
294 #[inline]
295 pub(crate) fn virtual_members(&self) -> &virtual_members::VirtualMembers {
296 &self.virt
297 }
298
299 #[inline]
300 pub(crate) fn virtual_members_mut(&mut self) -> &mut virtual_members::VirtualMembers {
301 &mut self.virt
302 }
303
304 #[inline]
307 pub(crate) fn materialize(
308 &mut self,
309 vertex: VertexId,
310 ) -> Option<virtual_members::VirtualMember> {
311 let m = self.virt.take(vertex)?;
312 self.map.insert(vertex, m.formula);
313 Some(m)
314 }
315
316 pub(crate) fn forget_materialized(&mut self, vertex: &VertexId) {
319 self.map.remove(vertex);
320 }
321
322 #[inline]
324 pub(crate) fn restore(&mut self, vertex: VertexId, formula: FormulaRef) {
325 self.map.insert(vertex, formula);
326 }
327
328 #[inline]
329 pub(crate) fn insert(&mut self, vertex: VertexId, ast: AstNodeId) -> Option<FormulaRef> {
330 self.insert_ref(vertex, FormulaRef::Own(ast))
331 }
332
333 #[inline]
335 pub(crate) fn insert_ref(
336 &mut self,
337 vertex: VertexId,
338 formula: FormulaRef,
339 ) -> Option<FormulaRef> {
340 debug_assert!(
341 !self.virt.contains_vertex(vertex),
342 "formula write to a virtual family member (materialize it first)"
343 );
344 self.touched.push(vertex);
345 self.map.insert(vertex, formula)
346 }
347
348 #[inline]
350 pub(crate) fn compress(&mut self, vertex: VertexId, template: AstNodeId, anchor: (u32, u32)) {
351 if let Some(slot) = self.map.get_mut(&vertex) {
352 *slot = FormulaRef::Member { template, anchor };
353 }
354 }
355
356 #[inline]
359 pub(crate) fn decompress(&mut self, vertex: VertexId, own: AstNodeId) {
360 debug_assert!(!self.virt.contains_vertex(vertex));
361 if let Some(slot) = self.map.get_mut(&vertex) {
362 *slot = FormulaRef::Own(own);
363 }
364 }
365
366 pub(crate) fn remap(&mut self, map: &impl Fn(AstNodeId) -> AstNodeId) {
368 for slot in self.map.values_mut() {
369 *slot = match *slot {
370 FormulaRef::Own(id) => FormulaRef::Own(map(id)),
371 FormulaRef::Member { template, anchor } => FormulaRef::Member {
372 template: map(template),
373 anchor,
374 },
375 };
376 }
377 self.virt.remap(map);
378 }
379
380 #[inline]
381 pub(crate) fn remove(&mut self, vertex: &VertexId) -> Option<FormulaRef> {
382 debug_assert!(
383 !self.virt.contains_vertex(*vertex),
384 "formula removal of a virtual family member (materialize it first)"
385 );
386 let old = self.map.remove(vertex);
387 if old.is_some() {
388 self.touched.push(*vertex);
389 }
390 old
391 }
392
393 #[inline]
394 pub(crate) fn reserve(&mut self, additional: usize) {
395 self.map.reserve(additional);
396 }
397
398 pub(crate) fn drop_virtual_from_map(&mut self, is_virtual: impl Fn(VertexId) -> bool) {
402 debug_assert!(
403 self.map
404 .keys()
405 .all(|&v| is_virtual(v) == self.virt.contains_vertex(v))
406 );
407 self.map.retain(|&v, _| !is_virtual(v));
408 self.map.shrink_to_fit();
409 }
410
411 pub(crate) fn map_capacity(&self) -> usize {
412 self.map.capacity()
413 }
414
415 pub(crate) fn take_touched(&mut self) -> Vec<VertexId> {
417 std::mem::take(&mut self.touched)
418 }
419
420 pub(crate) fn touch(&mut self, vertex: VertexId) {
423 self.touched.push(vertex);
424 }
425
426 pub(crate) fn touched(&self) -> &[VertexId] {
429 &self.touched
430 }
431
432 pub(crate) fn has_touched(&self) -> bool {
433 !self.touched.is_empty()
434 }
435}
436
437#[derive(Clone, Copy, Debug, PartialEq, Eq)]
445#[non_exhaustive]
446pub struct FormulaView {
447 pub template: AstNodeId,
448 pub row_delta: i64,
449 pub col_delta: i64,
450}
451
452#[derive(Debug)]
454pub struct DependencyGraph {
455 store: VertexStore,
457
458 #[cfg(any(test, feature = "legacy_oracle"))]
460 edges: CsrMutableEdges,
461 dep_edge_total: usize,
465 range_reader_count: usize,
467 renamed_sheet_aliases: FxHashMap<String, SheetId>,
471
472 data_store: DataStore,
474 vertex_values: FxHashMap<VertexId, ValueRef>,
475 vertex_formulas: FormulaMap,
476
477 value_cache_enabled: bool,
482
483 #[cfg(debug_assertions)]
486 graph_value_read_attempts: AtomicU64,
487
488 cell_to_vertex: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
492 load_packed_to_vertex: std::collections::HashMap<PackedSheetCell, VertexId, CoordBuildHasher>,
493 #[cfg(test)]
494 pub(crate) sheet_index_finalize_visits: usize,
495
496 vertex_journal: crate::engine::authority::history::IdJournal,
499
500 formula_dirty: FormulaDirtyState,
503 volatile_vertices: FxHashSet<VertexId>,
504 row_visibility_scan_armed: bool,
510
511 dirty_propagation_visits: u64,
516
517 deferred_dirty_depth: u32,
523 deferred_dirty_pending: Vec<VertexId>,
525 retired_ids: std::collections::BTreeMap<(SheetId, u32, u32), VertexId>,
530 retired_id_set: FxHashSet<VertexId>,
533 retired_dropped: Vec<RetiredBatch>,
538 retired_dropped_by_undo: Vec<RetiredBatch>,
541 extent_record: extent_record::ExtentRecord,
544 extent_dropped: Vec<Vec<extent_record::ExtentRun>>,
549 extent_undone: Vec<(u8, SheetId, u32, u32)>,
553 deferred_dirty_pending_rects: Vec<(u16, crate::engine::authority::geom::Rect)>,
555
556 ref_error_vertices: FxHashSet<VertexId>,
562
563 #[cfg(any(test, feature = "legacy_oracle"))]
566 formula_to_range_deps: FxHashMap<VertexId, Vec<SharedRangeRef<'static>>>,
567
568 #[cfg(any(test, feature = "legacy_oracle"))]
571 stripe_to_dependents: FxHashMap<StripeKey, FxHashSet<VertexId>>,
572
573 sheet_indexes: FxHashMap<SheetId, SheetIndex>,
576
577 sheet_reg: SheetRegistry,
579 default_sheet_id: SheetId,
580
581 named_ranges: FxHashMap<String, NamedRange>,
584
585 named_ranges_lookup: FxHashMap<String, String>,
590
591 sheet_named_ranges: FxHashMap<(SheetId, String), NamedRange>,
593
594 sheet_named_ranges_lookup: FxHashMap<(SheetId, String), String>,
599
600 #[cfg(any(test, feature = "legacy_oracle"))]
602 vertex_to_names: FxHashMap<VertexId, Vec<VertexId>>,
603
604 name_vertex_lookup: FxHashMap<VertexId, (NameScope, String)>,
606
607 pending_name_links: FxHashMap<String, FxHashSet<(SheetId, VertexId)>>,
612
613 vertex_to_pending_names: FxHashMap<VertexId, FxHashSet<String>>,
616
617 tables: FxHashMap<String, tables::TableEntry>,
619 tables_lookup: FxHashMap<String, String>,
621 table_vertex_lookup: FxHashMap<VertexId, String>,
622
623 source_scalars: FxHashMap<String, sources::SourceScalarEntry>,
625 source_tables: FxHashMap<String, sources::SourceTableEntry>,
626 source_vertex_lookup: FxHashMap<VertexId, String>,
627
628 symbol_vertex_seq: u32,
634
635 #[cfg(any(test, feature = "legacy_oracle"))]
637 cell_to_name_dependents: FxHashMap<VertexId, FxHashSet<VertexId>>,
638 #[cfg(any(test, feature = "legacy_oracle"))]
640 name_to_cell_dependencies: FxHashMap<VertexId, Vec<VertexId>>,
641 #[cfg(any(test, feature = "legacy_oracle"))]
645 oracle_vertexless_readers: FxHashMap<(SheetId, u32, u32), Vec<VertexId>>,
646 #[cfg(any(test, feature = "legacy_oracle"))]
647 oracle_vertexless_of: FxHashMap<VertexId, Vec<(SheetId, u32, u32)>>,
648
649 config: super::EvalConfig,
651 topology_revision: u64,
653 symbol_revision: u64,
655
656 authority: crate::engine::authority::host::AuthorityHost,
659
660 #[cfg(any(test, feature = "legacy_oracle"))]
662 pk_order: Option<DynamicTopo<VertexId>>,
663
664 spill_anchor_to_cells: FxHashMap<VertexId, Vec<CellRef>>,
668 pub(crate) fixed_single_arrays: FxHashSet<VertexId>,
670 pub(crate) fixed_array_shapes: FxHashMap<VertexId, (u32, u32)>,
671 spill_cell_to_anchor: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
672 spill_cells_by_sheet: FxHashMap<SheetId, std::collections::BTreeMap<(u32, u32), VertexId>>,
673 spill_intruded_anchors: FxHashSet<VertexId>,
684
685 declared_dynamic_anchors: FxHashMap<VertexId, CellRef>,
691
692 admission_budget_override: Option<crate::engine::EvaluationBudgets>,
694
695 first_load_assume_new: bool,
697 ensure_touched_sheets: FxHashSet<SheetId>,
698
699 pub tombstone_registry: TombstoneRegistry,
701
702 #[cfg(test)]
703 instr: std::sync::Mutex<GraphInstrumentation>,
704 #[cfg(test)]
705 prepared_legacy_graph_failure_for_test: bool,
706}
707
708impl Default for DependencyGraph {
709 fn default() -> Self {
710 Self::new()
711 }
712}
713
714impl DependencyGraph {
715 pub fn range_expansion_limit(&self) -> usize {
717 self.config.range_expansion_limit
718 }
719
720 pub fn get_config(&self) -> &super::EvalConfig {
721 &self.config
722 }
723
724 pub(crate) fn formula_vertex_count(&self) -> usize {
726 self.vertex_formulas.len()
727 }
728
729 pub(crate) fn clear_formula_vertex_dirty(&mut self, vertex_id: VertexId) {
730 self.store.set_dirty(vertex_id, false);
731 self.formula_dirty.legacy_remove(&vertex_id);
732 self.row_visibility_scan_armed = true;
733 }
734
735 pub fn baseline_stats(&self) -> GraphBaselineStats {
737 let data_stats = self.data_store.memory_usage();
738 GraphBaselineStats {
739 graph_vertex_count: self.store.len(),
740 graph_formula_vertex_count: self.vertex_formulas.len(),
741 graph_edge_count: self.dep_edge_total,
742 dirty_vertex_count: self.formula_dirty.legacy_len(),
743 evaluation_vertex_count: self.get_evaluation_vertices().len(),
744 formula_ast_root_count: self.vertex_formulas.len(),
745 formula_ast_node_count: data_stats.total_ast_nodes,
746 }
747 }
748
749 #[inline]
750 pub(crate) fn value_cache_enabled(&self) -> bool {
751 self.value_cache_enabled
752 }
753
754 #[cfg(test)]
758 pub fn debug_graph_value_read_attempts(&self) -> u64 {
759 #[cfg(debug_assertions)]
760 {
761 self.graph_value_read_attempts.load(Ordering::Relaxed)
762 }
763 #[cfg(not(debug_assertions))]
764 {
765 0
766 }
767 }
768
769 pub fn plan_dependencies<'a, I>(
771 &mut self,
772 items: I,
773 policy: &formualizer_parse::parser::CollectPolicy,
774 volatile: Option<&[bool]>,
775 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
776 where
777 I: IntoIterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
778 {
779 crate::engine::plan::build_dependency_plan(
780 &mut self.sheet_reg,
781 items.into_iter(),
782 policy,
783 volatile,
784 )
785 }
786
787 pub fn plan_dependencies_mixed<'a, I>(
788 &mut self,
789 items: I,
790 policy: &formualizer_parse::parser::CollectPolicy,
791 volatile: Option<&[bool]>,
792 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
793 where
794 I: IntoIterator<
795 Item = (
796 &'a str,
797 u32,
798 u32,
799 crate::engine::plan::DependencyPlanAst<'a>,
800 ),
801 >,
802 {
803 crate::engine::plan::build_dependency_plan_mixed(
804 &mut self.sheet_reg,
805 &self.data_store,
806 items.into_iter(),
807 policy,
808 volatile,
809 )
810 }
811
812 pub fn ensure_vertices_batch(
815 &mut self,
816 coords: &[(SheetId, AbsCoord)],
817 ) -> Vec<(VertexAddr, u32)> {
818 self.ensure_vertices_batch_ordered(coords).1
819 }
820
821 pub fn ensure_vertices_batch_packed_ordered(
825 &mut self,
826 packed_cells: &[PackedSheetCell],
827 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
828 let mut unmapped = vec![false; packed_cells.len()];
829 self.ensure_vertices_batch_packed_ordered_unmapped(packed_cells, &mut unmapped)
830 }
831
832 pub(crate) fn ensure_vertices_batch_packed_ordered_unmapped(
839 &mut self,
840 packed_cells: &[PackedSheetCell],
841 unmapped: &mut [bool],
842 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
843 debug_assert_eq!(unmapped.len(), packed_cells.len());
844 #[cfg(feature = "perf_instrumentation")]
845 use crate::instant::FzInstant as PerfInstant;
846 use rustc_hash::FxHashMap;
847
848 #[cfg(feature = "perf_instrumentation")]
849 let debug = std::env::var("FZ_DEBUG_LOAD")
850 .ok()
851 .is_some_and(|v| v != "0");
852 #[cfg(feature = "perf_instrumentation")]
853 let t0 = PerfInstant::now();
854
855 let mut ordered: Vec<Option<VertexId>> = vec![None; packed_cells.len()];
856 if packed_cells.is_empty() {
857 return (Vec::new(), Vec::new());
858 }
859
860 let first_sid = packed_cells[0].sheet_id();
861 let single_sheet = packed_cells.iter().all(|cell| cell.sheet_id() == first_sid);
862 let mut add_batch: Vec<(VertexAddr, u32)> = Vec::new();
863
864 #[cfg(feature = "perf_instrumentation")]
865 let mut packed_hits = 0usize;
866 #[cfg(feature = "perf_instrumentation")]
867 let mut generic_hits = 0usize;
868 #[cfg(feature = "perf_instrumentation")]
869 let mut missing = 0usize;
870 #[cfg(feature = "perf_instrumentation")]
871 let mut t_packed_lookup_us = 0u128;
872 #[cfg(feature = "perf_instrumentation")]
873 let mut t_generic_lookup_us = 0u128;
874 #[cfg(feature = "perf_instrumentation")]
875 let mut t_alloc_us = 0u128;
876 #[cfg(feature = "perf_instrumentation")]
877 let mut t_map_insert_us = 0u128;
878 #[cfg(feature = "perf_instrumentation")]
879 let mut t_index_insert_us = 0u128;
880 #[cfg(feature = "perf_instrumentation")]
881 let mut t_edge_register_us = 0u128;
882
883 if single_sheet {
884 let sid = first_sid;
885 let mut missing_items: Vec<(usize, PackedSheetCell)> =
886 Vec::with_capacity(packed_cells.len());
887
888 for (idx, packed) in packed_cells.iter().copied().enumerate() {
889 #[cfg(feature = "perf_instrumentation")]
890 let tl0 = PerfInstant::now();
891 if self.first_load_assume_new
892 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
893 {
894 ordered[idx] = Some(existing);
895 unmapped[idx] = false;
896 #[cfg(feature = "perf_instrumentation")]
897 {
898 packed_hits += 1;
899 t_packed_lookup_us += tl0.elapsed().as_micros();
900 }
901 continue;
902 }
903 #[cfg(feature = "perf_instrumentation")]
904 {
905 t_packed_lookup_us += tl0.elapsed().as_micros();
906 }
907
908 let pc = AbsCoord::new(packed.row0(), packed.col0());
909 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
910 #[cfg(feature = "perf_instrumentation")]
911 let tg0 = PerfInstant::now();
912 if let Some(existing) = self.cell_vertex(&addr) {
913 ordered[idx] = Some(existing);
914 unmapped[idx] = false;
915 if self.first_load_assume_new && !self.is_virtual_member(existing) {
917 self.load_packed_to_vertex.insert(packed, existing);
918 }
919 #[cfg(feature = "perf_instrumentation")]
920 {
921 generic_hits += 1;
922 }
923 } else {
924 missing_items.push((idx, packed));
925 #[cfg(feature = "perf_instrumentation")]
926 {
927 missing += 1;
928 }
929 }
930 #[cfg(feature = "perf_instrumentation")]
931 {
932 t_generic_lookup_us += tg0.elapsed().as_micros();
933 }
934 }
935
936 if !missing_items.is_empty() {
937 self.ensure_touched_sheets.insert(sid);
938
939 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(missing_items.len());
940 for (_, packed) in &missing_items {
941 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
942 }
943
944 #[cfg(feature = "perf_instrumentation")]
945 let ta0 = PerfInstant::now();
946 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
947 #[cfg(feature = "perf_instrumentation")]
948 {
949 t_alloc_us += ta0.elapsed().as_micros();
950 }
951 add_batch.reserve(missing_items.len());
952
953 match self.config.sheet_index_mode {
954 crate::engine::SheetIndexMode::Eager
955 | crate::engine::SheetIndexMode::FastBatch => {
956 for ((input_idx, packed), vid) in missing_items.into_iter().zip(vids) {
957 let pc = AbsCoord::new(packed.row0(), packed.col0());
958 ordered[input_idx] = Some(vid);
959 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
960 if unmapped[input_idx] {
961 continue;
962 }
963
964 #[cfg(feature = "perf_instrumentation")]
965 let tm0 = PerfInstant::now();
966 if self.first_load_assume_new {
967 self.load_packed_to_vertex.insert(packed, vid);
968 } else {
969 let addr =
970 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
971 self.cell_to_vertex.insert(addr, vid);
972 }
973 #[cfg(feature = "perf_instrumentation")]
974 {
975 t_map_insert_us += tm0.elapsed().as_micros();
976 }
977
978 #[cfg(feature = "perf_instrumentation")]
979 let ti0 = PerfInstant::now();
980 self.sheet_index_mut(sid)
981 .add_vertex(GridAddr::from_coord(pc), vid);
982 #[cfg(feature = "perf_instrumentation")]
983 {
984 t_index_insert_us += ti0.elapsed().as_micros();
985 }
986 }
987 }
988 crate::engine::SheetIndexMode::Lazy => {
989 for ((input_idx, packed), vid) in missing_items.into_iter().zip(vids) {
990 let pc = AbsCoord::new(packed.row0(), packed.col0());
991 ordered[input_idx] = Some(vid);
992 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
993 if unmapped[input_idx] {
994 continue;
995 }
996
997 #[cfg(feature = "perf_instrumentation")]
998 let tm0 = PerfInstant::now();
999 if self.first_load_assume_new {
1000 self.load_packed_to_vertex.insert(packed, vid);
1001 } else {
1002 let addr =
1003 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1004 self.cell_to_vertex.insert(addr, vid);
1005 }
1006 #[cfg(feature = "perf_instrumentation")]
1007 {
1008 t_map_insert_us += tm0.elapsed().as_micros();
1009 }
1010 }
1011 }
1012 }
1013 }
1014 } else {
1015 let mut grouped: FxHashMap<SheetId, Vec<(usize, PackedSheetCell)>> =
1016 FxHashMap::default();
1017
1018 for (idx, packed) in packed_cells.iter().copied().enumerate() {
1019 #[cfg(feature = "perf_instrumentation")]
1020 let tl0 = PerfInstant::now();
1021 if self.first_load_assume_new
1022 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
1023 {
1024 ordered[idx] = Some(existing);
1025 unmapped[idx] = false;
1026 #[cfg(feature = "perf_instrumentation")]
1027 {
1028 packed_hits += 1;
1029 t_packed_lookup_us += tl0.elapsed().as_micros();
1030 }
1031 continue;
1032 }
1033 #[cfg(feature = "perf_instrumentation")]
1034 {
1035 t_packed_lookup_us += tl0.elapsed().as_micros();
1036 }
1037
1038 let sid = packed.sheet_id();
1039 let pc = AbsCoord::new(packed.row0(), packed.col0());
1040 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1041 #[cfg(feature = "perf_instrumentation")]
1042 let tg0 = PerfInstant::now();
1043 if let Some(existing) = self.cell_vertex(&addr) {
1044 ordered[idx] = Some(existing);
1045 unmapped[idx] = false;
1046 if self.first_load_assume_new && !self.is_virtual_member(existing) {
1048 self.load_packed_to_vertex.insert(packed, existing);
1049 }
1050 #[cfg(feature = "perf_instrumentation")]
1051 {
1052 generic_hits += 1;
1053 }
1054 } else {
1055 grouped.entry(sid).or_default().push((idx, packed));
1056 #[cfg(feature = "perf_instrumentation")]
1057 {
1058 missing += 1;
1059 }
1060 }
1061 #[cfg(feature = "perf_instrumentation")]
1062 {
1063 t_generic_lookup_us += tg0.elapsed().as_micros();
1064 }
1065 }
1066
1067 for (sid, items) in grouped {
1068 if items.is_empty() {
1069 continue;
1070 }
1071 self.ensure_touched_sheets.insert(sid);
1072
1073 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(items.len());
1074 for (_, packed) in &items {
1075 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
1076 }
1077
1078 #[cfg(feature = "perf_instrumentation")]
1079 let ta0 = PerfInstant::now();
1080 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
1081 #[cfg(feature = "perf_instrumentation")]
1082 {
1083 t_alloc_us += ta0.elapsed().as_micros();
1084 }
1085
1086 for ((input_idx, packed), vid) in items.into_iter().zip(vids) {
1087 let pc = AbsCoord::new(packed.row0(), packed.col0());
1088 ordered[input_idx] = Some(vid);
1089 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
1090 if unmapped[input_idx] {
1091 continue;
1092 }
1093
1094 #[cfg(feature = "perf_instrumentation")]
1095 let tm0 = PerfInstant::now();
1096 if self.first_load_assume_new {
1097 self.load_packed_to_vertex.insert(packed, vid);
1098 } else {
1099 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1100 self.cell_to_vertex.insert(addr, vid);
1101 }
1102 #[cfg(feature = "perf_instrumentation")]
1103 {
1104 t_map_insert_us += tm0.elapsed().as_micros();
1105 }
1106
1107 match self.config.sheet_index_mode {
1108 crate::engine::SheetIndexMode::Eager
1109 | crate::engine::SheetIndexMode::FastBatch => {
1110 #[cfg(feature = "perf_instrumentation")]
1111 let ti0 = PerfInstant::now();
1112 self.sheet_index_mut(sid)
1113 .add_vertex(GridAddr::from_coord(pc), vid);
1114 #[cfg(feature = "perf_instrumentation")]
1115 {
1116 t_index_insert_us += ti0.elapsed().as_micros();
1117 }
1118 }
1119 crate::engine::SheetIndexMode::Lazy => {
1120 }
1122 }
1123 }
1124 }
1125 }
1126
1127 if !add_batch.is_empty() {
1128 #[cfg(feature = "perf_instrumentation")]
1129 let te0 = PerfInstant::now();
1130 #[cfg(any(test, feature = "legacy_oracle"))]
1131 {
1132 self.edges.add_vertices_batch(&add_batch);
1133 let created: FxHashSet<u32> = if self.oracle_vertexless_readers.is_empty() {
1134 FxHashSet::default()
1135 } else {
1136 add_batch.iter().map(|&(_, raw)| raw).collect()
1137 };
1138 for (i, packed) in packed_cells.iter().enumerate() {
1139 if let Some(v) = ordered[i]
1140 && created.contains(&v.0)
1141 {
1142 self.oracle_cell_vertex_created(
1143 (packed.sheet_id(), packed.row0(), packed.col0()),
1144 v,
1145 );
1146 }
1147 }
1148 }
1149 #[cfg(feature = "perf_instrumentation")]
1150 {
1151 t_edge_register_us += te0.elapsed().as_micros();
1152 }
1153 }
1154
1155 #[cfg(feature = "perf_instrumentation")]
1156 if debug {
1157 eprintln!(
1158 "[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",
1159 packed_cells.len(),
1160 single_sheet,
1161 packed_hits,
1162 generic_hits,
1163 missing,
1164 t_packed_lookup_us,
1165 t_generic_lookup_us,
1166 t_alloc_us,
1167 t_map_insert_us,
1168 t_index_insert_us,
1169 t_edge_register_us,
1170 t0.elapsed().as_millis(),
1171 );
1172 }
1173
1174 let ordered = ordered
1175 .into_iter()
1176 .map(|vid| vid.expect("ensure_vertices_batch_packed_ordered must resolve every coord"))
1177 .collect();
1178 (ordered, add_batch)
1179 }
1180
1181 pub fn ensure_vertices_batch_ordered(
1184 &mut self,
1185 coords: &[(SheetId, AbsCoord)],
1186 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
1187 let mut packed: Vec<PackedSheetCell> = Vec::with_capacity(coords.len());
1188 for &(sid, coord) in coords {
1189 packed.push(Self::packed_cell_key(sid, coord));
1190 }
1191 self.ensure_vertices_batch_packed_ordered(&packed)
1192 }
1193
1194 #[inline]
1195 fn packed_cell_key(sheet_id: SheetId, coord: AbsCoord) -> PackedSheetCell {
1196 PackedSheetCell::try_new(sheet_id, coord.row(), coord.col())
1197 .expect("graph coordinate must fit PackedSheetCell")
1198 }
1199
1200 fn flush_load_packed_mappings(&mut self) {
1201 if self.load_packed_to_vertex.is_empty() {
1202 return;
1203 }
1204 let debug = std::env::var("FZ_DEBUG_LOAD")
1205 .ok()
1206 .is_some_and(|v| v != "0");
1207 let t0 = crate::instant::FzInstant::now();
1208 let count = self.load_packed_to_vertex.len();
1209 self.cell_to_vertex.reserve(count);
1210 let packed_mappings = std::mem::replace(
1214 &mut self.load_packed_to_vertex,
1215 std::collections::HashMap::with_hasher(CoordBuildHasher),
1216 );
1217 for (packed, vid) in packed_mappings {
1218 let coord = AbsCoord::new(packed.row0(), packed.col0());
1219 let addr = CellRef::new(
1220 packed.sheet_id(),
1221 Coord::new(coord.row(), coord.col(), true, true),
1222 );
1223 self.cell_to_vertex.insert(addr, vid);
1224 }
1225 if debug {
1226 eprintln!(
1227 "[fz][load] flush_load_packed_mappings: {} entries in {:.1} ms",
1228 count,
1229 t0.elapsed().as_secs_f64() * 1000.0,
1230 );
1231 }
1232 }
1233
1234 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
1239 let leaving = self.first_load_assume_new && !enabled;
1240 if leaving {
1241 self.flush_load_packed_mappings();
1242 self.store.shrink_to_fit();
1245 self.extent_record.fold_pending();
1249 } else if enabled {
1250 self.load_packed_to_vertex.clear();
1251 }
1252 self.first_load_assume_new = enabled;
1253 if leaving {
1254 self.authority_sync();
1255 }
1256 }
1257
1258 pub(crate) fn formula_compression_enabled(&self) -> bool {
1260 self.config.formula_compression
1261 }
1262
1263 #[doc(hidden)]
1264 pub fn first_load_assume_new(&self) -> bool {
1265 self.first_load_assume_new
1266 }
1267
1268 pub fn reset_ensure_touched(&mut self) {
1270 self.ensure_touched_sheets.clear();
1271 }
1272
1273 pub fn store_ast(&mut self, ast: &formualizer_parse::parser::ASTNode) -> AstNodeId {
1275 self.data_store.store_ast(ast, &self.sheet_reg)
1276 }
1277
1278 pub fn store_asts_batch<'a, I>(&mut self, asts: I) -> Vec<AstNodeId>
1280 where
1281 I: IntoIterator<Item = &'a formualizer_parse::parser::ASTNode>,
1282 {
1283 self.data_store.store_asts_batch(asts, &self.sheet_reg)
1284 }
1285
1286 pub fn reserve_formula_metadata(&mut self, additional: usize) {
1288 self.vertex_formulas.reserve(additional);
1289 self.formula_dirty.legacy_reserve(additional);
1290 self.volatile_vertices.reserve(additional);
1291 }
1292
1293 pub fn vid_for_sid_pc(&self, sid: SheetId, pc: AbsCoord) -> Option<VertexId> {
1295 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1296 self.cell_vertex(&addr)
1297 }
1298
1299 pub fn vid_for_plan_idx(
1301 &self,
1302 plan: &crate::engine::plan::DependencyPlan,
1303 idx: u32,
1304 ) -> Option<VertexId> {
1305 let (sid, pc) = plan.global_cells.get(idx as usize).copied()?;
1306 self.vid_for_sid_pc(sid, pc)
1307 }
1308 pub fn assign_formula_vertex(
1310 &mut self,
1311 vid: VertexId,
1312 ast_id: AstNodeId,
1313 volatile: bool,
1314 dynamic: bool,
1315 ) {
1316 self.assign_formula_ref(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1317 }
1318
1319 pub(crate) fn assign_formula_ref(
1321 &mut self,
1322 vid: VertexId,
1323 formula: FormulaRef,
1324 volatile: bool,
1325 dynamic: bool,
1326 ) {
1327 self.materialize_vertex(vid);
1328 self.forget_declared_dynamic_anchor(vid);
1329 if self.vertex_formulas.contains_key(&vid) {
1330 self.remove_dependent_edges(vid);
1331 }
1332 self.store
1333 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1334 self.vertex_values.remove(&vid);
1335 self.vertex_formulas.insert_ref(vid, formula);
1336 self.mark_volatile(vid, volatile);
1337 self.store.set_dynamic(vid, dynamic);
1338
1339 self.mark_vertex_dirty(vid);
1341 }
1342
1343 pub fn assign_formula_vertex_load_fast(
1346 &mut self,
1347 vid: VertexId,
1348 ast_id: AstNodeId,
1349 volatile: bool,
1350 dynamic: bool,
1351 ) {
1352 self.assign_formula_ref_load_fast(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1353 }
1354
1355 pub(crate) fn assign_formula_ref_load_fast(
1358 &mut self,
1359 vid: VertexId,
1360 formula: FormulaRef,
1361 volatile: bool,
1362 dynamic: bool,
1363 ) {
1364 self.materialize_vertex(vid);
1365 debug_assert!(
1366 !self.vertex_formulas.contains_key(&vid),
1367 "load-fast formula assignment expects fresh/non-formula vertices"
1368 );
1369 self.forget_declared_dynamic_anchor(vid);
1370 self.store
1371 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1372 self.vertex_values.remove(&vid);
1373 self.vertex_formulas.insert_ref(vid, formula);
1374 self.mark_volatile(vid, volatile);
1375 self.store.set_dynamic(vid, dynamic);
1376 }
1377
1378 pub(crate) fn assign_unmapped_member_load_fast(&mut self, vid: VertexId) {
1383 self.store
1384 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1385 self.store.set_dynamic(vid, false);
1386 self.vertex_formulas.touch(vid);
1387 }
1388
1389 pub(crate) fn preallocate_load_targets(
1397 &mut self,
1398 sheet: SheetId,
1399 mut targets: Vec<(u32, u32, Option<FormulaRef>)>,
1400 ) -> usize {
1401 if targets.is_empty() {
1402 return 0;
1403 }
1404 targets.sort_by_key(|&(r, c, _)| (c, r));
1406 let mut dedup: Vec<(u32, u32, Option<FormulaRef>)> = Vec::with_capacity(targets.len());
1407 for t in targets {
1408 match dedup.last_mut() {
1409 Some(last) if (last.0, last.1) == (t.0, t.1) => *last = t,
1410 _ => dedup.push(t),
1411 }
1412 }
1413 let compress = self.config.formula_compression;
1414 let mut packed = Vec::with_capacity(dedup.len());
1415 let mut unmapped = Vec::with_capacity(dedup.len());
1416 for &(r, c, f) in &dedup {
1417 let Some(p) = PackedSheetCell::try_from_excel_1based(sheet, r, c) else {
1418 continue;
1420 };
1421 packed.push(p);
1422 unmapped.push(compress && matches!(f, Some(FormulaRef::Member { .. })));
1423 }
1424 let formulas: Vec<Option<FormulaRef>> = dedup
1425 .iter()
1426 .filter(|&&(r, c, _)| PackedSheetCell::try_from_excel_1based(sheet, r, c).is_some())
1427 .map(|&(_, _, f)| f)
1428 .collect();
1429 let (vids, created) =
1430 self.ensure_vertices_batch_packed_ordered_unmapped(&packed, &mut unmapped);
1431 let mut members = Vec::new();
1432 for (i, &v) in vids.iter().enumerate() {
1433 if unmapped[i]
1434 && let Some(f) = formulas[i]
1435 {
1436 members.push((v, sheet, packed[i].row0(), packed[i].col0(), f));
1437 }
1438 }
1439 self.install_preallocated_members(members);
1440 created.len()
1441 }
1442
1443 fn install_preallocated_members(
1447 &mut self,
1448 members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1449 ) {
1450 let mut i = 0;
1451 while i < members.len() {
1452 let (v, sheet, row, col, f) = members[i];
1453 let mut len = 1usize;
1454 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1455 && v2.0 == v.0 + len as u32
1456 && s2 == sheet
1457 && c2 == col
1458 && r2 == row + len as u32
1459 && f2 == f
1460 {
1461 len += 1;
1462 }
1463 match f {
1464 FormulaRef::Member { template, anchor } if len >= 2 => {
1465 let run = virtual_members::MemberRun {
1466 sheet,
1467 col,
1468 row0: row,
1469 len: len as u32,
1470 first: v.0,
1471 template,
1472 anchor,
1473 };
1474 for (m, _) in run.members() {
1475 self.store
1476 .set_kind(m, crate::engine::vertex::VertexKind::FormulaScalar);
1477 self.store.set_virtual(m, true);
1478 self.vertex_formulas.touch(m);
1479 }
1480 self.vertex_formulas.virtual_members_mut().insert(run);
1481 }
1482 _ => {
1483 for &(m, s, r, c, _) in &members[i..i + len] {
1484 self.map_load_vertex(m, s, r, c);
1485 }
1486 }
1487 }
1488 i += len;
1489 }
1490 }
1491
1492 fn map_load_vertex(&mut self, v: VertexId, sheet: SheetId, row: u32, col: u32) {
1494 if self.first_load_assume_new {
1495 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1496 self.load_packed_to_vertex.insert(packed, v);
1497 } else {
1498 self.cell_to_vertex
1499 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1500 }
1501 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1502 self.sheet_index_mut(sheet)
1503 .add_vertex(GridAddr::new(row, col), v);
1504 }
1505 }
1506
1507 pub(crate) fn assign_preallocated_member(
1512 &mut self,
1513 vid: VertexId,
1514 formula: FormulaRef,
1515 volatile: bool,
1516 dynamic: bool,
1517 ) {
1518 if !volatile && !dynamic && self.vertex_formulas.get(&vid) == Some(formula) {
1519 return;
1520 }
1521 self.materialize_vertex(vid);
1522 self.vertex_formulas.forget_materialized(&vid);
1523 self.assign_formula_ref_load_fast(vid, formula, volatile, dynamic);
1524 }
1525
1526 pub(crate) fn install_load_members(
1531 &mut self,
1532 mut members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1533 ) {
1534 if members.is_empty() {
1535 return;
1536 }
1537 members.sort_by_key(|m| m.0);
1539 members.reverse();
1540 members.dedup_by_key(|m| m.0);
1541 members.reverse();
1542 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
1543 let mut singles: Vec<usize> = Vec::new();
1544 let mut i = 0;
1545 while i < members.len() {
1546 let (v, sheet, row, col, f) = members[i];
1547 let mut len = 1usize;
1548 if let FormulaRef::Member { template, anchor } = f {
1549 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1550 && v2.0 == v.0 + len as u32
1551 && s2 == sheet
1552 && c2 == col
1553 && r2 == row + len as u32
1554 && f2 == f
1555 {
1556 len += 1;
1557 }
1558 if len >= 2 {
1559 runs.push(virtual_members::MemberRun {
1560 sheet,
1561 col,
1562 row0: row,
1563 len: len as u32,
1564 first: v.0,
1565 template,
1566 anchor,
1567 });
1568 i += len;
1569 continue;
1570 }
1571 }
1572 singles.push(i);
1573 i += 1;
1574 }
1575 for i in singles {
1576 let (v, sheet, row, col, f) = members[i];
1577 self.vertex_formulas.restore(v, f);
1578 if self.first_load_assume_new {
1579 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1580 self.load_packed_to_vertex.insert(packed, v);
1581 } else {
1582 self.cell_to_vertex
1583 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1584 }
1585 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1586 self.sheet_index_mut(sheet)
1587 .add_vertex(GridAddr::new(row, col), v);
1588 }
1589 }
1590 for r in runs {
1591 for (v, _) in r.members() {
1592 self.store.set_virtual(v, true);
1593 }
1594 self.vertex_formulas.virtual_members_mut().insert(r);
1595 }
1596 }
1597
1598 #[cfg(any(test, feature = "legacy_oracle"))]
1599 pub fn add_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
1601 self.add_dependent_edges_nobatch(dependent, dependencies);
1602 }
1603
1604 pub fn iter_vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
1606 self.store.all_vertices()
1607 }
1608
1609 pub fn vertex_addr(&self, vid: VertexId) -> VertexAddr {
1612 self.store.addr(vid)
1613 }
1614
1615 pub fn vertex_grid_addr(&self, vid: VertexId) -> Option<GridAddr> {
1617 self.store.grid_addr(vid)
1618 }
1619
1620 pub fn vertex_count(&self) -> usize {
1622 self.store.len()
1623 }
1624
1625 #[cfg(any(test, feature = "legacy_oracle"))]
1626 pub fn build_edges_from_adjacency(
1628 &mut self,
1629 adjacency: Vec<(u32, Vec<u32>)>,
1630 coords: Vec<VertexAddr>,
1631 vertex_ids: Vec<u32>,
1632 ) {
1633 #[cfg(not(any(test, feature = "legacy_oracle")))]
1634 let _ = (&adjacency, &coords, &vertex_ids);
1635 #[cfg(any(test, feature = "legacy_oracle"))]
1636 {
1637 let adjacency = self.edges.adjacency_with_carried_forward_edges(adjacency);
1641 self.edges
1642 .build_from_adjacency(adjacency, coords, vertex_ids);
1643 }
1644 }
1645 pub fn used_row_bounds_for_columns(
1647 &self,
1648 sheet_id: SheetId,
1649 start_col: u32,
1650 end_col: u32,
1651 ) -> Option<(u32, u32)> {
1652 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1654 && !index.is_empty()
1655 {
1656 let mut min_r: Option<u32> = None;
1657 let mut max_r: Option<u32> = None;
1658 for vid in index.vertices_in_col_range(start_col, end_col) {
1659 let Some(r) = self.store.grid_addr(vid).map(|addr| addr.row()) else {
1660 continue;
1661 };
1662 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1663 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1664 }
1665 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1666 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1667 return match (min_r, max_r) {
1668 (Some(a), Some(b)) => Some((a, b)),
1669 _ => None,
1670 };
1671 }
1672 let mut min_r: Option<u32> = None;
1674 let mut max_r: Option<u32> = None;
1675 for cref in self.cell_to_vertex.keys() {
1676 if cref.sheet_id == sheet_id {
1677 let c = cref.coord.col();
1678 if c >= start_col && c <= end_col {
1679 let r = cref.coord.row();
1680 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1681 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1682 }
1683 }
1684 }
1685 for packed in self.load_packed_to_vertex.keys() {
1686 if packed.sheet_id() == sheet_id {
1687 let c = packed.col0();
1688 if c >= start_col && c <= end_col {
1689 let r = packed.row0();
1690 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1691 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1692 }
1693 }
1694 }
1695 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1696 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1697 match (min_r, max_r) {
1698 (Some(a), Some(b)) => Some((a, b)),
1699 _ => None,
1700 }
1701 }
1702
1703 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1705 let Some(sheet_id) = self.sheet_reg.get_id(sheet) else {
1706 return;
1707 };
1708 self.rebuild_sheet_index(sheet_id);
1709 }
1710
1711 pub(crate) fn finalize_all_sheet_indexes<'a>(
1712 &mut self,
1713 sheets: impl IntoIterator<Item = &'a str>,
1714 ) {
1715 let mut batches: FxHashMap<SheetId, Vec<(GridAddr, VertexId)>> = sheets
1716 .into_iter()
1717 .filter_map(|sheet| self.sheet_reg.get_id(sheet))
1718 .map(|sheet| (sheet, Vec::new()))
1719 .collect();
1720 for (cell, &vertex) in &self.cell_to_vertex {
1721 #[cfg(test)]
1722 {
1723 self.sheet_index_finalize_visits =
1724 self.sheet_index_finalize_visits.saturating_add(1);
1725 }
1726 if let Some(batch) = batches.get_mut(&cell.sheet_id) {
1727 batch.push((GridAddr::new(cell.coord.row(), cell.coord.col()), vertex));
1728 }
1729 }
1730 for (&packed, &vertex) in &self.load_packed_to_vertex {
1731 #[cfg(test)]
1732 {
1733 self.sheet_index_finalize_visits =
1734 self.sheet_index_finalize_visits.saturating_add(1);
1735 }
1736 if let Some(batch) = batches.get_mut(&packed.sheet_id()) {
1737 let coord = GridAddr::new(packed.row0(), packed.col0());
1738 let cell = CellRef::new(
1739 packed.sheet_id(),
1740 Coord::new(coord.row(), coord.col(), true, true),
1741 );
1742 if !self.cell_to_vertex.contains_key(&cell) {
1743 batch.push((coord, vertex));
1744 }
1745 }
1746 }
1747 for (sheet, batch) in batches {
1748 let mut index = SheetIndex::new();
1749 index.add_vertices_batch(&batch);
1750 self.sheet_indexes.insert(sheet, index);
1751 }
1752 }
1753
1754 fn rebuild_sheet_index(&mut self, sheet_id: SheetId) {
1755 let mut idx = SheetIndex::new();
1756 let mut batch: Vec<(GridAddr, VertexId)> =
1757 Vec::with_capacity(self.cell_to_vertex.len() + self.load_packed_to_vertex.len());
1758 for (cref, vid) in &self.cell_to_vertex {
1759 #[cfg(test)]
1760 {
1761 self.sheet_index_finalize_visits =
1762 self.sheet_index_finalize_visits.saturating_add(1);
1763 }
1764 if cref.sheet_id == sheet_id {
1765 batch.push((GridAddr::new(cref.coord.row(), cref.coord.col()), *vid));
1766 }
1767 }
1768 for (&packed, &vid) in &self.load_packed_to_vertex {
1769 #[cfg(test)]
1770 {
1771 self.sheet_index_finalize_visits =
1772 self.sheet_index_finalize_visits.saturating_add(1);
1773 }
1774 if packed.sheet_id() != sheet_id {
1775 continue;
1776 }
1777 let coord = GridAddr::new(packed.row0(), packed.col0());
1778 let addr = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
1779 if self.cell_to_vertex.contains_key(&addr) {
1780 continue;
1781 }
1782 batch.push((coord, vid));
1783 }
1784 idx.add_vertices_batch(&batch);
1785 self.sheet_indexes.insert(sheet_id, idx);
1786 }
1787
1788 pub(crate) fn prepare_sheet_index_for_query(&mut self, sheet_id: SheetId) {
1792 if self.config.sheet_index_mode == crate::engine::SheetIndexMode::Lazy {
1793 self.rebuild_sheet_index(sheet_id);
1794 }
1795 }
1796
1797 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
1798 self.config.sheet_index_mode = mode;
1799 }
1800
1801 pub(crate) fn set_evaluation_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
1802 self.config.evaluation_budgets = budgets;
1803 }
1804
1805 pub fn used_col_bounds_for_rows(
1807 &self,
1808 sheet_id: SheetId,
1809 start_row: u32,
1810 end_row: u32,
1811 ) -> Option<(u32, u32)> {
1812 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1813 && !index.is_empty()
1814 {
1815 let mut min_c: Option<u32> = None;
1816 let mut max_c: Option<u32> = None;
1817 for vid in index.vertices_in_row_range(start_row, end_row) {
1818 let Some(c) = self.store.grid_addr(vid).map(|addr| addr.col()) else {
1819 continue;
1820 };
1821 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1822 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1823 }
1824 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1825 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1826 return match (min_c, max_c) {
1827 (Some(a), Some(b)) => Some((a, b)),
1828 _ => None,
1829 };
1830 }
1831 let mut min_c: Option<u32> = None;
1833 let mut max_c: Option<u32> = None;
1834 for cref in self.cell_to_vertex.keys() {
1835 if cref.sheet_id == sheet_id {
1836 let r = cref.coord.row();
1837 if r >= start_row && r <= end_row {
1838 let c = cref.coord.col();
1839 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1840 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1841 }
1842 }
1843 }
1844 for packed in self.load_packed_to_vertex.keys() {
1845 if packed.sheet_id() == sheet_id {
1846 let r = packed.row0();
1847 if r >= start_row && r <= end_row {
1848 let c = packed.col0();
1849 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1850 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1851 }
1852 }
1853 }
1854 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1855 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1856 match (min_c, max_c) {
1857 (Some(a), Some(b)) => Some((a, b)),
1858 _ => None,
1859 }
1860 }
1861
1862 fn extent_row_bounds(
1864 &self,
1865 sheet: SheetId,
1866 c0: u32,
1867 c1: u32,
1868 min: &mut Option<u32>,
1869 max: &mut Option<u32>,
1870 ) {
1871 if let Some((a, b)) = self.extent_record.row_bounds_for_cols(sheet, c0, c1) {
1872 *min = Some(min.map_or(a, |m| m.min(a)));
1873 *max = Some(max.map_or(b, |m| m.max(b)));
1874 }
1875 }
1876
1877 fn extent_col_bounds(
1879 &self,
1880 sheet: SheetId,
1881 r0: u32,
1882 r1: u32,
1883 min: &mut Option<u32>,
1884 max: &mut Option<u32>,
1885 ) {
1886 if let Some((a, b)) = self.extent_record.col_bounds_for_rows(sheet, r0, r1) {
1887 *min = Some(min.map_or(a, |m| m.min(a)));
1888 *max = Some(max.map_or(b, |m| m.max(b)));
1889 }
1890 }
1891
1892 fn virtual_row_bounds(
1894 &self,
1895 sheet: SheetId,
1896 c0: u32,
1897 c1: u32,
1898 min: &mut Option<u32>,
1899 max: &mut Option<u32>,
1900 ) {
1901 for r in self
1902 .vertex_formulas
1903 .virtual_members()
1904 .runs_in_cols(sheet, c0, c1)
1905 {
1906 let (a, b) = (r.row0, r.row0 + r.len - 1);
1907 *min = Some(min.map_or(a, |m| m.min(a)));
1908 *max = Some(max.map_or(b, |m| m.max(b)));
1909 }
1910 }
1911
1912 fn virtual_col_bounds(
1914 &self,
1915 sheet: SheetId,
1916 r0: u32,
1917 r1: u32,
1918 min: &mut Option<u32>,
1919 max: &mut Option<u32>,
1920 ) {
1921 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
1922 if r.row0 <= r1 && r.row0 + r.len > r0 {
1923 *min = Some(min.map_or(r.col, |m| m.min(r.col)));
1924 *max = Some(max.map_or(r.col, |m| m.max(r.col)));
1925 }
1926 }
1927 }
1928
1929 pub fn sheet_has_formulas(&self, sheet_id: SheetId) -> bool {
1931 for vid in self.vertex_formulas.keys() {
1933 if self.store.sheet_id(vid) == sheet_id {
1934 return true;
1935 }
1936 }
1937 false
1938 }
1939 pub fn new() -> Self {
1940 Self::new_with_config(super::EvalConfig::default())
1941 }
1942
1943 pub fn new_with_config(config: super::EvalConfig) -> Self {
1944 let mut sheet_reg = SheetRegistry::new();
1945 let default_sheet_id = sheet_reg.id_for(&config.default_sheet_name);
1946
1947 #[cfg_attr(not(any(test, feature = "legacy_oracle")), allow(unused_mut))]
1948 let mut g = Self {
1949 store: VertexStore::new(),
1950 #[cfg(any(test, feature = "legacy_oracle"))]
1951 edges: CsrMutableEdges::new(),
1952 dep_edge_total: 0,
1953 range_reader_count: 0,
1954 renamed_sheet_aliases: FxHashMap::default(),
1955 data_store: DataStore::new(),
1956 vertex_values: FxHashMap::default(),
1957 vertex_formulas: FormulaMap::default(),
1958 value_cache_enabled: false,
1961 #[cfg(debug_assertions)]
1962 graph_value_read_attempts: AtomicU64::new(0),
1963 cell_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1964 vertex_journal: Default::default(),
1965 load_packed_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1966 #[cfg(test)]
1967 sheet_index_finalize_visits: 0,
1968 formula_dirty: FormulaDirtyState::default(),
1969 dirty_propagation_visits: 0,
1970 deferred_dirty_depth: 0,
1971 deferred_dirty_pending: Vec::new(),
1972 deferred_dirty_pending_rects: Vec::new(),
1973 retired_ids: std::collections::BTreeMap::new(),
1974 retired_id_set: FxHashSet::default(),
1975 retired_dropped: Vec::new(),
1976 retired_dropped_by_undo: Vec::new(),
1977 extent_record: Default::default(),
1978 extent_dropped: Vec::new(),
1979 extent_undone: Vec::new(),
1980 volatile_vertices: FxHashSet::default(),
1981 row_visibility_scan_armed: true,
1982 ref_error_vertices: FxHashSet::default(),
1983 #[cfg(any(test, feature = "legacy_oracle"))]
1984 formula_to_range_deps: FxHashMap::default(),
1985 #[cfg(any(test, feature = "legacy_oracle"))]
1986 stripe_to_dependents: FxHashMap::default(),
1987 sheet_indexes: FxHashMap::default(),
1988 sheet_reg,
1989 default_sheet_id,
1990 named_ranges: FxHashMap::default(),
1991 named_ranges_lookup: FxHashMap::default(),
1992 sheet_named_ranges: FxHashMap::default(),
1993 sheet_named_ranges_lookup: FxHashMap::default(),
1994 #[cfg(any(test, feature = "legacy_oracle"))]
1995 vertex_to_names: FxHashMap::default(),
1996 name_vertex_lookup: FxHashMap::default(),
1997 pending_name_links: FxHashMap::default(),
1998 vertex_to_pending_names: FxHashMap::default(),
1999 tables: FxHashMap::default(),
2000 tables_lookup: FxHashMap::default(),
2001 table_vertex_lookup: FxHashMap::default(),
2002 source_scalars: FxHashMap::default(),
2003 source_tables: FxHashMap::default(),
2004 source_vertex_lookup: FxHashMap::default(),
2005 symbol_vertex_seq: 0,
2006 #[cfg(any(test, feature = "legacy_oracle"))]
2007 cell_to_name_dependents: FxHashMap::default(),
2008 #[cfg(any(test, feature = "legacy_oracle"))]
2009 name_to_cell_dependencies: FxHashMap::default(),
2010 #[cfg(any(test, feature = "legacy_oracle"))]
2011 oracle_vertexless_readers: FxHashMap::default(),
2012 #[cfg(any(test, feature = "legacy_oracle"))]
2013 oracle_vertexless_of: FxHashMap::default(),
2014 config: config.clone(),
2015 topology_revision: 0,
2016 symbol_revision: 0,
2017 authority: Default::default(),
2018 #[cfg(any(test, feature = "legacy_oracle"))]
2019 pk_order: None,
2020 spill_anchor_to_cells: FxHashMap::default(),
2021 fixed_single_arrays: FxHashSet::default(),
2022 fixed_array_shapes: FxHashMap::default(),
2023 spill_cell_to_anchor: std::collections::HashMap::with_hasher(CoordBuildHasher),
2024 spill_cells_by_sheet: FxHashMap::default(),
2025 spill_intruded_anchors: FxHashSet::default(),
2026 declared_dynamic_anchors: FxHashMap::default(),
2027 admission_budget_override: None,
2028 first_load_assume_new: false,
2029 ensure_touched_sheets: FxHashSet::default(),
2030 tombstone_registry: TombstoneRegistry::default(),
2031 #[cfg(test)]
2032 instr: std::sync::Mutex::new(GraphInstrumentation::default()),
2033 #[cfg(test)]
2034 prepared_legacy_graph_failure_for_test: false,
2035 };
2036
2037 #[cfg(any(test, feature = "legacy_oracle"))]
2038 if config.use_dynamic_topo {
2039 let nodes = g
2041 .store
2042 .all_vertices()
2043 .filter(|&id| g.store.vertex_exists_active(id));
2044 let mut pk = DynamicTopo::new(
2045 nodes,
2046 PkConfig {
2047 visit_budget: config.pk_visit_budget,
2048 compaction_interval_ops: config.pk_compaction_interval_ops,
2049 },
2050 );
2051 let adapter = GraphAdapter { g: &g };
2053 pk.rebuild_full(&adapter);
2054 g.pk_order = Some(pk);
2055 }
2056
2057 g
2058 }
2059
2060 #[cfg(any(test, feature = "legacy_oracle"))]
2061 pub(crate) fn pk_layers_for(&self, subset: &[VertexId]) -> Option<Vec<crate::engine::Layer>> {
2063 let pk = self.pk_order.as_ref()?;
2064 let adapter = crate::engine::topo::GraphAdapter { g: self };
2065 let layers = pk.layers_for(&adapter, subset, self.config.max_layer_width);
2066 Some(layers.into_iter().map(crate::engine::Layer::new).collect())
2067 }
2068
2069 #[cfg(any(test, feature = "legacy_oracle"))]
2070 #[inline]
2071 pub(crate) fn dynamic_topo_enabled(&self) -> bool {
2072 self.pk_order.is_some()
2073 }
2074
2075 #[cfg(test)]
2076 pub fn reset_instr(&mut self) {
2077 if let Ok(mut g) = self.instr.lock() {
2078 *g = GraphInstrumentation::default();
2079 }
2080 }
2081
2082 #[cfg(test)]
2083 pub fn instr(&self) -> GraphInstrumentation {
2084 self.instr.lock().map(|g| g.clone()).unwrap_or_default()
2085 }
2086
2087 pub(crate) fn pk_active(&self) -> bool {
2090 #[cfg(any(test, feature = "legacy_oracle"))]
2091 {
2092 self.pk_order.is_some()
2093 }
2094 #[cfg(not(any(test, feature = "legacy_oracle")))]
2095 {
2096 false
2097 }
2098 }
2099
2100 pub fn begin_batch(&mut self) {
2102 #[cfg(any(test, feature = "legacy_oracle"))]
2103 self.edges.begin_batch();
2104 }
2105
2106 pub fn end_batch(&mut self) {
2108 #[cfg(any(test, feature = "legacy_oracle"))]
2109 self.edges.end_batch();
2110 }
2111
2112 pub fn default_sheet_id(&self) -> SheetId {
2113 self.default_sheet_id
2114 }
2115
2116 pub fn default_sheet_name(&self) -> &str {
2117 self.sheet_reg.name(self.default_sheet_id)
2118 }
2119
2120 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2121 self.default_sheet_id = self.sheet_id_mut(name);
2122 }
2123
2124 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2125 self.default_sheet_id = id;
2126 }
2127
2128 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
2130 if let Some(id) = self.sheet_reg.get_id(name) {
2131 return id;
2132 }
2133 let id = self.sheet_reg.id_for(name);
2134 self.resolve_pending_symbol("sheet", name);
2135 id
2136 }
2137
2138 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
2139 self.sheet_reg.get_id(name)
2140 }
2141
2142 fn resolve_existing_sheet_id(&self, name: &str) -> Result<SheetId, ExcelError> {
2144 self.sheet_id(name).ok_or_else(|| {
2145 ExcelError::new(ExcelErrorKind::Ref).with_message(format!("Sheet not found: {name}"))
2146 })
2147 }
2148
2149 pub fn sheet_name(&self, id: SheetId) -> &str {
2151 self.sheet_reg.name(id)
2152 }
2153
2154 pub fn sheet_reg(&self) -> &SheetRegistry {
2156 &self.sheet_reg
2157 }
2158
2159 pub(crate) fn data_store(&self) -> &DataStore {
2160 &self.data_store
2161 }
2162
2163 pub(crate) fn make_ingest_pipeline<'a>(
2164 &'a mut self,
2165 function_provider: &'a dyn crate::traits::FunctionProvider,
2166 policy: formualizer_parse::parser::CollectPolicy,
2167 ) -> crate::engine::ingest_pipeline::IngestPipeline<'a> {
2168 use crate::engine::ingest_pipeline::{
2169 NameRegistryView, NamedEntryRef, SourceEntryRef, SourceRegistryView,
2170 TableEntrySnapshot, TableRegistryView,
2171 };
2172
2173 let DependencyGraph {
2174 data_store,
2175 sheet_reg,
2176 named_ranges,
2177 named_ranges_lookup,
2178 sheet_named_ranges,
2179 sheet_named_ranges_lookup,
2180 tables,
2181 tables_lookup,
2182 source_scalars,
2183 source_tables,
2184 config,
2185 ..
2186 } = self;
2187
2188 let unbound_pending =
2189 config.preparation_policy == crate::engine::PreparationPolicy::BestEffort;
2190 let case_sensitive_names = config.case_sensitive_names;
2191 let names = NameRegistryView::new(move |name, current_sheet| {
2192 let found = if case_sensitive_names {
2193 sheet_named_ranges
2194 .get(&(current_sheet, name.to_string()))
2195 .or_else(|| named_ranges.get(name))
2196 } else {
2197 let key = name.to_lowercase();
2198 sheet_named_ranges_lookup
2199 .get(&(current_sheet, key.clone()))
2200 .and_then(|canon| sheet_named_ranges.get(&(current_sheet, canon.clone())))
2201 .or_else(|| {
2202 named_ranges_lookup
2203 .get(&key)
2204 .and_then(|canon| named_ranges.get(canon))
2205 })
2206 };
2207 found.map(|entry| NamedEntryRef {
2208 vertex: entry.vertex,
2209 })
2210 });
2211
2212 let case_sensitive_tables = config.case_sensitive_tables;
2213 let tables_ref = &*tables;
2214 let tables_lookup_ref = &*tables_lookup;
2215 let snapshot_table = |entry: &tables::TableEntry| TableEntrySnapshot {
2216 name: entry.name.clone(),
2217 range: entry.range,
2218 header_row: entry.header_row,
2219 headers: entry.headers.clone(),
2220 vertex: entry.vertex,
2221 };
2222 let tables_view = TableRegistryView::new(
2223 move |name| {
2224 if case_sensitive_tables {
2225 tables_ref.get(name).map(snapshot_table)
2226 } else {
2227 let key = name.to_lowercase();
2228 tables_lookup_ref
2229 .get(&key)
2230 .and_then(|canon| tables_ref.get(canon))
2231 .map(snapshot_table)
2232 }
2233 },
2234 move |cell| {
2235 let row0 = cell.coord.row();
2236 let col0 = cell.coord.col();
2237 let mut best: Option<&tables::TableEntry> = None;
2238 let mut best_area = u64::MAX;
2239 let mut best_name = "";
2240 for table in tables_ref.values() {
2241 if table.sheet_id() != cell.sheet_id {
2242 continue;
2243 }
2244 let sr0 = table.range.start.coord.row();
2245 let sc0 = table.range.start.coord.col();
2246 let er0 = table.range.end.coord.row();
2247 let ec0 = table.range.end.coord.col();
2248 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2249 continue;
2250 }
2251 let area = ((er0 - sr0 + 1) as u64).saturating_mul((ec0 - sc0 + 1) as u64);
2252 let name = table.name.as_str();
2253 if best.is_none() || area < best_area || (area == best_area && name < best_name)
2254 {
2255 best = Some(table);
2256 best_area = area;
2257 best_name = name;
2258 }
2259 }
2260 best.map(snapshot_table)
2261 },
2262 );
2263
2264 let sources = SourceRegistryView::new(
2265 move |name| {
2266 source_scalars.get(name).map(|entry| SourceEntryRef {
2267 vertex: entry.vertex,
2268 })
2269 },
2270 move |name| {
2271 source_tables.get(name).map(|entry| SourceEntryRef {
2272 vertex: entry.vertex,
2273 })
2274 },
2275 );
2276
2277 crate::engine::ingest_pipeline::IngestPipeline::new(
2278 data_store,
2279 sheet_reg,
2280 names,
2281 tables_view,
2282 sources,
2283 function_provider,
2284 policy,
2285 )
2286 .with_unbound_pending(unbound_pending)
2287 }
2288
2289 pub fn to_a1(&self, cell_ref: CellRef) -> String {
2291 format!("{}!{}", self.sheet_name(cell_ref.sheet_id), cell_ref.coord)
2292 }
2293
2294 pub(crate) fn vertex_len(&self) -> usize {
2295 self.store.len()
2296 }
2297
2298 #[cfg(test)]
2300 pub(crate) fn next_vertex_id_for_test(&self) -> u32 {
2301 crate::engine::vertex_store::FIRST_NORMAL_VERTEX + self.store.len() as u32
2302 }
2303
2304 pub(crate) fn topology_revision(&self) -> u64 {
2305 self.topology_revision
2306 }
2307
2308 pub(crate) fn bump_topology_revision(&mut self) {
2309 self.topology_revision = self.topology_revision.wrapping_add(1);
2310 }
2311
2312 pub(crate) fn symbol_revision(&self) -> u64 {
2313 self.symbol_revision
2314 }
2315
2316 pub(crate) fn bump_symbol_revision(&mut self) {
2317 self.symbol_revision = self.symbol_revision.wrapping_add(1);
2318 self.authority_sync_if_ready();
2321 }
2322
2323 #[cfg(any(test, feature = "legacy_oracle"))]
2324 pub(crate) fn formula_range_dependencies(
2325 &self,
2326 vertex: VertexId,
2327 ) -> Option<&[SharedRangeRef<'static>]> {
2328 self.formula_to_range_deps.get(&vertex).map(Vec::as_slice)
2329 }
2330
2331 pub(crate) fn spill_anchors_in_region(
2332 &self,
2333 sheet_id: SheetId,
2334 start_row0: u32,
2335 start_col0: u32,
2336 end_row0: u32,
2337 end_col0: u32,
2338 ) -> Vec<VertexId> {
2339 let mut anchors = self
2340 .spill_cells_by_sheet
2341 .get(&sheet_id)
2342 .into_iter()
2343 .flat_map(|cells| cells.range((start_row0, 0)..=(end_row0, u32::MAX)))
2344 .filter_map(|(&(row, col), anchor)| {
2345 (row <= end_row0 && col >= start_col0 && col <= end_col0).then_some(*anchor)
2346 })
2347 .collect::<Vec<_>>();
2348 anchors.sort_unstable();
2349 anchors.dedup();
2350 anchors
2351 }
2352
2353 pub fn sheet_index_mut(&mut self, sheet_id: SheetId) -> &mut SheetIndex {
2356 self.sheet_indexes.entry(sheet_id).or_default()
2357 }
2358
2359 pub fn sheet_index(&self, sheet_id: SheetId) -> Option<&SheetIndex> {
2361 self.sheet_indexes.get(&sheet_id)
2362 }
2363
2364 pub(crate) fn sheet_index_vertex_count(&self, sheet_id: SheetId) -> usize {
2365 self.sheet_indexes.get(&sheet_id).map_or(0, SheetIndex::len)
2366 + self
2367 .vertex_formulas
2368 .virtual_members()
2369 .runs_in_sheet(sheet_id)
2370 .map(|r| r.len as usize)
2371 .sum::<usize>()
2372 }
2373
2374 pub(crate) fn set_admission_budget_override(
2375 &mut self,
2376 budgets: Option<crate::engine::EvaluationBudgets>,
2377 ) -> Option<crate::engine::EvaluationBudgets> {
2378 std::mem::replace(&mut self.admission_budget_override, budgets)
2379 }
2380
2381 fn self_admission_budgets(&self) -> crate::engine::EvaluationBudgets {
2382 self.admission_budget_override
2383 .clone()
2384 .unwrap_or_else(|| self.config.resolved_evaluation_budgets())
2385 }
2386
2387 fn preview_spill_materialization(
2388 &self,
2389 target_cells: &[CellRef],
2390 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2391 let unique = target_cells.iter().copied().collect::<FxHashSet<_>>();
2392 let added_vertices = unique
2393 .iter()
2394 .filter(|cell| self.cell_vertex(cell).is_none())
2395 .count();
2396 let stats = self.baseline_stats();
2397 Ok(crate::engine::resource_ledger::GraphAdmission {
2398 final_vertices: stats
2399 .graph_vertex_count
2400 .checked_add(added_vertices)
2401 .ok_or_else(|| {
2402 ExcelError::new(ExcelErrorKind::NImpl)
2403 .with_message("spill vertex count overflow")
2404 })?,
2405 final_edges: stats.graph_edge_count,
2406 materialization_cells: unique.len() as u64,
2407 added_vertices,
2408 added_edges: 0,
2409 })
2410 }
2411
2412 pub(crate) fn preview_value_mutation(
2413 &self,
2414 sheet_id: SheetId,
2415 row: u32,
2416 col: u32,
2417 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2418 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2419 let existing = self.cell_vertex(&cell);
2420 let stats = self.baseline_stats();
2421 let removed_edges = existing.map_or(0, |vertex| self.store.edge_offset(vertex) as usize);
2422 Ok(crate::engine::resource_ledger::GraphAdmission {
2424 final_vertices: stats.graph_vertex_count,
2425 final_edges: stats
2426 .graph_edge_count
2427 .checked_sub(removed_edges)
2428 .ok_or_else(|| {
2429 ExcelError::new(ExcelErrorKind::NImpl)
2430 .with_message("graph edge count underflow")
2431 })?,
2432 materialization_cells: 0,
2433 added_vertices: 0,
2434 added_edges: 0,
2435 })
2436 }
2437
2438 pub(crate) fn preview_value_mutations(
2439 &self,
2440 sheet_id: SheetId,
2441 cells: &[(u32, u32)],
2442 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2443 let mut targets = std::collections::BTreeSet::new();
2444 let added_vertices = 0usize;
2445 let mut removed_edges = 0usize;
2446 for (row, col) in cells {
2447 let packed = PackedSheetCell::try_from_excel_1based(sheet_id, *row, *col)
2448 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2449 if !targets.insert(packed) {
2450 continue;
2451 }
2452 let reference = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
2453 if let Some(vertex) = self.cell_vertex(&reference) {
2455 removed_edges = removed_edges
2456 .checked_add(self.store.edge_offset(vertex) as usize)
2457 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?;
2458 }
2459 }
2460 let stats = self.baseline_stats();
2461 Ok(crate::engine::resource_ledger::GraphAdmission {
2462 final_vertices: stats
2463 .graph_vertex_count
2464 .checked_add(added_vertices)
2465 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2466 final_edges: stats
2467 .graph_edge_count
2468 .checked_sub(removed_edges)
2469 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2470 materialization_cells: 0,
2471 added_vertices,
2472 added_edges: 0,
2473 })
2474 }
2475
2476 pub(crate) fn preview_formula_mutations(
2477 &self,
2478 plans: &[(SheetId, u32, u32, DependencyPlanRow)],
2479 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2480 let mut new_cells = std::collections::BTreeSet::new();
2481 let mut removed_edges = 0usize;
2482 let mut added_edges = 0usize;
2483 for (sheet_id, row, col, plan) in plans {
2484 let target = PackedSheetCell::try_from_excel_1based(*sheet_id, *row, *col)
2485 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2486 let target_ref = CellRef::new(*sheet_id, Coord::from_excel(*row, *col, true, true));
2487 if let Some(vertex) = self.cell_vertex(&target_ref) {
2488 removed_edges = removed_edges
2489 .checked_add(self.store.edge_offset(vertex) as usize)
2490 .ok_or_else(|| {
2491 ExcelError::new(ExcelErrorKind::NImpl)
2492 .with_message("graph edge count overflow")
2493 })?;
2494 } else {
2495 new_cells.insert(target);
2496 }
2497
2498 let mut dependencies = std::collections::BTreeSet::new();
2499 for dependency in &plan.direct_cell_deps {
2500 let packed = PackedSheetCell::try_new(
2501 dependency.sheet_id,
2502 dependency.coord.row(),
2503 dependency.coord.col(),
2504 )
2505 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2506 let reference = CellRef::new(dependency.sheet_id, dependency.coord);
2507 if let Some(vertex) = self.cell_vertex(&reference) {
2508 dependencies.insert((0u8, u64::from(vertex.0)));
2509 } else {
2510 new_cells.insert(packed);
2511 dependencies.insert((1u8, packed.as_u64()));
2512 }
2513 }
2514 for name in plan.resolved_named_refs.iter().chain(&plan.named_refs) {
2515 if let Some(entry) = self.resolve_name_entry(name, *sheet_id) {
2516 dependencies.insert((0, u64::from(entry.vertex.0)));
2517 } else if let Some(entry) = self.resolve_source_scalar_entry(name) {
2518 dependencies.insert((0, u64::from(entry.vertex.0)));
2519 }
2520 }
2521 for name in &plan.source_refs {
2522 if let Some(vertex) = self
2523 .resolve_source_scalar_entry(name)
2524 .map(|entry| entry.vertex)
2525 .or_else(|| {
2526 self.resolve_source_table_entry(name)
2527 .map(|entry| entry.vertex)
2528 })
2529 {
2530 dependencies.insert((0, u64::from(vertex.0)));
2531 }
2532 }
2533 for name in &plan.table_refs {
2534 if let Some(vertex) = self
2535 .resolve_table_entry(name)
2536 .map(|entry| entry.vertex)
2537 .or_else(|| {
2538 self.resolve_source_table_entry(name)
2539 .map(|entry| entry.vertex)
2540 })
2541 {
2542 dependencies.insert((0, u64::from(vertex.0)));
2543 }
2544 }
2545 let target_row = target.row0();
2546 let target_col = target.col0();
2547 if plan.range_deps.iter().any(|range| {
2548 let range_sheet = self
2550 .sheet_reg
2551 .resolve_locator(&range.sheet, *sheet_id)
2552 .unwrap_or(*sheet_id);
2553 range_sheet == *sheet_id
2554 && range
2555 .start_row
2556 .is_none_or(|bound| target_row >= bound.index)
2557 && range.end_row.is_none_or(|bound| target_row <= bound.index)
2558 && range
2559 .start_col
2560 .is_none_or(|bound| target_col >= bound.index)
2561 && range.end_col.is_none_or(|bound| target_col <= bound.index)
2562 }) {
2563 dependencies.insert((1, target.as_u64()));
2564 }
2565 added_edges = added_edges.checked_add(dependencies.len()).ok_or_else(|| {
2566 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2567 })?;
2568 }
2569 let stats = self.baseline_stats();
2570 Ok(crate::engine::resource_ledger::GraphAdmission {
2571 final_vertices: stats
2572 .graph_vertex_count
2573 .checked_add(new_cells.len())
2574 .ok_or_else(|| {
2575 ExcelError::new(ExcelErrorKind::NImpl)
2576 .with_message("graph vertex count overflow")
2577 })?,
2578 final_edges: stats
2579 .graph_edge_count
2580 .checked_sub(removed_edges)
2581 .and_then(|count| count.checked_add(added_edges))
2582 .ok_or_else(|| {
2583 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2584 })?,
2585 materialization_cells: plans.len() as u64,
2586 added_vertices: new_cells.len(),
2587 added_edges,
2588 })
2589 }
2590
2591 pub(crate) fn formula_intervals_in_region(
2600 &self,
2601 sheet_id: SheetId,
2602 start_row0: u32,
2603 end_row0: u32,
2604 start_col0: u32,
2605 end_col0: u32,
2606 mut matches: impl FnMut(AstNodeId) -> bool,
2607 ) -> Vec<(u32, u32, u32)> {
2608 let mut out = Vec::new();
2609 if let Some(index) = self.sheet_indexes.get(&sheet_id) {
2610 for v in index.rect_candidates(start_row0, end_row0, start_col0, end_col0) {
2611 if !self.formula_view(v).is_some_and(|f| matches(f.template)) {
2612 continue;
2613 }
2614 if let Some(cell) = self.get_cell_ref(v) {
2615 let (row, col) = (cell.coord.row(), cell.coord.col());
2616 if (start_row0..=end_row0).contains(&row)
2617 && (start_col0..=end_col0).contains(&col)
2618 {
2619 out.push((col, row, row));
2620 }
2621 }
2622 }
2623 }
2624 for r in self
2625 .vertex_formulas
2626 .virtual_members()
2627 .runs_in_cols(sheet_id, start_col0, end_col0)
2628 {
2629 let lo = r.row0.max(start_row0);
2630 let hi = (r.row0 + r.len - 1).min(end_row0);
2631 if lo <= hi && matches(r.template) {
2632 out.push((r.col, lo, hi));
2633 }
2634 }
2635 out
2636 }
2637
2638 pub(crate) fn vertices_in_region(
2639 &self,
2640 sheet_id: SheetId,
2641 start_row0: u32,
2642 end_row0: u32,
2643 start_col0: u32,
2644 end_col0: u32,
2645 ) -> Vec<VertexId> {
2646 let Some(index) = self.sheet_indexes.get(&sheet_id) else {
2647 return Vec::new();
2648 };
2649 let mut out = index.vertices_in_rect(start_row0, end_row0, start_col0, end_col0);
2650 for r in self
2653 .vertex_formulas
2654 .virtual_members()
2655 .runs_in_cols(sheet_id, start_col0, end_col0)
2656 {
2657 let lo = r.row0.max(start_row0);
2658 let hi = (r.row0 + r.len - 1).min(end_row0);
2659 if lo <= hi {
2660 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
2661 }
2662 }
2663 out
2664 }
2665
2666 #[cfg(test)]
2667 pub(crate) fn reset_sheet_index_query_stats(&self) {
2668 for index in self.sheet_indexes.values() {
2669 index.reset_query_stats();
2670 }
2671 }
2672
2673 #[cfg(test)]
2674 pub(crate) fn sheet_index_query_stats(
2675 &self,
2676 ) -> crate::engine::sheet_index::SheetIndexQueryStats {
2677 self.sheet_indexes.values().fold(
2678 crate::engine::sheet_index::SheetIndexQueryStats::default(),
2679 |mut total, index| {
2680 let stats = index.query_stats();
2681 total.coordinate_nodes_visited = total
2682 .coordinate_nodes_visited
2683 .saturating_add(stats.coordinate_nodes_visited);
2684 total.values_visited = total.values_visited.saturating_add(stats.values_visited);
2685 total
2686 },
2687 )
2688 }
2689
2690 pub fn set_cell_value(
2696 &mut self,
2697 sheet: &str,
2698 row: u32,
2699 col: u32,
2700 value: LiteralValue,
2701 ) -> Result<OperationSummary, ExcelError> {
2702 let _ = normalize_stored_literal(value);
2703 let sheet_id = self.sheet_id_mut(sheet);
2704 let budgets = self.self_admission_budgets();
2705 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2706 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2707 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2708 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2709 }
2710 let coord = Coord::from_excel(row, col, true, true);
2712 let addr = CellRef::new(sheet_id, coord);
2713 self.vacate_cell(&addr);
2714 Ok(OperationSummary {
2715 affected_vertices: self.mark_dirty_cells(&[(sheet_id, coord.row(), coord.col())]),
2716 created_placeholders: Vec::new(),
2717 })
2718 }
2719
2720 pub fn reserve_cells(&mut self, additional: usize) {
2722 self.store.reserve(additional);
2723 if self.value_cache_enabled {
2724 self.vertex_values.reserve(additional);
2725 }
2726 self.cell_to_vertex.reserve(additional);
2727 }
2729
2730 pub fn set_cell_value_bulk_untracked(
2733 &mut self,
2734 sheet: &str,
2735 row: u32,
2736 col: u32,
2737 value: LiteralValue,
2738 ) -> Result<(), ExcelError> {
2739 let _ = normalize_stored_literal(value);
2740 let sheet_id = self.sheet_id_mut(sheet);
2741 let budgets = self.self_admission_budgets();
2742 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2743 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2744 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2745 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2746 }
2747 let coord = Coord::from_excel(row, col, true, true);
2748 self.vacate_cell(&CellRef::new(sheet_id, coord));
2749 Ok(())
2750 }
2751
2752 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I) -> Result<(), ExcelError>
2756 where
2757 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
2758 {
2759 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
2760 if collected.is_empty() {
2761 return Ok(());
2762 }
2763 let sheet_id = self.sheet_id_mut(sheet);
2764 let budgets = self.self_admission_budgets();
2765 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2766 let coordinates = collected
2767 .iter()
2768 .map(|(row, col, _)| (*row, *col))
2769 .collect::<Vec<_>>();
2770 let usage = self.preview_value_mutations(sheet_id, &coordinates)?;
2771 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2772 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2773 }
2774 let assume_new = self.first_load_assume_new
2776 && self
2777 .sheet_id(sheet)
2778 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
2779 .unwrap_or(false);
2780 if assume_new {
2781 for (row, col, _) in collected {
2782 self.extent_record
2783 .note(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
2784 }
2785 return Ok(());
2786 }
2787 for (row, col, _) in collected {
2788 let coord = Coord::from_excel(row, col, true, true);
2789 self.vacate_cell(&CellRef::new(sheet_id, coord));
2790 }
2791 Ok(())
2792 }
2793
2794 pub fn set_cell_formula(
2796 &mut self,
2797 sheet: &str,
2798 row: u32,
2799 col: u32,
2800 ast: ASTNode,
2801 ) -> Result<OperationSummary, ExcelError> {
2802 self.set_cell_formula_with_volatility(sheet, row, col, ast, false)
2803 }
2804
2805 pub fn set_cell_formula_with_volatility(
2808 &mut self,
2809 sheet: &str,
2810 row: u32,
2811 col: u32,
2812 ast: ASTNode,
2813 _volatile: bool,
2814 ) -> Result<OperationSummary, ExcelError> {
2815 let sheet_id = self.sheet_id_mut(sheet);
2816 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2817 let provider = RegistryFunctionProvider;
2818 let ingested = {
2819 let mut pipeline = self.ingest_pipeline(&provider);
2820 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
2821 };
2822 self.set_cell_formula_with_plan(
2823 sheet,
2824 row,
2825 col,
2826 ingested.ast_id,
2827 &ingested.dep_plan,
2828 ingested.dep_plan.volatile,
2829 ingested.dep_plan.dynamic,
2830 )
2831 }
2832
2833 pub(crate) fn set_cell_formula_with_plan(
2834 &mut self,
2835 sheet: &str,
2836 row: u32,
2837 col: u32,
2838 ast_id: AstNodeId,
2839 plan: &DependencyPlanRow,
2840 volatile: bool,
2841 dynamic: bool,
2842 ) -> Result<OperationSummary, ExcelError> {
2843 let dbg = std::env::var("FZ_DEBUG_LOAD")
2844 .ok()
2845 .is_some_and(|v| v != "0");
2846 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
2847 .ok()
2848 .and_then(|s| s.parse().ok())
2849 .unwrap_or(0);
2850 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
2851 .ok()
2852 .and_then(|s| s.parse().ok())
2853 .unwrap_or(0);
2854 let t0 = if dbg {
2855 Some(crate::instant::FzInstant::now())
2856 } else {
2857 None
2858 };
2859 let sheet_id = self.sheet_id_mut(sheet);
2860 let budgets = self.self_admission_budgets();
2861 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2862 let usage = self.preview_formula_mutations(&[(sheet_id, row, col, plan.clone())])?;
2863 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2864 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2865 }
2866 let coord = Coord::from_excel(row, col, true, true);
2867 let addr = CellRef::new(sheet_id, coord);
2868
2869 let t_dep0 = if dbg {
2870 Some(crate::instant::FzInstant::now())
2871 } else {
2872 None
2873 };
2874 let mut created_placeholders = Vec::new();
2875 let (mut new_dependencies, vertexless_deps) =
2876 self.resolve_direct_deps(&plan.direct_cell_deps);
2877 let mut named_dependencies = Vec::new();
2878 let mut unresolved_names = Vec::new();
2879 for name in plan
2880 .resolved_named_refs
2881 .iter()
2882 .chain(plan.named_refs.iter())
2883 {
2884 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
2885 if !new_dependencies.contains(&named.vertex) {
2886 new_dependencies.push(named.vertex);
2887 }
2888 if !named_dependencies.contains(&named.vertex) {
2889 named_dependencies.push(named.vertex);
2890 }
2891 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
2892 if !new_dependencies.contains(&source.vertex) {
2893 new_dependencies.push(source.vertex);
2894 }
2895 } else {
2896 unresolved_names.push(name.clone());
2897 }
2898 }
2899 for source_name in &plan.source_refs {
2900 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
2901 if !new_dependencies.contains(&source.vertex) {
2902 new_dependencies.push(source.vertex);
2903 }
2904 } else if let Some(source) = self.resolve_source_table_entry(source_name)
2905 && !new_dependencies.contains(&source.vertex)
2906 {
2907 new_dependencies.push(source.vertex);
2908 }
2909 }
2910 for table_name in &plan.table_refs {
2911 if let Some(table) = self.resolve_table_entry(table_name) {
2912 if !new_dependencies.contains(&table.vertex) {
2913 new_dependencies.push(table.vertex);
2914 }
2915 } else if let Some(source) = self.resolve_source_table_entry(table_name)
2916 && !new_dependencies.contains(&source.vertex)
2917 {
2918 new_dependencies.push(source.vertex);
2919 }
2920 }
2921 if let (true, Some(t)) = (dbg, t_dep0) {
2922 let elapsed = t.elapsed().as_millis();
2923 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
2924 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
2925 if (dep_ms_thresh == 0 && sample_n == 0 && row.is_multiple_of(1000)) || do_log {
2926 eprintln!(
2927 "[fz][dep] {}!{} planned: deps={}, ranges={}, placeholders={}, names={} in {} ms",
2928 self.sheet_name(sheet_id),
2929 crate::reference::Coord::from_excel(row, col, true, true),
2930 new_dependencies.len(),
2931 plan.range_deps.len(),
2932 created_placeholders.len(),
2933 named_dependencies.len(),
2934 elapsed
2935 );
2936 }
2937 }
2938
2939 self.replay_formula_vertex(&addr);
2941 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
2942 self.materialize_vertex(addr_vertex_id);
2943
2944 self.ref_error_vertices.remove(&addr_vertex_id);
2946
2947 let self_reference = new_dependencies.contains(&addr_vertex_id)
2962 || vertexless_deps.iter().any(|c| same_cell(c, &addr));
2963 if self_reference && !self.config.cycle.allows_self_dependency() {
2964 return Err(ExcelError::new(ExcelErrorKind::Circ)
2965 .with_message("Self-reference detected".to_string()));
2966 }
2967
2968 for &name_vertex in &named_dependencies {
2969 let mut visited = FxHashSet::default();
2970 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
2971 return Err(ExcelError::new(ExcelErrorKind::Circ)
2972 .with_message("Circular reference through named range".to_string()));
2973 }
2974 }
2975
2976 self.forget_declared_dynamic_anchor(addr_vertex_id);
2978 self.remove_dependent_edges(addr_vertex_id);
2979 self.detach_vertex_from_names(addr_vertex_id);
2980 self.clear_pending_name_references(addr_vertex_id);
2981
2982 self.store
2984 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
2985 self.vertex_formulas.insert(addr_vertex_id, ast_id);
2986 self.store.set_dirty(addr_vertex_id, true);
2987
2988 self.vertex_values.remove(&addr_vertex_id);
2990
2991 self.mark_volatile(addr_vertex_id, volatile);
2992 self.store.set_dynamic(addr_vertex_id, dynamic);
2993
2994 if !named_dependencies.is_empty() {
2995 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
2996 }
2997 for unresolved_name in &unresolved_names {
2998 self.record_pending_name_reference(sheet_id, unresolved_name, addr_vertex_id);
2999 }
3000
3001 if let (true, Some(t)) = (dbg, t0) {
3002 let elapsed = t.elapsed().as_millis();
3003 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
3004 if log_set {
3005 eprintln!(
3006 "[fz][set] {}!{} total {} ms",
3007 self.sheet_name(sheet_id),
3008 crate::reference::Coord::from_excel(row, col, true, true),
3009 elapsed
3010 );
3011 }
3012 }
3013
3014 let vertexless_deps: Vec<CellRef> = vertexless_deps
3017 .into_iter()
3018 .filter(|c| {
3019 if same_cell(c, &addr) {
3020 if !new_dependencies.contains(&addr_vertex_id) {
3021 new_dependencies.push(addr_vertex_id);
3022 }
3023 false
3024 } else {
3025 true
3026 }
3027 })
3028 .collect();
3029 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
3030 self.note_vertexless_deps(
3031 addr_vertex_id,
3032 vertexless_deps
3033 .iter()
3034 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
3035 );
3036 self.add_range_dependent_edges(addr_vertex_id, &plan.range_deps, sheet_id);
3037
3038 Ok(OperationSummary {
3039 affected_vertices: self.mark_dirty(addr_vertex_id),
3040 created_placeholders,
3041 })
3042 }
3043
3044 pub(crate) fn rewrite_structured_references_for_cell(
3045 &self,
3046 ast: &mut ASTNode,
3047 cell: CellRef,
3048 ) -> Result<bool, ExcelError> {
3049 self.rewrite_structured_references_node(ast, cell)
3050 }
3051
3052 fn rewrite_structured_references_node(
3053 &self,
3054 node: &mut ASTNode,
3055 cell: CellRef,
3056 ) -> Result<bool, ExcelError> {
3057 match &mut node.node_type {
3058 ASTNodeType::Reference { reference, .. } => {
3059 self.rewrite_structured_reference(reference, cell)
3060 }
3061 ASTNodeType::UnaryOp { expr, .. } => {
3062 self.rewrite_structured_references_node(expr, cell)
3063 }
3064 ASTNodeType::BinaryOp { left, right, .. } => {
3065 let left_rewritten = self.rewrite_structured_references_node(left, cell)?;
3066 let right_rewritten = self.rewrite_structured_references_node(right, cell)?;
3067 Ok(left_rewritten || right_rewritten)
3068 }
3069 ASTNodeType::Function { args, .. } => {
3070 let mut rewritten = false;
3071 for a in args.iter_mut() {
3072 rewritten |= self.rewrite_structured_references_node(a, cell)?;
3073 }
3074 Ok(rewritten)
3075 }
3076 ASTNodeType::Call { callee, args } => {
3077 let mut rewritten = self.rewrite_structured_references_node(callee, cell)?;
3078 for a in args.iter_mut() {
3079 rewritten |= self.rewrite_structured_references_node(a, cell)?;
3080 }
3081 Ok(rewritten)
3082 }
3083 ASTNodeType::Array(rows) => {
3084 let mut rewritten = false;
3085 for r in rows.iter_mut() {
3086 for item in r.iter_mut() {
3087 rewritten |= self.rewrite_structured_references_node(item, cell)?;
3088 }
3089 }
3090 Ok(rewritten)
3091 }
3092 ASTNodeType::Literal(_) | ASTNodeType::Omitted => Ok(false),
3093 }
3094 }
3095
3096 fn rewrite_structured_reference(
3097 &self,
3098 reference: &mut ReferenceType,
3099 cell: CellRef,
3100 ) -> Result<bool, ExcelError> {
3101 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
3102
3103 let ReferenceType::Table(tref) = reference else {
3104 return Ok(false);
3105 };
3106
3107 if !tref.name.is_empty() {
3109 return Ok(false);
3110 }
3111
3112 let col_name = match &tref.specifier {
3113 Some(TableSpecifier::Combination(parts)) => {
3114 let mut saw_this_row = false;
3115 let mut col: Option<&str> = None;
3116 for p in parts {
3117 match p.as_ref() {
3118 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
3119 saw_this_row = true;
3120 }
3121 TableSpecifier::Column(c) => {
3122 if col.is_some() {
3123 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3124 "This-row structured reference with multiple columns is not supported"
3125 .to_string(),
3126 ));
3127 }
3128 col = Some(c.as_str());
3129 }
3130 other => {
3131 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3132 format!(
3133 "Unsupported this-row structured reference component: {other}"
3134 ),
3135 ));
3136 }
3137 }
3138 }
3139 if !saw_this_row {
3140 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3141 "Unnamed structured reference requires a this-row selector".to_string(),
3142 ));
3143 }
3144 col.ok_or_else(|| {
3145 ExcelError::new(ExcelErrorKind::NImpl).with_message(
3146 "This-row structured reference missing column selector".to_string(),
3147 )
3148 })?
3149 }
3150 _ => {
3151 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3152 "Unnamed structured reference form is not supported".to_string(),
3153 ));
3154 }
3155 };
3156
3157 let Some(table) = self.find_table_containing_cell(cell) else {
3158 return Err(ExcelError::new(ExcelErrorKind::Name)
3159 .with_message("This-row structured reference used outside a table".to_string()));
3160 };
3161
3162 let row0 = cell.coord.row();
3163 let col0 = cell.coord.col();
3164 let sr0 = table.range.start.coord.row();
3165 let sc0 = table.range.start.coord.col();
3166 let er0 = table.range.end.coord.row();
3167 let ec0 = table.range.end.coord.col();
3168
3169 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3170 return Err(ExcelError::new(ExcelErrorKind::Name)
3171 .with_message("This-row structured reference used outside a table".to_string()));
3172 }
3173
3174 if table.header_row && row0 == sr0 {
3175 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3176 "This-row structured references are not valid in the table header row".to_string(),
3177 ));
3178 }
3179
3180 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
3181 if row0 < data_start {
3182 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3183 "This-row structured references require a data/totals row context".to_string(),
3184 ));
3185 }
3186
3187 let Some(idx) = table.col_index(col_name) else {
3188 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
3189 "Unknown table column in this-row reference: {col_name}"
3190 )));
3191 };
3192 let target_col0 = sc0 + (idx as u32);
3193 let target_row = row0 + 1;
3194 let target_col = target_col0 + 1;
3195
3196 *reference = ReferenceType::Cell {
3197 sheet: None,
3198 row: target_row,
3199 col: target_col,
3200 row_abs: true,
3201 col_abs: true,
3202 };
3203
3204 Ok(true)
3205 }
3206
3207 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
3208 let row0 = cell.coord.row();
3209 let col0 = cell.coord.col();
3210
3211 let mut best: Option<&tables::TableEntry> = None;
3212 let mut best_area: u64 = u64::MAX;
3213 let mut best_name: &str = "";
3214
3215 for t in self.tables.values() {
3216 if t.sheet_id() != cell.sheet_id {
3217 continue;
3218 }
3219 let sr0 = t.range.start.coord.row();
3220 let sc0 = t.range.start.coord.col();
3221 let er0 = t.range.end.coord.row();
3222 let ec0 = t.range.end.coord.col();
3223 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3224 continue;
3225 }
3226
3227 let h = (er0 - sr0 + 1) as u64;
3228 let w = (ec0 - sc0 + 1) as u64;
3229 let area = h.saturating_mul(w);
3230 let name = t.name.as_str();
3231 let better = match best {
3232 None => true,
3233 Some(_) => area < best_area || (area == best_area && name < best_name),
3234 };
3235 if better {
3236 best = Some(t);
3237 best_area = area;
3238 best_name = name;
3239 }
3240 }
3241
3242 best
3243 }
3244
3245 #[allow(clippy::type_complexity)]
3246 pub(crate) fn fp8_parity_extract_dependencies_with_pending_names(
3247 &mut self,
3248 ast: &ASTNode,
3249 current_sheet_id: SheetId,
3250 ) -> Result<
3251 (
3252 Vec<VertexId>,
3253 Vec<SharedRangeRef<'static>>,
3254 Vec<CellRef>,
3255 Vec<VertexId>,
3256 Vec<String>,
3257 ),
3258 ExcelError,
3259 > {
3260 self.extract_dependencies_with_pending_names(ast, current_sheet_id)
3261 }
3262
3263 pub(crate) fn fp8_parity_is_ast_volatile(&self, ast: &ASTNode) -> bool {
3264 self.is_ast_volatile(ast)
3265 }
3266
3267 pub fn set_cell_value_ref(
3268 &mut self,
3269 cell: formualizer_common::SheetCellRef<'_>,
3270 value: LiteralValue,
3271 ) -> Result<OperationSummary, ExcelError> {
3272 let owned = cell.into_owned();
3273 let sheet_id = match owned.sheet {
3274 formualizer_common::SheetLocator::Id(id) => id,
3275 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3276 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3277 };
3278 let sheet_name = self.sheet_name(sheet_id).to_string();
3279 self.set_cell_value(
3280 &sheet_name,
3281 owned.coord.row() + 1,
3282 owned.coord.col() + 1,
3283 value,
3284 )
3285 }
3286
3287 pub fn set_cell_formula_ref(
3288 &mut self,
3289 cell: formualizer_common::SheetCellRef<'_>,
3290 ast: ASTNode,
3291 ) -> Result<OperationSummary, ExcelError> {
3292 let owned = cell.into_owned();
3293 let sheet_id = match owned.sheet {
3294 formualizer_common::SheetLocator::Id(id) => id,
3295 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3296 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3297 };
3298 let sheet_name = self.sheet_name(sheet_id).to_string();
3299 self.set_cell_formula(
3300 &sheet_name,
3301 owned.coord.row() + 1,
3302 owned.coord.col() + 1,
3303 ast,
3304 )
3305 }
3306
3307 pub fn get_cell_value_ref(
3308 &self,
3309 cell: formualizer_common::SheetCellRef<'_>,
3310 ) -> Option<LiteralValue> {
3311 let owned = cell.into_owned();
3312 let sheet_id = match owned.sheet {
3313 formualizer_common::SheetLocator::Id(id) => id,
3314 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
3315 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3316 };
3317 let sheet_name = self.sheet_name(sheet_id);
3318 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
3319 }
3320
3321 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3323 if !self.value_cache_enabled {
3324 #[cfg(debug_assertions)]
3325 {
3326 self.graph_value_read_attempts
3327 .fetch_add(1, Ordering::Relaxed);
3328 }
3329 return None;
3330 }
3331 let sheet_id = self.sheet_reg.get_id(sheet)?;
3332 let coord = Coord::from_excel(row, col, true, true);
3333 let addr = CellRef::new(sheet_id, coord);
3334
3335 self.get_vertex_id_for_address(&addr).and_then(|vertex_id| {
3336 self.vertex_values
3338 .get(&vertex_id)
3339 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
3340 })
3341 }
3342
3343 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
3345 self.mark_dirty_many(&[vertex_id])
3346 }
3347
3348 pub(crate) fn mark_dirty_many(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
3368 if self.deferred_dirty_depth > 0 {
3369 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
3370 return vertex_ids.to_vec();
3371 }
3372 self.authority_mark_dirty(vertex_ids)
3373 }
3374
3375 pub(crate) fn dirty_propagation_visits(&self) -> u64 {
3378 self.dirty_propagation_visits
3379 }
3380
3381 pub fn begin_deferred_dirty(&mut self) {
3402 #[cfg(any(test, feature = "legacy_oracle"))]
3403 self.edges.begin_batch();
3404 self.deferred_dirty_depth += 1;
3405 }
3406
3407 pub fn end_deferred_dirty(&mut self) -> Vec<VertexId> {
3412 debug_assert!(
3413 self.deferred_dirty_depth > 0,
3414 "end_deferred_dirty without matching begin_deferred_dirty"
3415 );
3416 #[cfg(any(test, feature = "legacy_oracle"))]
3417 self.edges.end_batch();
3418 self.deferred_dirty_depth = self.deferred_dirty_depth.saturating_sub(1);
3419 if self.deferred_dirty_depth > 0 {
3420 return Vec::new();
3421 }
3422 let pending = std::mem::take(&mut self.deferred_dirty_pending);
3423 let rects = std::mem::take(&mut self.deferred_dirty_pending_rects);
3424 let mut affected = self.authority_mark_dirty_rects(&rects);
3425 if pending.is_empty() {
3426 return affected;
3427 }
3428 let live: Vec<VertexId> = pending
3429 .into_iter()
3430 .filter(|&id| self.vertex_exists(id))
3431 .collect();
3432 affected.extend(self.mark_dirty_many(&live));
3433 affected
3434 }
3435
3436 pub(crate) fn mark_dirty_cells(
3439 &mut self,
3440 cells: &[crate::engine::authority::geom::Cell],
3441 ) -> Vec<VertexId> {
3442 let rects: Vec<(SheetId, u32, u32, u32, u32)> =
3443 cells.iter().map(|&(s, r, c)| (s, r, r, c, c)).collect();
3444 self.mark_dirty_rects(&rects)
3445 }
3446
3447 pub(crate) fn mark_dirty_rects(
3450 &mut self,
3451 rects: &[(SheetId, u32, u32, u32, u32)],
3452 ) -> Vec<VertexId> {
3453 let rects: Vec<(u16, crate::engine::authority::geom::Rect)> = rects
3454 .iter()
3455 .map(|&(s, r0, r1, c0, c1)| {
3456 (s, crate::engine::authority::geom::Rect::new(r0, c0, r1, c1))
3457 })
3458 .collect();
3459 if self.deferred_dirty_depth > 0 {
3460 self.deferred_dirty_pending_rects.extend_from_slice(&rects);
3461 return Vec::new();
3462 }
3463 self.authority_mark_dirty_rects(&rects)
3464 }
3465
3466 pub fn deferred_dirty_active(&self) -> bool {
3469 self.deferred_dirty_depth > 0
3470 }
3471
3472 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
3474 let mut result: Vec<VertexId> = self
3477 .formula_dirty
3478 .legacy_iter()
3479 .chain(self.volatile_vertices.iter().copied())
3480 .filter(|&id| {
3481 self.store.vertex_exists_active(id)
3484 && matches!(
3485 self.store.kind(id),
3486 VertexKind::FormulaScalar
3487 | VertexKind::FormulaArray
3488 | VertexKind::NamedScalar
3489 | VertexKind::NamedArray
3490 )
3491 })
3492 .collect();
3493 result.sort_unstable();
3494 result.dedup();
3495 result
3496 }
3497
3498 pub(crate) fn has_dirty_evaluation_vertices(&self) -> bool {
3501 self.formula_dirty.legacy_iter().any(|id| {
3502 self.store.vertex_exists_active(id)
3503 && matches!(
3504 self.store.kind(id),
3505 VertexKind::FormulaScalar
3506 | VertexKind::FormulaArray
3507 | VertexKind::NamedScalar
3508 | VertexKind::NamedArray
3509 )
3510 })
3511 }
3512
3513 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
3515 if !vertices.is_empty() {
3516 self.row_visibility_scan_armed = true;
3517 }
3518 for &vertex_id in vertices {
3519 self.store.set_dirty(vertex_id, false);
3520 self.formula_dirty.legacy_remove(&vertex_id);
3521 }
3522 self.formula_dirty.legacy_shrink_if_sparse();
3523 self.authority_observe_clean(vertices);
3524 }
3525
3526 pub fn clear_volatile_flags(&mut self) {
3528 self.volatile_vertices.clear();
3529 }
3530
3531 pub(crate) fn mark_row_visibility_readers_dirty(&mut self) {
3539 if !self.row_visibility_scan_armed {
3540 return;
3541 }
3542 self.row_visibility_scan_armed = false;
3543 let ds = &self.data_store;
3544 let mut by_template: FxHashMap<AstNodeId, bool> = FxHashMap::default();
3545 let mut readers: Vec<VertexId> = self
3546 .vertex_formulas
3547 .keys()
3548 .filter(|&v| {
3549 self.formula_view(v).is_some_and(|f| {
3550 *by_template
3551 .entry(f.template)
3552 .or_insert_with(|| ds.ast_subtotal_calls(f.template) != 0)
3553 })
3554 })
3555 .collect();
3556 readers.extend(
3557 self.named_ranges
3558 .values()
3559 .chain(self.sheet_named_ranges.values())
3560 .filter(|named| match &named.definition {
3561 NamedDefinition::Formula { ast, .. } => ast_calls_subtotal(ast),
3562 _ => false,
3563 })
3564 .map(|named| named.vertex),
3565 );
3566 if !readers.is_empty() {
3567 self.mark_dirty_many(&readers);
3568 }
3569 }
3570
3571 pub(crate) fn redirty_volatiles(&mut self) {
3576 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
3577 let _ = self.mark_dirty_many(&volatile_ids);
3578 }
3579
3580 pub(crate) fn redirty_iterative_members(&mut self, members: &[VertexId]) {
3593 let live: Vec<VertexId> = members
3594 .iter()
3595 .copied()
3596 .filter(|&id| self.vertex_exists(id))
3597 .collect();
3598 let _ = self.mark_dirty_many(&live);
3599 }
3600
3601 pub(crate) fn dep_vertex(&mut self, addr: &CellRef) -> Option<VertexId> {
3605 if let Some(vertex_id) = self.cell_vertex(addr) {
3606 return Some(vertex_id);
3607 }
3608 if self.first_load_assume_new {
3609 let packed = Self::packed_cell_key(
3610 addr.sheet_id,
3611 AbsCoord::new(addr.coord.row(), addr.coord.col()),
3612 );
3613 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
3614 self.cell_to_vertex.insert(*addr, existing);
3615 return Some(existing);
3616 }
3617 }
3618 None
3619 }
3620
3621 pub(crate) fn vacate_cell(&mut self, addr: &CellRef) -> Option<(VertexId, bool)> {
3627 self.extent_record
3630 .note(addr.sheet_id, addr.coord.row(), addr.coord.col());
3631 let v = self.cell_vertex_mut(addr)?;
3632 let was_formula = matches!(
3633 self.store.kind(v),
3634 VertexKind::FormulaScalar | VertexKind::FormulaArray
3635 ) || self.vertex_formulas.contains_key(&v);
3636 self.remove_dependent_edges(v);
3637 self.detach_vertex_from_names(v);
3638 self.clear_pending_name_references(v);
3639 self.forget_declared_dynamic_anchor(v);
3640 self.vertex_formulas.remove(&v);
3641 self.vertex_values.remove(&v);
3642 self.ref_error_vertices.remove(&v);
3643 self.clear_formula_vertex_dirty(v);
3644 self.mark_volatile(v, false);
3645 self.store.set_dynamic(v, false);
3646 self.store.set_kind(v, VertexKind::Empty);
3647 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3648 #[cfg(any(test, feature = "legacy_oracle"))]
3651 {
3652 let readers = self.get_dependents(v);
3653 self.remove_all_edges(v);
3654 for r in readers {
3655 if !self.store.is_deleted(r) {
3656 self.oracle_vertexless_readers
3657 .entry(key)
3658 .or_default()
3659 .push(r);
3660 self.oracle_vertexless_of.entry(r).or_default().push(key);
3661 }
3662 }
3663 }
3664 self.cell_to_vertex.remove(addr);
3665 if let Some(index) = self.sheet_indexes.get_mut(&addr.sheet_id) {
3666 index.remove_vertex(GridAddr::new(key.1, key.2), v);
3667 }
3668 self.store.mark_deleted(v, true);
3669 if was_formula {
3670 self.retired_ids.insert(key, v);
3671 self.retired_id_set.insert(v);
3672 }
3673 Some((v, was_formula))
3674 }
3675
3676 pub(crate) fn add_empty_vertex(
3680 &mut self,
3681 sheet: SheetId,
3682 row: u32,
3683 col: u32,
3684 ) -> Result<VertexId, ExcelError> {
3685 let budgets = self.self_admission_budgets();
3686 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3687 let mut usage = self.preview_value_mutation(sheet, row + 1, col + 1)?;
3688 if self
3689 .cell_vertex(&CellRef::new(sheet, Coord::new(row, col, true, true)))
3690 .is_none()
3691 {
3692 usage.final_vertices = usage.final_vertices.saturating_add(1);
3693 usage.added_vertices = 1;
3694 }
3695 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
3696 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3697 }
3698 let addr = CellRef::new(sheet, Coord::new(row, col, true, true));
3699 let mut created = Vec::new();
3700 let id = self.get_or_create_vertex(&addr, &mut created);
3701 self.materialize_vertex(id);
3702 let _ = self.mark_dirty(id);
3703 Ok(id)
3704 }
3705
3706 pub(crate) fn retire_cell_for_replay(&mut self, addr: CellRef) {
3710 self.vacate_cell(&addr);
3713 self.forget_extent_cells(
3715 addr.sheet_id,
3716 (addr.coord.row(), addr.coord.row()),
3717 (addr.coord.col(), addr.coord.col()),
3718 );
3719 let _ = self.mark_dirty_cells(&[(addr.sheet_id, addr.coord.row(), addr.coord.col())]);
3720 }
3721
3722 pub(crate) fn note_extent_cell(&mut self, sheet: SheetId, row0: u32, col0: u32) {
3725 self.extent_record.note(sheet, row0, col0);
3726 }
3727
3728 pub(crate) fn forget_extent_cells(
3732 &mut self,
3733 sheet: SheetId,
3734 rows: (u32, u32),
3735 cols: (u32, u32),
3736 ) -> Vec<(u32, u32, u32)> {
3737 self.extent_record.forget_rect(sheet, rows, cols)
3738 }
3739
3740 pub(crate) fn had_legacy_cell_vertex(&self, cell: &CellRef) -> bool {
3743 self.extent_record
3744 .contains(cell.sheet_id, cell.coord.row(), cell.coord.col())
3745 }
3746
3747 pub(crate) fn extent_record_columns(&self, sheet: SheetId) -> Vec<u32> {
3749 self.extent_record.columns(sheet)
3750 }
3751
3752 #[cfg(test)]
3754 pub(crate) fn extent_record_runs(&self) -> usize {
3755 self.extent_record.run_count()
3756 }
3757
3758 pub(crate) fn revive_retired_id(&mut self, addr: &CellRef) -> Option<VertexId> {
3761 if self.retired_ids.is_empty() {
3762 return None;
3763 }
3764 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3765 let id = *self.retired_ids.get(&key)?;
3766 let coord = GridAddr::new(key.1, key.2);
3767 if let Some(x) = self.cell_vertex(addr)
3770 && !self.store.is_deleted(x)
3771 && self.store.grid_addr(x) == Some(coord)
3772 {
3773 return None;
3774 }
3775 self.retired_ids.remove(&key);
3776 self.revive_vertex(id, addr.sheet_id, coord).then_some(id)
3777 }
3778
3779 pub(crate) fn retired_id_count(&self) -> usize {
3781 self.retired_ids.len()
3782 }
3783
3784 pub(crate) fn shift_retired_ids(
3790 &mut self,
3791 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3792 journal: bool,
3793 ) {
3794 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3795 let dropped = self.extent_record.shift(op);
3796 if journal && matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. }) {
3797 self.extent_dropped.push(dropped);
3798 }
3799 self.shift_retired_id_table(op, journal);
3800 }
3801
3802 fn shift_retired_id_table(
3804 &mut self,
3805 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3806 journal: bool,
3807 ) {
3808 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3809 let deleting = matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. });
3810 if self.retired_ids.is_empty() {
3811 if deleting && journal {
3812 self.retired_dropped.push(Vec::new());
3813 }
3814 return;
3815 }
3816 let (sheet, rows, start, count, insert) = match *op {
3817 Op::InsertRows {
3818 sheet_id,
3819 before,
3820 count,
3821 } => (sheet_id, true, before, count, true),
3822 Op::DeleteRows {
3823 sheet_id,
3824 start,
3825 count,
3826 } => (sheet_id, true, start, count, false),
3827 Op::InsertColumns {
3828 sheet_id,
3829 before,
3830 count,
3831 } => (sheet_id, false, before, count, true),
3832 Op::DeleteColumns {
3833 sheet_id,
3834 start,
3835 count,
3836 } => (sheet_id, false, start, count, false),
3837 };
3838 let entries: Vec<((SheetId, u32, u32), VertexId)> = self
3839 .retired_ids
3840 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
3841 .map(|(k, v)| (*k, *v))
3842 .collect();
3843 for (key, _) in &entries {
3844 self.retired_ids.remove(key);
3845 }
3846 let mut dropped = Vec::new();
3847 for ((s, r, c), id) in entries {
3848 let pos = if rows { r } else { c };
3849 let moved = if pos < start {
3850 Some(pos)
3851 } else if insert {
3852 pos.checked_add(count)
3853 } else if pos < start.saturating_add(count) {
3854 None
3855 } else {
3856 Some(pos - count)
3857 };
3858 match moved {
3859 Some(p) => {
3860 let key = if rows { (s, p, c) } else { (s, r, p) };
3861 self.retired_ids.insert(key, id);
3862 }
3863 None => dropped.push(((s, r, c), id)),
3864 }
3865 }
3866 if !insert && journal {
3867 self.retired_dropped.push(dropped);
3869 }
3870 }
3871
3872 fn restore_retired_batch(&mut self, batch: RetiredBatch) {
3874 for (key, id) in batch {
3875 self.retired_ids.insert(key, id);
3876 }
3877 }
3878
3879 pub(crate) fn replay_structural_marker(&mut self, description: &str, forward: bool) {
3886 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3887 let Some(op) = parse_structural_description(description) else {
3888 return;
3889 };
3890 if forward {
3891 self.shift_retired_ids(&op, true);
3892 if matches!(op, Op::InsertRows { .. } | Op::InsertColumns { .. })
3893 && let Some(batch) = self.retired_dropped_by_undo.pop()
3894 {
3895 self.restore_retired_batch(batch);
3897 }
3898 return;
3899 }
3900 let (inverse, band) = match op {
3901 Op::InsertRows {
3902 sheet_id,
3903 before,
3904 count,
3905 } => (
3906 Op::DeleteRows {
3907 sheet_id,
3908 start: before,
3909 count,
3910 },
3911 None,
3912 ),
3913 Op::InsertColumns {
3914 sheet_id,
3915 before,
3916 count,
3917 } => (
3918 Op::DeleteColumns {
3919 sheet_id,
3920 start: before,
3921 count,
3922 },
3923 None,
3924 ),
3925 Op::DeleteRows {
3926 sheet_id,
3927 start,
3928 count,
3929 } => (
3930 Op::InsertRows {
3931 sheet_id,
3932 before: start,
3933 count,
3934 },
3935 Some((sheet_id, true, start, count)),
3936 ),
3937 Op::DeleteColumns {
3938 sheet_id,
3939 start,
3940 count,
3941 } => (
3942 Op::InsertColumns {
3943 sheet_id,
3944 before: start,
3945 count,
3946 },
3947 Some((sheet_id, false, start, count)),
3948 ),
3949 };
3950 if self.extent_undone.last() == Some(&shift_key(&op)) {
3953 self.extent_undone.pop();
3954 } else {
3955 self.shift_extent_back(&op, &inverse);
3956 }
3957 self.shift_retired_id_table(&inverse, true);
3960 if band.is_none() {
3961 if let Some(batch) = self.retired_dropped.pop() {
3962 self.retired_dropped_by_undo.push(batch);
3963 }
3964 return;
3965 }
3966 if let Some((sheet, rows, start, count)) = band {
3968 if count == 0 {
3969 return;
3970 }
3971 if let Some(batch) = self.retired_dropped.pop() {
3973 self.restore_retired_batch(batch);
3974 }
3975 let end = start.saturating_add(count - 1);
3976 let rect = if rows {
3978 (sheet, start, end, 0, 16_383)
3979 } else {
3980 (sheet, 0, 1_048_575, start, end)
3981 };
3982 let _ = self.mark_dirty_rects(&[rect]);
3983 }
3984 }
3985
3986 pub(crate) fn undo_structural_extent(&mut self, description: &str) {
3993 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3994 let Some(op) = parse_structural_description(description) else {
3995 return;
3996 };
3997 let inverse = match op {
3998 Op::InsertRows {
3999 sheet_id,
4000 before,
4001 count,
4002 } => Op::DeleteRows {
4003 sheet_id,
4004 start: before,
4005 count,
4006 },
4007 Op::InsertColumns {
4008 sheet_id,
4009 before,
4010 count,
4011 } => Op::DeleteColumns {
4012 sheet_id,
4013 start: before,
4014 count,
4015 },
4016 Op::DeleteRows {
4017 sheet_id,
4018 start,
4019 count,
4020 } => Op::InsertRows {
4021 sheet_id,
4022 before: start,
4023 count,
4024 },
4025 Op::DeleteColumns {
4026 sheet_id,
4027 start,
4028 count,
4029 } => Op::InsertColumns {
4030 sheet_id,
4031 before: start,
4032 count,
4033 },
4034 };
4035 self.shift_extent_back(&op, &inverse);
4036 self.extent_undone.push(shift_key(&op));
4037 }
4038
4039 pub(crate) fn clear_extent_undone(&mut self) {
4041 self.extent_undone.clear();
4042 }
4043
4044 fn shift_extent_back(
4047 &mut self,
4048 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
4049 inverse: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
4050 ) {
4051 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
4052 let _ = self.extent_record.shift(inverse);
4053 if matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. })
4054 && let Some(dropped) = self.extent_dropped.pop()
4055 {
4056 self.extent_record.restore(dropped);
4057 }
4058 }
4059
4060 #[cfg(test)]
4062 pub(crate) fn extent_record_pending(&self) -> usize {
4063 self.extent_record.pending_len()
4064 }
4065
4066 #[cfg(test)]
4069 pub(crate) fn extent_history_counts(&self) -> (usize, usize) {
4070 (
4071 self.extent_dropped.len(),
4072 self.extent_dropped.iter().map(Vec::len).sum(),
4073 )
4074 }
4075
4076 pub(crate) fn drop_retired_ids_of_sheet(&mut self, sheet: SheetId) {
4078 self.extent_record.drop_sheet(sheet);
4079 let keys: Vec<(SheetId, u32, u32)> = self
4080 .retired_ids
4081 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
4082 .map(|(k, _)| *k)
4083 .collect();
4084 for key in keys {
4085 if let Some(id) = self.retired_ids.remove(&key) {
4086 self.vertex_journal.retired(key, id.0);
4087 }
4088 }
4089 }
4090
4091 pub(crate) fn resolve_direct_deps(
4094 &mut self,
4095 cells: &[CellRef],
4096 ) -> (Vec<VertexId>, Vec<CellRef>) {
4097 let mut vertices: Vec<VertexId> = Vec::with_capacity(cells.len());
4098 let mut vertexless: Vec<CellRef> = Vec::new();
4099 for cell in cells {
4100 match self.dep_vertex(cell) {
4101 Some(v) => {
4102 if !vertices.contains(&v) {
4103 vertices.push(v);
4104 }
4105 }
4106 None => {
4107 if !vertexless.iter().any(|c| same_cell(c, cell)) {
4108 vertexless.push(*cell);
4109 }
4110 }
4111 }
4112 }
4113 (vertices, vertexless)
4114 }
4115
4116 fn get_or_create_vertex(
4117 &mut self,
4118 addr: &CellRef,
4119 created_placeholders: &mut Vec<CellRef>,
4120 ) -> VertexId {
4121 if let Some(vertex_id) = self.cell_vertex(addr) {
4122 return vertex_id;
4123 }
4124 if let Some(vertex_id) = self.revive_retired_id(addr) {
4126 return vertex_id;
4127 }
4128
4129 if self.first_load_assume_new {
4134 let packed = Self::packed_cell_key(
4135 addr.sheet_id,
4136 AbsCoord::new(addr.coord.row(), addr.coord.col()),
4137 );
4138 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
4139 self.cell_to_vertex.insert(*addr, existing);
4140 return existing;
4141 }
4142 }
4143
4144 created_placeholders.push(*addr);
4145 let position = GridAddr::new(addr.coord.row(), addr.coord.col());
4146 let vertex_id = self
4147 .store
4148 .allocate(VertexAddr::grid(position), addr.sheet_id, 0x00);
4149
4150 #[cfg(any(test, feature = "legacy_oracle"))]
4151 {
4152 self.edges
4153 .add_vertex(VertexAddr::grid(position), vertex_id.0);
4154 self.oracle_cell_vertex_created(
4155 (addr.sheet_id, position.row(), position.col()),
4156 vertex_id,
4157 );
4158 }
4159
4160 self.sheet_index_mut(addr.sheet_id)
4162 .add_vertex(position, vertex_id);
4163
4164 self.store.set_kind(vertex_id, VertexKind::Empty);
4165 self.cell_to_vertex.insert(*addr, vertex_id);
4166 vertex_id
4167 }
4168
4169 pub(crate) fn note_vertexless_deps(
4174 &mut self,
4175 dependent: VertexId,
4176 cells: impl IntoIterator<Item = (SheetId, u32, u32)>,
4177 ) {
4178 let mut n = 0usize;
4179 #[cfg(any(test, feature = "legacy_oracle"))]
4180 let mut keys = Vec::new();
4181 for cell in cells {
4182 n += 1;
4183 self.extent_record.note(cell.0, cell.1, cell.2);
4185 #[cfg(any(test, feature = "legacy_oracle"))]
4186 {
4187 self.oracle_vertexless_readers
4188 .entry(cell)
4189 .or_default()
4190 .push(dependent);
4191 keys.push(cell);
4192 }
4193 #[cfg(not(any(test, feature = "legacy_oracle")))]
4194 let _ = cell;
4195 }
4196 #[cfg(any(test, feature = "legacy_oracle"))]
4197 if !keys.is_empty() {
4198 self.oracle_vertexless_of
4199 .entry(dependent)
4200 .or_default()
4201 .extend(keys);
4202 }
4203 self.note_dep_edges(dependent, n);
4204 }
4205
4206 #[cfg(any(test, feature = "legacy_oracle"))]
4209 pub(crate) fn oracle_cell_vertex_created(&mut self, cell: (SheetId, u32, u32), v: VertexId) {
4210 if self.oracle_vertexless_readers.is_empty() {
4211 return;
4212 }
4213 let Some(readers) = self.oracle_vertexless_readers.remove(&cell) else {
4214 return;
4215 };
4216 for reader in readers {
4217 if let Some(cells) = self.oracle_vertexless_of.get_mut(&reader) {
4218 cells.retain(|c| *c != cell);
4219 if cells.is_empty() {
4220 self.oracle_vertexless_of.remove(&reader);
4221 }
4222 }
4223 self.oracle_add_dependent_edges(reader, &[v]);
4224 }
4225 }
4226
4227 #[cfg(any(test, feature = "legacy_oracle"))]
4230 pub(crate) fn oracle_vertexless_readers_of(
4231 &self,
4232 cell: crate::engine::authority::geom::Cell,
4233 ) -> Vec<VertexId> {
4234 self.oracle_vertexless_readers
4235 .get(&(cell.0 as SheetId, cell.1, cell.2))
4236 .cloned()
4237 .unwrap_or_default()
4238 }
4239
4240 #[cfg(any(test, feature = "legacy_oracle"))]
4243 pub(crate) fn oracle_vertexless_cells(&self, dependent: VertexId) -> Vec<CellRef> {
4244 self.oracle_vertexless_of
4245 .get(&dependent)
4246 .map(|cells| {
4247 cells
4248 .iter()
4249 .map(|&(s, r, c)| CellRef::new(s, Coord::new(r, c, true, true)))
4250 .collect()
4251 })
4252 .unwrap_or_default()
4253 }
4254
4255 #[cfg(any(test, feature = "legacy_oracle"))]
4256 fn oracle_forget_vertexless(&mut self, dependent: VertexId) {
4257 if let Some(cells) = self.oracle_vertexless_of.remove(&dependent) {
4258 for cell in cells {
4259 if let Some(readers) = self.oracle_vertexless_readers.get_mut(&cell) {
4260 readers.retain(|r| *r != dependent);
4261 if readers.is_empty() {
4262 self.oracle_vertexless_readers.remove(&cell);
4263 }
4264 }
4265 }
4266 }
4267 }
4268
4269 pub(crate) fn note_dep_edges(&mut self, dependent: VertexId, n: usize) {
4271 let now = self.store.edge_offset(dependent) as usize + n;
4272 self.store
4273 .set_edge_offset(dependent, u32::try_from(now).unwrap_or(u32::MAX));
4274 self.dep_edge_total += n;
4275 }
4276
4277 pub(crate) fn renamed_sheet_alias(&self, name: &str) -> Option<SheetId> {
4280 self.renamed_sheet_aliases
4281 .get(&name.to_ascii_lowercase())
4282 .copied()
4283 .filter(|&id| self.sheet_reg.name(id) != name)
4284 }
4285
4286 pub(crate) fn reads_compressed_range(&self, vertex: VertexId) -> bool {
4288 self.store.reads_range(vertex)
4289 }
4290
4291 pub(crate) fn note_reads_range(&mut self, vertex: VertexId) {
4293 if !self.store.reads_range(vertex) {
4294 self.store.set_reads_range(vertex, true);
4295 self.range_reader_count += 1;
4296 }
4297 }
4298
4299 pub(crate) fn has_compressed_range_readers(&self) -> bool {
4301 self.range_reader_count > 0
4302 }
4303
4304 pub(crate) fn forget_dep_edges(&mut self, vertex: VertexId) {
4306 let n = self.store.edge_offset(vertex) as usize;
4307 if n > 0 {
4308 self.store.set_edge_offset(vertex, 0);
4309 self.dep_edge_total = self.dep_edge_total.saturating_sub(n);
4310 }
4311 }
4312
4313 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4314 self.note_dep_edges(dependent, dependencies.len());
4315 #[cfg(any(test, feature = "legacy_oracle"))]
4316 self.oracle_add_dependent_edges(dependent, dependencies);
4317 }
4318
4319 #[cfg(any(test, feature = "legacy_oracle"))]
4321 fn oracle_add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4322 #[cfg(any(test, feature = "legacy_oracle"))]
4324 self.edges.begin_batch();
4325
4326 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4329 if self.pk_order.is_some()
4330 && let Some(mut pk) = self.pk_order.take()
4331 {
4332 pk.ensure_nodes(std::iter::once(dependent));
4333 pk.ensure_nodes(dependencies.iter().copied());
4334 {
4335 let adapter = GraphAdapter { g: self };
4336 for &dep_id in dependencies {
4337 match pk.try_add_edge(&adapter, dep_id, dependent) {
4338 Ok(_) => {}
4339 Err(_cycle) => {
4340 if self.config.pk_reject_cycle_edges {
4341 skip_deps.insert(dep_id);
4342 } else {
4343 pk.rebuild_full(&adapter);
4344 }
4345 }
4346 }
4347 }
4348 } self.pk_order = Some(pk);
4350 }
4351
4352 for &dep_id in dependencies {
4354 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4355 continue;
4356 }
4357 self.edges.add_edge(dependent, dep_id);
4358 #[cfg(test)]
4359 {
4360 if let Ok(mut g) = self.instr.lock() {
4361 g.edges_added += 1;
4362 }
4363 }
4364 }
4365
4366 #[cfg(any(test, feature = "legacy_oracle"))]
4367 self.edges.end_batch();
4368 }
4369
4370 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4372 self.note_dep_edges(dependent, dependencies.len());
4373 #[cfg(any(test, feature = "legacy_oracle"))]
4374 {
4375 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4377 if self.pk_order.is_some()
4378 && let Some(mut pk) = self.pk_order.take()
4379 {
4380 pk.ensure_nodes(std::iter::once(dependent));
4381 pk.ensure_nodes(dependencies.iter().copied());
4382 {
4383 let adapter = GraphAdapter { g: self };
4384 for &dep_id in dependencies {
4385 match pk.try_add_edge(&adapter, dep_id, dependent) {
4386 Ok(_) => {}
4387 Err(_cycle) => {
4388 if self.config.pk_reject_cycle_edges {
4389 skip_deps.insert(dep_id);
4390 } else {
4391 pk.rebuild_full(&adapter);
4392 }
4393 }
4394 }
4395 }
4396 }
4397 self.pk_order = Some(pk);
4398 }
4399
4400 for &dep_id in dependencies {
4401 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4402 continue;
4403 }
4404 self.edges.add_edge(dependent, dep_id);
4405 #[cfg(test)]
4406 {
4407 if let Ok(mut g) = self.instr.lock() {
4408 g.edges_added += 1;
4409 }
4410 }
4411 }
4412 }
4413 }
4414
4415 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
4417 where
4418 I: IntoIterator<Item = (u32, u32, ASTNode)>,
4419 {
4420 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
4421 if collected.is_empty() {
4422 return Ok(0);
4423 }
4424 let vol_flags: Vec<bool> = collected
4425 .iter()
4426 .map(|(_, _, ast)| self.is_ast_volatile(ast))
4427 .collect();
4428 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
4429 }
4430
4431 pub fn bulk_set_formulas_with_volatility(
4432 &mut self,
4433 sheet: &str,
4434 collected: Vec<(u32, u32, ASTNode)>,
4435 _vol_flags: Vec<bool>,
4436 ) -> Result<usize, ExcelError> {
4437 let sheet_id = self.sheet_id_mut(sheet);
4438 if collected.is_empty() {
4439 return Ok(0);
4440 }
4441 let provider = RegistryFunctionProvider;
4442 let ingested = {
4443 let mut pipeline = self.ingest_pipeline(&provider);
4444 let inputs = collected.into_iter().map(|(row, col, ast)| {
4445 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
4446 (FormulaAstInput::Tree(ast), placement, None)
4447 });
4448 pipeline.ingest_batch(inputs)?
4449 };
4450 let planned = ingested
4451 .into_iter()
4452 .map(|formula| {
4453 (
4454 formula.placement.coord.row() + 1,
4455 formula.placement.coord.col() + 1,
4456 formula.ast_id,
4457 formula.dep_plan,
4458 )
4459 })
4460 .collect();
4461 self.bulk_set_formulas_with_plans(sheet, planned)
4462 }
4463
4464 pub(crate) fn bulk_set_formulas_with_plans(
4465 &mut self,
4466 sheet: &str,
4467 planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
4468 ) -> Result<usize, ExcelError> {
4469 let sheet_id = self.sheet_id_mut(sheet);
4470 if planned.is_empty() {
4471 return Ok(0);
4472 }
4473 let budgets = self.self_admission_budgets();
4474 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4475 let admission_plans = planned
4476 .iter()
4477 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone()))
4478 .collect::<Vec<_>>();
4479 let usage = self.preview_formula_mutations(&admission_plans)?;
4480 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
4481 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4482 }
4483 let mut created_placeholders: Vec<CellRef> = Vec::new();
4484 let mut target_vids: Vec<VertexId> = Vec::with_capacity(planned.len());
4485 for (row, col, _, _) in &planned {
4486 let addr = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
4487 let target = self.get_or_create_vertex(&addr, &mut created_placeholders);
4488 self.materialize_vertex(target);
4489 target_vids.push(target);
4490 }
4491
4492 for (i, &tvid) in target_vids.iter().enumerate() {
4493 self.forget_declared_dynamic_anchor(tvid);
4494 if self.vertex_formulas.contains_key(&tvid) {
4495 self.remove_dependent_edges(tvid);
4496 }
4497 self.detach_vertex_from_names(tvid);
4498 self.clear_pending_name_references(tvid);
4499 self.store.set_kind(tvid, VertexKind::FormulaScalar);
4500 self.store.set_dirty(tvid, true);
4501 self.vertex_values.remove(&tvid);
4502 self.vertex_formulas.insert(tvid, planned[i].2);
4503 self.mark_volatile(tvid, planned[i].3.volatile);
4504 self.store.set_dynamic(tvid, planned[i].3.dynamic);
4505 }
4506 self.formula_dirty
4507 .legacy_extend(target_vids.iter().copied());
4508
4509 #[cfg(any(test, feature = "legacy_oracle"))]
4510 self.edges.begin_batch();
4511 for (i, tvid) in target_vids.iter().copied().enumerate() {
4512 let plan = &planned[i].3;
4513 let (mut deps, vertexless) = self.resolve_direct_deps(&plan.direct_cell_deps);
4514 self.note_vertexless_deps(
4515 tvid,
4516 vertexless
4517 .iter()
4518 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
4519 );
4520
4521 let mut name_vertices = Vec::new();
4522 for name in plan
4523 .resolved_named_refs
4524 .iter()
4525 .chain(plan.named_refs.iter())
4526 {
4527 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
4528 if !deps.contains(&named.vertex) {
4529 deps.push(named.vertex);
4530 }
4531 if !name_vertices.contains(&named.vertex) {
4532 name_vertices.push(named.vertex);
4533 }
4534 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
4535 if !deps.contains(&source.vertex) {
4536 deps.push(source.vertex);
4537 }
4538 } else {
4539 self.record_pending_name_reference(sheet_id, name, tvid);
4540 }
4541 }
4542 for source_name in &plan.source_refs {
4543 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
4544 if !deps.contains(&source.vertex) {
4545 deps.push(source.vertex);
4546 }
4547 } else if let Some(source) = self.resolve_source_table_entry(source_name)
4548 && !deps.contains(&source.vertex)
4549 {
4550 deps.push(source.vertex);
4551 }
4552 }
4553 for table_name in &plan.table_refs {
4554 if let Some(table) = self.resolve_table_entry(table_name) {
4555 if !deps.contains(&table.vertex) {
4556 deps.push(table.vertex);
4557 }
4558 } else if let Some(source) = self.resolve_source_table_entry(table_name)
4559 && !deps.contains(&source.vertex)
4560 {
4561 deps.push(source.vertex);
4562 }
4563 }
4564 if !name_vertices.is_empty() {
4565 self.attach_vertex_to_names(tvid, &name_vertices);
4566 }
4567 if !deps.is_empty() {
4568 self.add_dependent_edges_nobatch(tvid, &deps);
4569 }
4570 self.add_range_dependent_edges(tvid, &plan.range_deps, sheet_id);
4571 }
4572 #[cfg(any(test, feature = "legacy_oracle"))]
4573 self.edges.end_batch();
4574
4575 let written = planned.len();
4584 let mut cells: Vec<(u32, u32)> = planned
4585 .iter()
4586 .map(|(row, col, _, _)| (col.saturating_sub(1), row.saturating_sub(1)))
4587 .collect();
4588 drop(planned);
4589 cells.sort_unstable();
4590 cells.dedup();
4591 let mut runs: Vec<(SheetId, u32, u32, u32, u32)> = Vec::new();
4592 for (col, row) in cells {
4593 match runs.last_mut() {
4594 Some((_, _, r1, c0, _)) if *c0 == col && *r1 + 1 == row => *r1 = row,
4595 _ => runs.push((sheet_id, row, row, col, col)),
4596 }
4597 }
4598 self.mark_dirty_rects(&runs);
4599
4600 Ok(written)
4601 }
4602
4603 #[cfg(any(test, feature = "legacy_oracle"))]
4604 pub fn add_dependency_edge(
4606 &mut self,
4607 dependent: VertexId,
4608 dependency: VertexId,
4609 ) -> Result<(), ExcelError> {
4610 if dependent == dependency {
4611 return Ok(());
4612 }
4613 let budgets = self.self_admission_budgets();
4614 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4615 let stats = self.baseline_stats();
4616 let added = usize::from(!self.get_dependencies(dependent).contains(&dependency));
4617 crate::engine::resource_ledger::preflight_graph_admission(
4618 &budgets,
4619 crate::engine::resource_ledger::GraphAdmission {
4620 final_vertices: stats.graph_vertex_count,
4621 final_edges: stats.graph_edge_count.checked_add(added).ok_or_else(|| {
4622 ExcelError::new(ExcelErrorKind::NImpl)
4623 .with_message("graph edge count overflow")
4624 })?,
4625 materialization_cells: 0,
4626 added_vertices: 0,
4627 added_edges: added,
4628 },
4629 None,
4630 )
4631 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4632 }
4633 if self.pk_order.is_some()
4635 && let Some(mut pk) = self.pk_order.take()
4636 {
4637 pk.ensure_nodes(std::iter::once(dependent));
4638 pk.ensure_nodes(std::iter::once(dependency));
4639 let adapter = GraphAdapter { g: self };
4640 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
4641 pk.rebuild_full(&adapter);
4643 }
4644 self.pk_order = Some(pk);
4645 }
4646 self.edges.add_edge(dependent, dependency);
4647 self.store.set_dirty(dependent, true);
4648 self.formula_dirty.legacy_insert(dependent);
4649 Ok(())
4650 }
4651
4652 fn remove_dependent_edges(&mut self, vertex: VertexId) {
4653 self.forget_dep_edges(vertex);
4654 if self.store.reads_range(vertex) {
4655 self.store.set_reads_range(vertex, false);
4656 self.range_reader_count = self.range_reader_count.saturating_sub(1);
4657 }
4658 #[cfg(any(test, feature = "legacy_oracle"))]
4659 self.oracle_remove_dependent_edges(vertex);
4660 }
4661
4662 #[cfg(any(test, feature = "legacy_oracle"))]
4664 fn oracle_remove_dependent_edges(&mut self, vertex: VertexId) {
4665 self.oracle_forget_vertexless(vertex);
4666 let dependencies = self.edges.out_edges(vertex);
4668
4669 #[cfg(any(test, feature = "legacy_oracle"))]
4670 self.edges.begin_batch();
4671 if self.pk_order.is_some()
4672 && let Some(mut pk) = self.pk_order.take()
4673 {
4674 for dep in &dependencies {
4675 pk.remove_edge(*dep, vertex);
4676 }
4677 self.pk_order = Some(pk);
4678 }
4679 for dep in dependencies {
4680 self.edges.remove_edge(vertex, dep);
4681 }
4682 #[cfg(any(test, feature = "legacy_oracle"))]
4683 self.edges.end_batch();
4684
4685 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
4687 let old_sheet_id = self.store.sheet_id(vertex);
4688
4689 for range in &old_ranges {
4690 let sheet_id = self
4692 .sheet_reg
4693 .resolve_locator(&range.sheet, old_sheet_id)
4694 .unwrap_or(old_sheet_id);
4695 let s_row = range.start_row.map(|b| b.index);
4696 let e_row = range.end_row.map(|b| b.index);
4697 let s_col = range.start_col.map(|b| b.index);
4698 let e_col = range.end_col.map(|b| b.index);
4699
4700 let mut keys_to_clean = FxHashSet::default();
4701
4702 let col_stripes = (s_row.is_none() && e_row.is_none())
4703 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
4704 let row_stripes = (s_col.is_none() && e_col.is_none())
4705 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
4706
4707 if col_stripes && !row_stripes {
4708 let sc = s_col.unwrap_or(0);
4709 let ec = e_col.unwrap_or(sc);
4710 for col in sc..=ec {
4711 keys_to_clean.insert(StripeKey {
4712 sheet_id,
4713 stripe_type: StripeType::Column,
4714 index: col,
4715 });
4716 }
4717 } else if row_stripes && !col_stripes {
4718 let sr = s_row.unwrap_or(0);
4719 let er = e_row.unwrap_or(sr);
4720 for row in sr..=er {
4721 keys_to_clean.insert(StripeKey {
4722 sheet_id,
4723 stripe_type: StripeType::Row,
4724 index: row,
4725 });
4726 }
4727 } else {
4728 let start_row = s_row.unwrap_or(0);
4729 let start_col = s_col.unwrap_or(0);
4730 let end_row = e_row.unwrap_or(start_row);
4731 let end_col = e_col.unwrap_or(start_col);
4732
4733 let height = end_row.saturating_sub(start_row) + 1;
4734 let width = end_col.saturating_sub(start_col) + 1;
4735
4736 if self.config.enable_block_stripes && height > 1 && width > 1 {
4737 let start_block_row = start_row / BLOCK_H;
4738 let end_block_row = end_row / BLOCK_H;
4739 let start_block_col = start_col / BLOCK_W;
4740 let end_block_col = end_col / BLOCK_W;
4741
4742 for block_row in start_block_row..=end_block_row {
4743 for block_col in start_block_col..=end_block_col {
4744 keys_to_clean.insert(StripeKey {
4745 sheet_id,
4746 stripe_type: StripeType::Block,
4747 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
4748 });
4749 }
4750 }
4751 } else if height > width {
4752 for col in start_col..=end_col {
4753 keys_to_clean.insert(StripeKey {
4754 sheet_id,
4755 stripe_type: StripeType::Column,
4756 index: col,
4757 });
4758 }
4759 } else {
4760 for row in start_row..=end_row {
4761 keys_to_clean.insert(StripeKey {
4762 sheet_id,
4763 stripe_type: StripeType::Row,
4764 index: row,
4765 });
4766 }
4767 }
4768 }
4769
4770 for key in keys_to_clean {
4771 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
4772 dependents.remove(&vertex);
4773 if dependents.is_empty() {
4774 self.stripe_to_dependents.remove(&key);
4775 #[cfg(test)]
4776 {
4777 if let Ok(mut g) = self.instr.lock() {
4778 g.stripe_removes += 1;
4779 }
4780 }
4781 }
4782 }
4783 }
4784 }
4785 }
4786 }
4787
4788 pub(crate) fn has_spill_anchors(&self) -> bool {
4795 !self.spill_anchor_to_cells.is_empty()
4796 }
4797
4798 pub(crate) fn is_spill_anchor(&self, vertex: VertexId) -> bool {
4800 self.spill_anchor_to_cells.contains_key(&vertex)
4801 }
4802
4803 pub(crate) fn update_vertex_value_ref(&mut self, vertex_id: VertexId, value: &LiteralValue) {
4806 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4807 if !self.vertex_values.is_empty() {
4808 self.vertex_values.remove(&vertex_id);
4809 }
4810 return;
4811 }
4812 self.update_vertex_value(vertex_id, value.clone());
4813 }
4814
4815 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
4816 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4817 self.vertex_values.remove(&vertex_id);
4820 return;
4821 }
4822 self.materialize_vertex(vertex_id);
4824 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
4825 self.vertex_values.insert(vertex_id, value_ref);
4826 }
4827
4828 pub fn plan_spill_region(
4834 &self,
4835 anchor: VertexId,
4836 target_cells: &[CellRef],
4837 ) -> Result<(), ExcelError> {
4838 self.plan_spill_region_yielding(anchor, target_cells, &[])
4839 .map_err(|(error, _)| error)
4840 }
4841
4842 pub(crate) fn plan_spill_region_with_blocker(
4844 &self,
4845 anchor: VertexId,
4846 target_cells: &[CellRef],
4847 ) -> Result<(), (ExcelError, CellRef)> {
4848 self.plan_spill_region_yielding(anchor, target_cells, &[])
4849 }
4850
4851 fn plan_spill_region_yielding(
4854 &self,
4855 anchor: VertexId,
4856 target_cells: &[CellRef],
4857 yielding: &[VertexId],
4858 ) -> Result<(), (ExcelError, CellRef)> {
4859 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
4860 let (expected_rows, expected_cols) = if target_cells.is_empty() {
4862 (0u32, 0u32)
4863 } else {
4864 let mut min_r = u32::MAX;
4865 let mut max_r = 0u32;
4866 let mut min_c = u32::MAX;
4867 let mut max_c = 0u32;
4868 for cell in target_cells {
4869 let r = cell.coord.row();
4870 let c = cell.coord.col();
4871 if r < min_r {
4872 min_r = r;
4873 }
4874 if r > max_r {
4875 max_r = r;
4876 }
4877 if c < min_c {
4878 min_c = c;
4879 }
4880 if c > max_c {
4881 max_c = c;
4882 }
4883 }
4884 (
4885 max_r.saturating_sub(min_r).saturating_add(1),
4886 max_c.saturating_sub(min_c).saturating_add(1),
4887 )
4888 };
4889 let probe_owned = self.spill_may_be_intruded(anchor);
4891 for cell in target_cells {
4893 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
4895 Some(&existing_anchor) if existing_anchor == anchor => true,
4896 Some(other) if yielding.contains(other) => false,
4897 Some(_other) => {
4898 return Err((
4899 ExcelError::new(ExcelErrorKind::Spill)
4900 .with_message("BlockedBySpill")
4901 .with_extra(ExcelErrorExtra::Spill {
4902 expected_rows,
4903 expected_cols,
4904 }),
4905 *cell,
4906 ));
4907 }
4908 None => false,
4909 };
4910
4911 if owned_by_anchor && !(probe_owned && self.is_foreign_formula_cell(cell, anchor)) {
4916 continue;
4917 }
4918
4919 if let Some(vid) = self.cell_vertex(cell)
4921 && vid != anchor
4922 {
4923 match self.store.kind(vid) {
4925 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
4926 return Err((
4927 ExcelError::new(ExcelErrorKind::Spill)
4928 .with_message("BlockedByFormula")
4929 .with_extra(ExcelErrorExtra::Spill {
4930 expected_rows,
4931 expected_cols,
4932 }),
4933 *cell,
4934 ));
4935 }
4936 _ => {
4937 if let Some(vref) = self.vertex_values.get(&vid) {
4939 let v = self.data_store.retrieve_value(*vref);
4940 if !matches!(v, LiteralValue::Empty) {
4941 return Err((
4942 ExcelError::new(ExcelErrorKind::Spill)
4943 .with_message("BlockedByValue")
4944 .with_extra(ExcelErrorExtra::Spill {
4945 expected_rows,
4946 expected_cols,
4947 }),
4948 *cell,
4949 ));
4950 }
4951 }
4952 }
4953 }
4954 }
4955 }
4956 Ok(())
4957 }
4958
4959 pub fn commit_spill_region_atomic_with_fault(
4972 &mut self,
4973 anchor: VertexId,
4974 target_cells: Vec<CellRef>,
4975 values: Vec<Vec<LiteralValue>>,
4976 fault_after_ops: Option<usize>,
4977 ) -> Result<(), ExcelError> {
4978 self.materialize_vertex(anchor);
4979 let budgets = self.self_admission_budgets();
4980 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4981 let admission = self.preview_spill_materialization(&target_cells)?;
4982 crate::engine::resource_ledger::preflight_graph_admission(&budgets, admission, None)
4983 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4984 }
4985
4986 let anchor_cell = self
4990 .get_cell_ref(anchor)
4991 .expect("anchor cell ref for spill commit");
4992 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
4993 let anchor_row = anchor_cell.coord.row();
4994 let anchor_col = anchor_cell.coord.col();
4995
4996 let prev_cells = self
4998 .spill_anchor_to_cells
4999 .get(&anchor)
5000 .cloned()
5001 .unwrap_or_default();
5002 let new_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
5005 target_cells.iter().copied().collect();
5006 let prev_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
5007 prev_cells.iter().copied().collect();
5008
5009 #[derive(Clone)]
5011 struct Op {
5012 sheet: String,
5013 row: u32,
5014 col: u32,
5015 new_value: LiteralValue,
5016 }
5017 let mut ops: Vec<Op> = Vec::new();
5018
5019 let probe_owned = self.spill_may_be_intruded(anchor);
5022 let entered = |cell: &CellRef| probe_owned && self.is_foreign_formula_cell(cell, anchor);
5023 for cell in prev_cells.iter() {
5024 if !new_set.contains(cell) && !entered(cell) {
5025 let sheet = self.sheet_name(cell.sheet_id).to_string();
5026 ops.push(Op {
5027 sheet,
5028 row: cell.coord.row(),
5029 col: cell.coord.col(),
5030 new_value: LiteralValue::Empty,
5031 });
5032 }
5033 }
5034
5035 if !target_cells.is_empty() {
5037 let first = target_cells.first().copied().unwrap();
5038 let row0 = first.coord.row();
5039 let col0 = first.coord.col();
5040 let sheet = self.sheet_name(first.sheet_id).to_string();
5041 for (r_off, row_vals) in values.iter().enumerate() {
5042 for (c_off, v) in row_vals.iter().enumerate() {
5043 ops.push(Op {
5044 sheet: sheet.clone(),
5045 row: row0 + r_off as u32,
5046 col: col0 + c_off as u32,
5047 new_value: v.clone(),
5048 });
5049 }
5050 }
5051 }
5052
5053 #[derive(Clone)]
5055 struct OldVal {
5056 present: bool,
5057 value: LiteralValue,
5058 }
5059 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
5060
5061 for op in &ops {
5063 let old = self
5065 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
5066 .unwrap_or(LiteralValue::Empty);
5067 let present = true; old_values.push((
5069 (op.sheet.clone(), op.row, op.col),
5070 OldVal {
5071 present,
5072 value: old,
5073 },
5074 ));
5075 }
5076
5077 for (applied, op) in ops.iter().enumerate() {
5079 if let Some(n) = fault_after_ops
5080 && applied == n
5081 {
5082 for idx in (0..applied).rev() {
5083 let ((ref sheet, row, col), ref old) = old_values[idx];
5084 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
5085 self.update_vertex_value_ref(anchor, &old.value);
5086 } else {
5087 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
5088 }
5089 }
5090 return Err(ExcelError::new(ExcelErrorKind::Error)
5091 .with_message("Injected persistence fault during spill commit"));
5092 }
5093 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
5094 self.update_vertex_value_ref(anchor, &op.new_value);
5095 } else {
5096 let _ =
5097 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
5098 }
5099 }
5100
5101 for cell in prev_cells.iter() {
5104 if !new_set.contains(cell) {
5105 self.spill_cell_to_anchor.remove(cell);
5106 let remove_sheet = self
5107 .spill_cells_by_sheet
5108 .get_mut(&cell.sheet_id)
5109 .is_some_and(|sheet| {
5110 sheet.remove(&(cell.coord.row(), cell.coord.col()));
5111 sheet.is_empty()
5112 });
5113 if remove_sheet {
5114 self.spill_cells_by_sheet.remove(&cell.sheet_id);
5115 }
5116 }
5117 }
5118 for cell in &target_cells {
5120 self.spill_cell_to_anchor.insert(*cell, anchor);
5121 self.spill_cells_by_sheet
5122 .entry(cell.sheet_id)
5123 .or_default()
5124 .insert((cell.coord.row(), cell.coord.col()), anchor);
5125 }
5126 if !self.spill_intruded_anchors.is_empty()
5130 && self.spill_intruded_anchors.contains(&anchor)
5131 && !target_cells
5132 .iter()
5133 .any(|cell| self.is_foreign_formula_cell(cell, anchor))
5134 {
5135 self.spill_intruded_anchors.remove(&anchor);
5136 }
5137 self.spill_anchor_to_cells.insert(anchor, target_cells);
5138 Ok(())
5139 }
5140
5141 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
5142 self.spill_anchor_to_cells
5143 .get(&anchor)
5144 .map(|v| v.as_slice())
5145 }
5146
5147 pub(crate) fn spill_extent_for_anchor(&self, anchor: VertexId) -> Option<(CellRef, CellRef)> {
5157 let cells = self.spill_anchor_to_cells.get(&anchor)?;
5158 let first = *cells.first()?;
5159 let last = *cells.last()?;
5160 if last.sheet_id != first.sheet_id
5161 || last.coord.row() < first.coord.row()
5162 || last.coord.col() < first.coord.col()
5163 {
5164 return None;
5165 }
5166 let rows = (last.coord.row() - first.coord.row()) as usize + 1;
5167 let cols = (last.coord.col() - first.coord.col()) as usize + 1;
5168 if rows.checked_mul(cols) != Some(cells.len()) {
5169 return None;
5170 }
5171 Some((first, last))
5172 }
5173
5174 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
5175 self.spill_anchor_to_cells.contains_key(&anchor)
5176 }
5177
5178 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
5179 self.spill_cell_to_anchor.get(&cell).copied()
5180 }
5181
5182 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
5183 (
5184 self.spill_anchor_to_cells.len(),
5185 self.spill_cell_to_anchor.len(),
5186 )
5187 }
5188
5189 pub(crate) fn declare_dynamic_anchor(&mut self, anchor: VertexId, cell: CellRef) -> bool {
5192 let cell = CellRef::new(
5193 cell.sheet_id,
5194 Coord::new(cell.coord.row(), cell.coord.col(), true, true),
5195 );
5196 self.declared_dynamic_anchors.insert(anchor, cell) != Some(cell)
5197 }
5198
5199 #[inline]
5202 pub(crate) fn forget_declared_dynamic_anchor(&mut self, vertex: VertexId) {
5203 if !self.fixed_single_arrays.is_empty() {
5204 self.fixed_single_arrays.remove(&vertex);
5205 }
5206 if !self.fixed_array_shapes.is_empty() {
5207 self.fixed_array_shapes.remove(&vertex);
5208 }
5209 if !self.declared_dynamic_anchors.is_empty() {
5210 self.declared_dynamic_anchors.remove(&vertex);
5211 }
5212 }
5213
5214 pub(crate) fn is_current_declared_dynamic_anchor(&self, vertex: VertexId) -> bool {
5219 if self.declared_dynamic_anchors.is_empty() {
5220 return false;
5221 }
5222 self.declared_dynamic_anchors
5223 .get(&vertex)
5224 .is_some_and(|declared| {
5225 self.vertex_has_formula(vertex)
5226 && self.get_cell_ref(vertex).is_some_and(|at| {
5227 at.sheet_id == declared.sheet_id
5228 && at.coord.row() == declared.coord.row()
5229 && at.coord.col() == declared.coord.col()
5230 })
5231 })
5232 }
5233
5234 #[cfg(test)]
5235 pub(crate) fn declared_dynamic_anchor_count(&self) -> usize {
5236 self.declared_dynamic_anchors.len()
5237 }
5238
5239 pub(crate) fn spills_yielding_to(
5253 &self,
5254 anchor: VertexId,
5255 target_cells: &[CellRef],
5256 ) -> Option<Vec<VertexId>> {
5257 let anchor_cell = self.get_cell_ref(anchor)?;
5258 let (first, last) = (*target_cells.first()?, *target_cells.last()?);
5259 let inside = |cell: CellRef, first: CellRef, last: CellRef| {
5260 cell.sheet_id == first.sheet_id
5261 && (first.coord.row()..=last.coord.row()).contains(&cell.coord.row())
5262 && (first.coord.col()..=last.coord.col()).contains(&cell.coord.col())
5263 };
5264 let key = |cell: CellRef| (cell.sheet_id, cell.coord.col(), cell.coord.row());
5265 let mut yielding: Vec<VertexId> = Vec::new();
5266 for cell in target_cells {
5267 let Some(&owner) = self.spill_cell_to_anchor.get(cell) else {
5268 continue;
5269 };
5270 if owner == anchor || yielding.contains(&owner) {
5271 continue;
5272 }
5273 let owner_cell = self.get_cell_ref(owner)?;
5274 let (owner_first, owner_last) = self.spill_extent_for_anchor(owner)?;
5275 if inside(owner_cell, first, last)
5276 || inside(anchor_cell, owner_first, owner_last)
5277 || key(owner_cell) <= key(anchor_cell)
5278 {
5279 return None;
5280 }
5281 yielding.push(owner);
5282 }
5283 if yielding.is_empty() {
5284 return None;
5285 }
5286 self.plan_spill_region_yielding(anchor, target_cells, &yielding)
5287 .ok()
5288 .map(|()| yielding)
5289 }
5290
5291 pub(crate) fn spill_cells_to_clear(&self, anchor: VertexId) -> Vec<CellRef> {
5295 let Some(cells) = self.spill_cells_for_anchor(anchor) else {
5296 return Vec::new();
5297 };
5298 if !self.spill_may_be_intruded(anchor) {
5299 return cells.to_vec();
5300 }
5301 cells
5302 .iter()
5303 .copied()
5304 .filter(|cell| !self.is_foreign_formula_cell(cell, anchor))
5305 .collect()
5306 }
5307
5308 #[inline]
5313 pub(crate) fn spill_may_be_intruded(&self, anchor: VertexId) -> bool {
5314 self.vertex_formulas.has_touched()
5315 || (!self.spill_intruded_anchors.is_empty()
5316 && self.spill_intruded_anchors.contains(&anchor))
5317 }
5318
5319 pub(crate) fn note_spill_intrusions_of_touched(&mut self) -> Vec<VertexId> {
5325 if self.spill_cell_to_anchor.is_empty() {
5326 return Vec::new();
5327 }
5328 let owners: Vec<VertexId> = self
5329 .vertex_formulas
5330 .touched()
5331 .iter()
5332 .filter_map(|&vertex| {
5333 let cell = self.get_cell_ref(vertex)?;
5334 let &owner = self.spill_cell_to_anchor.get(&cell)?;
5335 (owner != vertex && self.is_foreign_formula_cell(&cell, owner)).then_some(owner)
5336 })
5337 .collect();
5338 owners
5339 .into_iter()
5340 .filter(|&owner| self.spill_intruded_anchors.insert(owner))
5341 .collect()
5342 }
5343
5344 pub(crate) fn note_structural_spill_intrusions(&mut self) {
5348 if self.spill_anchor_to_cells.is_empty() {
5349 return;
5350 }
5351 let anchors: Vec<VertexId> = self.spill_anchor_to_cells.keys().copied().collect();
5352 self.spill_intruded_anchors.extend(anchors);
5353 }
5354
5355 #[cfg(test)]
5356 pub(crate) fn spill_intruded_anchor_count(&self) -> usize {
5357 self.spill_intruded_anchors.len()
5358 }
5359
5360 pub(crate) fn is_foreign_formula_cell(&self, cell: &CellRef, anchor: VertexId) -> bool {
5362 self.cell_vertex(cell).is_some_and(|vid| {
5363 vid != anchor
5364 && matches!(
5365 self.store.kind(vid),
5366 VertexKind::FormulaScalar | VertexKind::FormulaArray
5367 )
5368 })
5369 }
5370
5371 pub fn clear_spill_region(&mut self, anchor: VertexId) {
5373 let _ = self.clear_spill_region_bulk(anchor);
5374 }
5375
5376 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
5385 let anchor_cell = self.get_cell_ref(anchor);
5386 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
5387 return Vec::new();
5388 };
5389 let probe_owned = self.spill_may_be_intruded(anchor);
5390 if !self.spill_intruded_anchors.is_empty() {
5391 self.spill_intruded_anchors.remove(&anchor);
5392 }
5393
5394 for cell in cells.iter() {
5396 self.spill_cell_to_anchor.remove(cell);
5397 let remove_sheet = self
5398 .spill_cells_by_sheet
5399 .get_mut(&cell.sheet_id)
5400 .is_some_and(|sheet| {
5401 sheet.remove(&(cell.coord.row(), cell.coord.col()));
5402 sheet.is_empty()
5403 });
5404 if remove_sheet {
5405 self.spill_cells_by_sheet.remove(&cell.sheet_id);
5406 }
5407 }
5408
5409 let mut changed: Vec<crate::engine::authority::geom::Cell> = Vec::new();
5412 for cell in cells.iter().copied() {
5413 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
5414 if is_anchor || (probe_owned && self.is_foreign_formula_cell(&cell, anchor)) {
5417 continue;
5418 }
5419 self.vacate_cell(&cell);
5420 changed.push((cell.sheet_id, cell.coord.row(), cell.coord.col()));
5421 }
5422
5423 if !changed.is_empty() {
5425 let _ = self.mark_dirty_cells(&changed);
5426 }
5427
5428 cells
5429 }
5430
5431 #[cfg(any(test, feature = "legacy_oracle"))]
5432 fn collect_range_dependents_for_vertex(&self, vertex_id: VertexId) -> Vec<VertexId> {
5433 let Some(position) = self.store.grid_addr(vertex_id) else {
5435 return Vec::new();
5436 };
5437 self.collect_range_dependents_for_rect(
5438 self.store.sheet_id(vertex_id),
5439 position.row(),
5440 position.col(),
5441 position.row(),
5442 position.col(),
5443 )
5444 }
5445
5446 #[cfg(any(test, feature = "legacy_oracle"))]
5447 fn collect_range_dependents_for_rect(
5448 &self,
5449 sheet_id: SheetId,
5450 start_row: u32,
5451 start_col: u32,
5452 end_row: u32,
5453 end_col: u32,
5454 ) -> Vec<VertexId> {
5455 if self.stripe_to_dependents.is_empty() {
5456 return Vec::new();
5457 }
5458 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
5459
5460 for col in start_col..=end_col {
5461 let key = StripeKey {
5462 sheet_id,
5463 stripe_type: StripeType::Column,
5464 index: col,
5465 };
5466 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5467 candidates.extend(deps);
5468 }
5469 }
5470 for row in start_row..=end_row {
5471 let key = StripeKey {
5472 sheet_id,
5473 stripe_type: StripeType::Row,
5474 index: row,
5475 };
5476 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5477 candidates.extend(deps);
5478 }
5479 }
5480 if self.config.enable_block_stripes {
5481 let br0 = start_row / BLOCK_H;
5482 let br1 = end_row / BLOCK_H;
5483 let bc0 = start_col / BLOCK_W;
5484 let bc1 = end_col / BLOCK_W;
5485 for br in br0..=br1 {
5486 for bc in bc0..=bc1 {
5487 let key = StripeKey {
5488 sheet_id,
5489 stripe_type: StripeType::Block,
5490 index: block_index(br * BLOCK_H, bc * BLOCK_W),
5491 };
5492 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5493 candidates.extend(deps);
5494 }
5495 }
5496 }
5497 }
5498
5499 let mut out: Vec<VertexId> = Vec::new();
5501 for dep_id in candidates {
5502 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
5503 continue;
5504 };
5505 let mut hit = false;
5506 for range in ranges {
5507 let range_sheet_id = self
5510 .sheet_reg
5511 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dep_id))
5512 .unwrap_or(sheet_id);
5513 if range_sheet_id != sheet_id {
5514 continue;
5515 }
5516 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
5517 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
5518 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
5519 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
5520 let overlap =
5521 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
5522 if overlap {
5523 hit = true;
5524 break;
5525 }
5526 }
5527 if hit {
5528 out.push(dep_id);
5529 }
5530 }
5531 out
5532 }
5533
5534 pub(crate) fn is_live_formula_vertex(&self, vertex_id: VertexId) -> bool {
5538 self.store.vertex_exists_active(vertex_id) && self.has_formula(vertex_id)
5539 }
5540
5541 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
5543 if vertex_id.0 < FIRST_NORMAL_VERTEX {
5544 return false;
5545 }
5546 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
5547 index < self.store.len()
5548 }
5549
5550 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
5552 self.store.kind(vertex_id)
5553 }
5554
5555 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
5557 self.store.sheet_id(vertex_id)
5558 }
5559
5560 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
5564 self.vertex_formulas.get(&vertex_id).and_then(|f| f.own())
5565 }
5566
5567 pub fn formula_view(&self, vertex_id: VertexId) -> Option<FormulaView> {
5569 let f = self.vertex_formulas.get(&vertex_id)?;
5570 Some(match f {
5571 FormulaRef::Own(template) => FormulaView {
5572 template,
5573 row_delta: 0,
5574 col_delta: 0,
5575 },
5576 FormulaRef::Member { template, anchor } => {
5577 let addr = self.store.grid_addr(vertex_id)?;
5578 FormulaView {
5579 template,
5580 row_delta: i64::from(addr.row()) - i64::from(anchor.0),
5581 col_delta: i64::from(addr.col()) - i64::from(anchor.1),
5582 }
5583 }
5584 })
5585 }
5586
5587 pub(crate) fn has_formula(&self, vertex_id: VertexId) -> bool {
5589 self.vertex_formulas.contains_key(&vertex_id)
5590 }
5591
5592 pub(crate) fn own_formula_id(&mut self, vertex_id: VertexId) -> Option<AstNodeId> {
5596 self.materialize_vertex(vertex_id);
5597 match self.vertex_formulas.get(&vertex_id)? {
5598 FormulaRef::Own(id) => Some(id),
5599 FormulaRef::Member { .. } => {
5600 let ast = self.get_formula(vertex_id)?;
5601 let id = self.data_store.store_ast(&ast, &self.sheet_reg);
5602 self.vertex_formulas.decompress(vertex_id, id);
5603 Some(id)
5604 }
5605 }
5606 }
5607
5608 pub(crate) fn materialized_formula_vertices_sorted(&self) -> Vec<VertexId> {
5611 let mut vertices: Vec<VertexId> = self.vertex_formulas.map_iter().map(|(v, _)| v).collect();
5612 vertices.sort_unstable();
5613 vertices
5614 }
5615
5616 pub(crate) fn formula_vertices(&self) -> Vec<VertexId> {
5617 let mut vertices = self.vertex_formulas.keys().collect::<Vec<_>>();
5618 vertices.sort_unstable();
5619 vertices
5620 }
5621
5622 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
5623 let ast_id = self.get_formula_id(vertex_id)?;
5624 Some((ast_id, self.is_volatile(vertex_id)))
5625 }
5626
5627 pub fn get_formula_node(
5628 &self,
5629 vertex_id: VertexId,
5630 ) -> Option<&super::arena::AstNodeData<super::arena::RefId>> {
5631 let ast_id = self.get_formula_id(vertex_id)?;
5632 self.data_store.get_node(ast_id)
5633 }
5634
5635 pub fn get_formula_node_and_volatile(
5636 &self,
5637 vertex_id: VertexId,
5638 ) -> Option<(&super::arena::AstNodeData<super::arena::RefId>, bool)> {
5639 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
5640 let node = self.data_store.get_node(ast_id)?;
5641 Some((node, vol))
5642 }
5643
5644 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
5648 let view = self.formula_view(vertex_id)?;
5649 let ast = self
5650 .data_store
5651 .retrieve_ast(view.template, &self.sheet_reg)?;
5652 if view.row_delta == 0 && view.col_delta == 0 {
5653 return Some(ast);
5654 }
5655 crate::engine::template::relocate::instantiate_member_ast(
5656 &ast,
5657 view.row_delta,
5658 view.col_delta,
5659 )
5660 .ok()
5661 }
5662
5663 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
5665 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
5666 #[cfg(debug_assertions)]
5669 {
5670 self.graph_value_read_attempts
5671 .fetch_add(1, Ordering::Relaxed);
5672 }
5673 return None;
5674 }
5675 self.vertex_values
5676 .get(&vertex_id)
5677 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
5678 }
5679
5680 #[inline]
5687 fn is_grid_backed(&self, vertex_id: VertexId) -> bool {
5688 self.store.grid_addr(vertex_id).is_some()
5689 }
5690
5691 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
5696 let grid = self.store.grid_addr(vertex_id)?;
5697 let sheet_id = self.store.sheet_id(vertex_id);
5698 let coord = Coord::new(grid.row(), grid.col(), true, true);
5699 Some(CellRef::new(sheet_id, coord))
5700 }
5701
5702 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
5704 let coord = Coord::new(row, col, true, true);
5705 CellRef::new(sheet_id, coord)
5706 }
5707
5708 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
5710 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
5711 let coord = Coord::from_excel(row, col, true, true);
5712 CellRef::new(sheet_id, coord)
5713 }
5714
5715 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
5717 self.store.is_dirty(vertex_id)
5718 }
5719
5720 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
5722 self.store.is_volatile(vertex_id)
5723 }
5724
5725 pub(crate) fn is_dynamic(&self, vertex_id: VertexId) -> bool {
5726 self.store.is_dynamic(vertex_id)
5727 }
5728
5729 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<VertexId> {
5735 self.cell_vertex(addr)
5736 }
5737
5738 #[cfg(test)]
5739 pub fn cell_to_vertex(
5740 &self,
5741 ) -> &std::collections::HashMap<CellRef, VertexId, CoordBuildHasher> {
5742 &self.cell_to_vertex
5743 }
5744
5745 #[cfg(any(test, feature = "legacy_oracle"))]
5746 #[inline]
5750 pub(crate) fn dependencies_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5751 self.edges.out_edges_ref(vertex_id)
5752 }
5753
5754 #[cfg(any(test, feature = "legacy_oracle"))]
5755 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
5757 self.edges.out_edges(vertex_id)
5758 }
5759
5760 #[cfg(any(test, feature = "legacy_oracle"))]
5761 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
5763 if let Some(deps) = self.dependencies_slice(vertex_id) {
5764 deps.contains(&vertex_id)
5765 } else {
5766 self.edges.out_edges(vertex_id).contains(&vertex_id)
5767 }
5768 }
5769
5770 #[cfg(any(test, feature = "legacy_oracle"))]
5771 #[inline]
5775 pub(crate) fn dependents_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5776 self.edges.in_edges_ref(vertex_id)
5777 }
5778
5779 #[cfg(any(test, feature = "legacy_oracle"))]
5780 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
5786 self.edges.in_edges_merged(vertex_id)
5787 }
5788
5789 #[cfg(any(test, feature = "legacy_oracle"))]
5790 pub(crate) fn visit_direct_dependents_bounded(
5794 &self,
5795 vertex_id: VertexId,
5796 remaining_work: &mut u64,
5797 visitor: &mut dyn FnMut(VertexId) -> bool,
5798 ) -> bool {
5799 self.edges
5800 .visit_in_edges_bounded(vertex_id, remaining_work, visitor)
5801 }
5802
5803 #[doc(hidden)]
5807 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
5808 let coord = self.store.grid_addr(id).unwrap_or_default();
5809 let sheet_id = self.store.sheet_id(id);
5810 let kind = self.store.kind(id);
5811 let flags = self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG;
5812
5813 let value_ref = self.vertex_values.get(&id).copied();
5815 let formula_ref = self.vertex_formulas.get(&id).map(|f| f.root());
5816
5817 #[cfg(any(test, feature = "legacy_oracle"))]
5819 let out_edges = self.get_dependencies(id);
5820 #[cfg(not(any(test, feature = "legacy_oracle")))]
5821 let out_edges = Vec::new();
5822
5823 crate::engine::VertexSnapshot {
5824 coord,
5825 sheet_id,
5826 kind,
5827 flags,
5828 value_ref,
5829 formula_ref,
5830 out_edges,
5831 }
5832 }
5833
5834 #[doc(hidden)]
5836 pub(crate) fn remove_all_edges(&mut self, id: VertexId) {
5837 self.materialize_vertex(id);
5838 #[cfg(not(any(test, feature = "legacy_oracle")))]
5839 self.remove_dependent_edges(id);
5840 #[cfg(any(test, feature = "legacy_oracle"))]
5841 {
5842 #[cfg(any(test, feature = "legacy_oracle"))]
5844 self.edges.begin_batch();
5845
5846 self.remove_dependent_edges(id);
5848
5849 let dependents = self.get_dependents(id);
5852 if self.pk_order.is_some()
5853 && let Some(mut pk) = self.pk_order.take()
5854 {
5855 for dependent in &dependents {
5856 pk.remove_edge(id, *dependent);
5857 }
5858 self.pk_order = Some(pk);
5859 }
5860 for dependent in dependents {
5861 self.edges.remove_edge(dependent, id);
5862 }
5863
5864 #[cfg(any(test, feature = "legacy_oracle"))]
5866 self.edges.end_batch();
5867 }
5868 }
5869
5870 #[doc(hidden)]
5872 pub fn mark_as_ref_error(&mut self, id: VertexId) {
5873 self.materialize_vertex(id);
5874 if !self.value_cache_enabled && self.is_grid_backed(id) {
5875 self.ref_error_vertices.insert(id);
5876 self.vertex_values.remove(&id);
5879 let _ = self.mark_dirty(id);
5880 return;
5881 }
5882 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
5883 let value_ref = self.data_store.store_value(error);
5884 self.vertex_values.insert(id, value_ref);
5885 let _ = self.mark_dirty(id);
5886 }
5887
5888 pub fn is_ref_error(&self, id: VertexId) -> bool {
5890 if !self.value_cache_enabled && self.is_grid_backed(id) {
5891 return self.ref_error_vertices.contains(&id);
5892 }
5893 if let Some(value_ref) = self.vertex_values.get(&id) {
5894 let value = self.data_store.retrieve_value(*value_ref);
5895 if let LiteralValue::Error(err) = value {
5896 return err.kind == ExcelErrorKind::Ref;
5897 }
5898 }
5899 false
5900 }
5901
5902 #[doc(hidden)]
5904 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
5905 if self.authority_defers_marks() {
5909 self.authority_queue_direct_dirty(id);
5910 return;
5911 }
5912 for dep_id in self.authority_in_edge_readers(id) {
5913 self.store.set_dirty(dep_id, true);
5914 self.formula_dirty.legacy_insert(dep_id);
5915 }
5916 }
5917
5918 #[doc(hidden)]
5920 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
5921 if volatile {
5922 self.materialize_vertex(id);
5923 }
5924 self.store.set_volatile(id, volatile);
5925 if volatile {
5926 self.volatile_vertices.insert(id);
5927 } else {
5928 self.volatile_vertices.remove(&id);
5929 }
5930 }
5931
5932 #[doc(hidden)]
5937 pub fn set_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5938 self.materialize_vertex(id);
5939 if (!self.declared_dynamic_anchors.is_empty()
5942 || !self.fixed_single_arrays.is_empty()
5943 || !self.fixed_array_shapes.is_empty())
5944 && self.store.grid_addr(id) != Some(coord)
5945 {
5946 self.forget_declared_dynamic_anchor(id);
5947 }
5948 let old = self.store.grid_addr(id);
5949 self.store.set_addr(id, VertexAddr::grid(coord));
5950 if let Some(old) = old
5953 && old != coord
5954 && let Some(index) = self.sheet_indexes.get_mut(&self.store.sheet_id(id))
5955 && index.remove_indexed_vertex(old, id)
5956 {
5957 index.add_vertex(coord, id);
5958 }
5959 }
5960
5961 #[doc(hidden)]
5963 pub(crate) fn update_edge_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5964 self.materialize_vertex(id);
5965 #[cfg(not(any(test, feature = "legacy_oracle")))]
5966 let _ = (&id, &coord);
5967 #[cfg(any(test, feature = "legacy_oracle"))]
5968 {
5969 self.edges.update_addr(id, VertexAddr::grid(coord));
5970 }
5971 }
5972
5973 #[doc(hidden)]
5975 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
5976 self.materialize_vertex(id);
5977 self.store.mark_deleted(id, deleted);
5978 }
5979
5980 #[doc(hidden)]
5982 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
5983 self.materialize_vertex(id);
5984 self.store.set_kind(id, kind);
5985 }
5986
5987 #[doc(hidden)]
5989 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
5990 self.store.set_dirty(id, dirty);
5991 if dirty {
5992 self.formula_dirty.legacy_insert(id);
5993 } else {
5994 self.formula_dirty.legacy_remove(&id);
5995 self.row_visibility_scan_armed = true;
5996 }
5997 }
5998
5999 #[cfg(test)]
6001 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
6002 self.store.kind(id)
6003 }
6004
6005 #[cfg(test)]
6007 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
6008 self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG
6009 }
6010
6011 #[inline]
6013 pub(crate) fn virtual_member_at(&self, addr: &CellRef) -> Option<VertexId> {
6014 self.vertex_formulas
6015 .virtual_members()
6016 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
6017 .map(|m| m.vertex)
6018 }
6019
6020 #[cfg(test)]
6022 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
6023 self.store.is_deleted(id)
6024 }
6025
6026 #[cfg(any(test, feature = "legacy_oracle"))]
6027 #[doc(hidden)]
6029 pub fn rebuild_edges(&mut self) {
6030 self.edges.rebuild();
6031 }
6032
6033 #[cfg(any(test, feature = "legacy_oracle"))]
6034 pub fn flush_pending_edge_deltas(&mut self) {
6040 self.edges.rebuild();
6041 }
6042
6043 #[cfg(any(test, feature = "legacy_oracle"))]
6044 #[doc(hidden)]
6046 pub fn edges_delta_size(&self) -> usize {
6047 self.edges.delta_size()
6048 }
6049
6050 #[cfg(any(test, feature = "legacy_oracle"))]
6051 #[doc(hidden)]
6054 pub fn edges_rebuild_count(&self) -> u64 {
6055 self.edges.rebuild_count()
6056 }
6057
6058 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
6060 self.cell_vertex(addr)
6061 }
6062
6063 #[inline]
6068 pub(crate) fn cell_vertex(&self, addr: &CellRef) -> Option<VertexId> {
6069 if let Some(&v) = self.cell_to_vertex.get(addr) {
6070 return Some(v);
6071 }
6072 self.vertex_formulas
6073 .virtual_members()
6074 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
6075 .map(|m| m.vertex)
6076 }
6077
6078 #[inline]
6081 pub(crate) fn cell_vertex_mut(&mut self, addr: &CellRef) -> Option<VertexId> {
6082 if let Some(&v) = self.cell_to_vertex.get(addr) {
6083 return Some(v);
6084 }
6085 let m = self.vertex_formulas.virtual_members().by_cell(
6086 addr.sheet_id,
6087 addr.coord.row(),
6088 addr.coord.col(),
6089 )?;
6090 self.materialize_vertex(m.vertex);
6091 Some(m.vertex)
6092 }
6093
6094 #[inline]
6096 pub(crate) fn is_virtual_member(&self, v: VertexId) -> bool {
6097 !self.vertex_formulas.virtual_members().is_empty() && self.store.is_virtual(v)
6098 }
6099
6100 pub(crate) fn materialize_vertex(&mut self, v: VertexId) -> bool {
6104 if !self.is_virtual_member(v) {
6105 return false;
6106 }
6107 let Some(m) = self.vertex_formulas.materialize(v) else {
6108 return false;
6109 };
6110 self.store.ensure_dense(v, 1);
6111 self.store.set_virtual(v, false);
6112 let addr = CellRef::new(m.sheet, Coord::new(m.row, m.col, true, true));
6113 self.cell_to_vertex.insert(addr, v);
6114 self.sheet_index_mut(m.sheet)
6115 .add_vertex(GridAddr::new(m.row, m.col), v);
6116 true
6117 }
6118
6119 pub(crate) fn materialize_sheet(&mut self, sheet: SheetId) {
6121 let runs = self
6122 .vertex_formulas
6123 .virtual_members_mut()
6124 .drain_sheet(sheet);
6125 self.restore_runs(runs);
6126 }
6127
6128 pub(crate) fn materialize_all(&mut self) {
6131 if self.vertex_formulas.virtual_members().is_empty() {
6132 return;
6133 }
6134 let runs = self.vertex_formulas.virtual_members_mut().drain();
6135 self.restore_runs(runs);
6136 }
6137
6138 fn restore_runs(&mut self, runs: Vec<virtual_members::MemberRun>) {
6139 if runs.is_empty() {
6140 return;
6141 }
6142 let n: usize = runs.iter().map(|r| r.len as usize).sum();
6143 self.cell_to_vertex.reserve(n);
6144 self.vertex_formulas.reserve(n);
6145 let mut by_sheet: FxHashMap<SheetId, Vec<(GridAddr, VertexId)>> = FxHashMap::default();
6146 for r in &runs {
6147 self.store.ensure_dense(VertexId(r.first), r.len);
6148 let f = r.formula();
6149 let batch = by_sheet.entry(r.sheet).or_default();
6150 for (v, row) in r.members() {
6151 self.store.set_virtual(v, false);
6152 self.vertex_formulas.restore(v, f);
6153 self.cell_to_vertex
6154 .insert(CellRef::new(r.sheet, Coord::new(row, r.col, true, true)), v);
6155 batch.push((GridAddr::new(row, r.col), v));
6156 }
6157 }
6158 for (sheet, batch) in by_sheet {
6159 self.sheet_index_mut(sheet).add_vertices_batch(&batch);
6160 }
6161 }
6162
6163 pub(crate) fn virtual_members_in_cols(
6167 &self,
6168 sheet: SheetId,
6169 c0: u32,
6170 c1: u32,
6171 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
6172 self.vertex_formulas
6173 .virtual_members()
6174 .runs_in_cols(sheet, c0, c1)
6175 .flat_map(|r| {
6176 r.members()
6177 .map(move |(v, row)| (v, GridAddr::new(row, r.col)))
6178 })
6179 }
6180
6181 pub(crate) fn vertices_in_cols(&self, sheet: SheetId, c0: u32, c1: u32) -> Vec<VertexId> {
6185 match self.sheet_indexes.get(&sheet) {
6186 Some(index) => {
6187 let mut out = index.vertices_in_col_range(c0, c1);
6188 out.extend(self.virtual_members_in_cols(sheet, c0, c1).map(|(v, _)| v));
6189 out
6190 }
6191 None => self
6192 .grid_vertices_in_sheet(sheet)
6193 .filter(|(_, a)| a.col() >= c0 && a.col() <= c1)
6194 .map(|(v, _)| v)
6195 .collect(),
6196 }
6197 }
6198
6199 pub(crate) fn vertices_in_rows(&self, sheet: SheetId, r0: u32, r1: u32) -> Vec<VertexId> {
6202 match self.sheet_indexes.get(&sheet) {
6203 Some(index) => {
6204 let mut out = index.vertices_in_row_range(r0, r1);
6205 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
6206 let lo = r.row0.max(r0);
6207 let hi = (r.row0 + r.len - 1).min(r1);
6208 if lo <= hi {
6209 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
6210 }
6211 }
6212 out
6213 }
6214 None => self
6215 .grid_vertices_in_sheet(sheet)
6216 .filter(|(_, a)| a.row() >= r0 && a.row() <= r1)
6217 .map(|(v, _)| v)
6218 .collect(),
6219 }
6220 }
6221
6222 pub(crate) fn virtualize_family_members(&mut self) -> usize {
6234 let mut cand: Vec<(u32, AstNodeId, (u32, u32))> = self
6235 .vertex_formulas
6236 .map_iter()
6237 .filter_map(|(v, f)| match f {
6238 FormulaRef::Member { template, anchor } => Some((v.0, template, anchor)),
6239 FormulaRef::Own(_) => None,
6240 })
6241 .collect();
6242 if cand.len() < 2 {
6243 return 0;
6244 }
6245 cand.sort_unstable_by_key(|c| c.0);
6246 let runs = self.member_runs_in_id_order(&cand);
6247 drop(cand);
6248 self.install_virtual_runs(runs)
6249 }
6250
6251 fn member_runs_in_id_order(
6255 &self,
6256 cand: &[(u32, AstNodeId, (u32, u32))],
6257 ) -> Vec<virtual_members::MemberRun> {
6258 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
6259 let mut cur: Option<virtual_members::MemberRun> = None;
6260 let close = |cur: &mut Option<virtual_members::MemberRun>,
6261 runs: &mut Vec<virtual_members::MemberRun>| {
6262 if let Some(r) = cur.take()
6263 && r.len >= 2
6264 {
6265 runs.push(r);
6266 }
6267 };
6268 for &(v, template, anchor) in cand {
6269 let vid = VertexId(v);
6270 if !self.virtualizable(vid) {
6271 close(&mut cur, &mut runs);
6272 continue;
6273 }
6274 let Some(addr) = self.store.grid_addr(vid) else {
6275 close(&mut cur, &mut runs);
6276 continue;
6277 };
6278 let sheet = self.store.sheet_id(vid);
6279 if let Some(r) = cur.as_mut()
6280 && r.sheet == sheet
6281 && r.col == addr.col()
6282 && r.first + r.len == v
6283 && r.row0 + r.len == addr.row()
6284 && r.template == template
6285 && r.anchor == anchor
6286 {
6287 r.len += 1;
6288 continue;
6289 }
6290 close(&mut cur, &mut runs);
6291 cur = Some(virtual_members::MemberRun {
6292 sheet,
6293 col: addr.col(),
6294 row0: addr.row(),
6295 len: 1,
6296 first: v,
6297 template,
6298 anchor,
6299 });
6300 }
6301 close(&mut cur, &mut runs);
6302 runs
6303 }
6304
6305 fn install_virtual_runs(&mut self, runs: Vec<virtual_members::MemberRun>) -> usize {
6308 if runs.is_empty() {
6309 return 0;
6310 }
6311 let mut made = 0usize;
6312 let mut sheets: Vec<SheetId> = Vec::new();
6313 for &r in &runs {
6314 for (v, _) in r.members() {
6315 self.store.set_virtual(v, true);
6316 }
6317 sheets.push(r.sheet);
6318 made += r.len as usize;
6319 self.vertex_formulas.virtual_members_mut().insert(r);
6320 }
6321 let store = &self.store;
6327 let mut removed: Vec<u32> = Vec::with_capacity(made);
6328 self.cell_to_vertex.retain(|c, v| {
6329 let mine = store.is_virtual(*v)
6330 && store.sheet_id(*v) == c.sheet_id
6331 && store.grid_addr(*v) == Some(GridAddr::new(c.coord.row(), c.coord.col()));
6332 if mine {
6333 removed.push(v.0);
6334 }
6335 !mine
6336 });
6337 self.cell_to_vertex.shrink_to_fit();
6338 let store = &self.store;
6339 self.vertex_formulas
6340 .drop_virtual_from_map(|v| store.is_virtual(v));
6341 if removed.len() != made {
6342 removed.sort_unstable();
6346 let orphans: Vec<VertexId> = runs
6347 .iter()
6348 .flat_map(|r| r.members().map(|(v, _)| v))
6349 .filter(|v| removed.binary_search(&v.0).is_err())
6350 .collect();
6351 for v in orphans {
6352 if self.vertex_formulas.materialize(v).is_some() {
6353 self.store.set_virtual(v, false);
6354 made -= 1;
6355 }
6356 }
6357 }
6358 sheets.sort_unstable();
6359 sheets.dedup();
6360 self.rebuild_sheet_indexes(&sheets);
6361 self.virtualize_member_pages();
6362 made
6363 }
6364
6365 pub(crate) fn virtualize_member_pages(&mut self) -> usize {
6368 let runs: Vec<virtual_members::MemberRun> = self
6369 .vertex_formulas
6370 .virtual_members()
6371 .runs()
6372 .filter(|r| r.len as usize >= 1024)
6373 .copied()
6374 .collect();
6375 runs.iter()
6376 .map(|r| {
6377 self.store
6378 .virtualize_member_span(VertexId(r.first), r.len, r.sheet, r.col, r.row0)
6379 })
6380 .sum()
6381 }
6382
6383 fn virtualizable(&self, v: VertexId) -> bool {
6385 let flags = self.store.flags(v);
6386 if flags & (0x02 | 0x04 | 0x08 | crate::engine::vertex_store::VIRTUAL_FLAG) != 0
6388 || self.store.kind(v) != VertexKind::FormulaScalar
6389 {
6390 return false;
6391 }
6392 let absent = |empty: bool, contains: &dyn Fn() -> bool| empty || !contains();
6393 absent(self.ref_error_vertices.is_empty(), &|| {
6394 self.ref_error_vertices.contains(&v)
6395 }) && absent(self.vertex_values.is_empty(), &|| {
6396 self.vertex_values.contains_key(&v)
6397 }) && absent(self.vertex_to_pending_names.is_empty(), &|| {
6398 self.vertex_to_pending_names.contains_key(&v)
6399 }) && absent(self.spill_anchor_to_cells.is_empty(), &|| {
6400 self.spill_anchor_to_cells.contains_key(&v)
6401 }) && absent(self.name_vertex_lookup.is_empty(), &|| {
6402 self.name_vertex_lookup.contains_key(&v)
6403 }) && absent(self.volatile_vertices.is_empty(), &|| {
6404 self.volatile_vertices.contains(&v)
6405 })
6406 }
6407
6408 fn rebuild_sheet_indexes(&mut self, sheets: &[SheetId]) {
6412 let store = &self.store;
6413 for sheet in sheets {
6414 if let Some(index) = self.sheet_indexes.get_mut(sheet) {
6415 index.retain_vertices(|v| !store.is_virtual(v));
6416 }
6417 }
6418 }
6419
6420 pub(crate) fn virtual_vertex_pages(&self) -> usize {
6423 self.store.virtual_pages()
6424 }
6425
6426 pub(crate) fn virtual_member_counts(&self) -> (usize, usize) {
6427 let m = self.vertex_formulas.virtual_members();
6428 (m.len(), m.run_count())
6429 }
6430
6431 pub fn get_grid_addr(&self, id: VertexId) -> Option<GridAddr> {
6436 self.store.grid_addr(id)
6437 }
6438
6439 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
6441 self.store.sheet_id(id)
6442 }
6443
6444 pub fn grid_vertices_in_sheet(
6451 &self,
6452 sheet_id: SheetId,
6453 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
6454 self.store.all_vertices().filter_map(move |id| {
6455 if !self.vertex_exists(id) || self.store.sheet_id(id) != sheet_id {
6456 return None;
6457 }
6458 if !self.retired_id_set.is_empty()
6459 && self.retired_id_set.contains(&id)
6460 && self.store.is_deleted(id)
6461 {
6462 return None;
6463 }
6464 self.store.grid_addr(id).map(|addr| (id, addr))
6465 })
6466 }
6467
6468 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
6470 self.vertex_formulas.contains_key(&id)
6471 }
6472
6473 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
6475 self.vertex_formulas.keys()
6476 }
6477
6478 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
6480 self.materialize_vertex(id);
6481 let sheet_id = self.store.sheet_id(id);
6483
6484 let (
6486 new_dependencies,
6487 new_range_dependencies,
6488 vertexless,
6489 named_dependencies,
6490 unresolved_names,
6491 ) = self.extract_dependencies_with_pending_names(&ast, sheet_id)?;
6492
6493 let old_kind = self.store.kind(id);
6494
6495 self.remove_dependent_edges(id);
6497 self.detach_vertex_from_names(id);
6498 self.clear_pending_name_references(id);
6499
6500 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
6502 self.vertex_formulas.insert(id, ast_id);
6503
6504 self.add_dependent_edges(id, &new_dependencies);
6506 self.note_vertexless_deps(
6507 id,
6508 vertexless
6509 .iter()
6510 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6511 );
6512 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
6513
6514 if !named_dependencies.is_empty() {
6515 self.attach_vertex_to_names(id, &named_dependencies);
6516 }
6517 for unresolved_name in &unresolved_names {
6518 self.record_pending_name_reference(sheet_id, unresolved_name, id);
6519 }
6520
6521 self.ref_error_vertices.remove(&id);
6524 self.vertex_values.remove(&id);
6525
6526 self.store.set_kind(
6528 id,
6529 if old_kind == VertexKind::FormulaArray {
6530 VertexKind::FormulaArray
6531 } else {
6532 VertexKind::FormulaScalar
6533 },
6534 );
6535
6536 Ok(())
6537 }
6538
6539 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
6541 self.store.set_dirty(vertex_id, true);
6542 self.formula_dirty.legacy_insert(vertex_id);
6543 }
6544
6545 pub fn mark_vertices_dirty_batch(&mut self, vertices: &[VertexId]) {
6547 self.formula_dirty.legacy_reserve(vertices.len());
6548 for &vertex_id in vertices {
6549 self.store.set_dirty(vertex_id, true);
6550 }
6551 self.formula_dirty.legacy_extend(vertices.iter().copied());
6552 }
6553
6554 pub fn update_cell_mapping(
6556 &mut self,
6557 id: VertexId,
6558 old_addr: Option<CellRef>,
6559 new_addr: CellRef,
6560 ) {
6561 self.materialize_vertex(id);
6562 if let Some(old) = old_addr {
6563 self.cell_vertex_mut(&old);
6564 }
6565 self.cell_vertex_mut(&new_addr);
6566 if let Some(old) = old_addr {
6568 self.cell_to_vertex.remove(&old);
6569 }
6570 self.cell_to_vertex.insert(new_addr, id);
6572 }
6573
6574 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
6576 self.cell_vertex_mut(addr);
6577 self.cell_to_vertex.remove(addr);
6578 }
6579
6580 pub(crate) fn revive_vertex(&mut self, id: VertexId, sheet: SheetId, coord: GridAddr) -> bool {
6585 if !self.store.vertex_exists(id)
6586 || !self.store.is_deleted(id)
6587 || self.store.grid_addr(id).is_none()
6588 || self.store.sheet_id(id) != sheet
6589 {
6590 return false;
6591 }
6592 let cell = CellRef::new(sheet, Coord::new(coord.row(), coord.col(), true, true));
6593 let mut placeholder = None;
6598 if let Some(x) = self.cell_vertex(&cell)
6599 && !self.store.is_deleted(x)
6600 && self.store.grid_addr(x) == Some(coord)
6601 {
6602 if !self.is_pure_placeholder(x) {
6603 return false;
6604 }
6605 placeholder = Some(x);
6606 }
6607 #[cfg(any(test, feature = "legacy_oracle"))]
6608 let readers = placeholder
6609 .map(|x| self.get_dependents(x))
6610 .unwrap_or_default();
6611 if let Some(x) = placeholder {
6612 self.cell_to_vertex.remove(&cell);
6613 if let Some(index) = self.sheet_indexes.get_mut(&sheet) {
6614 index.remove_vertex(coord, x);
6615 }
6616 self.remove_all_edges(x);
6617 self.store.mark_deleted(x, true);
6618 }
6619 self.retired_id_set.remove(&id);
6620 self.store.mark_deleted(id, false);
6621 self.store.set_addr(id, VertexAddr::grid(coord));
6622 #[cfg(any(test, feature = "legacy_oracle"))]
6623 self.edges.update_addr(id, VertexAddr::grid(coord));
6624 self.store.set_kind(id, VertexKind::Empty);
6625 self.store.set_dynamic(id, false);
6626 self.store.set_volatile(id, false);
6627 self.cell_to_vertex.insert(cell, id);
6628 self.sheet_index_mut(sheet).add_vertex(coord, id);
6629 self.ref_error_vertices.remove(&id);
6630 #[cfg(any(test, feature = "legacy_oracle"))]
6633 for r in readers {
6634 if let Some(ast) = self.get_formula(r) {
6635 self.rebuild_formula_dependencies(r, &ast);
6636 }
6637 }
6638 let _ = self.mark_dirty(id);
6641 true
6642 }
6643
6644 fn is_pure_placeholder(&self, x: VertexId) -> bool {
6647 self.store.kind(x) == VertexKind::Empty
6648 && !self.vertex_formulas.contains_key(&x)
6649 && !self.vertex_values.contains_key(&x)
6650 && !self.ref_error_vertices.contains(&x)
6651 && !self.spill_anchor_to_cells.contains_key(&x)
6652 && !self.vertex_to_pending_names.contains_key(&x)
6653 && !self.name_vertex_lookup.contains_key(&x)
6654 }
6655
6656 pub(crate) fn journal_formula_left(&mut self, v: VertexId) {
6660 if !self.vertex_formulas.contains_key(&v) {
6661 return;
6662 }
6663 if let Some(cell) = self.get_cell_ref(v) {
6664 self.vertex_journal
6665 .retired((cell.sheet_id, cell.coord.row(), cell.coord.col()), v.0);
6666 }
6667 }
6668
6669 fn replay_formula_vertex(&mut self, addr: &CellRef) {
6675 if self.revive_retired_id(addr).is_some() {
6676 return;
6677 }
6678 let cell = (addr.sheet_id, addr.coord.row(), addr.coord.col());
6679 let Some(id) = self.vertex_journal.created(cell) else {
6680 return;
6681 };
6682 if self.cell_vertex(addr).is_none() {
6683 let coord = GridAddr::new(addr.coord.row(), addr.coord.col());
6684 self.revive_vertex(VertexId(id), addr.sheet_id, coord);
6685 }
6686 }
6687
6688 pub(crate) fn set_replay_mode(&mut self, mode: crate::engine::authority::history::Replay) {
6689 self.vertex_journal.set_mode(mode);
6690 }
6691
6692 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
6694 let coord = self.store.grid_addr(id)?;
6695 let sheet_id = self.store.sheet_id(id);
6696 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
6698 if self.cell_vertex(&cell_ref) == Some(id) {
6700 Some(cell_ref)
6701 } else {
6702 None
6703 }
6704 }
6705
6706 pub(crate) fn rebuild_formula_dependencies(&mut self, vertex_id: VertexId, ast: &ASTNode) {
6712 self.materialize_vertex(vertex_id);
6713 let sheet_id = self.store.sheet_id(vertex_id);
6714
6715 self.remove_dependent_edges(vertex_id);
6717 self.detach_vertex_from_names(vertex_id);
6718 self.clear_pending_name_references(vertex_id);
6719
6720 let (
6721 new_dependencies,
6722 new_range_dependencies,
6723 vertexless,
6724 named_dependencies,
6725 unresolved_names,
6726 ) = match self.extract_dependencies_with_pending_names(ast, sheet_id) {
6727 Ok(v) => v,
6728 Err(_) => {
6729 self.mark_as_ref_error(vertex_id);
6730 return;
6731 }
6732 };
6733
6734 if new_dependencies.contains(&vertex_id) && !self.config.cycle.allows_self_dependency() {
6737 self.mark_as_ref_error(vertex_id);
6738 return;
6739 }
6740
6741 for &name_vertex in &named_dependencies {
6742 let mut visited = FxHashSet::default();
6743 if self.name_depends_on_vertex(name_vertex, vertex_id, &mut visited) {
6744 self.mark_as_ref_error(vertex_id);
6745 return;
6746 }
6747 }
6748
6749 self.ref_error_vertices.remove(&vertex_id);
6751 self.vertex_values.remove(&vertex_id);
6752
6753 if !named_dependencies.is_empty() {
6754 self.attach_vertex_to_names(vertex_id, &named_dependencies);
6755 }
6756 for unresolved_name in &unresolved_names {
6757 self.record_pending_name_reference(sheet_id, unresolved_name, vertex_id);
6758 }
6759
6760 self.add_dependent_edges(vertex_id, &new_dependencies);
6761 self.note_vertexless_deps(
6762 vertex_id,
6763 vertexless
6764 .iter()
6765 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6766 );
6767 self.add_range_dependent_edges(vertex_id, &new_range_dependencies, sheet_id);
6768 self.vertex_formulas.touch(vertex_id);
6769 let _ = self.mark_dirty(vertex_id);
6770 }
6771}
6772
6773type RetiredBatch = Vec<((SheetId, u32, u32), VertexId)>;
6778
6779pub(crate) fn same_cell(a: &CellRef, b: &CellRef) -> bool {
6781 a.sheet_id == b.sheet_id && a.coord.row() == b.coord.row() && a.coord.col() == b.coord.col()
6782}
6783
6784fn shift_key(
6789 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
6790) -> (u8, SheetId, u32, u32) {
6791 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6792 match *op {
6793 Op::InsertRows {
6794 sheet_id,
6795 before,
6796 count,
6797 } => (0, sheet_id, before, count),
6798 Op::DeleteRows {
6799 sheet_id,
6800 start,
6801 count,
6802 } => (1, sheet_id, start, count),
6803 Op::InsertColumns {
6804 sheet_id,
6805 before,
6806 count,
6807 } => (2, sheet_id, before, count),
6808 Op::DeleteColumns {
6809 sheet_id,
6810 start,
6811 count,
6812 } => (3, sheet_id, start, count),
6813 }
6814}
6815
6816pub(crate) fn parse_structural_description(
6817 description: &str,
6818) -> Option<crate::engine::graph::editor::reference_adjuster::ShiftOperation> {
6819 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6820 let mut parts = description.split_whitespace();
6821 let kind = parts.next()?;
6822 let mut field = |name: &str| -> Option<u32> {
6823 parts
6824 .next()?
6825 .strip_prefix(name)?
6826 .strip_prefix('=')?
6827 .parse()
6828 .ok()
6829 };
6830 let sheet_id = u16::try_from(field("sheet")?).ok()?;
6831 Some(match kind {
6832 "InsertRows" => {
6833 let before = field("before")?;
6834 Op::InsertRows {
6835 sheet_id,
6836 before,
6837 count: field("count")?,
6838 }
6839 }
6840 "DeleteRows" => {
6841 let start = field("start")?;
6842 Op::DeleteRows {
6843 sheet_id,
6844 start,
6845 count: field("count")?,
6846 }
6847 }
6848 "InsertColumns" => {
6849 let before = field("before")?;
6850 Op::InsertColumns {
6851 sheet_id,
6852 before,
6853 count: field("count")?,
6854 }
6855 }
6856 "DeleteColumns" => {
6857 let start = field("start")?;
6858 Op::DeleteColumns {
6859 sheet_id,
6860 start,
6861 count: field("count")?,
6862 }
6863 }
6864 _ => return None,
6865 })
6866}