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;
63struct RegistryFunctionProvider;
66
67impl crate::traits::FunctionProvider for RegistryFunctionProvider {
68 fn planning_semantic_revision(&self) -> Option<u64> {
69 Some(0)
70 }
71
72 fn get_function(
73 &self,
74 ns: &str,
75 name: &str,
76 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
77 crate::function_registry::get(ns, name)
78 }
79
80 fn get_function_for_planning(
81 &self,
82 ns: &str,
83 name: &str,
84 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
85 crate::function_registry::get_for_planning(ns, name)
86 }
87}
88
89#[inline]
90fn normalize_stored_literal(value: LiteralValue) -> LiteralValue {
91 match value {
92 LiteralValue::Int(i) => LiteralValue::Number(i as f64),
94 other => other,
95 }
96}
97
98pub use editor::change_log::{ChangeEvent, ChangeLog};
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
104pub enum DependencyRef {
105 Cell(VertexId),
107 Range {
109 sheet: String,
110 start_row: u32,
111 start_col: u32,
112 end_row: u32, end_col: u32, },
115 WholeColumn { sheet: String, col: u32 },
117 WholeRow { sheet: String, row: u32 },
119}
120
121#[derive(Debug, Clone, Hash, PartialEq, Eq)]
123pub struct StripeKey {
124 pub sheet_id: SheetId,
125 pub stripe_type: StripeType,
126 pub index: u32, }
128
129#[derive(Debug, Clone, Hash, PartialEq, Eq)]
130pub enum StripeType {
131 Row,
132 Column,
133 Block, }
135
136const BLOCK_H: u32 = 256;
138const BLOCK_W: u32 = 256;
139
140pub fn block_index(row: u32, col: u32) -> u32 {
141 (row / BLOCK_H) << 16 | (col / BLOCK_W)
142}
143
144#[derive(Debug, Clone)]
147pub struct OperationSummary {
148 pub affected_vertices: Vec<VertexId>,
150 pub created_placeholders: Vec<CellRef>,
152}
153
154#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
159pub struct GraphBaselineStats {
160 pub graph_vertex_count: usize,
161 pub graph_formula_vertex_count: usize,
162 pub graph_edge_count: usize,
163 pub dirty_vertex_count: usize,
164 pub evaluation_vertex_count: usize,
165 pub formula_ast_root_count: usize,
166 pub formula_ast_node_count: usize,
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub(crate) enum FormulaRef {
172 Own(AstNodeId),
174 Member {
178 template: AstNodeId,
179 anchor: (u32, u32),
180 },
181}
182
183impl FormulaRef {
184 #[inline]
187 pub(crate) fn root(self) -> AstNodeId {
188 match self {
189 FormulaRef::Own(id) | FormulaRef::Member { template: id, .. } => id,
190 }
191 }
192
193 #[inline]
195 pub(crate) fn of_ingested(ast_id: AstNodeId, member_anchor: Option<(u32, u32)>) -> Self {
196 match member_anchor {
197 None => FormulaRef::Own(ast_id),
198 Some(anchor) => FormulaRef::Member {
199 template: ast_id,
200 anchor,
201 },
202 }
203 }
204
205 #[inline]
207 pub(crate) fn own(self) -> Option<AstNodeId> {
208 match self {
209 FormulaRef::Own(id) => Some(id),
210 FormulaRef::Member { .. } => None,
211 }
212 }
213}
214
215#[derive(Debug, Default)]
222pub(crate) struct FormulaMap {
223 map: FxHashMap<VertexId, FormulaRef>,
224 virt: virtual_members::VirtualMembers,
225 touched: Vec<VertexId>,
226}
227
228impl FormulaMap {
229 #[inline]
231 pub(crate) fn get(&self, vertex: &VertexId) -> Option<FormulaRef> {
232 match self.map.get(vertex) {
233 Some(&f) => Some(f),
234 None => self.virt.by_vertex(*vertex).map(|m| m.formula),
235 }
236 }
237
238 #[inline]
239 pub(crate) fn contains_key(&self, vertex: &VertexId) -> bool {
240 self.map.contains_key(vertex) || self.virt.contains_vertex(*vertex)
241 }
242
243 #[inline]
245 pub(crate) fn len(&self) -> usize {
246 self.map.len() + self.virt.len()
247 }
248
249 pub(crate) fn iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
252 self.map
253 .iter()
254 .map(|(&v, &f)| (v, f))
255 .chain(self.virt.iter().map(|m| (m.vertex, m.formula)))
256 }
257
258 pub(crate) fn keys(&self) -> impl Iterator<Item = VertexId> + '_ {
259 self.iter().map(|(v, _)| v)
260 }
261
262 pub(crate) fn roots(&self) -> impl Iterator<Item = AstNodeId> + '_ {
266 self.map
267 .values()
268 .map(|f| f.root())
269 .chain(self.virt.runs().map(|r| r.template))
270 }
271
272 pub(crate) fn map_iter(&self) -> impl Iterator<Item = (VertexId, FormulaRef)> + '_ {
273 self.map.iter().map(|(&v, &f)| (v, f))
274 }
275
276 #[inline]
277 pub(crate) fn virtual_members(&self) -> &virtual_members::VirtualMembers {
278 &self.virt
279 }
280
281 #[inline]
282 pub(crate) fn virtual_members_mut(&mut self) -> &mut virtual_members::VirtualMembers {
283 &mut self.virt
284 }
285
286 #[inline]
289 pub(crate) fn materialize(
290 &mut self,
291 vertex: VertexId,
292 ) -> Option<virtual_members::VirtualMember> {
293 let m = self.virt.take(vertex)?;
294 self.map.insert(vertex, m.formula);
295 Some(m)
296 }
297
298 pub(crate) fn forget_materialized(&mut self, vertex: &VertexId) {
301 self.map.remove(vertex);
302 }
303
304 #[inline]
306 pub(crate) fn restore(&mut self, vertex: VertexId, formula: FormulaRef) {
307 self.map.insert(vertex, formula);
308 }
309
310 #[inline]
311 pub(crate) fn insert(&mut self, vertex: VertexId, ast: AstNodeId) -> Option<FormulaRef> {
312 self.insert_ref(vertex, FormulaRef::Own(ast))
313 }
314
315 #[inline]
317 pub(crate) fn insert_ref(
318 &mut self,
319 vertex: VertexId,
320 formula: FormulaRef,
321 ) -> Option<FormulaRef> {
322 debug_assert!(
323 !self.virt.contains_vertex(vertex),
324 "formula write to a virtual family member (materialize it first)"
325 );
326 self.touched.push(vertex);
327 self.map.insert(vertex, formula)
328 }
329
330 #[inline]
332 pub(crate) fn compress(&mut self, vertex: VertexId, template: AstNodeId, anchor: (u32, u32)) {
333 if let Some(slot) = self.map.get_mut(&vertex) {
334 *slot = FormulaRef::Member { template, anchor };
335 }
336 }
337
338 #[inline]
341 pub(crate) fn decompress(&mut self, vertex: VertexId, own: AstNodeId) {
342 debug_assert!(!self.virt.contains_vertex(vertex));
343 if let Some(slot) = self.map.get_mut(&vertex) {
344 *slot = FormulaRef::Own(own);
345 }
346 }
347
348 pub(crate) fn remap(&mut self, map: &impl Fn(AstNodeId) -> AstNodeId) {
350 for slot in self.map.values_mut() {
351 *slot = match *slot {
352 FormulaRef::Own(id) => FormulaRef::Own(map(id)),
353 FormulaRef::Member { template, anchor } => FormulaRef::Member {
354 template: map(template),
355 anchor,
356 },
357 };
358 }
359 self.virt.remap(map);
360 }
361
362 #[inline]
363 pub(crate) fn remove(&mut self, vertex: &VertexId) -> Option<FormulaRef> {
364 debug_assert!(
365 !self.virt.contains_vertex(*vertex),
366 "formula removal of a virtual family member (materialize it first)"
367 );
368 let old = self.map.remove(vertex);
369 if old.is_some() {
370 self.touched.push(*vertex);
371 }
372 old
373 }
374
375 #[inline]
376 pub(crate) fn reserve(&mut self, additional: usize) {
377 self.map.reserve(additional);
378 }
379
380 pub(crate) fn drop_virtual_from_map(&mut self, is_virtual: impl Fn(VertexId) -> bool) {
384 debug_assert!(
385 self.map
386 .keys()
387 .all(|&v| is_virtual(v) == self.virt.contains_vertex(v))
388 );
389 self.map.retain(|&v, _| !is_virtual(v));
390 self.map.shrink_to_fit();
391 }
392
393 pub(crate) fn map_capacity(&self) -> usize {
394 self.map.capacity()
395 }
396
397 pub(crate) fn take_touched(&mut self) -> Vec<VertexId> {
399 std::mem::take(&mut self.touched)
400 }
401
402 pub(crate) fn touch(&mut self, vertex: VertexId) {
405 self.touched.push(vertex);
406 }
407
408 pub(crate) fn has_touched(&self) -> bool {
409 !self.touched.is_empty()
410 }
411}
412
413#[derive(Clone, Copy, Debug, PartialEq, Eq)]
421#[non_exhaustive]
422pub struct FormulaView {
423 pub template: AstNodeId,
424 pub row_delta: i64,
425 pub col_delta: i64,
426}
427
428#[derive(Debug)]
430pub struct DependencyGraph {
431 store: VertexStore,
433
434 #[cfg(any(test, feature = "legacy_oracle"))]
436 edges: CsrMutableEdges,
437 dep_edge_total: usize,
441 range_reader_count: usize,
443 renamed_sheet_aliases: FxHashMap<String, SheetId>,
447
448 data_store: DataStore,
450 vertex_values: FxHashMap<VertexId, ValueRef>,
451 vertex_formulas: FormulaMap,
452
453 value_cache_enabled: bool,
458
459 #[cfg(debug_assertions)]
462 graph_value_read_attempts: AtomicU64,
463
464 cell_to_vertex: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
468 load_packed_to_vertex: std::collections::HashMap<PackedSheetCell, VertexId, CoordBuildHasher>,
469
470 vertex_journal: crate::engine::authority::history::IdJournal,
473
474 formula_dirty: FormulaDirtyState,
477 volatile_vertices: FxHashSet<VertexId>,
478
479 dirty_propagation_visits: u64,
484
485 deferred_dirty_depth: u32,
491 deferred_dirty_pending: Vec<VertexId>,
493 retired_ids: std::collections::BTreeMap<(SheetId, u32, u32), VertexId>,
498 retired_id_set: FxHashSet<VertexId>,
501 retired_dropped: Vec<RetiredBatch>,
506 retired_dropped_by_undo: Vec<RetiredBatch>,
509 extent_record: extent_record::ExtentRecord,
512 extent_dropped: Vec<Vec<extent_record::ExtentRun>>,
517 extent_undone: Vec<(u8, SheetId, u32, u32)>,
521 deferred_dirty_pending_rects: Vec<(u16, crate::engine::authority::geom::Rect)>,
523
524 ref_error_vertices: FxHashSet<VertexId>,
530
531 #[cfg(any(test, feature = "legacy_oracle"))]
534 formula_to_range_deps: FxHashMap<VertexId, Vec<SharedRangeRef<'static>>>,
535
536 #[cfg(any(test, feature = "legacy_oracle"))]
539 stripe_to_dependents: FxHashMap<StripeKey, FxHashSet<VertexId>>,
540
541 sheet_indexes: FxHashMap<SheetId, SheetIndex>,
544
545 sheet_reg: SheetRegistry,
547 default_sheet_id: SheetId,
548
549 named_ranges: FxHashMap<String, NamedRange>,
552
553 named_ranges_lookup: FxHashMap<String, String>,
558
559 sheet_named_ranges: FxHashMap<(SheetId, String), NamedRange>,
561
562 sheet_named_ranges_lookup: FxHashMap<(SheetId, String), String>,
567
568 #[cfg(any(test, feature = "legacy_oracle"))]
570 vertex_to_names: FxHashMap<VertexId, Vec<VertexId>>,
571
572 name_vertex_lookup: FxHashMap<VertexId, (NameScope, String)>,
574
575 pending_name_links: FxHashMap<String, FxHashSet<(SheetId, VertexId)>>,
580
581 vertex_to_pending_names: FxHashMap<VertexId, FxHashSet<String>>,
584
585 tables: FxHashMap<String, tables::TableEntry>,
587 tables_lookup: FxHashMap<String, String>,
589 table_vertex_lookup: FxHashMap<VertexId, String>,
590
591 source_scalars: FxHashMap<String, sources::SourceScalarEntry>,
593 source_tables: FxHashMap<String, sources::SourceTableEntry>,
594 source_vertex_lookup: FxHashMap<VertexId, String>,
595
596 symbol_vertex_seq: u32,
602
603 #[cfg(any(test, feature = "legacy_oracle"))]
605 cell_to_name_dependents: FxHashMap<VertexId, FxHashSet<VertexId>>,
606 #[cfg(any(test, feature = "legacy_oracle"))]
608 name_to_cell_dependencies: FxHashMap<VertexId, Vec<VertexId>>,
609 #[cfg(any(test, feature = "legacy_oracle"))]
613 oracle_vertexless_readers: FxHashMap<(SheetId, u32, u32), Vec<VertexId>>,
614 #[cfg(any(test, feature = "legacy_oracle"))]
615 oracle_vertexless_of: FxHashMap<VertexId, Vec<(SheetId, u32, u32)>>,
616
617 config: super::EvalConfig,
619 topology_revision: u64,
621 symbol_revision: u64,
623
624 authority: crate::engine::authority::host::AuthorityHost,
627
628 #[cfg(any(test, feature = "legacy_oracle"))]
630 pk_order: Option<DynamicTopo<VertexId>>,
631
632 spill_anchor_to_cells: FxHashMap<VertexId, Vec<CellRef>>,
636 spill_cell_to_anchor: std::collections::HashMap<CellRef, VertexId, CoordBuildHasher>,
637 spill_cells_by_sheet: FxHashMap<SheetId, std::collections::BTreeMap<(u32, u32), VertexId>>,
638
639 admission_budget_override: Option<crate::engine::EvaluationBudgets>,
641
642 first_load_assume_new: bool,
644 ensure_touched_sheets: FxHashSet<SheetId>,
645
646 pub tombstone_registry: TombstoneRegistry,
648
649 #[cfg(test)]
650 instr: std::sync::Mutex<GraphInstrumentation>,
651 #[cfg(test)]
652 prepared_legacy_graph_failure_for_test: bool,
653}
654
655impl Default for DependencyGraph {
656 fn default() -> Self {
657 Self::new()
658 }
659}
660
661impl DependencyGraph {
662 pub fn range_expansion_limit(&self) -> usize {
664 self.config.range_expansion_limit
665 }
666
667 pub fn get_config(&self) -> &super::EvalConfig {
668 &self.config
669 }
670
671 pub(crate) fn formula_vertex_count(&self) -> usize {
673 self.vertex_formulas.len()
674 }
675
676 pub(crate) fn clear_formula_vertex_dirty(&mut self, vertex_id: VertexId) {
677 self.store.set_dirty(vertex_id, false);
678 self.formula_dirty.legacy_remove(&vertex_id);
679 }
680
681 pub fn baseline_stats(&self) -> GraphBaselineStats {
683 let data_stats = self.data_store.memory_usage();
684 GraphBaselineStats {
685 graph_vertex_count: self.store.len(),
686 graph_formula_vertex_count: self.vertex_formulas.len(),
687 graph_edge_count: self.dep_edge_total,
688 dirty_vertex_count: self.formula_dirty.legacy_len(),
689 evaluation_vertex_count: self.get_evaluation_vertices().len(),
690 formula_ast_root_count: self.vertex_formulas.len(),
691 formula_ast_node_count: data_stats.total_ast_nodes,
692 }
693 }
694
695 #[inline]
696 pub(crate) fn value_cache_enabled(&self) -> bool {
697 self.value_cache_enabled
698 }
699
700 #[cfg(test)]
704 pub fn debug_graph_value_read_attempts(&self) -> u64 {
705 #[cfg(debug_assertions)]
706 {
707 self.graph_value_read_attempts.load(Ordering::Relaxed)
708 }
709 #[cfg(not(debug_assertions))]
710 {
711 0
712 }
713 }
714
715 pub fn plan_dependencies<'a, I>(
717 &mut self,
718 items: I,
719 policy: &formualizer_parse::parser::CollectPolicy,
720 volatile: Option<&[bool]>,
721 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
722 where
723 I: IntoIterator<Item = (&'a str, u32, u32, &'a formualizer_parse::parser::ASTNode)>,
724 {
725 crate::engine::plan::build_dependency_plan(
726 &mut self.sheet_reg,
727 items.into_iter(),
728 policy,
729 volatile,
730 )
731 }
732
733 pub fn plan_dependencies_mixed<'a, I>(
734 &mut self,
735 items: I,
736 policy: &formualizer_parse::parser::CollectPolicy,
737 volatile: Option<&[bool]>,
738 ) -> Result<crate::engine::plan::DependencyPlan, formualizer_common::ExcelError>
739 where
740 I: IntoIterator<
741 Item = (
742 &'a str,
743 u32,
744 u32,
745 crate::engine::plan::DependencyPlanAst<'a>,
746 ),
747 >,
748 {
749 crate::engine::plan::build_dependency_plan_mixed(
750 &mut self.sheet_reg,
751 &self.data_store,
752 items.into_iter(),
753 policy,
754 volatile,
755 )
756 }
757
758 pub fn ensure_vertices_batch(
761 &mut self,
762 coords: &[(SheetId, AbsCoord)],
763 ) -> Vec<(VertexAddr, u32)> {
764 self.ensure_vertices_batch_ordered(coords).1
765 }
766
767 pub fn ensure_vertices_batch_packed_ordered(
771 &mut self,
772 packed_cells: &[PackedSheetCell],
773 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
774 let mut unmapped = vec![false; packed_cells.len()];
775 self.ensure_vertices_batch_packed_ordered_unmapped(packed_cells, &mut unmapped)
776 }
777
778 pub(crate) fn ensure_vertices_batch_packed_ordered_unmapped(
785 &mut self,
786 packed_cells: &[PackedSheetCell],
787 unmapped: &mut [bool],
788 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
789 debug_assert_eq!(unmapped.len(), packed_cells.len());
790 #[cfg(feature = "perf_instrumentation")]
791 use crate::instant::FzInstant as PerfInstant;
792 use rustc_hash::FxHashMap;
793
794 #[cfg(feature = "perf_instrumentation")]
795 let debug = std::env::var("FZ_DEBUG_LOAD")
796 .ok()
797 .is_some_and(|v| v != "0");
798 #[cfg(feature = "perf_instrumentation")]
799 let t0 = PerfInstant::now();
800
801 let mut ordered: Vec<Option<VertexId>> = vec![None; packed_cells.len()];
802 if packed_cells.is_empty() {
803 return (Vec::new(), Vec::new());
804 }
805
806 let first_sid = packed_cells[0].sheet_id();
807 let single_sheet = packed_cells.iter().all(|cell| cell.sheet_id() == first_sid);
808 let mut add_batch: Vec<(VertexAddr, u32)> = Vec::new();
809
810 #[cfg(feature = "perf_instrumentation")]
811 let mut packed_hits = 0usize;
812 #[cfg(feature = "perf_instrumentation")]
813 let mut generic_hits = 0usize;
814 #[cfg(feature = "perf_instrumentation")]
815 let mut missing = 0usize;
816 #[cfg(feature = "perf_instrumentation")]
817 let mut t_packed_lookup_us = 0u128;
818 #[cfg(feature = "perf_instrumentation")]
819 let mut t_generic_lookup_us = 0u128;
820 #[cfg(feature = "perf_instrumentation")]
821 let mut t_alloc_us = 0u128;
822 #[cfg(feature = "perf_instrumentation")]
823 let mut t_map_insert_us = 0u128;
824 #[cfg(feature = "perf_instrumentation")]
825 let mut t_index_insert_us = 0u128;
826 #[cfg(feature = "perf_instrumentation")]
827 let mut t_edge_register_us = 0u128;
828
829 if single_sheet {
830 let sid = first_sid;
831 let mut missing_items: Vec<(usize, PackedSheetCell)> =
832 Vec::with_capacity(packed_cells.len());
833
834 for (idx, packed) in packed_cells.iter().copied().enumerate() {
835 #[cfg(feature = "perf_instrumentation")]
836 let tl0 = PerfInstant::now();
837 if self.first_load_assume_new
838 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
839 {
840 ordered[idx] = Some(existing);
841 unmapped[idx] = false;
842 #[cfg(feature = "perf_instrumentation")]
843 {
844 packed_hits += 1;
845 t_packed_lookup_us += tl0.elapsed().as_micros();
846 }
847 continue;
848 }
849 #[cfg(feature = "perf_instrumentation")]
850 {
851 t_packed_lookup_us += tl0.elapsed().as_micros();
852 }
853
854 let pc = AbsCoord::new(packed.row0(), packed.col0());
855 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
856 #[cfg(feature = "perf_instrumentation")]
857 let tg0 = PerfInstant::now();
858 if let Some(existing) = self.cell_vertex(&addr) {
859 ordered[idx] = Some(existing);
860 unmapped[idx] = false;
861 if self.first_load_assume_new && !self.is_virtual_member(existing) {
863 self.load_packed_to_vertex.insert(packed, existing);
864 }
865 #[cfg(feature = "perf_instrumentation")]
866 {
867 generic_hits += 1;
868 }
869 } else {
870 missing_items.push((idx, packed));
871 #[cfg(feature = "perf_instrumentation")]
872 {
873 missing += 1;
874 }
875 }
876 #[cfg(feature = "perf_instrumentation")]
877 {
878 t_generic_lookup_us += tg0.elapsed().as_micros();
879 }
880 }
881
882 if !missing_items.is_empty() {
883 self.ensure_touched_sheets.insert(sid);
884
885 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(missing_items.len());
886 for (_, packed) in &missing_items {
887 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
888 }
889
890 #[cfg(feature = "perf_instrumentation")]
891 let ta0 = PerfInstant::now();
892 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
893 #[cfg(feature = "perf_instrumentation")]
894 {
895 t_alloc_us += ta0.elapsed().as_micros();
896 }
897 add_batch.reserve(missing_items.len());
898
899 match self.config.sheet_index_mode {
900 crate::engine::SheetIndexMode::Eager
901 | crate::engine::SheetIndexMode::FastBatch => {
902 for ((input_idx, packed), vid) in
903 missing_items.into_iter().zip(vids.into_iter())
904 {
905 let pc = AbsCoord::new(packed.row0(), packed.col0());
906 ordered[input_idx] = Some(vid);
907 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
908 if unmapped[input_idx] {
909 continue;
910 }
911
912 #[cfg(feature = "perf_instrumentation")]
913 let tm0 = PerfInstant::now();
914 if self.first_load_assume_new {
915 self.load_packed_to_vertex.insert(packed, vid);
916 } else {
917 let addr =
918 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
919 self.cell_to_vertex.insert(addr, vid);
920 }
921 #[cfg(feature = "perf_instrumentation")]
922 {
923 t_map_insert_us += tm0.elapsed().as_micros();
924 }
925
926 #[cfg(feature = "perf_instrumentation")]
927 let ti0 = PerfInstant::now();
928 self.sheet_index_mut(sid)
929 .add_vertex(GridAddr::from_coord(pc), vid);
930 #[cfg(feature = "perf_instrumentation")]
931 {
932 t_index_insert_us += ti0.elapsed().as_micros();
933 }
934 }
935 }
936 crate::engine::SheetIndexMode::Lazy => {
937 for ((input_idx, packed), vid) in
938 missing_items.into_iter().zip(vids.into_iter())
939 {
940 let pc = AbsCoord::new(packed.row0(), packed.col0());
941 ordered[input_idx] = Some(vid);
942 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
943 if unmapped[input_idx] {
944 continue;
945 }
946
947 #[cfg(feature = "perf_instrumentation")]
948 let tm0 = PerfInstant::now();
949 if self.first_load_assume_new {
950 self.load_packed_to_vertex.insert(packed, vid);
951 } else {
952 let addr =
953 CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
954 self.cell_to_vertex.insert(addr, vid);
955 }
956 #[cfg(feature = "perf_instrumentation")]
957 {
958 t_map_insert_us += tm0.elapsed().as_micros();
959 }
960 }
961 }
962 }
963 }
964 } else {
965 let mut grouped: FxHashMap<SheetId, Vec<(usize, PackedSheetCell)>> =
966 FxHashMap::default();
967
968 for (idx, packed) in packed_cells.iter().copied().enumerate() {
969 #[cfg(feature = "perf_instrumentation")]
970 let tl0 = PerfInstant::now();
971 if self.first_load_assume_new
972 && let Some(&existing) = self.load_packed_to_vertex.get(&packed)
973 {
974 ordered[idx] = Some(existing);
975 unmapped[idx] = false;
976 #[cfg(feature = "perf_instrumentation")]
977 {
978 packed_hits += 1;
979 t_packed_lookup_us += tl0.elapsed().as_micros();
980 }
981 continue;
982 }
983 #[cfg(feature = "perf_instrumentation")]
984 {
985 t_packed_lookup_us += tl0.elapsed().as_micros();
986 }
987
988 let sid = packed.sheet_id();
989 let pc = AbsCoord::new(packed.row0(), packed.col0());
990 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
991 #[cfg(feature = "perf_instrumentation")]
992 let tg0 = PerfInstant::now();
993 if let Some(existing) = self.cell_vertex(&addr) {
994 ordered[idx] = Some(existing);
995 unmapped[idx] = false;
996 if self.first_load_assume_new && !self.is_virtual_member(existing) {
998 self.load_packed_to_vertex.insert(packed, existing);
999 }
1000 #[cfg(feature = "perf_instrumentation")]
1001 {
1002 generic_hits += 1;
1003 }
1004 } else {
1005 grouped.entry(sid).or_default().push((idx, packed));
1006 #[cfg(feature = "perf_instrumentation")]
1007 {
1008 missing += 1;
1009 }
1010 }
1011 #[cfg(feature = "perf_instrumentation")]
1012 {
1013 t_generic_lookup_us += tg0.elapsed().as_micros();
1014 }
1015 }
1016
1017 for (sid, items) in grouped {
1018 if items.is_empty() {
1019 continue;
1020 }
1021 self.ensure_touched_sheets.insert(sid);
1022
1023 let mut pcs: Vec<VertexAddr> = Vec::with_capacity(items.len());
1024 for (_, packed) in &items {
1025 pcs.push(GridAddr::new(packed.row0(), packed.col0()).into());
1026 }
1027
1028 #[cfg(feature = "perf_instrumentation")]
1029 let ta0 = PerfInstant::now();
1030 let vids = self.store.allocate_contiguous(sid, &pcs, 0x00);
1031 #[cfg(feature = "perf_instrumentation")]
1032 {
1033 t_alloc_us += ta0.elapsed().as_micros();
1034 }
1035
1036 for ((input_idx, packed), vid) in items.into_iter().zip(vids.into_iter()) {
1037 let pc = AbsCoord::new(packed.row0(), packed.col0());
1038 ordered[input_idx] = Some(vid);
1039 add_batch.push((VertexAddr::grid(GridAddr::from_coord(pc)), vid.0));
1040 if unmapped[input_idx] {
1041 continue;
1042 }
1043
1044 #[cfg(feature = "perf_instrumentation")]
1045 let tm0 = PerfInstant::now();
1046 if self.first_load_assume_new {
1047 self.load_packed_to_vertex.insert(packed, vid);
1048 } else {
1049 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1050 self.cell_to_vertex.insert(addr, vid);
1051 }
1052 #[cfg(feature = "perf_instrumentation")]
1053 {
1054 t_map_insert_us += tm0.elapsed().as_micros();
1055 }
1056
1057 match self.config.sheet_index_mode {
1058 crate::engine::SheetIndexMode::Eager
1059 | crate::engine::SheetIndexMode::FastBatch => {
1060 #[cfg(feature = "perf_instrumentation")]
1061 let ti0 = PerfInstant::now();
1062 self.sheet_index_mut(sid)
1063 .add_vertex(GridAddr::from_coord(pc), vid);
1064 #[cfg(feature = "perf_instrumentation")]
1065 {
1066 t_index_insert_us += ti0.elapsed().as_micros();
1067 }
1068 }
1069 crate::engine::SheetIndexMode::Lazy => {
1070 }
1072 }
1073 }
1074 }
1075 }
1076
1077 if !add_batch.is_empty() {
1078 #[cfg(feature = "perf_instrumentation")]
1079 let te0 = PerfInstant::now();
1080 #[cfg(any(test, feature = "legacy_oracle"))]
1081 {
1082 self.edges.add_vertices_batch(&add_batch);
1083 let created: FxHashSet<u32> = if self.oracle_vertexless_readers.is_empty() {
1084 FxHashSet::default()
1085 } else {
1086 add_batch.iter().map(|&(_, raw)| raw).collect()
1087 };
1088 for (i, packed) in packed_cells.iter().enumerate() {
1089 if let Some(v) = ordered[i]
1090 && created.contains(&v.0)
1091 {
1092 self.oracle_cell_vertex_created(
1093 (packed.sheet_id(), packed.row0(), packed.col0()),
1094 v,
1095 );
1096 }
1097 }
1098 }
1099 #[cfg(feature = "perf_instrumentation")]
1100 {
1101 t_edge_register_us += te0.elapsed().as_micros();
1102 }
1103 }
1104
1105 #[cfg(feature = "perf_instrumentation")]
1106 if debug {
1107 eprintln!(
1108 "[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",
1109 packed_cells.len(),
1110 single_sheet,
1111 packed_hits,
1112 generic_hits,
1113 missing,
1114 t_packed_lookup_us,
1115 t_generic_lookup_us,
1116 t_alloc_us,
1117 t_map_insert_us,
1118 t_index_insert_us,
1119 t_edge_register_us,
1120 t0.elapsed().as_millis(),
1121 );
1122 }
1123
1124 let ordered = ordered
1125 .into_iter()
1126 .map(|vid| vid.expect("ensure_vertices_batch_packed_ordered must resolve every coord"))
1127 .collect();
1128 (ordered, add_batch)
1129 }
1130
1131 pub fn ensure_vertices_batch_ordered(
1134 &mut self,
1135 coords: &[(SheetId, AbsCoord)],
1136 ) -> (Vec<VertexId>, Vec<(VertexAddr, u32)>) {
1137 let mut packed: Vec<PackedSheetCell> = Vec::with_capacity(coords.len());
1138 for &(sid, coord) in coords {
1139 packed.push(Self::packed_cell_key(sid, coord));
1140 }
1141 self.ensure_vertices_batch_packed_ordered(&packed)
1142 }
1143
1144 #[inline]
1145 fn packed_cell_key(sheet_id: SheetId, coord: AbsCoord) -> PackedSheetCell {
1146 PackedSheetCell::try_new(sheet_id, coord.row(), coord.col())
1147 .expect("graph coordinate must fit PackedSheetCell")
1148 }
1149
1150 fn flush_load_packed_mappings(&mut self) {
1151 if self.load_packed_to_vertex.is_empty() {
1152 return;
1153 }
1154 let debug = std::env::var("FZ_DEBUG_LOAD")
1155 .ok()
1156 .is_some_and(|v| v != "0");
1157 let t0 = crate::instant::FzInstant::now();
1158 let count = self.load_packed_to_vertex.len();
1159 self.cell_to_vertex.reserve(count);
1160 let packed_mappings = std::mem::replace(
1164 &mut self.load_packed_to_vertex,
1165 std::collections::HashMap::with_hasher(CoordBuildHasher),
1166 );
1167 for (packed, vid) in packed_mappings {
1168 let coord = AbsCoord::new(packed.row0(), packed.col0());
1169 let addr = CellRef::new(
1170 packed.sheet_id(),
1171 Coord::new(coord.row(), coord.col(), true, true),
1172 );
1173 self.cell_to_vertex.insert(addr, vid);
1174 }
1175 if debug {
1176 eprintln!(
1177 "[fz][load] flush_load_packed_mappings: {} entries in {:.1} ms",
1178 count,
1179 t0.elapsed().as_secs_f64() * 1000.0,
1180 );
1181 }
1182 }
1183
1184 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
1189 let leaving = self.first_load_assume_new && !enabled;
1190 if leaving {
1191 self.flush_load_packed_mappings();
1192 self.store.shrink_to_fit();
1195 self.extent_record.fold_pending();
1199 } else if enabled {
1200 self.load_packed_to_vertex.clear();
1201 }
1202 self.first_load_assume_new = enabled;
1203 if leaving {
1204 self.authority_sync();
1205 }
1206 }
1207
1208 pub(crate) fn formula_compression_enabled(&self) -> bool {
1210 self.config.formula_compression
1211 }
1212
1213 #[doc(hidden)]
1214 pub fn first_load_assume_new(&self) -> bool {
1215 self.first_load_assume_new
1216 }
1217
1218 pub fn reset_ensure_touched(&mut self) {
1220 self.ensure_touched_sheets.clear();
1221 }
1222
1223 pub fn store_ast(&mut self, ast: &formualizer_parse::parser::ASTNode) -> AstNodeId {
1225 self.data_store.store_ast(ast, &self.sheet_reg)
1226 }
1227
1228 pub fn store_asts_batch<'a, I>(&mut self, asts: I) -> Vec<AstNodeId>
1230 where
1231 I: IntoIterator<Item = &'a formualizer_parse::parser::ASTNode>,
1232 {
1233 self.data_store.store_asts_batch(asts, &self.sheet_reg)
1234 }
1235
1236 pub fn reserve_formula_metadata(&mut self, additional: usize) {
1238 self.vertex_formulas.reserve(additional);
1239 self.formula_dirty.legacy_reserve(additional);
1240 self.volatile_vertices.reserve(additional);
1241 }
1242
1243 pub fn vid_for_sid_pc(&self, sid: SheetId, pc: AbsCoord) -> Option<VertexId> {
1245 let addr = CellRef::new(sid, Coord::new(pc.row(), pc.col(), true, true));
1246 self.cell_vertex(&addr)
1247 }
1248
1249 pub fn vid_for_plan_idx(
1251 &self,
1252 plan: &crate::engine::plan::DependencyPlan,
1253 idx: u32,
1254 ) -> Option<VertexId> {
1255 let (sid, pc) = plan.global_cells.get(idx as usize).copied()?;
1256 self.vid_for_sid_pc(sid, pc)
1257 }
1258 pub fn assign_formula_vertex(
1260 &mut self,
1261 vid: VertexId,
1262 ast_id: AstNodeId,
1263 volatile: bool,
1264 dynamic: bool,
1265 ) {
1266 self.assign_formula_ref(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1267 }
1268
1269 pub(crate) fn assign_formula_ref(
1271 &mut self,
1272 vid: VertexId,
1273 formula: FormulaRef,
1274 volatile: bool,
1275 dynamic: bool,
1276 ) {
1277 self.materialize_vertex(vid);
1278 if self.vertex_formulas.contains_key(&vid) {
1279 self.remove_dependent_edges(vid);
1280 }
1281 self.store
1282 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1283 self.vertex_values.remove(&vid);
1284 self.vertex_formulas.insert_ref(vid, formula);
1285 self.mark_volatile(vid, volatile);
1286 self.store.set_dynamic(vid, dynamic);
1287
1288 self.mark_vertex_dirty(vid);
1290 }
1291
1292 pub fn assign_formula_vertex_load_fast(
1295 &mut self,
1296 vid: VertexId,
1297 ast_id: AstNodeId,
1298 volatile: bool,
1299 dynamic: bool,
1300 ) {
1301 self.assign_formula_ref_load_fast(vid, FormulaRef::Own(ast_id), volatile, dynamic);
1302 }
1303
1304 pub(crate) fn assign_formula_ref_load_fast(
1307 &mut self,
1308 vid: VertexId,
1309 formula: FormulaRef,
1310 volatile: bool,
1311 dynamic: bool,
1312 ) {
1313 self.materialize_vertex(vid);
1314 debug_assert!(
1315 !self.vertex_formulas.contains_key(&vid),
1316 "load-fast formula assignment expects fresh/non-formula vertices"
1317 );
1318 self.store
1319 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1320 self.vertex_values.remove(&vid);
1321 self.vertex_formulas.insert_ref(vid, formula);
1322 self.mark_volatile(vid, volatile);
1323 self.store.set_dynamic(vid, dynamic);
1324 }
1325
1326 pub(crate) fn assign_unmapped_member_load_fast(&mut self, vid: VertexId) {
1331 self.store
1332 .set_kind(vid, crate::engine::vertex::VertexKind::FormulaScalar);
1333 self.store.set_dynamic(vid, false);
1334 self.vertex_formulas.touch(vid);
1335 }
1336
1337 pub(crate) fn preallocate_load_targets(
1345 &mut self,
1346 sheet: SheetId,
1347 mut targets: Vec<(u32, u32, Option<FormulaRef>)>,
1348 ) -> usize {
1349 if targets.is_empty() {
1350 return 0;
1351 }
1352 targets.sort_by_key(|&(r, c, _)| (c, r));
1354 let mut dedup: Vec<(u32, u32, Option<FormulaRef>)> = Vec::with_capacity(targets.len());
1355 for t in targets {
1356 match dedup.last_mut() {
1357 Some(last) if (last.0, last.1) == (t.0, t.1) => *last = t,
1358 _ => dedup.push(t),
1359 }
1360 }
1361 let compress = self.config.formula_compression;
1362 let mut packed = Vec::with_capacity(dedup.len());
1363 let mut unmapped = Vec::with_capacity(dedup.len());
1364 for &(r, c, f) in &dedup {
1365 let Some(p) = PackedSheetCell::try_from_excel_1based(sheet, r, c) else {
1366 continue;
1368 };
1369 packed.push(p);
1370 unmapped.push(compress && matches!(f, Some(FormulaRef::Member { .. })));
1371 }
1372 let formulas: Vec<Option<FormulaRef>> = dedup
1373 .iter()
1374 .filter(|&&(r, c, _)| PackedSheetCell::try_from_excel_1based(sheet, r, c).is_some())
1375 .map(|&(_, _, f)| f)
1376 .collect();
1377 let (vids, created) =
1378 self.ensure_vertices_batch_packed_ordered_unmapped(&packed, &mut unmapped);
1379 let mut members = Vec::new();
1380 for (i, &v) in vids.iter().enumerate() {
1381 if unmapped[i]
1382 && let Some(f) = formulas[i]
1383 {
1384 members.push((v, sheet, packed[i].row0(), packed[i].col0(), f));
1385 }
1386 }
1387 self.install_preallocated_members(members);
1388 created.len()
1389 }
1390
1391 fn install_preallocated_members(
1395 &mut self,
1396 members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1397 ) {
1398 let mut i = 0;
1399 while i < members.len() {
1400 let (v, sheet, row, col, f) = members[i];
1401 let mut len = 1usize;
1402 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1403 && v2.0 == v.0 + len as u32
1404 && s2 == sheet
1405 && c2 == col
1406 && r2 == row + len as u32
1407 && f2 == f
1408 {
1409 len += 1;
1410 }
1411 match f {
1412 FormulaRef::Member { template, anchor } if len >= 2 => {
1413 let run = virtual_members::MemberRun {
1414 sheet,
1415 col,
1416 row0: row,
1417 len: len as u32,
1418 first: v.0,
1419 template,
1420 anchor,
1421 };
1422 for (m, _) in run.members() {
1423 self.store
1424 .set_kind(m, crate::engine::vertex::VertexKind::FormulaScalar);
1425 self.store.set_virtual(m, true);
1426 self.vertex_formulas.touch(m);
1427 }
1428 self.vertex_formulas.virtual_members_mut().insert(run);
1429 }
1430 _ => {
1431 for &(m, s, r, c, _) in &members[i..i + len] {
1432 self.map_load_vertex(m, s, r, c);
1433 }
1434 }
1435 }
1436 i += len;
1437 }
1438 }
1439
1440 fn map_load_vertex(&mut self, v: VertexId, sheet: SheetId, row: u32, col: u32) {
1442 if self.first_load_assume_new {
1443 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1444 self.load_packed_to_vertex.insert(packed, v);
1445 } else {
1446 self.cell_to_vertex
1447 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1448 }
1449 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1450 self.sheet_index_mut(sheet)
1451 .add_vertex(GridAddr::new(row, col), v);
1452 }
1453 }
1454
1455 pub(crate) fn assign_preallocated_member(
1460 &mut self,
1461 vid: VertexId,
1462 formula: FormulaRef,
1463 volatile: bool,
1464 dynamic: bool,
1465 ) {
1466 if !volatile && !dynamic && self.vertex_formulas.get(&vid) == Some(formula) {
1467 return;
1468 }
1469 self.materialize_vertex(vid);
1470 self.vertex_formulas.forget_materialized(&vid);
1471 self.assign_formula_ref_load_fast(vid, formula, volatile, dynamic);
1472 }
1473
1474 pub(crate) fn install_load_members(
1479 &mut self,
1480 mut members: Vec<(VertexId, SheetId, u32, u32, FormulaRef)>,
1481 ) {
1482 if members.is_empty() {
1483 return;
1484 }
1485 members.sort_by_key(|m| m.0);
1487 members.reverse();
1488 members.dedup_by_key(|m| m.0);
1489 members.reverse();
1490 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
1491 let mut singles: Vec<usize> = Vec::new();
1492 let mut i = 0;
1493 while i < members.len() {
1494 let (v, sheet, row, col, f) = members[i];
1495 let mut len = 1usize;
1496 if let FormulaRef::Member { template, anchor } = f {
1497 while let Some(&(v2, s2, r2, c2, f2)) = members.get(i + len)
1498 && v2.0 == v.0 + len as u32
1499 && s2 == sheet
1500 && c2 == col
1501 && r2 == row + len as u32
1502 && f2 == f
1503 {
1504 len += 1;
1505 }
1506 if len >= 2 {
1507 runs.push(virtual_members::MemberRun {
1508 sheet,
1509 col,
1510 row0: row,
1511 len: len as u32,
1512 first: v.0,
1513 template,
1514 anchor,
1515 });
1516 i += len;
1517 continue;
1518 }
1519 }
1520 singles.push(i);
1521 i += 1;
1522 }
1523 for i in singles {
1524 let (v, sheet, row, col, f) = members[i];
1525 self.vertex_formulas.restore(v, f);
1526 if self.first_load_assume_new {
1527 let packed = Self::packed_cell_key(sheet, AbsCoord::new(row, col));
1528 self.load_packed_to_vertex.insert(packed, v);
1529 } else {
1530 self.cell_to_vertex
1531 .insert(CellRef::new(sheet, Coord::new(row, col, true, true)), v);
1532 }
1533 if self.config.sheet_index_mode != crate::engine::SheetIndexMode::Lazy {
1534 self.sheet_index_mut(sheet)
1535 .add_vertex(GridAddr::new(row, col), v);
1536 }
1537 }
1538 for r in runs {
1539 for (v, _) in r.members() {
1540 self.store.set_virtual(v, true);
1541 }
1542 self.vertex_formulas.virtual_members_mut().insert(r);
1543 }
1544 }
1545
1546 #[cfg(any(test, feature = "legacy_oracle"))]
1547 pub fn add_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
1549 self.add_dependent_edges_nobatch(dependent, dependencies);
1550 }
1551
1552 pub fn iter_vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
1554 self.store.all_vertices()
1555 }
1556
1557 pub fn vertex_addr(&self, vid: VertexId) -> VertexAddr {
1560 self.store.addr(vid)
1561 }
1562
1563 pub fn vertex_grid_addr(&self, vid: VertexId) -> Option<GridAddr> {
1565 self.store.grid_addr(vid)
1566 }
1567
1568 pub fn vertex_count(&self) -> usize {
1570 self.store.len()
1571 }
1572
1573 #[cfg(any(test, feature = "legacy_oracle"))]
1574 pub fn build_edges_from_adjacency(
1576 &mut self,
1577 adjacency: Vec<(u32, Vec<u32>)>,
1578 coords: Vec<VertexAddr>,
1579 vertex_ids: Vec<u32>,
1580 ) {
1581 #[cfg(not(any(test, feature = "legacy_oracle")))]
1582 let _ = (&adjacency, &coords, &vertex_ids);
1583 #[cfg(any(test, feature = "legacy_oracle"))]
1584 {
1585 let adjacency = self.edges.adjacency_with_carried_forward_edges(adjacency);
1589 self.edges
1590 .build_from_adjacency(adjacency, coords, vertex_ids);
1591 }
1592 }
1593 pub fn used_row_bounds_for_columns(
1595 &self,
1596 sheet_id: SheetId,
1597 start_col: u32,
1598 end_col: u32,
1599 ) -> Option<(u32, u32)> {
1600 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1602 && !index.is_empty()
1603 {
1604 let mut min_r: Option<u32> = None;
1605 let mut max_r: Option<u32> = None;
1606 for vid in index.vertices_in_col_range(start_col, end_col) {
1607 let Some(r) = self.store.grid_addr(vid).map(|addr| addr.row()) else {
1608 continue;
1609 };
1610 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1611 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1612 }
1613 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1614 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1615 return match (min_r, max_r) {
1616 (Some(a), Some(b)) => Some((a, b)),
1617 _ => None,
1618 };
1619 }
1620 let mut min_r: Option<u32> = None;
1622 let mut max_r: Option<u32> = None;
1623 for cref in self.cell_to_vertex.keys() {
1624 if cref.sheet_id == sheet_id {
1625 let c = cref.coord.col();
1626 if c >= start_col && c <= end_col {
1627 let r = cref.coord.row();
1628 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1629 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1630 }
1631 }
1632 }
1633 for packed in self.load_packed_to_vertex.keys() {
1634 if packed.sheet_id() == sheet_id {
1635 let c = packed.col0();
1636 if c >= start_col && c <= end_col {
1637 let r = packed.row0();
1638 min_r = Some(min_r.map(|m| m.min(r)).unwrap_or(r));
1639 max_r = Some(max_r.map(|m| m.max(r)).unwrap_or(r));
1640 }
1641 }
1642 }
1643 self.virtual_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1644 self.extent_row_bounds(sheet_id, start_col, end_col, &mut min_r, &mut max_r);
1645 match (min_r, max_r) {
1646 (Some(a), Some(b)) => Some((a, b)),
1647 _ => None,
1648 }
1649 }
1650
1651 pub fn finalize_sheet_index(&mut self, sheet: &str) {
1653 let Some(sheet_id) = self.sheet_reg.get_id(sheet) else {
1654 return;
1655 };
1656 self.rebuild_sheet_index(sheet_id);
1657 }
1658
1659 fn rebuild_sheet_index(&mut self, sheet_id: SheetId) {
1660 let mut idx = SheetIndex::new();
1661 let mut batch: Vec<(GridAddr, VertexId)> =
1662 Vec::with_capacity(self.cell_to_vertex.len() + self.load_packed_to_vertex.len());
1663 for (cref, vid) in &self.cell_to_vertex {
1664 if cref.sheet_id == sheet_id {
1665 batch.push((GridAddr::new(cref.coord.row(), cref.coord.col()), *vid));
1666 }
1667 }
1668 for (&packed, &vid) in &self.load_packed_to_vertex {
1669 if packed.sheet_id() != sheet_id {
1670 continue;
1671 }
1672 let coord = GridAddr::new(packed.row0(), packed.col0());
1673 let addr = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
1674 if self.cell_to_vertex.contains_key(&addr) {
1675 continue;
1676 }
1677 batch.push((coord, vid));
1678 }
1679 idx.add_vertices_batch(&batch);
1680 self.sheet_indexes.insert(sheet_id, idx);
1681 }
1682
1683 pub(crate) fn prepare_sheet_index_for_query(&mut self, sheet_id: SheetId) {
1687 if self.config.sheet_index_mode == crate::engine::SheetIndexMode::Lazy {
1688 self.rebuild_sheet_index(sheet_id);
1689 }
1690 }
1691
1692 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
1693 self.config.sheet_index_mode = mode;
1694 }
1695
1696 pub(crate) fn set_evaluation_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
1697 self.config.evaluation_budgets = budgets;
1698 }
1699
1700 pub fn used_col_bounds_for_rows(
1702 &self,
1703 sheet_id: SheetId,
1704 start_row: u32,
1705 end_row: u32,
1706 ) -> Option<(u32, u32)> {
1707 if let Some(index) = self.sheet_indexes.get(&sheet_id)
1708 && !index.is_empty()
1709 {
1710 let mut min_c: Option<u32> = None;
1711 let mut max_c: Option<u32> = None;
1712 for vid in index.vertices_in_row_range(start_row, end_row) {
1713 let Some(c) = self.store.grid_addr(vid).map(|addr| addr.col()) else {
1714 continue;
1715 };
1716 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1717 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1718 }
1719 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1720 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1721 return match (min_c, max_c) {
1722 (Some(a), Some(b)) => Some((a, b)),
1723 _ => None,
1724 };
1725 }
1726 let mut min_c: Option<u32> = None;
1728 let mut max_c: Option<u32> = None;
1729 for cref in self.cell_to_vertex.keys() {
1730 if cref.sheet_id == sheet_id {
1731 let r = cref.coord.row();
1732 if r >= start_row && r <= end_row {
1733 let c = cref.coord.col();
1734 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1735 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1736 }
1737 }
1738 }
1739 for packed in self.load_packed_to_vertex.keys() {
1740 if packed.sheet_id() == sheet_id {
1741 let r = packed.row0();
1742 if r >= start_row && r <= end_row {
1743 let c = packed.col0();
1744 min_c = Some(min_c.map(|m| m.min(c)).unwrap_or(c));
1745 max_c = Some(max_c.map(|m| m.max(c)).unwrap_or(c));
1746 }
1747 }
1748 }
1749 self.virtual_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1750 self.extent_col_bounds(sheet_id, start_row, end_row, &mut min_c, &mut max_c);
1751 match (min_c, max_c) {
1752 (Some(a), Some(b)) => Some((a, b)),
1753 _ => None,
1754 }
1755 }
1756
1757 fn extent_row_bounds(
1759 &self,
1760 sheet: SheetId,
1761 c0: u32,
1762 c1: u32,
1763 min: &mut Option<u32>,
1764 max: &mut Option<u32>,
1765 ) {
1766 if let Some((a, b)) = self.extent_record.row_bounds_for_cols(sheet, c0, c1) {
1767 *min = Some(min.map_or(a, |m| m.min(a)));
1768 *max = Some(max.map_or(b, |m| m.max(b)));
1769 }
1770 }
1771
1772 fn extent_col_bounds(
1774 &self,
1775 sheet: SheetId,
1776 r0: u32,
1777 r1: u32,
1778 min: &mut Option<u32>,
1779 max: &mut Option<u32>,
1780 ) {
1781 if let Some((a, b)) = self.extent_record.col_bounds_for_rows(sheet, r0, r1) {
1782 *min = Some(min.map_or(a, |m| m.min(a)));
1783 *max = Some(max.map_or(b, |m| m.max(b)));
1784 }
1785 }
1786
1787 fn virtual_row_bounds(
1789 &self,
1790 sheet: SheetId,
1791 c0: u32,
1792 c1: u32,
1793 min: &mut Option<u32>,
1794 max: &mut Option<u32>,
1795 ) {
1796 for r in self
1797 .vertex_formulas
1798 .virtual_members()
1799 .runs_in_cols(sheet, c0, c1)
1800 {
1801 let (a, b) = (r.row0, r.row0 + r.len - 1);
1802 *min = Some(min.map_or(a, |m| m.min(a)));
1803 *max = Some(max.map_or(b, |m| m.max(b)));
1804 }
1805 }
1806
1807 fn virtual_col_bounds(
1809 &self,
1810 sheet: SheetId,
1811 r0: u32,
1812 r1: u32,
1813 min: &mut Option<u32>,
1814 max: &mut Option<u32>,
1815 ) {
1816 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
1817 if r.row0 <= r1 && r.row0 + r.len > r0 {
1818 *min = Some(min.map_or(r.col, |m| m.min(r.col)));
1819 *max = Some(max.map_or(r.col, |m| m.max(r.col)));
1820 }
1821 }
1822 }
1823
1824 pub fn sheet_has_formulas(&self, sheet_id: SheetId) -> bool {
1826 for vid in self.vertex_formulas.keys() {
1828 if self.store.sheet_id(vid) == sheet_id {
1829 return true;
1830 }
1831 }
1832 false
1833 }
1834 pub fn new() -> Self {
1835 Self::new_with_config(super::EvalConfig::default())
1836 }
1837
1838 pub fn new_with_config(config: super::EvalConfig) -> Self {
1839 let mut sheet_reg = SheetRegistry::new();
1840 let default_sheet_id = sheet_reg.id_for(&config.default_sheet_name);
1841
1842 #[cfg_attr(not(any(test, feature = "legacy_oracle")), allow(unused_mut))]
1843 let mut g = Self {
1844 store: VertexStore::new(),
1845 #[cfg(any(test, feature = "legacy_oracle"))]
1846 edges: CsrMutableEdges::new(),
1847 dep_edge_total: 0,
1848 range_reader_count: 0,
1849 renamed_sheet_aliases: FxHashMap::default(),
1850 data_store: DataStore::new(),
1851 vertex_values: FxHashMap::default(),
1852 vertex_formulas: FormulaMap::default(),
1853 value_cache_enabled: false,
1856 #[cfg(debug_assertions)]
1857 graph_value_read_attempts: AtomicU64::new(0),
1858 cell_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1859 vertex_journal: Default::default(),
1860 load_packed_to_vertex: std::collections::HashMap::with_hasher(CoordBuildHasher),
1861 formula_dirty: FormulaDirtyState::default(),
1862 dirty_propagation_visits: 0,
1863 deferred_dirty_depth: 0,
1864 deferred_dirty_pending: Vec::new(),
1865 deferred_dirty_pending_rects: Vec::new(),
1866 retired_ids: std::collections::BTreeMap::new(),
1867 retired_id_set: FxHashSet::default(),
1868 retired_dropped: Vec::new(),
1869 retired_dropped_by_undo: Vec::new(),
1870 extent_record: Default::default(),
1871 extent_dropped: Vec::new(),
1872 extent_undone: Vec::new(),
1873 volatile_vertices: FxHashSet::default(),
1874 ref_error_vertices: FxHashSet::default(),
1875 #[cfg(any(test, feature = "legacy_oracle"))]
1876 formula_to_range_deps: FxHashMap::default(),
1877 #[cfg(any(test, feature = "legacy_oracle"))]
1878 stripe_to_dependents: FxHashMap::default(),
1879 sheet_indexes: FxHashMap::default(),
1880 sheet_reg,
1881 default_sheet_id,
1882 named_ranges: FxHashMap::default(),
1883 named_ranges_lookup: FxHashMap::default(),
1884 sheet_named_ranges: FxHashMap::default(),
1885 sheet_named_ranges_lookup: FxHashMap::default(),
1886 #[cfg(any(test, feature = "legacy_oracle"))]
1887 vertex_to_names: FxHashMap::default(),
1888 name_vertex_lookup: FxHashMap::default(),
1889 pending_name_links: FxHashMap::default(),
1890 vertex_to_pending_names: FxHashMap::default(),
1891 tables: FxHashMap::default(),
1892 tables_lookup: FxHashMap::default(),
1893 table_vertex_lookup: FxHashMap::default(),
1894 source_scalars: FxHashMap::default(),
1895 source_tables: FxHashMap::default(),
1896 source_vertex_lookup: FxHashMap::default(),
1897 symbol_vertex_seq: 0,
1898 #[cfg(any(test, feature = "legacy_oracle"))]
1899 cell_to_name_dependents: FxHashMap::default(),
1900 #[cfg(any(test, feature = "legacy_oracle"))]
1901 name_to_cell_dependencies: FxHashMap::default(),
1902 #[cfg(any(test, feature = "legacy_oracle"))]
1903 oracle_vertexless_readers: FxHashMap::default(),
1904 #[cfg(any(test, feature = "legacy_oracle"))]
1905 oracle_vertexless_of: FxHashMap::default(),
1906 config: config.clone(),
1907 topology_revision: 0,
1908 symbol_revision: 0,
1909 authority: Default::default(),
1910 #[cfg(any(test, feature = "legacy_oracle"))]
1911 pk_order: None,
1912 spill_anchor_to_cells: FxHashMap::default(),
1913 spill_cell_to_anchor: std::collections::HashMap::with_hasher(CoordBuildHasher),
1914 spill_cells_by_sheet: FxHashMap::default(),
1915 admission_budget_override: None,
1916 first_load_assume_new: false,
1917 ensure_touched_sheets: FxHashSet::default(),
1918 tombstone_registry: TombstoneRegistry::default(),
1919 #[cfg(test)]
1920 instr: std::sync::Mutex::new(GraphInstrumentation::default()),
1921 #[cfg(test)]
1922 prepared_legacy_graph_failure_for_test: false,
1923 };
1924
1925 #[cfg(any(test, feature = "legacy_oracle"))]
1926 if config.use_dynamic_topo {
1927 let nodes = g
1929 .store
1930 .all_vertices()
1931 .filter(|&id| g.store.vertex_exists_active(id));
1932 let mut pk = DynamicTopo::new(
1933 nodes,
1934 PkConfig {
1935 visit_budget: config.pk_visit_budget,
1936 compaction_interval_ops: config.pk_compaction_interval_ops,
1937 },
1938 );
1939 let adapter = GraphAdapter { g: &g };
1941 pk.rebuild_full(&adapter);
1942 g.pk_order = Some(pk);
1943 }
1944
1945 g
1946 }
1947
1948 #[cfg(any(test, feature = "legacy_oracle"))]
1949 pub(crate) fn pk_layers_for(&self, subset: &[VertexId]) -> Option<Vec<crate::engine::Layer>> {
1951 let pk = self.pk_order.as_ref()?;
1952 let adapter = crate::engine::topo::GraphAdapter { g: self };
1953 let layers = pk.layers_for(&adapter, subset, self.config.max_layer_width);
1954 Some(layers.into_iter().map(crate::engine::Layer::new).collect())
1955 }
1956
1957 #[cfg(any(test, feature = "legacy_oracle"))]
1958 #[inline]
1959 pub(crate) fn dynamic_topo_enabled(&self) -> bool {
1960 self.pk_order.is_some()
1961 }
1962
1963 #[cfg(test)]
1964 pub fn reset_instr(&mut self) {
1965 if let Ok(mut g) = self.instr.lock() {
1966 *g = GraphInstrumentation::default();
1967 }
1968 }
1969
1970 #[cfg(test)]
1971 pub fn instr(&self) -> GraphInstrumentation {
1972 self.instr.lock().map(|g| g.clone()).unwrap_or_default()
1973 }
1974
1975 pub(crate) fn pk_active(&self) -> bool {
1978 #[cfg(any(test, feature = "legacy_oracle"))]
1979 {
1980 self.pk_order.is_some()
1981 }
1982 #[cfg(not(any(test, feature = "legacy_oracle")))]
1983 {
1984 false
1985 }
1986 }
1987
1988 pub fn begin_batch(&mut self) {
1990 #[cfg(any(test, feature = "legacy_oracle"))]
1991 self.edges.begin_batch();
1992 }
1993
1994 pub fn end_batch(&mut self) {
1996 #[cfg(any(test, feature = "legacy_oracle"))]
1997 self.edges.end_batch();
1998 }
1999
2000 pub fn default_sheet_id(&self) -> SheetId {
2001 self.default_sheet_id
2002 }
2003
2004 pub fn default_sheet_name(&self) -> &str {
2005 self.sheet_reg.name(self.default_sheet_id)
2006 }
2007
2008 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2009 self.default_sheet_id = self.sheet_id_mut(name);
2010 }
2011
2012 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2013 self.default_sheet_id = id;
2014 }
2015
2016 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
2018 if let Some(id) = self.sheet_reg.get_id(name) {
2019 return id;
2020 }
2021 let id = self.sheet_reg.id_for(name);
2022 self.resolve_pending_symbol("sheet", name);
2023 id
2024 }
2025
2026 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
2027 self.sheet_reg.get_id(name)
2028 }
2029
2030 fn resolve_existing_sheet_id(&self, name: &str) -> Result<SheetId, ExcelError> {
2032 self.sheet_id(name).ok_or_else(|| {
2033 ExcelError::new(ExcelErrorKind::Ref).with_message(format!("Sheet not found: {name}"))
2034 })
2035 }
2036
2037 pub fn sheet_name(&self, id: SheetId) -> &str {
2039 self.sheet_reg.name(id)
2040 }
2041
2042 pub fn sheet_reg(&self) -> &SheetRegistry {
2044 &self.sheet_reg
2045 }
2046
2047 pub(crate) fn data_store(&self) -> &DataStore {
2048 &self.data_store
2049 }
2050
2051 pub(crate) fn make_ingest_pipeline<'a>(
2052 &'a mut self,
2053 function_provider: &'a dyn crate::traits::FunctionProvider,
2054 policy: formualizer_parse::parser::CollectPolicy,
2055 ) -> crate::engine::ingest_pipeline::IngestPipeline<'a> {
2056 use crate::engine::ingest_pipeline::{
2057 NameRegistryView, NamedEntryRef, NamedTarget, SourceEntryRef, SourceRegistryView,
2058 TableEntrySnapshot, TableRegistryView,
2059 };
2060
2061 let DependencyGraph {
2062 data_store,
2063 sheet_reg,
2064 named_ranges,
2065 named_ranges_lookup,
2066 sheet_named_ranges,
2067 sheet_named_ranges_lookup,
2068 tables,
2069 tables_lookup,
2070 source_scalars,
2071 source_tables,
2072 config,
2073 ..
2074 } = self;
2075
2076 let unbound_pending =
2077 config.preparation_policy == crate::engine::PreparationPolicy::BestEffort;
2078 let case_sensitive_names = config.case_sensitive_names;
2079 let names = NameRegistryView::new(move |name, current_sheet| {
2080 let found = if case_sensitive_names {
2081 sheet_named_ranges
2082 .get(&(current_sheet, name.to_string()))
2083 .or_else(|| named_ranges.get(name))
2084 } else {
2085 let key = name.to_lowercase();
2086 sheet_named_ranges_lookup
2087 .get(&(current_sheet, key.clone()))
2088 .and_then(|canon| sheet_named_ranges.get(&(current_sheet, canon.clone())))
2089 .or_else(|| {
2090 named_ranges_lookup
2091 .get(&key)
2092 .and_then(|canon| named_ranges.get(canon))
2093 })
2094 };
2095 found.map(|entry| NamedEntryRef {
2096 vertex: entry.vertex,
2097 target: match &entry.definition {
2098 crate::engine::named_range::NamedDefinition::Cell(cell) => {
2099 NamedTarget::Cell(*cell)
2100 }
2101 crate::engine::named_range::NamedDefinition::Range(range) => {
2102 NamedTarget::Range(*range)
2103 }
2104 crate::engine::named_range::NamedDefinition::Literal(_)
2105 | crate::engine::named_range::NamedDefinition::Formula { .. } => {
2106 NamedTarget::Other
2107 }
2108 },
2109 })
2110 });
2111
2112 let case_sensitive_tables = config.case_sensitive_tables;
2113 let tables_ref = &*tables;
2114 let tables_lookup_ref = &*tables_lookup;
2115 let snapshot_table = |entry: &tables::TableEntry| TableEntrySnapshot {
2116 name: entry.name.clone(),
2117 range: entry.range,
2118 header_row: entry.header_row,
2119 headers: entry.headers.clone(),
2120 vertex: entry.vertex,
2121 };
2122 let tables_view = TableRegistryView::new(
2123 move |name| {
2124 if case_sensitive_tables {
2125 tables_ref.get(name).map(snapshot_table)
2126 } else {
2127 let key = name.to_lowercase();
2128 tables_lookup_ref
2129 .get(&key)
2130 .and_then(|canon| tables_ref.get(canon))
2131 .map(snapshot_table)
2132 }
2133 },
2134 move |cell| {
2135 let row0 = cell.coord.row();
2136 let col0 = cell.coord.col();
2137 let mut best: Option<&tables::TableEntry> = None;
2138 let mut best_area = u64::MAX;
2139 let mut best_name = "";
2140 for table in tables_ref.values() {
2141 if table.sheet_id() != cell.sheet_id {
2142 continue;
2143 }
2144 let sr0 = table.range.start.coord.row();
2145 let sc0 = table.range.start.coord.col();
2146 let er0 = table.range.end.coord.row();
2147 let ec0 = table.range.end.coord.col();
2148 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
2149 continue;
2150 }
2151 let area = ((er0 - sr0 + 1) as u64).saturating_mul((ec0 - sc0 + 1) as u64);
2152 let name = table.name.as_str();
2153 if best.is_none() || area < best_area || (area == best_area && name < best_name)
2154 {
2155 best = Some(table);
2156 best_area = area;
2157 best_name = name;
2158 }
2159 }
2160 best.map(snapshot_table)
2161 },
2162 );
2163
2164 let sources = SourceRegistryView::new(
2165 move |name| {
2166 source_scalars.get(name).map(|entry| SourceEntryRef {
2167 vertex: entry.vertex,
2168 })
2169 },
2170 move |name| {
2171 source_tables.get(name).map(|entry| SourceEntryRef {
2172 vertex: entry.vertex,
2173 })
2174 },
2175 );
2176
2177 crate::engine::ingest_pipeline::IngestPipeline::new(
2178 data_store,
2179 sheet_reg,
2180 names,
2181 tables_view,
2182 sources,
2183 function_provider,
2184 policy,
2185 )
2186 .with_unbound_pending(unbound_pending)
2187 }
2188
2189 pub fn to_a1(&self, cell_ref: CellRef) -> String {
2191 format!("{}!{}", self.sheet_name(cell_ref.sheet_id), cell_ref.coord)
2192 }
2193
2194 pub(crate) fn vertex_len(&self) -> usize {
2195 self.store.len()
2196 }
2197
2198 #[cfg(test)]
2200 pub(crate) fn next_vertex_id_for_test(&self) -> u32 {
2201 crate::engine::vertex_store::FIRST_NORMAL_VERTEX + self.store.len() as u32
2202 }
2203
2204 pub(crate) fn topology_revision(&self) -> u64 {
2205 self.topology_revision
2206 }
2207
2208 pub(crate) fn bump_topology_revision(&mut self) {
2209 self.topology_revision = self.topology_revision.wrapping_add(1);
2210 }
2211
2212 pub(crate) fn symbol_revision(&self) -> u64 {
2213 self.symbol_revision
2214 }
2215
2216 pub(crate) fn bump_symbol_revision(&mut self) {
2217 self.symbol_revision = self.symbol_revision.wrapping_add(1);
2218 self.authority_sync_if_ready();
2221 }
2222
2223 #[cfg(any(test, feature = "legacy_oracle"))]
2224 pub(crate) fn formula_range_dependencies(
2225 &self,
2226 vertex: VertexId,
2227 ) -> Option<&[SharedRangeRef<'static>]> {
2228 self.formula_to_range_deps.get(&vertex).map(Vec::as_slice)
2229 }
2230
2231 pub(crate) fn spill_anchors_in_region(
2232 &self,
2233 sheet_id: SheetId,
2234 start_row0: u32,
2235 start_col0: u32,
2236 end_row0: u32,
2237 end_col0: u32,
2238 ) -> Vec<VertexId> {
2239 let mut anchors = self
2240 .spill_cells_by_sheet
2241 .get(&sheet_id)
2242 .into_iter()
2243 .flat_map(|cells| cells.range((start_row0, 0)..=(end_row0, u32::MAX)))
2244 .filter_map(|(&(row, col), anchor)| {
2245 (row <= end_row0 && col >= start_col0 && col <= end_col0).then_some(*anchor)
2246 })
2247 .collect::<Vec<_>>();
2248 anchors.sort_unstable();
2249 anchors.dedup();
2250 anchors
2251 }
2252
2253 pub fn sheet_index_mut(&mut self, sheet_id: SheetId) -> &mut SheetIndex {
2256 self.sheet_indexes.entry(sheet_id).or_default()
2257 }
2258
2259 pub fn sheet_index(&self, sheet_id: SheetId) -> Option<&SheetIndex> {
2261 self.sheet_indexes.get(&sheet_id)
2262 }
2263
2264 pub(crate) fn sheet_index_vertex_count(&self, sheet_id: SheetId) -> usize {
2265 self.sheet_indexes.get(&sheet_id).map_or(0, SheetIndex::len)
2266 + self
2267 .vertex_formulas
2268 .virtual_members()
2269 .runs_in_sheet(sheet_id)
2270 .map(|r| r.len as usize)
2271 .sum::<usize>()
2272 }
2273
2274 pub(crate) fn set_admission_budget_override(
2275 &mut self,
2276 budgets: Option<crate::engine::EvaluationBudgets>,
2277 ) -> Option<crate::engine::EvaluationBudgets> {
2278 std::mem::replace(&mut self.admission_budget_override, budgets)
2279 }
2280
2281 fn self_admission_budgets(&self) -> crate::engine::EvaluationBudgets {
2282 self.admission_budget_override
2283 .clone()
2284 .unwrap_or_else(|| self.config.resolved_evaluation_budgets())
2285 }
2286
2287 fn preview_spill_materialization(
2288 &self,
2289 target_cells: &[CellRef],
2290 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2291 let unique = target_cells.iter().copied().collect::<FxHashSet<_>>();
2292 let added_vertices = unique
2293 .iter()
2294 .filter(|cell| self.cell_vertex(cell).is_none())
2295 .count();
2296 let stats = self.baseline_stats();
2297 Ok(crate::engine::resource_ledger::GraphAdmission {
2298 final_vertices: stats
2299 .graph_vertex_count
2300 .checked_add(added_vertices)
2301 .ok_or_else(|| {
2302 ExcelError::new(ExcelErrorKind::NImpl)
2303 .with_message("spill vertex count overflow")
2304 })?,
2305 final_edges: stats.graph_edge_count,
2306 materialization_cells: unique.len() as u64,
2307 added_vertices,
2308 added_edges: 0,
2309 })
2310 }
2311
2312 pub(crate) fn preview_value_mutation(
2313 &self,
2314 sheet_id: SheetId,
2315 row: u32,
2316 col: u32,
2317 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2318 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2319 let existing = self.cell_vertex(&cell);
2320 let stats = self.baseline_stats();
2321 let removed_edges = existing.map_or(0, |vertex| self.store.edge_offset(vertex) as usize);
2322 Ok(crate::engine::resource_ledger::GraphAdmission {
2324 final_vertices: stats.graph_vertex_count,
2325 final_edges: stats
2326 .graph_edge_count
2327 .checked_sub(removed_edges)
2328 .ok_or_else(|| {
2329 ExcelError::new(ExcelErrorKind::NImpl)
2330 .with_message("graph edge count underflow")
2331 })?,
2332 materialization_cells: 0,
2333 added_vertices: 0,
2334 added_edges: 0,
2335 })
2336 }
2337
2338 pub(crate) fn preview_value_mutations(
2339 &self,
2340 sheet_id: SheetId,
2341 cells: &[(u32, u32)],
2342 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2343 let mut targets = std::collections::BTreeSet::new();
2344 let added_vertices = 0usize;
2345 let mut removed_edges = 0usize;
2346 for (row, col) in cells {
2347 let packed = PackedSheetCell::try_from_excel_1based(sheet_id, *row, *col)
2348 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2349 if !targets.insert(packed) {
2350 continue;
2351 }
2352 let reference = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
2353 if let Some(vertex) = self.cell_vertex(&reference) {
2355 removed_edges = removed_edges
2356 .checked_add(self.store.edge_offset(vertex) as usize)
2357 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?;
2358 }
2359 }
2360 let stats = self.baseline_stats();
2361 Ok(crate::engine::resource_ledger::GraphAdmission {
2362 final_vertices: stats
2363 .graph_vertex_count
2364 .checked_add(added_vertices)
2365 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2366 final_edges: stats
2367 .graph_edge_count
2368 .checked_sub(removed_edges)
2369 .ok_or_else(|| ExcelError::new(ExcelErrorKind::NImpl))?,
2370 materialization_cells: 0,
2371 added_vertices,
2372 added_edges: 0,
2373 })
2374 }
2375
2376 pub(crate) fn preview_formula_mutations(
2377 &self,
2378 plans: &[(SheetId, u32, u32, DependencyPlanRow)],
2379 ) -> Result<crate::engine::resource_ledger::GraphAdmission, ExcelError> {
2380 let mut new_cells = std::collections::BTreeSet::new();
2381 let mut removed_edges = 0usize;
2382 let mut added_edges = 0usize;
2383 for (sheet_id, row, col, plan) in plans {
2384 let target = PackedSheetCell::try_from_excel_1based(*sheet_id, *row, *col)
2385 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2386 let target_ref = CellRef::new(*sheet_id, Coord::from_excel(*row, *col, true, true));
2387 if let Some(vertex) = self.cell_vertex(&target_ref) {
2388 removed_edges = removed_edges
2389 .checked_add(self.store.edge_offset(vertex) as usize)
2390 .ok_or_else(|| {
2391 ExcelError::new(ExcelErrorKind::NImpl)
2392 .with_message("graph edge count overflow")
2393 })?;
2394 } else {
2395 new_cells.insert(target);
2396 }
2397
2398 let mut dependencies = std::collections::BTreeSet::new();
2399 for dependency in &plan.direct_cell_deps {
2400 let packed = PackedSheetCell::try_new(
2401 dependency.sheet_id,
2402 dependency.coord.row(),
2403 dependency.coord.col(),
2404 )
2405 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
2406 let reference = CellRef::new(dependency.sheet_id, dependency.coord);
2407 if let Some(vertex) = self.cell_vertex(&reference) {
2408 dependencies.insert((0u8, u64::from(vertex.0)));
2409 } else {
2410 new_cells.insert(packed);
2411 dependencies.insert((1u8, packed.as_u64()));
2412 }
2413 }
2414 for name in plan.resolved_named_refs.iter().chain(&plan.named_refs) {
2415 if let Some(entry) = self.resolve_name_entry(name, *sheet_id) {
2416 dependencies.insert((0, u64::from(entry.vertex.0)));
2417 } else if let Some(entry) = self.resolve_source_scalar_entry(name) {
2418 dependencies.insert((0, u64::from(entry.vertex.0)));
2419 }
2420 }
2421 for name in &plan.source_refs {
2422 if let Some(vertex) = self
2423 .resolve_source_scalar_entry(name)
2424 .map(|entry| entry.vertex)
2425 .or_else(|| {
2426 self.resolve_source_table_entry(name)
2427 .map(|entry| entry.vertex)
2428 })
2429 {
2430 dependencies.insert((0, u64::from(vertex.0)));
2431 }
2432 }
2433 for name in &plan.table_refs {
2434 if let Some(vertex) = self
2435 .resolve_table_entry(name)
2436 .map(|entry| entry.vertex)
2437 .or_else(|| {
2438 self.resolve_source_table_entry(name)
2439 .map(|entry| entry.vertex)
2440 })
2441 {
2442 dependencies.insert((0, u64::from(vertex.0)));
2443 }
2444 }
2445 let target_row = target.row0();
2446 let target_col = target.col0();
2447 if plan.range_deps.iter().any(|range| {
2448 let range_sheet = self
2450 .sheet_reg
2451 .resolve_locator(&range.sheet, *sheet_id)
2452 .unwrap_or(*sheet_id);
2453 range_sheet == *sheet_id
2454 && range
2455 .start_row
2456 .is_none_or(|bound| target_row >= bound.index)
2457 && range.end_row.is_none_or(|bound| target_row <= bound.index)
2458 && range
2459 .start_col
2460 .is_none_or(|bound| target_col >= bound.index)
2461 && range.end_col.is_none_or(|bound| target_col <= bound.index)
2462 }) {
2463 dependencies.insert((1, target.as_u64()));
2464 }
2465 added_edges = added_edges.checked_add(dependencies.len()).ok_or_else(|| {
2466 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2467 })?;
2468 }
2469 let stats = self.baseline_stats();
2470 Ok(crate::engine::resource_ledger::GraphAdmission {
2471 final_vertices: stats
2472 .graph_vertex_count
2473 .checked_add(new_cells.len())
2474 .ok_or_else(|| {
2475 ExcelError::new(ExcelErrorKind::NImpl)
2476 .with_message("graph vertex count overflow")
2477 })?,
2478 final_edges: stats
2479 .graph_edge_count
2480 .checked_sub(removed_edges)
2481 .and_then(|count| count.checked_add(added_edges))
2482 .ok_or_else(|| {
2483 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
2484 })?,
2485 materialization_cells: plans.len() as u64,
2486 added_vertices: new_cells.len(),
2487 added_edges,
2488 })
2489 }
2490
2491 pub(crate) fn vertices_in_region(
2492 &self,
2493 sheet_id: SheetId,
2494 start_row0: u32,
2495 end_row0: u32,
2496 start_col0: u32,
2497 end_col0: u32,
2498 ) -> Vec<VertexId> {
2499 let Some(index) = self.sheet_indexes.get(&sheet_id) else {
2500 return Vec::new();
2501 };
2502 let mut out = index.vertices_in_rect(start_row0, end_row0, start_col0, end_col0);
2503 for r in self
2506 .vertex_formulas
2507 .virtual_members()
2508 .runs_in_cols(sheet_id, start_col0, end_col0)
2509 {
2510 let lo = r.row0.max(start_row0);
2511 let hi = (r.row0 + r.len - 1).min(end_row0);
2512 if lo <= hi {
2513 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
2514 }
2515 }
2516 out
2517 }
2518
2519 #[cfg(test)]
2520 pub(crate) fn reset_sheet_index_query_stats(&self) {
2521 for index in self.sheet_indexes.values() {
2522 index.reset_query_stats();
2523 }
2524 }
2525
2526 #[cfg(test)]
2527 pub(crate) fn sheet_index_query_stats(
2528 &self,
2529 ) -> crate::engine::sheet_index::SheetIndexQueryStats {
2530 self.sheet_indexes.values().fold(
2531 crate::engine::sheet_index::SheetIndexQueryStats::default(),
2532 |mut total, index| {
2533 let stats = index.query_stats();
2534 total.coordinate_nodes_visited = total
2535 .coordinate_nodes_visited
2536 .saturating_add(stats.coordinate_nodes_visited);
2537 total.values_visited = total.values_visited.saturating_add(stats.values_visited);
2538 total
2539 },
2540 )
2541 }
2542
2543 pub fn set_cell_value(
2549 &mut self,
2550 sheet: &str,
2551 row: u32,
2552 col: u32,
2553 value: LiteralValue,
2554 ) -> Result<OperationSummary, ExcelError> {
2555 let _ = normalize_stored_literal(value);
2556 let sheet_id = self.sheet_id_mut(sheet);
2557 let budgets = self.self_admission_budgets();
2558 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2559 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2560 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2561 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2562 }
2563 let coord = Coord::from_excel(row, col, true, true);
2565 let addr = CellRef::new(sheet_id, coord);
2566 self.vacate_cell(&addr);
2567 Ok(OperationSummary {
2568 affected_vertices: self.mark_dirty_cells(&[(sheet_id, coord.row(), coord.col())]),
2569 created_placeholders: Vec::new(),
2570 })
2571 }
2572
2573 pub fn reserve_cells(&mut self, additional: usize) {
2575 self.store.reserve(additional);
2576 if self.value_cache_enabled {
2577 self.vertex_values.reserve(additional);
2578 }
2579 self.cell_to_vertex.reserve(additional);
2580 }
2582
2583 pub fn set_cell_value_bulk_untracked(
2586 &mut self,
2587 sheet: &str,
2588 row: u32,
2589 col: u32,
2590 value: LiteralValue,
2591 ) -> Result<(), ExcelError> {
2592 let _ = normalize_stored_literal(value);
2593 let sheet_id = self.sheet_id_mut(sheet);
2594 let budgets = self.self_admission_budgets();
2595 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2596 let usage = self.preview_value_mutation(sheet_id, row, col)?;
2597 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2598 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2599 }
2600 let coord = Coord::from_excel(row, col, true, true);
2601 self.vacate_cell(&CellRef::new(sheet_id, coord));
2602 Ok(())
2603 }
2604
2605 pub fn bulk_insert_values<I>(&mut self, sheet: &str, cells: I) -> Result<(), ExcelError>
2609 where
2610 I: IntoIterator<Item = (u32, u32, LiteralValue)>,
2611 {
2612 let collected: Vec<(u32, u32, LiteralValue)> = cells.into_iter().collect();
2613 if collected.is_empty() {
2614 return Ok(());
2615 }
2616 let sheet_id = self.sheet_id_mut(sheet);
2617 let budgets = self.self_admission_budgets();
2618 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2619 let coordinates = collected
2620 .iter()
2621 .map(|(row, col, _)| (*row, *col))
2622 .collect::<Vec<_>>();
2623 let usage = self.preview_value_mutations(sheet_id, &coordinates)?;
2624 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2625 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2626 }
2627 let assume_new = self.first_load_assume_new
2629 && self
2630 .sheet_id(sheet)
2631 .map(|sid| !self.ensure_touched_sheets.contains(&sid))
2632 .unwrap_or(false);
2633 if assume_new {
2634 for (row, col, _) in collected {
2635 self.extent_record
2636 .note(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
2637 }
2638 return Ok(());
2639 }
2640 for (row, col, _) in collected {
2641 let coord = Coord::from_excel(row, col, true, true);
2642 self.vacate_cell(&CellRef::new(sheet_id, coord));
2643 }
2644 Ok(())
2645 }
2646
2647 pub fn set_cell_formula(
2649 &mut self,
2650 sheet: &str,
2651 row: u32,
2652 col: u32,
2653 ast: ASTNode,
2654 ) -> Result<OperationSummary, ExcelError> {
2655 self.set_cell_formula_with_volatility(sheet, row, col, ast, false)
2656 }
2657
2658 pub fn set_cell_formula_with_volatility(
2661 &mut self,
2662 sheet: &str,
2663 row: u32,
2664 col: u32,
2665 ast: ASTNode,
2666 _volatile: bool,
2667 ) -> Result<OperationSummary, ExcelError> {
2668 let sheet_id = self.sheet_id_mut(sheet);
2669 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
2670 let provider = RegistryFunctionProvider;
2671 let ingested = {
2672 let mut pipeline = self.ingest_pipeline(&provider);
2673 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
2674 };
2675 self.set_cell_formula_with_plan(
2676 sheet,
2677 row,
2678 col,
2679 ingested.ast_id,
2680 &ingested.dep_plan,
2681 ingested.dep_plan.volatile,
2682 ingested.dep_plan.dynamic,
2683 )
2684 }
2685
2686 pub(crate) fn set_cell_formula_with_plan(
2687 &mut self,
2688 sheet: &str,
2689 row: u32,
2690 col: u32,
2691 ast_id: AstNodeId,
2692 plan: &DependencyPlanRow,
2693 volatile: bool,
2694 dynamic: bool,
2695 ) -> Result<OperationSummary, ExcelError> {
2696 let dbg = std::env::var("FZ_DEBUG_LOAD")
2697 .ok()
2698 .is_some_and(|v| v != "0");
2699 let dep_ms_thresh: u128 = std::env::var("FZ_DEBUG_DEP_MS")
2700 .ok()
2701 .and_then(|s| s.parse().ok())
2702 .unwrap_or(0);
2703 let sample_n: usize = std::env::var("FZ_DEBUG_SAMPLE_N")
2704 .ok()
2705 .and_then(|s| s.parse().ok())
2706 .unwrap_or(0);
2707 let t0 = if dbg {
2708 Some(crate::instant::FzInstant::now())
2709 } else {
2710 None
2711 };
2712 let sheet_id = self.sheet_id_mut(sheet);
2713 let budgets = self.self_admission_budgets();
2714 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
2715 let usage = self.preview_formula_mutations(&[(sheet_id, row, col, plan.clone())])?;
2716 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
2717 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2718 }
2719 let coord = Coord::from_excel(row, col, true, true);
2720 let addr = CellRef::new(sheet_id, coord);
2721
2722 let t_dep0 = if dbg {
2723 Some(crate::instant::FzInstant::now())
2724 } else {
2725 None
2726 };
2727 let mut created_placeholders = Vec::new();
2728 let (mut new_dependencies, vertexless_deps) =
2729 self.resolve_direct_deps(&plan.direct_cell_deps);
2730 let mut named_dependencies = Vec::new();
2731 let mut unresolved_names = Vec::new();
2732 for name in plan
2733 .resolved_named_refs
2734 .iter()
2735 .chain(plan.named_refs.iter())
2736 {
2737 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
2738 if !new_dependencies.contains(&named.vertex) {
2739 new_dependencies.push(named.vertex);
2740 }
2741 if !named_dependencies.contains(&named.vertex) {
2742 named_dependencies.push(named.vertex);
2743 }
2744 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
2745 if !new_dependencies.contains(&source.vertex) {
2746 new_dependencies.push(source.vertex);
2747 }
2748 } else {
2749 unresolved_names.push(name.clone());
2750 }
2751 }
2752 for source_name in &plan.source_refs {
2753 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
2754 if !new_dependencies.contains(&source.vertex) {
2755 new_dependencies.push(source.vertex);
2756 }
2757 } else if let Some(source) = self.resolve_source_table_entry(source_name)
2758 && !new_dependencies.contains(&source.vertex)
2759 {
2760 new_dependencies.push(source.vertex);
2761 }
2762 }
2763 for table_name in &plan.table_refs {
2764 if let Some(table) = self.resolve_table_entry(table_name) {
2765 if !new_dependencies.contains(&table.vertex) {
2766 new_dependencies.push(table.vertex);
2767 }
2768 } else if let Some(source) = self.resolve_source_table_entry(table_name)
2769 && !new_dependencies.contains(&source.vertex)
2770 {
2771 new_dependencies.push(source.vertex);
2772 }
2773 }
2774 if let (true, Some(t)) = (dbg, t_dep0) {
2775 let elapsed = t.elapsed().as_millis();
2776 let do_log = (dep_ms_thresh > 0 && elapsed >= dep_ms_thresh)
2777 || (sample_n > 0 && (row as usize).is_multiple_of(sample_n));
2778 if (dep_ms_thresh == 0 && sample_n == 0 && row.is_multiple_of(1000)) || do_log {
2779 eprintln!(
2780 "[fz][dep] {}!{} planned: deps={}, ranges={}, placeholders={}, names={} in {} ms",
2781 self.sheet_name(sheet_id),
2782 crate::reference::Coord::from_excel(row, col, true, true),
2783 new_dependencies.len(),
2784 plan.range_deps.len(),
2785 created_placeholders.len(),
2786 named_dependencies.len(),
2787 elapsed
2788 );
2789 }
2790 }
2791
2792 self.replay_formula_vertex(&addr);
2794 let addr_vertex_id = self.get_or_create_vertex(&addr, &mut created_placeholders);
2795 self.materialize_vertex(addr_vertex_id);
2796
2797 self.ref_error_vertices.remove(&addr_vertex_id);
2799
2800 let self_reference = new_dependencies.contains(&addr_vertex_id)
2815 || vertexless_deps.iter().any(|c| same_cell(c, &addr));
2816 if self_reference && !self.config.cycle.allows_self_dependency() {
2817 return Err(ExcelError::new(ExcelErrorKind::Circ)
2818 .with_message("Self-reference detected".to_string()));
2819 }
2820
2821 for &name_vertex in &named_dependencies {
2822 let mut visited = FxHashSet::default();
2823 if self.name_depends_on_vertex(name_vertex, addr_vertex_id, &mut visited) {
2824 return Err(ExcelError::new(ExcelErrorKind::Circ)
2825 .with_message("Circular reference through named range".to_string()));
2826 }
2827 }
2828
2829 self.remove_dependent_edges(addr_vertex_id);
2831 self.detach_vertex_from_names(addr_vertex_id);
2832 self.clear_pending_name_references(addr_vertex_id);
2833
2834 self.store
2836 .set_kind(addr_vertex_id, VertexKind::FormulaScalar);
2837 self.vertex_formulas.insert(addr_vertex_id, ast_id);
2838 self.store.set_dirty(addr_vertex_id, true);
2839
2840 self.vertex_values.remove(&addr_vertex_id);
2842
2843 self.mark_volatile(addr_vertex_id, volatile);
2844 self.store.set_dynamic(addr_vertex_id, dynamic);
2845
2846 if !named_dependencies.is_empty() {
2847 self.attach_vertex_to_names(addr_vertex_id, &named_dependencies);
2848 }
2849 for unresolved_name in &unresolved_names {
2850 self.record_pending_name_reference(sheet_id, unresolved_name, addr_vertex_id);
2851 }
2852
2853 if let (true, Some(t)) = (dbg, t0) {
2854 let elapsed = t.elapsed().as_millis();
2855 let log_set = dep_ms_thresh > 0 && elapsed >= dep_ms_thresh;
2856 if log_set {
2857 eprintln!(
2858 "[fz][set] {}!{} total {} ms",
2859 self.sheet_name(sheet_id),
2860 crate::reference::Coord::from_excel(row, col, true, true),
2861 elapsed
2862 );
2863 }
2864 }
2865
2866 let vertexless_deps: Vec<CellRef> = vertexless_deps
2869 .into_iter()
2870 .filter(|c| {
2871 if same_cell(c, &addr) {
2872 if !new_dependencies.contains(&addr_vertex_id) {
2873 new_dependencies.push(addr_vertex_id);
2874 }
2875 false
2876 } else {
2877 true
2878 }
2879 })
2880 .collect();
2881 self.add_dependent_edges(addr_vertex_id, &new_dependencies);
2882 self.note_vertexless_deps(
2883 addr_vertex_id,
2884 vertexless_deps
2885 .iter()
2886 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
2887 );
2888 self.add_range_dependent_edges(addr_vertex_id, &plan.range_deps, sheet_id);
2889
2890 Ok(OperationSummary {
2891 affected_vertices: self.mark_dirty(addr_vertex_id),
2892 created_placeholders,
2893 })
2894 }
2895
2896 pub(crate) fn rewrite_structured_references_for_cell(
2897 &self,
2898 ast: &mut ASTNode,
2899 cell: CellRef,
2900 ) -> Result<bool, ExcelError> {
2901 self.rewrite_structured_references_node(ast, cell)
2902 }
2903
2904 fn rewrite_structured_references_node(
2905 &self,
2906 node: &mut ASTNode,
2907 cell: CellRef,
2908 ) -> Result<bool, ExcelError> {
2909 match &mut node.node_type {
2910 ASTNodeType::Reference { reference, .. } => {
2911 self.rewrite_structured_reference(reference, cell)
2912 }
2913 ASTNodeType::UnaryOp { expr, .. } => {
2914 self.rewrite_structured_references_node(expr, cell)
2915 }
2916 ASTNodeType::BinaryOp { left, right, .. } => {
2917 let left_rewritten = self.rewrite_structured_references_node(left, cell)?;
2918 let right_rewritten = self.rewrite_structured_references_node(right, cell)?;
2919 Ok(left_rewritten || right_rewritten)
2920 }
2921 ASTNodeType::Function { args, .. } => {
2922 let mut rewritten = false;
2923 for a in args.iter_mut() {
2924 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2925 }
2926 Ok(rewritten)
2927 }
2928 ASTNodeType::Call { callee, args } => {
2929 let mut rewritten = self.rewrite_structured_references_node(callee, cell)?;
2930 for a in args.iter_mut() {
2931 rewritten |= self.rewrite_structured_references_node(a, cell)?;
2932 }
2933 Ok(rewritten)
2934 }
2935 ASTNodeType::Array(rows) => {
2936 let mut rewritten = false;
2937 for r in rows.iter_mut() {
2938 for item in r.iter_mut() {
2939 rewritten |= self.rewrite_structured_references_node(item, cell)?;
2940 }
2941 }
2942 Ok(rewritten)
2943 }
2944 ASTNodeType::Literal(_) | ASTNodeType::Omitted => Ok(false),
2945 }
2946 }
2947
2948 fn rewrite_structured_reference(
2949 &self,
2950 reference: &mut ReferenceType,
2951 cell: CellRef,
2952 ) -> Result<bool, ExcelError> {
2953 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
2954
2955 let ReferenceType::Table(tref) = reference else {
2956 return Ok(false);
2957 };
2958
2959 if !tref.name.is_empty() {
2961 return Ok(false);
2962 }
2963
2964 let col_name = match &tref.specifier {
2965 Some(TableSpecifier::Combination(parts)) => {
2966 let mut saw_this_row = false;
2967 let mut col: Option<&str> = None;
2968 for p in parts {
2969 match p.as_ref() {
2970 TableSpecifier::SpecialItem(SpecialItem::ThisRow) => {
2971 saw_this_row = true;
2972 }
2973 TableSpecifier::Column(c) => {
2974 if col.is_some() {
2975 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2976 "This-row structured reference with multiple columns is not supported"
2977 .to_string(),
2978 ));
2979 }
2980 col = Some(c.as_str());
2981 }
2982 other => {
2983 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2984 format!(
2985 "Unsupported this-row structured reference component: {other}"
2986 ),
2987 ));
2988 }
2989 }
2990 }
2991 if !saw_this_row {
2992 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
2993 "Unnamed structured reference requires a this-row selector".to_string(),
2994 ));
2995 }
2996 col.ok_or_else(|| {
2997 ExcelError::new(ExcelErrorKind::NImpl).with_message(
2998 "This-row structured reference missing column selector".to_string(),
2999 )
3000 })?
3001 }
3002 _ => {
3003 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3004 "Unnamed structured reference form is not supported".to_string(),
3005 ));
3006 }
3007 };
3008
3009 let Some(table) = self.find_table_containing_cell(cell) else {
3010 return Err(ExcelError::new(ExcelErrorKind::Name)
3011 .with_message("This-row structured reference used outside a table".to_string()));
3012 };
3013
3014 let row0 = cell.coord.row();
3015 let col0 = cell.coord.col();
3016 let sr0 = table.range.start.coord.row();
3017 let sc0 = table.range.start.coord.col();
3018 let er0 = table.range.end.coord.row();
3019 let ec0 = table.range.end.coord.col();
3020
3021 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3022 return Err(ExcelError::new(ExcelErrorKind::Name)
3023 .with_message("This-row structured reference used outside a table".to_string()));
3024 }
3025
3026 if table.header_row && row0 == sr0 {
3027 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3028 "This-row structured references are not valid in the table header row".to_string(),
3029 ));
3030 }
3031
3032 let data_start = if table.header_row { sr0 + 1 } else { sr0 };
3033 if row0 < data_start {
3034 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
3035 "This-row structured references require a data/totals row context".to_string(),
3036 ));
3037 }
3038
3039 let Some(idx) = table.col_index(col_name) else {
3040 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(format!(
3041 "Unknown table column in this-row reference: {col_name}"
3042 )));
3043 };
3044 let target_col0 = sc0 + (idx as u32);
3045 let target_row = row0 + 1;
3046 let target_col = target_col0 + 1;
3047
3048 *reference = ReferenceType::Cell {
3049 sheet: None,
3050 row: target_row,
3051 col: target_col,
3052 row_abs: true,
3053 col_abs: true,
3054 };
3055
3056 Ok(true)
3057 }
3058
3059 fn find_table_containing_cell(&self, cell: CellRef) -> Option<&tables::TableEntry> {
3060 let row0 = cell.coord.row();
3061 let col0 = cell.coord.col();
3062
3063 let mut best: Option<&tables::TableEntry> = None;
3064 let mut best_area: u64 = u64::MAX;
3065 let mut best_name: &str = "";
3066
3067 for t in self.tables.values() {
3068 if t.sheet_id() != cell.sheet_id {
3069 continue;
3070 }
3071 let sr0 = t.range.start.coord.row();
3072 let sc0 = t.range.start.coord.col();
3073 let er0 = t.range.end.coord.row();
3074 let ec0 = t.range.end.coord.col();
3075 if row0 < sr0 || row0 > er0 || col0 < sc0 || col0 > ec0 {
3076 continue;
3077 }
3078
3079 let h = (er0 - sr0 + 1) as u64;
3080 let w = (ec0 - sc0 + 1) as u64;
3081 let area = h.saturating_mul(w);
3082 let name = t.name.as_str();
3083 let better = match best {
3084 None => true,
3085 Some(_) => area < best_area || (area == best_area && name < best_name),
3086 };
3087 if better {
3088 best = Some(t);
3089 best_area = area;
3090 best_name = name;
3091 }
3092 }
3093
3094 best
3095 }
3096
3097 #[allow(clippy::type_complexity)]
3098 pub(crate) fn fp8_parity_extract_dependencies_with_pending_names(
3099 &mut self,
3100 ast: &ASTNode,
3101 current_sheet_id: SheetId,
3102 ) -> Result<
3103 (
3104 Vec<VertexId>,
3105 Vec<SharedRangeRef<'static>>,
3106 Vec<CellRef>,
3107 Vec<VertexId>,
3108 Vec<String>,
3109 ),
3110 ExcelError,
3111 > {
3112 self.extract_dependencies_with_pending_names(ast, current_sheet_id)
3113 }
3114
3115 pub(crate) fn fp8_parity_is_ast_volatile(&self, ast: &ASTNode) -> bool {
3116 self.is_ast_volatile(ast)
3117 }
3118
3119 pub fn set_cell_value_ref(
3120 &mut self,
3121 cell: formualizer_common::SheetCellRef<'_>,
3122 value: LiteralValue,
3123 ) -> Result<OperationSummary, ExcelError> {
3124 let owned = cell.into_owned();
3125 let sheet_id = match owned.sheet {
3126 formualizer_common::SheetLocator::Id(id) => id,
3127 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3128 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3129 };
3130 let sheet_name = self.sheet_name(sheet_id).to_string();
3131 self.set_cell_value(
3132 &sheet_name,
3133 owned.coord.row() + 1,
3134 owned.coord.col() + 1,
3135 value,
3136 )
3137 }
3138
3139 pub fn set_cell_formula_ref(
3140 &mut self,
3141 cell: formualizer_common::SheetCellRef<'_>,
3142 ast: ASTNode,
3143 ) -> Result<OperationSummary, ExcelError> {
3144 let owned = cell.into_owned();
3145 let sheet_id = match owned.sheet {
3146 formualizer_common::SheetLocator::Id(id) => id,
3147 formualizer_common::SheetLocator::Name(name) => self.sheet_id_mut(name.as_ref()),
3148 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3149 };
3150 let sheet_name = self.sheet_name(sheet_id).to_string();
3151 self.set_cell_formula(
3152 &sheet_name,
3153 owned.coord.row() + 1,
3154 owned.coord.col() + 1,
3155 ast,
3156 )
3157 }
3158
3159 pub fn get_cell_value_ref(
3160 &self,
3161 cell: formualizer_common::SheetCellRef<'_>,
3162 ) -> Option<LiteralValue> {
3163 let owned = cell.into_owned();
3164 let sheet_id = match owned.sheet {
3165 formualizer_common::SheetLocator::Id(id) => id,
3166 formualizer_common::SheetLocator::Name(name) => self.sheet_id(name.as_ref())?,
3167 formualizer_common::SheetLocator::Current => self.default_sheet_id,
3168 };
3169 let sheet_name = self.sheet_name(sheet_id);
3170 self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1)
3171 }
3172
3173 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
3175 if !self.value_cache_enabled {
3176 #[cfg(debug_assertions)]
3177 {
3178 self.graph_value_read_attempts
3179 .fetch_add(1, Ordering::Relaxed);
3180 }
3181 return None;
3182 }
3183 let sheet_id = self.sheet_reg.get_id(sheet)?;
3184 let coord = Coord::from_excel(row, col, true, true);
3185 let addr = CellRef::new(sheet_id, coord);
3186
3187 self.get_vertex_id_for_address(&addr).and_then(|vertex_id| {
3188 self.vertex_values
3190 .get(&vertex_id)
3191 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
3192 })
3193 }
3194
3195 fn mark_dirty(&mut self, vertex_id: VertexId) -> Vec<VertexId> {
3197 self.mark_dirty_many(&[vertex_id])
3198 }
3199
3200 pub(crate) fn mark_dirty_many(&mut self, vertex_ids: &[VertexId]) -> Vec<VertexId> {
3220 if self.deferred_dirty_depth > 0 {
3221 self.deferred_dirty_pending.extend_from_slice(vertex_ids);
3222 return vertex_ids.to_vec();
3223 }
3224 self.authority_mark_dirty(vertex_ids)
3225 }
3226
3227 pub(crate) fn dirty_propagation_visits(&self) -> u64 {
3230 self.dirty_propagation_visits
3231 }
3232
3233 pub fn begin_deferred_dirty(&mut self) {
3254 #[cfg(any(test, feature = "legacy_oracle"))]
3255 self.edges.begin_batch();
3256 self.deferred_dirty_depth += 1;
3257 }
3258
3259 pub fn end_deferred_dirty(&mut self) -> Vec<VertexId> {
3264 debug_assert!(
3265 self.deferred_dirty_depth > 0,
3266 "end_deferred_dirty without matching begin_deferred_dirty"
3267 );
3268 #[cfg(any(test, feature = "legacy_oracle"))]
3269 self.edges.end_batch();
3270 self.deferred_dirty_depth = self.deferred_dirty_depth.saturating_sub(1);
3271 if self.deferred_dirty_depth > 0 {
3272 return Vec::new();
3273 }
3274 let pending = std::mem::take(&mut self.deferred_dirty_pending);
3275 let rects = std::mem::take(&mut self.deferred_dirty_pending_rects);
3276 let mut affected = self.authority_mark_dirty_rects(&rects);
3277 if pending.is_empty() {
3278 return affected;
3279 }
3280 let live: Vec<VertexId> = pending
3281 .into_iter()
3282 .filter(|&id| self.vertex_exists(id))
3283 .collect();
3284 affected.extend(self.mark_dirty_many(&live));
3285 affected
3286 }
3287
3288 pub(crate) fn mark_dirty_cells(
3291 &mut self,
3292 cells: &[crate::engine::authority::geom::Cell],
3293 ) -> Vec<VertexId> {
3294 let rects: Vec<(SheetId, u32, u32, u32, u32)> =
3295 cells.iter().map(|&(s, r, c)| (s, r, r, c, c)).collect();
3296 self.mark_dirty_rects(&rects)
3297 }
3298
3299 pub(crate) fn mark_dirty_rects(
3302 &mut self,
3303 rects: &[(SheetId, u32, u32, u32, u32)],
3304 ) -> Vec<VertexId> {
3305 let rects: Vec<(u16, crate::engine::authority::geom::Rect)> = rects
3306 .iter()
3307 .map(|&(s, r0, r1, c0, c1)| {
3308 (s, crate::engine::authority::geom::Rect::new(r0, c0, r1, c1))
3309 })
3310 .collect();
3311 if self.deferred_dirty_depth > 0 {
3312 self.deferred_dirty_pending_rects.extend_from_slice(&rects);
3313 return Vec::new();
3314 }
3315 self.authority_mark_dirty_rects(&rects)
3316 }
3317
3318 pub fn deferred_dirty_active(&self) -> bool {
3321 self.deferred_dirty_depth > 0
3322 }
3323
3324 pub fn get_evaluation_vertices(&self) -> Vec<VertexId> {
3326 let mut result: Vec<VertexId> = self
3329 .formula_dirty
3330 .legacy_iter()
3331 .chain(self.volatile_vertices.iter().copied())
3332 .filter(|&id| {
3333 self.store.vertex_exists_active(id)
3336 && matches!(
3337 self.store.kind(id),
3338 VertexKind::FormulaScalar
3339 | VertexKind::FormulaArray
3340 | VertexKind::NamedScalar
3341 | VertexKind::NamedArray
3342 )
3343 })
3344 .collect();
3345 result.sort_unstable();
3346 result.dedup();
3347 result
3348 }
3349
3350 pub(crate) fn has_dirty_evaluation_vertices(&self) -> bool {
3353 self.formula_dirty.legacy_iter().any(|id| {
3354 self.store.vertex_exists_active(id)
3355 && matches!(
3356 self.store.kind(id),
3357 VertexKind::FormulaScalar
3358 | VertexKind::FormulaArray
3359 | VertexKind::NamedScalar
3360 | VertexKind::NamedArray
3361 )
3362 })
3363 }
3364
3365 pub fn clear_dirty_flags(&mut self, vertices: &[VertexId]) {
3367 for &vertex_id in vertices {
3368 self.store.set_dirty(vertex_id, false);
3369 self.formula_dirty.legacy_remove(&vertex_id);
3370 }
3371 self.formula_dirty.legacy_shrink_if_sparse();
3372 self.authority_observe_clean(vertices);
3373 }
3374
3375 pub fn clear_volatile_flags(&mut self) {
3377 self.volatile_vertices.clear();
3378 }
3379
3380 pub(crate) fn redirty_volatiles(&mut self) {
3385 let volatile_ids: Vec<VertexId> = self.volatile_vertices.iter().copied().collect();
3386 let _ = self.mark_dirty_many(&volatile_ids);
3387 }
3388
3389 pub(crate) fn redirty_iterative_members(&mut self, members: &[VertexId]) {
3402 let live: Vec<VertexId> = members
3403 .iter()
3404 .copied()
3405 .filter(|&id| self.vertex_exists(id))
3406 .collect();
3407 let _ = self.mark_dirty_many(&live);
3408 }
3409
3410 pub(crate) fn dep_vertex(&mut self, addr: &CellRef) -> Option<VertexId> {
3414 if let Some(vertex_id) = self.cell_vertex(addr) {
3415 return Some(vertex_id);
3416 }
3417 if self.first_load_assume_new {
3418 let packed = Self::packed_cell_key(
3419 addr.sheet_id,
3420 AbsCoord::new(addr.coord.row(), addr.coord.col()),
3421 );
3422 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
3423 self.cell_to_vertex.insert(*addr, existing);
3424 return Some(existing);
3425 }
3426 }
3427 None
3428 }
3429
3430 pub(crate) fn vacate_cell(&mut self, addr: &CellRef) -> Option<(VertexId, bool)> {
3436 self.extent_record
3439 .note(addr.sheet_id, addr.coord.row(), addr.coord.col());
3440 let v = self.cell_vertex_mut(addr)?;
3441 let was_formula = matches!(
3442 self.store.kind(v),
3443 VertexKind::FormulaScalar | VertexKind::FormulaArray
3444 ) || self.vertex_formulas.contains_key(&v);
3445 self.remove_dependent_edges(v);
3446 self.detach_vertex_from_names(v);
3447 self.clear_pending_name_references(v);
3448 self.vertex_formulas.remove(&v);
3449 self.vertex_values.remove(&v);
3450 self.ref_error_vertices.remove(&v);
3451 self.clear_formula_vertex_dirty(v);
3452 self.mark_volatile(v, false);
3453 self.store.set_dynamic(v, false);
3454 self.store.set_kind(v, VertexKind::Empty);
3455 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3456 #[cfg(any(test, feature = "legacy_oracle"))]
3459 {
3460 let readers = self.get_dependents(v);
3461 self.remove_all_edges(v);
3462 for r in readers {
3463 if !self.store.is_deleted(r) {
3464 self.oracle_vertexless_readers
3465 .entry(key)
3466 .or_default()
3467 .push(r);
3468 self.oracle_vertexless_of.entry(r).or_default().push(key);
3469 }
3470 }
3471 }
3472 self.cell_to_vertex.remove(addr);
3473 if let Some(index) = self.sheet_indexes.get_mut(&addr.sheet_id) {
3474 index.remove_vertex(GridAddr::new(key.1, key.2), v);
3475 }
3476 self.store.mark_deleted(v, true);
3477 if was_formula {
3478 self.retired_ids.insert(key, v);
3479 self.retired_id_set.insert(v);
3480 }
3481 Some((v, was_formula))
3482 }
3483
3484 pub(crate) fn add_empty_vertex(
3488 &mut self,
3489 sheet: SheetId,
3490 row: u32,
3491 col: u32,
3492 ) -> Result<VertexId, ExcelError> {
3493 let budgets = self.self_admission_budgets();
3494 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
3495 let mut usage = self.preview_value_mutation(sheet, row + 1, col + 1)?;
3496 if self
3497 .cell_vertex(&CellRef::new(sheet, Coord::new(row, col, true, true)))
3498 .is_none()
3499 {
3500 usage.final_vertices = usage.final_vertices.saturating_add(1);
3501 usage.added_vertices = 1;
3502 }
3503 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
3504 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3505 }
3506 let addr = CellRef::new(sheet, Coord::new(row, col, true, true));
3507 let mut created = Vec::new();
3508 let id = self.get_or_create_vertex(&addr, &mut created);
3509 self.materialize_vertex(id);
3510 let _ = self.mark_dirty(id);
3511 Ok(id)
3512 }
3513
3514 pub(crate) fn retire_cell_for_replay(&mut self, addr: CellRef) {
3518 self.vacate_cell(&addr);
3521 self.forget_extent_cells(
3523 addr.sheet_id,
3524 (addr.coord.row(), addr.coord.row()),
3525 (addr.coord.col(), addr.coord.col()),
3526 );
3527 let _ = self.mark_dirty_cells(&[(addr.sheet_id, addr.coord.row(), addr.coord.col())]);
3528 }
3529
3530 pub(crate) fn note_extent_cell(&mut self, sheet: SheetId, row0: u32, col0: u32) {
3533 self.extent_record.note(sheet, row0, col0);
3534 }
3535
3536 pub(crate) fn forget_extent_cells(
3540 &mut self,
3541 sheet: SheetId,
3542 rows: (u32, u32),
3543 cols: (u32, u32),
3544 ) -> Vec<(u32, u32, u32)> {
3545 self.extent_record.forget_rect(sheet, rows, cols)
3546 }
3547
3548 pub(crate) fn had_legacy_cell_vertex(&self, cell: &CellRef) -> bool {
3551 self.extent_record
3552 .contains(cell.sheet_id, cell.coord.row(), cell.coord.col())
3553 }
3554
3555 pub(crate) fn extent_record_columns(&self, sheet: SheetId) -> Vec<u32> {
3557 self.extent_record.columns(sheet)
3558 }
3559
3560 #[cfg(test)]
3562 pub(crate) fn extent_record_runs(&self) -> usize {
3563 self.extent_record.run_count()
3564 }
3565
3566 pub(crate) fn revive_retired_id(&mut self, addr: &CellRef) -> Option<VertexId> {
3569 if self.retired_ids.is_empty() {
3570 return None;
3571 }
3572 let key = (addr.sheet_id, addr.coord.row(), addr.coord.col());
3573 let id = *self.retired_ids.get(&key)?;
3574 let coord = GridAddr::new(key.1, key.2);
3575 if let Some(x) = self.cell_vertex(addr)
3578 && !self.store.is_deleted(x)
3579 && self.store.grid_addr(x) == Some(coord)
3580 {
3581 return None;
3582 }
3583 self.retired_ids.remove(&key);
3584 self.revive_vertex(id, addr.sheet_id, coord).then_some(id)
3585 }
3586
3587 pub(crate) fn retired_id_count(&self) -> usize {
3589 self.retired_ids.len()
3590 }
3591
3592 pub(crate) fn shift_retired_ids(
3598 &mut self,
3599 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3600 journal: bool,
3601 ) {
3602 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3603 let dropped = self.extent_record.shift(op);
3604 if journal && matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. }) {
3605 self.extent_dropped.push(dropped);
3606 }
3607 self.shift_retired_id_table(op, journal);
3608 }
3609
3610 fn shift_retired_id_table(
3612 &mut self,
3613 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3614 journal: bool,
3615 ) {
3616 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3617 let deleting = matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. });
3618 if self.retired_ids.is_empty() {
3619 if deleting && journal {
3620 self.retired_dropped.push(Vec::new());
3621 }
3622 return;
3623 }
3624 let (sheet, rows, start, count, insert) = match *op {
3625 Op::InsertRows {
3626 sheet_id,
3627 before,
3628 count,
3629 } => (sheet_id, true, before, count, true),
3630 Op::DeleteRows {
3631 sheet_id,
3632 start,
3633 count,
3634 } => (sheet_id, true, start, count, false),
3635 Op::InsertColumns {
3636 sheet_id,
3637 before,
3638 count,
3639 } => (sheet_id, false, before, count, true),
3640 Op::DeleteColumns {
3641 sheet_id,
3642 start,
3643 count,
3644 } => (sheet_id, false, start, count, false),
3645 };
3646 let entries: Vec<((SheetId, u32, u32), VertexId)> = self
3647 .retired_ids
3648 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
3649 .map(|(k, v)| (*k, *v))
3650 .collect();
3651 for (key, _) in &entries {
3652 self.retired_ids.remove(key);
3653 }
3654 let mut dropped = Vec::new();
3655 for ((s, r, c), id) in entries {
3656 let pos = if rows { r } else { c };
3657 let moved = if pos < start {
3658 Some(pos)
3659 } else if insert {
3660 pos.checked_add(count)
3661 } else if pos < start.saturating_add(count) {
3662 None
3663 } else {
3664 Some(pos - count)
3665 };
3666 match moved {
3667 Some(p) => {
3668 let key = if rows { (s, p, c) } else { (s, r, p) };
3669 self.retired_ids.insert(key, id);
3670 }
3671 None => dropped.push(((s, r, c), id)),
3672 }
3673 }
3674 if !insert && journal {
3675 self.retired_dropped.push(dropped);
3677 }
3678 }
3679
3680 fn restore_retired_batch(&mut self, batch: RetiredBatch) {
3682 for (key, id) in batch {
3683 self.retired_ids.insert(key, id);
3684 }
3685 }
3686
3687 pub(crate) fn replay_structural_marker(&mut self, description: &str, forward: bool) {
3694 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3695 let Some(op) = parse_structural_description(description) else {
3696 return;
3697 };
3698 if forward {
3699 self.shift_retired_ids(&op, true);
3700 if matches!(op, Op::InsertRows { .. } | Op::InsertColumns { .. })
3701 && let Some(batch) = self.retired_dropped_by_undo.pop()
3702 {
3703 self.restore_retired_batch(batch);
3705 }
3706 return;
3707 }
3708 let (inverse, band) = match op {
3709 Op::InsertRows {
3710 sheet_id,
3711 before,
3712 count,
3713 } => (
3714 Op::DeleteRows {
3715 sheet_id,
3716 start: before,
3717 count,
3718 },
3719 None,
3720 ),
3721 Op::InsertColumns {
3722 sheet_id,
3723 before,
3724 count,
3725 } => (
3726 Op::DeleteColumns {
3727 sheet_id,
3728 start: before,
3729 count,
3730 },
3731 None,
3732 ),
3733 Op::DeleteRows {
3734 sheet_id,
3735 start,
3736 count,
3737 } => (
3738 Op::InsertRows {
3739 sheet_id,
3740 before: start,
3741 count,
3742 },
3743 Some((sheet_id, true, start, count)),
3744 ),
3745 Op::DeleteColumns {
3746 sheet_id,
3747 start,
3748 count,
3749 } => (
3750 Op::InsertColumns {
3751 sheet_id,
3752 before: start,
3753 count,
3754 },
3755 Some((sheet_id, false, start, count)),
3756 ),
3757 };
3758 if self.extent_undone.last() == Some(&shift_key(&op)) {
3761 self.extent_undone.pop();
3762 } else {
3763 self.shift_extent_back(&op, &inverse);
3764 }
3765 self.shift_retired_id_table(&inverse, true);
3768 if band.is_none() {
3769 if let Some(batch) = self.retired_dropped.pop() {
3770 self.retired_dropped_by_undo.push(batch);
3771 }
3772 return;
3773 }
3774 if let Some((sheet, rows, start, count)) = band {
3776 if count == 0 {
3777 return;
3778 }
3779 if let Some(batch) = self.retired_dropped.pop() {
3781 self.restore_retired_batch(batch);
3782 }
3783 let end = start.saturating_add(count - 1);
3784 let rect = if rows {
3786 (sheet, start, end, 0, 16_383)
3787 } else {
3788 (sheet, 0, 1_048_575, start, end)
3789 };
3790 let _ = self.mark_dirty_rects(&[rect]);
3791 }
3792 }
3793
3794 pub(crate) fn undo_structural_extent(&mut self, description: &str) {
3801 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3802 let Some(op) = parse_structural_description(description) else {
3803 return;
3804 };
3805 let inverse = match op {
3806 Op::InsertRows {
3807 sheet_id,
3808 before,
3809 count,
3810 } => Op::DeleteRows {
3811 sheet_id,
3812 start: before,
3813 count,
3814 },
3815 Op::InsertColumns {
3816 sheet_id,
3817 before,
3818 count,
3819 } => Op::DeleteColumns {
3820 sheet_id,
3821 start: before,
3822 count,
3823 },
3824 Op::DeleteRows {
3825 sheet_id,
3826 start,
3827 count,
3828 } => Op::InsertRows {
3829 sheet_id,
3830 before: start,
3831 count,
3832 },
3833 Op::DeleteColumns {
3834 sheet_id,
3835 start,
3836 count,
3837 } => Op::InsertColumns {
3838 sheet_id,
3839 before: start,
3840 count,
3841 },
3842 };
3843 self.shift_extent_back(&op, &inverse);
3844 self.extent_undone.push(shift_key(&op));
3845 }
3846
3847 pub(crate) fn clear_extent_undone(&mut self) {
3849 self.extent_undone.clear();
3850 }
3851
3852 fn shift_extent_back(
3855 &mut self,
3856 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3857 inverse: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
3858 ) {
3859 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
3860 let _ = self.extent_record.shift(inverse);
3861 if matches!(*op, Op::DeleteRows { .. } | Op::DeleteColumns { .. })
3862 && let Some(dropped) = self.extent_dropped.pop()
3863 {
3864 self.extent_record.restore(dropped);
3865 }
3866 }
3867
3868 #[cfg(test)]
3870 pub(crate) fn extent_record_pending(&self) -> usize {
3871 self.extent_record.pending_len()
3872 }
3873
3874 #[cfg(test)]
3877 pub(crate) fn extent_history_counts(&self) -> (usize, usize) {
3878 (
3879 self.extent_dropped.len(),
3880 self.extent_dropped.iter().map(Vec::len).sum(),
3881 )
3882 }
3883
3884 pub(crate) fn drop_retired_ids_of_sheet(&mut self, sheet: SheetId) {
3886 self.extent_record.drop_sheet(sheet);
3887 let keys: Vec<(SheetId, u32, u32)> = self
3888 .retired_ids
3889 .range((sheet, 0, 0)..=(sheet, u32::MAX, u32::MAX))
3890 .map(|(k, _)| *k)
3891 .collect();
3892 for key in keys {
3893 if let Some(id) = self.retired_ids.remove(&key) {
3894 self.vertex_journal.retired(key, id.0);
3895 }
3896 }
3897 }
3898
3899 pub(crate) fn resolve_direct_deps(
3902 &mut self,
3903 cells: &[CellRef],
3904 ) -> (Vec<VertexId>, Vec<CellRef>) {
3905 let mut vertices: Vec<VertexId> = Vec::with_capacity(cells.len());
3906 let mut vertexless: Vec<CellRef> = Vec::new();
3907 for cell in cells {
3908 match self.dep_vertex(cell) {
3909 Some(v) => {
3910 if !vertices.contains(&v) {
3911 vertices.push(v);
3912 }
3913 }
3914 None => {
3915 if !vertexless.iter().any(|c| same_cell(c, cell)) {
3916 vertexless.push(*cell);
3917 }
3918 }
3919 }
3920 }
3921 (vertices, vertexless)
3922 }
3923
3924 fn get_or_create_vertex(
3925 &mut self,
3926 addr: &CellRef,
3927 created_placeholders: &mut Vec<CellRef>,
3928 ) -> VertexId {
3929 if let Some(vertex_id) = self.cell_vertex(addr) {
3930 return vertex_id;
3931 }
3932 if let Some(vertex_id) = self.revive_retired_id(addr) {
3934 return vertex_id;
3935 }
3936
3937 if self.first_load_assume_new {
3942 let packed = Self::packed_cell_key(
3943 addr.sheet_id,
3944 AbsCoord::new(addr.coord.row(), addr.coord.col()),
3945 );
3946 if let Some(&existing) = self.load_packed_to_vertex.get(&packed) {
3947 self.cell_to_vertex.insert(*addr, existing);
3948 return existing;
3949 }
3950 }
3951
3952 created_placeholders.push(*addr);
3953 let position = GridAddr::new(addr.coord.row(), addr.coord.col());
3954 let vertex_id = self
3955 .store
3956 .allocate(VertexAddr::grid(position), addr.sheet_id, 0x00);
3957
3958 #[cfg(any(test, feature = "legacy_oracle"))]
3959 {
3960 self.edges
3961 .add_vertex(VertexAddr::grid(position), vertex_id.0);
3962 self.oracle_cell_vertex_created(
3963 (addr.sheet_id, position.row(), position.col()),
3964 vertex_id,
3965 );
3966 }
3967
3968 self.sheet_index_mut(addr.sheet_id)
3970 .add_vertex(position, vertex_id);
3971
3972 self.store.set_kind(vertex_id, VertexKind::Empty);
3973 self.cell_to_vertex.insert(*addr, vertex_id);
3974 vertex_id
3975 }
3976
3977 pub(crate) fn note_vertexless_deps(
3982 &mut self,
3983 dependent: VertexId,
3984 cells: impl IntoIterator<Item = (SheetId, u32, u32)>,
3985 ) {
3986 let mut n = 0usize;
3987 #[cfg(any(test, feature = "legacy_oracle"))]
3988 let mut keys = Vec::new();
3989 for cell in cells {
3990 n += 1;
3991 self.extent_record.note(cell.0, cell.1, cell.2);
3993 #[cfg(any(test, feature = "legacy_oracle"))]
3994 {
3995 self.oracle_vertexless_readers
3996 .entry(cell)
3997 .or_default()
3998 .push(dependent);
3999 keys.push(cell);
4000 }
4001 #[cfg(not(any(test, feature = "legacy_oracle")))]
4002 let _ = cell;
4003 }
4004 #[cfg(any(test, feature = "legacy_oracle"))]
4005 if !keys.is_empty() {
4006 self.oracle_vertexless_of
4007 .entry(dependent)
4008 .or_default()
4009 .extend(keys);
4010 }
4011 self.note_dep_edges(dependent, n);
4012 }
4013
4014 #[cfg(any(test, feature = "legacy_oracle"))]
4017 pub(crate) fn oracle_cell_vertex_created(&mut self, cell: (SheetId, u32, u32), v: VertexId) {
4018 if self.oracle_vertexless_readers.is_empty() {
4019 return;
4020 }
4021 let Some(readers) = self.oracle_vertexless_readers.remove(&cell) else {
4022 return;
4023 };
4024 for reader in readers {
4025 if let Some(cells) = self.oracle_vertexless_of.get_mut(&reader) {
4026 cells.retain(|c| *c != cell);
4027 if cells.is_empty() {
4028 self.oracle_vertexless_of.remove(&reader);
4029 }
4030 }
4031 self.oracle_add_dependent_edges(reader, &[v]);
4032 }
4033 }
4034
4035 #[cfg(any(test, feature = "legacy_oracle"))]
4038 pub(crate) fn oracle_vertexless_readers_of(
4039 &self,
4040 cell: crate::engine::authority::geom::Cell,
4041 ) -> Vec<VertexId> {
4042 self.oracle_vertexless_readers
4043 .get(&(cell.0 as SheetId, cell.1, cell.2))
4044 .cloned()
4045 .unwrap_or_default()
4046 }
4047
4048 #[cfg(any(test, feature = "legacy_oracle"))]
4051 pub(crate) fn oracle_vertexless_cells(&self, dependent: VertexId) -> Vec<CellRef> {
4052 self.oracle_vertexless_of
4053 .get(&dependent)
4054 .map(|cells| {
4055 cells
4056 .iter()
4057 .map(|&(s, r, c)| CellRef::new(s, Coord::new(r, c, true, true)))
4058 .collect()
4059 })
4060 .unwrap_or_default()
4061 }
4062
4063 #[cfg(any(test, feature = "legacy_oracle"))]
4064 fn oracle_forget_vertexless(&mut self, dependent: VertexId) {
4065 if let Some(cells) = self.oracle_vertexless_of.remove(&dependent) {
4066 for cell in cells {
4067 if let Some(readers) = self.oracle_vertexless_readers.get_mut(&cell) {
4068 readers.retain(|r| *r != dependent);
4069 if readers.is_empty() {
4070 self.oracle_vertexless_readers.remove(&cell);
4071 }
4072 }
4073 }
4074 }
4075 }
4076
4077 pub(crate) fn note_dep_edges(&mut self, dependent: VertexId, n: usize) {
4079 let now = self.store.edge_offset(dependent) as usize + n;
4080 self.store
4081 .set_edge_offset(dependent, u32::try_from(now).unwrap_or(u32::MAX));
4082 self.dep_edge_total += n;
4083 }
4084
4085 pub(crate) fn renamed_sheet_alias(&self, name: &str) -> Option<SheetId> {
4088 self.renamed_sheet_aliases
4089 .get(&name.to_ascii_lowercase())
4090 .copied()
4091 .filter(|&id| self.sheet_reg.name(id) != name)
4092 }
4093
4094 pub(crate) fn reads_compressed_range(&self, vertex: VertexId) -> bool {
4096 self.store.reads_range(vertex)
4097 }
4098
4099 pub(crate) fn note_reads_range(&mut self, vertex: VertexId) {
4101 if !self.store.reads_range(vertex) {
4102 self.store.set_reads_range(vertex, true);
4103 self.range_reader_count += 1;
4104 }
4105 }
4106
4107 pub(crate) fn has_compressed_range_readers(&self) -> bool {
4109 self.range_reader_count > 0
4110 }
4111
4112 pub(crate) fn forget_dep_edges(&mut self, vertex: VertexId) {
4114 let n = self.store.edge_offset(vertex) as usize;
4115 if n > 0 {
4116 self.store.set_edge_offset(vertex, 0);
4117 self.dep_edge_total = self.dep_edge_total.saturating_sub(n);
4118 }
4119 }
4120
4121 fn add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4122 self.note_dep_edges(dependent, dependencies.len());
4123 #[cfg(any(test, feature = "legacy_oracle"))]
4124 self.oracle_add_dependent_edges(dependent, dependencies);
4125 }
4126
4127 #[cfg(any(test, feature = "legacy_oracle"))]
4129 fn oracle_add_dependent_edges(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4130 #[cfg(any(test, feature = "legacy_oracle"))]
4132 self.edges.begin_batch();
4133
4134 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4137 if self.pk_order.is_some()
4138 && let Some(mut pk) = self.pk_order.take()
4139 {
4140 pk.ensure_nodes(std::iter::once(dependent));
4141 pk.ensure_nodes(dependencies.iter().copied());
4142 {
4143 let adapter = GraphAdapter { g: self };
4144 for &dep_id in dependencies {
4145 match pk.try_add_edge(&adapter, dep_id, dependent) {
4146 Ok(_) => {}
4147 Err(_cycle) => {
4148 if self.config.pk_reject_cycle_edges {
4149 skip_deps.insert(dep_id);
4150 } else {
4151 pk.rebuild_full(&adapter);
4152 }
4153 }
4154 }
4155 }
4156 } self.pk_order = Some(pk);
4158 }
4159
4160 for &dep_id in dependencies {
4162 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4163 continue;
4164 }
4165 self.edges.add_edge(dependent, dep_id);
4166 #[cfg(test)]
4167 {
4168 if let Ok(mut g) = self.instr.lock() {
4169 g.edges_added += 1;
4170 }
4171 }
4172 }
4173
4174 #[cfg(any(test, feature = "legacy_oracle"))]
4175 self.edges.end_batch();
4176 }
4177
4178 fn add_dependent_edges_nobatch(&mut self, dependent: VertexId, dependencies: &[VertexId]) {
4180 self.note_dep_edges(dependent, dependencies.len());
4181 #[cfg(any(test, feature = "legacy_oracle"))]
4182 {
4183 let mut skip_deps: rustc_hash::FxHashSet<VertexId> = rustc_hash::FxHashSet::default();
4185 if self.pk_order.is_some()
4186 && let Some(mut pk) = self.pk_order.take()
4187 {
4188 pk.ensure_nodes(std::iter::once(dependent));
4189 pk.ensure_nodes(dependencies.iter().copied());
4190 {
4191 let adapter = GraphAdapter { g: self };
4192 for &dep_id in dependencies {
4193 match pk.try_add_edge(&adapter, dep_id, dependent) {
4194 Ok(_) => {}
4195 Err(_cycle) => {
4196 if self.config.pk_reject_cycle_edges {
4197 skip_deps.insert(dep_id);
4198 } else {
4199 pk.rebuild_full(&adapter);
4200 }
4201 }
4202 }
4203 }
4204 }
4205 self.pk_order = Some(pk);
4206 }
4207
4208 for &dep_id in dependencies {
4209 if self.config.pk_reject_cycle_edges && skip_deps.contains(&dep_id) {
4210 continue;
4211 }
4212 self.edges.add_edge(dependent, dep_id);
4213 #[cfg(test)]
4214 {
4215 if let Ok(mut g) = self.instr.lock() {
4216 g.edges_added += 1;
4217 }
4218 }
4219 }
4220 }
4221 }
4222
4223 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
4225 where
4226 I: IntoIterator<Item = (u32, u32, ASTNode)>,
4227 {
4228 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
4229 if collected.is_empty() {
4230 return Ok(0);
4231 }
4232 let vol_flags: Vec<bool> = collected
4233 .iter()
4234 .map(|(_, _, ast)| self.is_ast_volatile(ast))
4235 .collect();
4236 self.bulk_set_formulas_with_volatility(sheet, collected, vol_flags)
4237 }
4238
4239 pub fn bulk_set_formulas_with_volatility(
4240 &mut self,
4241 sheet: &str,
4242 collected: Vec<(u32, u32, ASTNode)>,
4243 _vol_flags: Vec<bool>,
4244 ) -> Result<usize, ExcelError> {
4245 let sheet_id = self.sheet_id_mut(sheet);
4246 if collected.is_empty() {
4247 return Ok(0);
4248 }
4249 let provider = RegistryFunctionProvider;
4250 let ingested = {
4251 let mut pipeline = self.ingest_pipeline(&provider);
4252 let inputs = collected.into_iter().map(|(row, col, ast)| {
4253 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
4254 (FormulaAstInput::Tree(ast), placement, None)
4255 });
4256 pipeline.ingest_batch(inputs)?
4257 };
4258 let planned = ingested
4259 .into_iter()
4260 .map(|formula| {
4261 (
4262 formula.placement.coord.row() + 1,
4263 formula.placement.coord.col() + 1,
4264 formula.ast_id,
4265 formula.dep_plan,
4266 )
4267 })
4268 .collect();
4269 self.bulk_set_formulas_with_plans(sheet, planned)
4270 }
4271
4272 pub(crate) fn bulk_set_formulas_with_plans(
4273 &mut self,
4274 sheet: &str,
4275 planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
4276 ) -> Result<usize, ExcelError> {
4277 let sheet_id = self.sheet_id_mut(sheet);
4278 if planned.is_empty() {
4279 return Ok(0);
4280 }
4281 let budgets = self.self_admission_budgets();
4282 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4283 let admission_plans = planned
4284 .iter()
4285 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone()))
4286 .collect::<Vec<_>>();
4287 let usage = self.preview_formula_mutations(&admission_plans)?;
4288 crate::engine::resource_ledger::preflight_graph_admission(&budgets, usage, None)
4289 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4290 }
4291 let mut created_placeholders: Vec<CellRef> = Vec::new();
4292 let mut target_vids: Vec<VertexId> = Vec::with_capacity(planned.len());
4293 for (row, col, _, _) in &planned {
4294 let addr = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
4295 let target = self.get_or_create_vertex(&addr, &mut created_placeholders);
4296 self.materialize_vertex(target);
4297 target_vids.push(target);
4298 }
4299
4300 for (i, &tvid) in target_vids.iter().enumerate() {
4301 if self.vertex_formulas.contains_key(&tvid) {
4302 self.remove_dependent_edges(tvid);
4303 }
4304 self.detach_vertex_from_names(tvid);
4305 self.clear_pending_name_references(tvid);
4306 self.store.set_kind(tvid, VertexKind::FormulaScalar);
4307 self.store.set_dirty(tvid, true);
4308 self.vertex_values.remove(&tvid);
4309 self.vertex_formulas.insert(tvid, planned[i].2);
4310 self.mark_volatile(tvid, planned[i].3.volatile);
4311 self.store.set_dynamic(tvid, planned[i].3.dynamic);
4312 }
4313 self.formula_dirty
4314 .legacy_extend(target_vids.iter().copied());
4315
4316 #[cfg(any(test, feature = "legacy_oracle"))]
4317 self.edges.begin_batch();
4318 for (i, tvid) in target_vids.iter().copied().enumerate() {
4319 let plan = &planned[i].3;
4320 let (mut deps, vertexless) = self.resolve_direct_deps(&plan.direct_cell_deps);
4321 self.note_vertexless_deps(
4322 tvid,
4323 vertexless
4324 .iter()
4325 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
4326 );
4327
4328 let mut name_vertices = Vec::new();
4329 for name in plan
4330 .resolved_named_refs
4331 .iter()
4332 .chain(plan.named_refs.iter())
4333 {
4334 if let Some(named) = self.resolve_name_entry(name, sheet_id) {
4335 if !deps.contains(&named.vertex) {
4336 deps.push(named.vertex);
4337 }
4338 if !name_vertices.contains(&named.vertex) {
4339 name_vertices.push(named.vertex);
4340 }
4341 } else if let Some(source) = self.resolve_source_scalar_entry(name) {
4342 if !deps.contains(&source.vertex) {
4343 deps.push(source.vertex);
4344 }
4345 } else {
4346 self.record_pending_name_reference(sheet_id, name, tvid);
4347 }
4348 }
4349 for source_name in &plan.source_refs {
4350 if let Some(source) = self.resolve_source_scalar_entry(source_name) {
4351 if !deps.contains(&source.vertex) {
4352 deps.push(source.vertex);
4353 }
4354 } else if let Some(source) = self.resolve_source_table_entry(source_name)
4355 && !deps.contains(&source.vertex)
4356 {
4357 deps.push(source.vertex);
4358 }
4359 }
4360 for table_name in &plan.table_refs {
4361 if let Some(table) = self.resolve_table_entry(table_name) {
4362 if !deps.contains(&table.vertex) {
4363 deps.push(table.vertex);
4364 }
4365 } else if let Some(source) = self.resolve_source_table_entry(table_name)
4366 && !deps.contains(&source.vertex)
4367 {
4368 deps.push(source.vertex);
4369 }
4370 }
4371 if !name_vertices.is_empty() {
4372 self.attach_vertex_to_names(tvid, &name_vertices);
4373 }
4374 if !deps.is_empty() {
4375 self.add_dependent_edges_nobatch(tvid, &deps);
4376 }
4377 self.add_range_dependent_edges(tvid, &plan.range_deps, sheet_id);
4378 }
4379 #[cfg(any(test, feature = "legacy_oracle"))]
4380 self.edges.end_batch();
4381
4382 Ok(planned.len())
4383 }
4384
4385 #[cfg(any(test, feature = "legacy_oracle"))]
4386 pub fn add_dependency_edge(
4388 &mut self,
4389 dependent: VertexId,
4390 dependency: VertexId,
4391 ) -> Result<(), ExcelError> {
4392 if dependent == dependency {
4393 return Ok(());
4394 }
4395 let budgets = self.self_admission_budgets();
4396 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4397 let stats = self.baseline_stats();
4398 let added = usize::from(!self.get_dependencies(dependent).contains(&dependency));
4399 crate::engine::resource_ledger::preflight_graph_admission(
4400 &budgets,
4401 crate::engine::resource_ledger::GraphAdmission {
4402 final_vertices: stats.graph_vertex_count,
4403 final_edges: stats.graph_edge_count.checked_add(added).ok_or_else(|| {
4404 ExcelError::new(ExcelErrorKind::NImpl)
4405 .with_message("graph edge count overflow")
4406 })?,
4407 materialization_cells: 0,
4408 added_vertices: 0,
4409 added_edges: added,
4410 },
4411 None,
4412 )
4413 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4414 }
4415 if self.pk_order.is_some()
4417 && let Some(mut pk) = self.pk_order.take()
4418 {
4419 pk.ensure_nodes(std::iter::once(dependent));
4420 pk.ensure_nodes(std::iter::once(dependency));
4421 let adapter = GraphAdapter { g: self };
4422 if pk.try_add_edge(&adapter, dependency, dependent).is_err() {
4423 pk.rebuild_full(&adapter);
4425 }
4426 self.pk_order = Some(pk);
4427 }
4428 self.edges.add_edge(dependent, dependency);
4429 self.store.set_dirty(dependent, true);
4430 self.formula_dirty.legacy_insert(dependent);
4431 Ok(())
4432 }
4433
4434 fn remove_dependent_edges(&mut self, vertex: VertexId) {
4435 self.forget_dep_edges(vertex);
4436 if self.store.reads_range(vertex) {
4437 self.store.set_reads_range(vertex, false);
4438 self.range_reader_count = self.range_reader_count.saturating_sub(1);
4439 }
4440 #[cfg(any(test, feature = "legacy_oracle"))]
4441 self.oracle_remove_dependent_edges(vertex);
4442 }
4443
4444 #[cfg(any(test, feature = "legacy_oracle"))]
4446 fn oracle_remove_dependent_edges(&mut self, vertex: VertexId) {
4447 self.oracle_forget_vertexless(vertex);
4448 let dependencies = self.edges.out_edges(vertex);
4450
4451 #[cfg(any(test, feature = "legacy_oracle"))]
4452 self.edges.begin_batch();
4453 if self.pk_order.is_some()
4454 && let Some(mut pk) = self.pk_order.take()
4455 {
4456 for dep in &dependencies {
4457 pk.remove_edge(*dep, vertex);
4458 }
4459 self.pk_order = Some(pk);
4460 }
4461 for dep in dependencies {
4462 self.edges.remove_edge(vertex, dep);
4463 }
4464 #[cfg(any(test, feature = "legacy_oracle"))]
4465 self.edges.end_batch();
4466
4467 if let Some(old_ranges) = self.formula_to_range_deps.remove(&vertex) {
4469 let old_sheet_id = self.store.sheet_id(vertex);
4470
4471 for range in &old_ranges {
4472 let sheet_id = self
4474 .sheet_reg
4475 .resolve_locator(&range.sheet, old_sheet_id)
4476 .unwrap_or(old_sheet_id);
4477 let s_row = range.start_row.map(|b| b.index);
4478 let e_row = range.end_row.map(|b| b.index);
4479 let s_col = range.start_col.map(|b| b.index);
4480 let e_col = range.end_col.map(|b| b.index);
4481
4482 let mut keys_to_clean = FxHashSet::default();
4483
4484 let col_stripes = (s_row.is_none() && e_row.is_none())
4485 || (s_col.is_some() && e_col.is_some() && (s_row.is_none() || e_row.is_none()));
4486 let row_stripes = (s_col.is_none() && e_col.is_none())
4487 || (s_row.is_some() && e_row.is_some() && (s_col.is_none() || e_col.is_none()));
4488
4489 if col_stripes && !row_stripes {
4490 let sc = s_col.unwrap_or(0);
4491 let ec = e_col.unwrap_or(sc);
4492 for col in sc..=ec {
4493 keys_to_clean.insert(StripeKey {
4494 sheet_id,
4495 stripe_type: StripeType::Column,
4496 index: col,
4497 });
4498 }
4499 } else if row_stripes && !col_stripes {
4500 let sr = s_row.unwrap_or(0);
4501 let er = e_row.unwrap_or(sr);
4502 for row in sr..=er {
4503 keys_to_clean.insert(StripeKey {
4504 sheet_id,
4505 stripe_type: StripeType::Row,
4506 index: row,
4507 });
4508 }
4509 } else {
4510 let start_row = s_row.unwrap_or(0);
4511 let start_col = s_col.unwrap_or(0);
4512 let end_row = e_row.unwrap_or(start_row);
4513 let end_col = e_col.unwrap_or(start_col);
4514
4515 let height = end_row.saturating_sub(start_row) + 1;
4516 let width = end_col.saturating_sub(start_col) + 1;
4517
4518 if self.config.enable_block_stripes && height > 1 && width > 1 {
4519 let start_block_row = start_row / BLOCK_H;
4520 let end_block_row = end_row / BLOCK_H;
4521 let start_block_col = start_col / BLOCK_W;
4522 let end_block_col = end_col / BLOCK_W;
4523
4524 for block_row in start_block_row..=end_block_row {
4525 for block_col in start_block_col..=end_block_col {
4526 keys_to_clean.insert(StripeKey {
4527 sheet_id,
4528 stripe_type: StripeType::Block,
4529 index: block_index(block_row * BLOCK_H, block_col * BLOCK_W),
4530 });
4531 }
4532 }
4533 } else if height > width {
4534 for col in start_col..=end_col {
4535 keys_to_clean.insert(StripeKey {
4536 sheet_id,
4537 stripe_type: StripeType::Column,
4538 index: col,
4539 });
4540 }
4541 } else {
4542 for row in start_row..=end_row {
4543 keys_to_clean.insert(StripeKey {
4544 sheet_id,
4545 stripe_type: StripeType::Row,
4546 index: row,
4547 });
4548 }
4549 }
4550 }
4551
4552 for key in keys_to_clean {
4553 if let Some(dependents) = self.stripe_to_dependents.get_mut(&key) {
4554 dependents.remove(&vertex);
4555 if dependents.is_empty() {
4556 self.stripe_to_dependents.remove(&key);
4557 #[cfg(test)]
4558 {
4559 if let Ok(mut g) = self.instr.lock() {
4560 g.stripe_removes += 1;
4561 }
4562 }
4563 }
4564 }
4565 }
4566 }
4567 }
4568 }
4569
4570 pub(crate) fn has_spill_anchors(&self) -> bool {
4577 !self.spill_anchor_to_cells.is_empty()
4578 }
4579
4580 pub(crate) fn is_spill_anchor(&self, vertex: VertexId) -> bool {
4582 self.spill_anchor_to_cells.contains_key(&vertex)
4583 }
4584
4585 pub(crate) fn update_vertex_value_ref(&mut self, vertex_id: VertexId, value: &LiteralValue) {
4588 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4589 if !self.vertex_values.is_empty() {
4590 self.vertex_values.remove(&vertex_id);
4591 }
4592 return;
4593 }
4594 self.update_vertex_value(vertex_id, value.clone());
4595 }
4596
4597 pub(crate) fn update_vertex_value(&mut self, vertex_id: VertexId, value: LiteralValue) {
4598 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
4599 self.vertex_values.remove(&vertex_id);
4602 return;
4603 }
4604 self.materialize_vertex(vertex_id);
4606 let value_ref = self.data_store.store_value(normalize_stored_literal(value));
4607 self.vertex_values.insert(vertex_id, value_ref);
4608 }
4609
4610 pub fn plan_spill_region(
4612 &self,
4613 anchor: VertexId,
4614 target_cells: &[CellRef],
4615 ) -> Result<(), ExcelError> {
4616 self.plan_spill_region_allowing_formula_overwrite(anchor, target_cells, None)
4617 }
4618
4619 pub(crate) fn plan_spill_region_allowing_formula_overwrite(
4624 &self,
4625 anchor: VertexId,
4626 target_cells: &[CellRef],
4627 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
4628 ) -> Result<(), ExcelError> {
4629 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
4630 let (expected_rows, expected_cols) = if target_cells.is_empty() {
4632 (0u32, 0u32)
4633 } else {
4634 let mut min_r = u32::MAX;
4635 let mut max_r = 0u32;
4636 let mut min_c = u32::MAX;
4637 let mut max_c = 0u32;
4638 for cell in target_cells {
4639 let r = cell.coord.row();
4640 let c = cell.coord.col();
4641 if r < min_r {
4642 min_r = r;
4643 }
4644 if r > max_r {
4645 max_r = r;
4646 }
4647 if c < min_c {
4648 min_c = c;
4649 }
4650 if c > max_c {
4651 max_c = c;
4652 }
4653 }
4654 (
4655 max_r.saturating_sub(min_r).saturating_add(1),
4656 max_c.saturating_sub(min_c).saturating_add(1),
4657 )
4658 };
4659 for cell in target_cells {
4661 let owned_by_anchor = match self.spill_cell_to_anchor.get(cell) {
4663 Some(&existing_anchor) if existing_anchor == anchor => true,
4664 Some(_other) => {
4665 return Err(ExcelError::new(ExcelErrorKind::Spill)
4666 .with_message("BlockedBySpill")
4667 .with_extra(ExcelErrorExtra::Spill {
4668 expected_rows,
4669 expected_cols,
4670 }));
4671 }
4672 None => false,
4673 };
4674
4675 if owned_by_anchor {
4676 continue;
4677 }
4678
4679 if let Some(vid) = self.cell_vertex(cell)
4681 && vid != anchor
4682 {
4683 match self.store.kind(vid) {
4685 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
4686 if let Some(allow) = overwritable_formulas
4687 && allow.contains(&vid)
4688 {
4689 continue;
4690 }
4691 return Err(ExcelError::new(ExcelErrorKind::Spill)
4692 .with_message("BlockedByFormula")
4693 .with_extra(ExcelErrorExtra::Spill {
4694 expected_rows,
4695 expected_cols,
4696 }));
4697 }
4698 _ => {
4699 if let Some(vref) = self.vertex_values.get(&vid) {
4701 let v = self.data_store.retrieve_value(*vref);
4702 if !matches!(v, LiteralValue::Empty) {
4703 return Err(ExcelError::new(ExcelErrorKind::Spill)
4704 .with_message("BlockedByValue")
4705 .with_extra(ExcelErrorExtra::Spill {
4706 expected_rows,
4707 expected_cols,
4708 }));
4709 }
4710 }
4711 }
4712 }
4713 }
4714 }
4715 Ok(())
4716 }
4717
4718 pub fn commit_spill_region_atomic_with_fault(
4725 &mut self,
4726 anchor: VertexId,
4727 target_cells: Vec<CellRef>,
4728 values: Vec<Vec<LiteralValue>>,
4729 fault_after_ops: Option<usize>,
4730 ) -> Result<(), ExcelError> {
4731 self.materialize_vertex(anchor);
4732 let budgets = self.self_admission_budgets();
4733 if crate::engine::resource_ledger::graph_admission_enabled(&budgets) {
4734 let admission = self.preview_spill_materialization(&target_cells)?;
4735 crate::engine::resource_ledger::preflight_graph_admission(&budgets, admission, None)
4736 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
4737 }
4738
4739 let anchor_cell = self
4743 .get_cell_ref(anchor)
4744 .expect("anchor cell ref for spill commit");
4745 let anchor_sheet_name = self.sheet_name(anchor_cell.sheet_id).to_string();
4746 let anchor_row = anchor_cell.coord.row();
4747 let anchor_col = anchor_cell.coord.col();
4748
4749 let prev_cells = self
4751 .spill_anchor_to_cells
4752 .get(&anchor)
4753 .cloned()
4754 .unwrap_or_default();
4755 let new_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
4758 target_cells.iter().copied().collect();
4759 let prev_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
4760 prev_cells.iter().copied().collect();
4761
4762 #[derive(Clone)]
4764 struct Op {
4765 sheet: String,
4766 row: u32,
4767 col: u32,
4768 new_value: LiteralValue,
4769 }
4770 let mut ops: Vec<Op> = Vec::new();
4771
4772 for cell in prev_cells.iter() {
4774 if !new_set.contains(cell) {
4775 let sheet = self.sheet_name(cell.sheet_id).to_string();
4776 ops.push(Op {
4777 sheet,
4778 row: cell.coord.row(),
4779 col: cell.coord.col(),
4780 new_value: LiteralValue::Empty,
4781 });
4782 }
4783 }
4784
4785 if !target_cells.is_empty() {
4787 let first = target_cells.first().copied().unwrap();
4788 let row0 = first.coord.row();
4789 let col0 = first.coord.col();
4790 let sheet = self.sheet_name(first.sheet_id).to_string();
4791 for (r_off, row_vals) in values.iter().enumerate() {
4792 for (c_off, v) in row_vals.iter().enumerate() {
4793 ops.push(Op {
4794 sheet: sheet.clone(),
4795 row: row0 + r_off as u32,
4796 col: col0 + c_off as u32,
4797 new_value: v.clone(),
4798 });
4799 }
4800 }
4801 }
4802
4803 #[derive(Clone)]
4805 struct OldVal {
4806 present: bool,
4807 value: LiteralValue,
4808 }
4809 let mut old_values: Vec<((String, u32, u32), OldVal)> = Vec::with_capacity(ops.len());
4810
4811 for op in &ops {
4813 let old = self
4815 .get_cell_value(&op.sheet, op.row + 1, op.col + 1)
4816 .unwrap_or(LiteralValue::Empty);
4817 let present = true; old_values.push((
4819 (op.sheet.clone(), op.row, op.col),
4820 OldVal {
4821 present,
4822 value: old,
4823 },
4824 ));
4825 }
4826
4827 for (applied, op) in ops.iter().enumerate() {
4829 if let Some(n) = fault_after_ops
4830 && applied == n
4831 {
4832 for idx in (0..applied).rev() {
4833 let ((ref sheet, row, col), ref old) = old_values[idx];
4834 if sheet == &anchor_sheet_name && row == anchor_row && col == anchor_col {
4835 self.update_vertex_value_ref(anchor, &old.value);
4836 } else {
4837 let _ = self.set_cell_value(sheet, row + 1, col + 1, old.value.clone());
4838 }
4839 }
4840 return Err(ExcelError::new(ExcelErrorKind::Error)
4841 .with_message("Injected persistence fault during spill commit"));
4842 }
4843 if op.sheet == anchor_sheet_name && op.row == anchor_row && op.col == anchor_col {
4844 self.update_vertex_value_ref(anchor, &op.new_value);
4845 } else {
4846 let _ =
4847 self.set_cell_value(&op.sheet, op.row + 1, op.col + 1, op.new_value.clone());
4848 }
4849 }
4850
4851 for cell in prev_cells.iter() {
4854 if !new_set.contains(cell) {
4855 self.spill_cell_to_anchor.remove(cell);
4856 let remove_sheet = self
4857 .spill_cells_by_sheet
4858 .get_mut(&cell.sheet_id)
4859 .is_some_and(|sheet| {
4860 sheet.remove(&(cell.coord.row(), cell.coord.col()));
4861 sheet.is_empty()
4862 });
4863 if remove_sheet {
4864 self.spill_cells_by_sheet.remove(&cell.sheet_id);
4865 }
4866 }
4867 }
4868 for cell in &target_cells {
4870 self.spill_cell_to_anchor.insert(*cell, anchor);
4871 self.spill_cells_by_sheet
4872 .entry(cell.sheet_id)
4873 .or_default()
4874 .insert((cell.coord.row(), cell.coord.col()), anchor);
4875 }
4876 self.spill_anchor_to_cells.insert(anchor, target_cells);
4877 Ok(())
4878 }
4879
4880 pub(crate) fn spill_cells_for_anchor(&self, anchor: VertexId) -> Option<&[CellRef]> {
4881 self.spill_anchor_to_cells
4882 .get(&anchor)
4883 .map(|v| v.as_slice())
4884 }
4885
4886 pub(crate) fn spill_registry_has_anchor(&self, anchor: VertexId) -> bool {
4887 self.spill_anchor_to_cells.contains_key(&anchor)
4888 }
4889
4890 pub(crate) fn spill_registry_anchor_for_cell(&self, cell: CellRef) -> Option<VertexId> {
4891 self.spill_cell_to_anchor.get(&cell).copied()
4892 }
4893
4894 pub(crate) fn spill_registry_counts(&self) -> (usize, usize) {
4895 (
4896 self.spill_anchor_to_cells.len(),
4897 self.spill_cell_to_anchor.len(),
4898 )
4899 }
4900
4901 pub fn clear_spill_region(&mut self, anchor: VertexId) {
4903 let _ = self.clear_spill_region_bulk(anchor);
4904 }
4905
4906 pub fn clear_spill_region_bulk(&mut self, anchor: VertexId) -> Vec<CellRef> {
4915 let anchor_cell = self.get_cell_ref(anchor);
4916 let Some(cells) = self.spill_anchor_to_cells.remove(&anchor) else {
4917 return Vec::new();
4918 };
4919
4920 for cell in cells.iter() {
4922 self.spill_cell_to_anchor.remove(cell);
4923 let remove_sheet = self
4924 .spill_cells_by_sheet
4925 .get_mut(&cell.sheet_id)
4926 .is_some_and(|sheet| {
4927 sheet.remove(&(cell.coord.row(), cell.coord.col()));
4928 sheet.is_empty()
4929 });
4930 if remove_sheet {
4931 self.spill_cells_by_sheet.remove(&cell.sheet_id);
4932 }
4933 }
4934
4935 let mut changed: Vec<crate::engine::authority::geom::Cell> = Vec::new();
4938 for cell in cells.iter().copied() {
4939 let is_anchor = anchor_cell.map(|a| a == cell).unwrap_or(false);
4940 if is_anchor {
4941 continue;
4942 }
4943 self.vacate_cell(&cell);
4944 changed.push((cell.sheet_id, cell.coord.row(), cell.coord.col()));
4945 }
4946
4947 if !changed.is_empty() {
4949 let _ = self.mark_dirty_cells(&changed);
4950 }
4951
4952 cells
4953 }
4954
4955 #[cfg(any(test, feature = "legacy_oracle"))]
4956 fn collect_range_dependents_for_vertex(&self, vertex_id: VertexId) -> Vec<VertexId> {
4957 let Some(position) = self.store.grid_addr(vertex_id) else {
4959 return Vec::new();
4960 };
4961 self.collect_range_dependents_for_rect(
4962 self.store.sheet_id(vertex_id),
4963 position.row(),
4964 position.col(),
4965 position.row(),
4966 position.col(),
4967 )
4968 }
4969
4970 #[cfg(any(test, feature = "legacy_oracle"))]
4971 fn collect_range_dependents_for_rect(
4972 &self,
4973 sheet_id: SheetId,
4974 start_row: u32,
4975 start_col: u32,
4976 end_row: u32,
4977 end_col: u32,
4978 ) -> Vec<VertexId> {
4979 if self.stripe_to_dependents.is_empty() {
4980 return Vec::new();
4981 }
4982 let mut candidates: FxHashSet<VertexId> = FxHashSet::default();
4983
4984 for col in start_col..=end_col {
4985 let key = StripeKey {
4986 sheet_id,
4987 stripe_type: StripeType::Column,
4988 index: col,
4989 };
4990 if let Some(deps) = self.stripe_to_dependents.get(&key) {
4991 candidates.extend(deps);
4992 }
4993 }
4994 for row in start_row..=end_row {
4995 let key = StripeKey {
4996 sheet_id,
4997 stripe_type: StripeType::Row,
4998 index: row,
4999 };
5000 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5001 candidates.extend(deps);
5002 }
5003 }
5004 if self.config.enable_block_stripes {
5005 let br0 = start_row / BLOCK_H;
5006 let br1 = end_row / BLOCK_H;
5007 let bc0 = start_col / BLOCK_W;
5008 let bc1 = end_col / BLOCK_W;
5009 for br in br0..=br1 {
5010 for bc in bc0..=bc1 {
5011 let key = StripeKey {
5012 sheet_id,
5013 stripe_type: StripeType::Block,
5014 index: block_index(br * BLOCK_H, bc * BLOCK_W),
5015 };
5016 if let Some(deps) = self.stripe_to_dependents.get(&key) {
5017 candidates.extend(deps);
5018 }
5019 }
5020 }
5021 }
5022
5023 let mut out: Vec<VertexId> = Vec::new();
5025 for dep_id in candidates {
5026 let Some(ranges) = self.formula_to_range_deps.get(&dep_id) else {
5027 continue;
5028 };
5029 let mut hit = false;
5030 for range in ranges {
5031 let range_sheet_id = self
5034 .sheet_reg
5035 .resolve_locator(&range.sheet, self.get_vertex_sheet_id(dep_id))
5036 .unwrap_or(sheet_id);
5037 if range_sheet_id != sheet_id {
5038 continue;
5039 }
5040 let sr0 = range.start_row.map(|b| b.index).unwrap_or(0);
5041 let er0 = range.end_row.map(|b| b.index).unwrap_or(u32::MAX);
5042 let sc0 = range.start_col.map(|b| b.index).unwrap_or(0);
5043 let ec0 = range.end_col.map(|b| b.index).unwrap_or(u32::MAX);
5044 let overlap =
5045 sr0 <= end_row && er0 >= start_row && sc0 <= end_col && ec0 >= start_col;
5046 if overlap {
5047 hit = true;
5048 break;
5049 }
5050 }
5051 if hit {
5052 out.push(dep_id);
5053 }
5054 }
5055 out
5056 }
5057
5058 pub(crate) fn is_live_formula_vertex(&self, vertex_id: VertexId) -> bool {
5062 self.store.vertex_exists_active(vertex_id) && self.has_formula(vertex_id)
5063 }
5064
5065 pub(crate) fn vertex_exists(&self, vertex_id: VertexId) -> bool {
5067 if vertex_id.0 < FIRST_NORMAL_VERTEX {
5068 return false;
5069 }
5070 let index = (vertex_id.0 - FIRST_NORMAL_VERTEX) as usize;
5071 index < self.store.len()
5072 }
5073
5074 pub(crate) fn get_vertex_kind(&self, vertex_id: VertexId) -> VertexKind {
5076 self.store.kind(vertex_id)
5077 }
5078
5079 pub(crate) fn get_vertex_sheet_id(&self, vertex_id: VertexId) -> SheetId {
5081 self.store.sheet_id(vertex_id)
5082 }
5083
5084 pub fn get_formula_id(&self, vertex_id: VertexId) -> Option<AstNodeId> {
5088 self.vertex_formulas.get(&vertex_id).and_then(|f| f.own())
5089 }
5090
5091 pub fn formula_view(&self, vertex_id: VertexId) -> Option<FormulaView> {
5093 let f = self.vertex_formulas.get(&vertex_id)?;
5094 Some(match f {
5095 FormulaRef::Own(template) => FormulaView {
5096 template,
5097 row_delta: 0,
5098 col_delta: 0,
5099 },
5100 FormulaRef::Member { template, anchor } => {
5101 let addr = self.store.grid_addr(vertex_id)?;
5102 FormulaView {
5103 template,
5104 row_delta: i64::from(addr.row()) - i64::from(anchor.0),
5105 col_delta: i64::from(addr.col()) - i64::from(anchor.1),
5106 }
5107 }
5108 })
5109 }
5110
5111 pub(crate) fn has_formula(&self, vertex_id: VertexId) -> bool {
5113 self.vertex_formulas.contains_key(&vertex_id)
5114 }
5115
5116 pub(crate) fn own_formula_id(&mut self, vertex_id: VertexId) -> Option<AstNodeId> {
5120 self.materialize_vertex(vertex_id);
5121 match self.vertex_formulas.get(&vertex_id)? {
5122 FormulaRef::Own(id) => Some(id),
5123 FormulaRef::Member { .. } => {
5124 let ast = self.get_formula(vertex_id)?;
5125 let id = self.data_store.store_ast(&ast, &self.sheet_reg);
5126 self.vertex_formulas.decompress(vertex_id, id);
5127 Some(id)
5128 }
5129 }
5130 }
5131
5132 pub(crate) fn materialized_formula_vertices_sorted(&self) -> Vec<VertexId> {
5135 let mut vertices: Vec<VertexId> = self.vertex_formulas.map_iter().map(|(v, _)| v).collect();
5136 vertices.sort_unstable();
5137 vertices
5138 }
5139
5140 pub(crate) fn formula_vertices(&self) -> Vec<VertexId> {
5141 let mut vertices = self.vertex_formulas.keys().collect::<Vec<_>>();
5142 vertices.sort_unstable();
5143 vertices
5144 }
5145
5146 pub fn get_formula_id_and_volatile(&self, vertex_id: VertexId) -> Option<(AstNodeId, bool)> {
5147 let ast_id = self.get_formula_id(vertex_id)?;
5148 Some((ast_id, self.is_volatile(vertex_id)))
5149 }
5150
5151 pub fn get_formula_node(&self, vertex_id: VertexId) -> Option<&super::arena::AstNodeData> {
5152 let ast_id = self.get_formula_id(vertex_id)?;
5153 self.data_store.get_node(ast_id)
5154 }
5155
5156 pub fn get_formula_node_and_volatile(
5157 &self,
5158 vertex_id: VertexId,
5159 ) -> Option<(&super::arena::AstNodeData, bool)> {
5160 let (ast_id, vol) = self.get_formula_id_and_volatile(vertex_id)?;
5161 let node = self.data_store.get_node(ast_id)?;
5162 Some((node, vol))
5163 }
5164
5165 pub fn get_formula(&self, vertex_id: VertexId) -> Option<ASTNode> {
5169 let view = self.formula_view(vertex_id)?;
5170 let ast = self
5171 .data_store
5172 .retrieve_ast(view.template, &self.sheet_reg)?;
5173 if view.row_delta == 0 && view.col_delta == 0 {
5174 return Some(ast);
5175 }
5176 crate::engine::template::relocate::instantiate_member_ast(
5177 &ast,
5178 view.row_delta,
5179 view.col_delta,
5180 )
5181 .ok()
5182 }
5183
5184 pub fn get_value(&self, vertex_id: VertexId) -> Option<LiteralValue> {
5186 if !self.value_cache_enabled && self.is_grid_backed(vertex_id) {
5187 #[cfg(debug_assertions)]
5190 {
5191 self.graph_value_read_attempts
5192 .fetch_add(1, Ordering::Relaxed);
5193 }
5194 return None;
5195 }
5196 self.vertex_values
5197 .get(&vertex_id)
5198 .map(|&value_ref| self.data_store.retrieve_value(value_ref))
5199 }
5200
5201 #[inline]
5208 fn is_grid_backed(&self, vertex_id: VertexId) -> bool {
5209 self.store.grid_addr(vertex_id).is_some()
5210 }
5211
5212 pub(crate) fn get_cell_ref(&self, vertex_id: VertexId) -> Option<CellRef> {
5217 let grid = self.store.grid_addr(vertex_id)?;
5218 let sheet_id = self.store.sheet_id(vertex_id);
5219 let coord = Coord::new(grid.row(), grid.col(), true, true);
5220 Some(CellRef::new(sheet_id, coord))
5221 }
5222
5223 pub(crate) fn make_cell_ref_internal(&self, sheet_id: SheetId, row: u32, col: u32) -> CellRef {
5225 let coord = Coord::new(row, col, true, true);
5226 CellRef::new(sheet_id, coord)
5227 }
5228
5229 pub fn make_cell_ref(&self, sheet_name: &str, row: u32, col: u32) -> CellRef {
5231 let sheet_id = self.sheet_reg.get_id(sheet_name).unwrap_or(0);
5232 let coord = Coord::from_excel(row, col, true, true);
5233 CellRef::new(sheet_id, coord)
5234 }
5235
5236 pub(crate) fn is_dirty(&self, vertex_id: VertexId) -> bool {
5238 self.store.is_dirty(vertex_id)
5239 }
5240
5241 pub(crate) fn is_volatile(&self, vertex_id: VertexId) -> bool {
5243 self.store.is_volatile(vertex_id)
5244 }
5245
5246 pub(crate) fn is_dynamic(&self, vertex_id: VertexId) -> bool {
5247 self.store.is_dynamic(vertex_id)
5248 }
5249
5250 pub fn get_vertex_id_for_address(&self, addr: &CellRef) -> Option<VertexId> {
5256 self.cell_vertex(addr)
5257 }
5258
5259 #[cfg(test)]
5260 pub fn cell_to_vertex(
5261 &self,
5262 ) -> &std::collections::HashMap<CellRef, VertexId, CoordBuildHasher> {
5263 &self.cell_to_vertex
5264 }
5265
5266 #[cfg(any(test, feature = "legacy_oracle"))]
5267 #[inline]
5271 pub(crate) fn dependencies_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5272 self.edges.out_edges_ref(vertex_id)
5273 }
5274
5275 #[cfg(any(test, feature = "legacy_oracle"))]
5276 pub(crate) fn get_dependencies(&self, vertex_id: VertexId) -> Vec<VertexId> {
5278 self.edges.out_edges(vertex_id)
5279 }
5280
5281 #[cfg(any(test, feature = "legacy_oracle"))]
5282 pub(crate) fn has_self_loop(&self, vertex_id: VertexId) -> bool {
5284 if let Some(deps) = self.dependencies_slice(vertex_id) {
5285 deps.contains(&vertex_id)
5286 } else {
5287 self.edges.out_edges(vertex_id).contains(&vertex_id)
5288 }
5289 }
5290
5291 #[cfg(any(test, feature = "legacy_oracle"))]
5292 #[inline]
5296 pub(crate) fn dependents_slice(&self, vertex_id: VertexId) -> Option<&[VertexId]> {
5297 self.edges.in_edges_ref(vertex_id)
5298 }
5299
5300 #[cfg(any(test, feature = "legacy_oracle"))]
5301 pub(crate) fn get_dependents(&self, vertex_id: VertexId) -> Vec<VertexId> {
5307 self.edges.in_edges_merged(vertex_id)
5308 }
5309
5310 #[cfg(any(test, feature = "legacy_oracle"))]
5311 pub(crate) fn visit_direct_dependents_bounded(
5315 &self,
5316 vertex_id: VertexId,
5317 remaining_work: &mut u64,
5318 visitor: &mut dyn FnMut(VertexId) -> bool,
5319 ) -> bool {
5320 self.edges
5321 .visit_in_edges_bounded(vertex_id, remaining_work, visitor)
5322 }
5323
5324 #[doc(hidden)]
5328 pub fn snapshot_vertex(&self, id: VertexId) -> crate::engine::VertexSnapshot {
5329 let coord = self.store.grid_addr(id).unwrap_or_default();
5330 let sheet_id = self.store.sheet_id(id);
5331 let kind = self.store.kind(id);
5332 let flags = self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG;
5333
5334 let value_ref = self.vertex_values.get(&id).copied();
5336 let formula_ref = self.vertex_formulas.get(&id).map(|f| f.root());
5337
5338 #[cfg(any(test, feature = "legacy_oracle"))]
5340 let out_edges = self.get_dependencies(id);
5341 #[cfg(not(any(test, feature = "legacy_oracle")))]
5342 let out_edges = Vec::new();
5343
5344 crate::engine::VertexSnapshot {
5345 coord,
5346 sheet_id,
5347 kind,
5348 flags,
5349 value_ref,
5350 formula_ref,
5351 out_edges,
5352 }
5353 }
5354
5355 #[doc(hidden)]
5357 pub(crate) fn remove_all_edges(&mut self, id: VertexId) {
5358 self.materialize_vertex(id);
5359 #[cfg(not(any(test, feature = "legacy_oracle")))]
5360 self.remove_dependent_edges(id);
5361 #[cfg(any(test, feature = "legacy_oracle"))]
5362 {
5363 #[cfg(any(test, feature = "legacy_oracle"))]
5365 self.edges.begin_batch();
5366
5367 self.remove_dependent_edges(id);
5369
5370 let dependents = self.get_dependents(id);
5373 if self.pk_order.is_some()
5374 && let Some(mut pk) = self.pk_order.take()
5375 {
5376 for dependent in &dependents {
5377 pk.remove_edge(id, *dependent);
5378 }
5379 self.pk_order = Some(pk);
5380 }
5381 for dependent in dependents {
5382 self.edges.remove_edge(dependent, id);
5383 }
5384
5385 #[cfg(any(test, feature = "legacy_oracle"))]
5387 self.edges.end_batch();
5388 }
5389 }
5390
5391 #[doc(hidden)]
5393 pub fn mark_as_ref_error(&mut self, id: VertexId) {
5394 self.materialize_vertex(id);
5395 if !self.value_cache_enabled && self.is_grid_backed(id) {
5396 self.ref_error_vertices.insert(id);
5397 self.vertex_values.remove(&id);
5400 let _ = self.mark_dirty(id);
5401 return;
5402 }
5403 let error = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
5404 let value_ref = self.data_store.store_value(error);
5405 self.vertex_values.insert(id, value_ref);
5406 let _ = self.mark_dirty(id);
5407 }
5408
5409 pub fn is_ref_error(&self, id: VertexId) -> bool {
5411 if !self.value_cache_enabled && self.is_grid_backed(id) {
5412 return self.ref_error_vertices.contains(&id);
5413 }
5414 if let Some(value_ref) = self.vertex_values.get(&id) {
5415 let value = self.data_store.retrieve_value(*value_ref);
5416 if let LiteralValue::Error(err) = value {
5417 return err.kind == ExcelErrorKind::Ref;
5418 }
5419 }
5420 false
5421 }
5422
5423 #[doc(hidden)]
5425 pub fn mark_dependents_dirty(&mut self, id: VertexId) {
5426 if self.authority_defers_marks() {
5430 self.authority_queue_direct_dirty(id);
5431 return;
5432 }
5433 for dep_id in self.authority_in_edge_readers(id) {
5434 self.store.set_dirty(dep_id, true);
5435 self.formula_dirty.legacy_insert(dep_id);
5436 }
5437 }
5438
5439 #[doc(hidden)]
5441 pub fn mark_volatile(&mut self, id: VertexId, volatile: bool) {
5442 if volatile {
5443 self.materialize_vertex(id);
5444 }
5445 self.store.set_volatile(id, volatile);
5446 if volatile {
5447 self.volatile_vertices.insert(id);
5448 } else {
5449 self.volatile_vertices.remove(&id);
5450 }
5451 }
5452
5453 #[doc(hidden)]
5458 pub fn set_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5459 self.materialize_vertex(id);
5460 self.store.set_addr(id, VertexAddr::grid(coord));
5461 }
5462
5463 #[doc(hidden)]
5465 pub(crate) fn update_edge_grid_addr(&mut self, id: VertexId, coord: GridAddr) {
5466 self.materialize_vertex(id);
5467 #[cfg(not(any(test, feature = "legacy_oracle")))]
5468 let _ = (&id, &coord);
5469 #[cfg(any(test, feature = "legacy_oracle"))]
5470 {
5471 self.edges.update_addr(id, VertexAddr::grid(coord));
5472 }
5473 }
5474
5475 #[doc(hidden)]
5477 pub fn mark_deleted(&mut self, id: VertexId, deleted: bool) {
5478 self.materialize_vertex(id);
5479 self.store.mark_deleted(id, deleted);
5480 }
5481
5482 #[doc(hidden)]
5484 pub fn set_kind(&mut self, id: VertexId, kind: VertexKind) {
5485 self.materialize_vertex(id);
5486 self.store.set_kind(id, kind);
5487 }
5488
5489 #[doc(hidden)]
5491 pub fn set_dirty(&mut self, id: VertexId, dirty: bool) {
5492 self.store.set_dirty(id, dirty);
5493 if dirty {
5494 self.formula_dirty.legacy_insert(id);
5495 } else {
5496 self.formula_dirty.legacy_remove(&id);
5497 }
5498 }
5499
5500 #[cfg(test)]
5502 pub(crate) fn get_kind(&self, id: VertexId) -> VertexKind {
5503 self.store.kind(id)
5504 }
5505
5506 #[cfg(test)]
5508 pub(crate) fn get_flags(&self, id: VertexId) -> u8 {
5509 self.store.flags(id) & !crate::engine::vertex_store::VIRTUAL_FLAG
5510 }
5511
5512 #[inline]
5514 pub(crate) fn virtual_member_at(&self, addr: &CellRef) -> Option<VertexId> {
5515 self.vertex_formulas
5516 .virtual_members()
5517 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
5518 .map(|m| m.vertex)
5519 }
5520
5521 #[cfg(test)]
5523 pub(crate) fn is_deleted(&self, id: VertexId) -> bool {
5524 self.store.is_deleted(id)
5525 }
5526
5527 #[cfg(any(test, feature = "legacy_oracle"))]
5528 #[doc(hidden)]
5530 pub fn rebuild_edges(&mut self) {
5531 self.edges.rebuild();
5532 }
5533
5534 #[cfg(any(test, feature = "legacy_oracle"))]
5535 pub fn flush_pending_edge_deltas(&mut self) {
5541 self.edges.rebuild();
5542 }
5543
5544 #[cfg(any(test, feature = "legacy_oracle"))]
5545 #[doc(hidden)]
5547 pub fn edges_delta_size(&self) -> usize {
5548 self.edges.delta_size()
5549 }
5550
5551 #[cfg(any(test, feature = "legacy_oracle"))]
5552 #[doc(hidden)]
5555 pub fn edges_rebuild_count(&self) -> u64 {
5556 self.edges.rebuild_count()
5557 }
5558
5559 pub fn get_vertex_for_cell(&self, addr: &CellRef) -> Option<VertexId> {
5561 self.cell_vertex(addr)
5562 }
5563
5564 #[inline]
5569 pub(crate) fn cell_vertex(&self, addr: &CellRef) -> Option<VertexId> {
5570 if let Some(&v) = self.cell_to_vertex.get(addr) {
5571 return Some(v);
5572 }
5573 self.vertex_formulas
5574 .virtual_members()
5575 .by_cell(addr.sheet_id, addr.coord.row(), addr.coord.col())
5576 .map(|m| m.vertex)
5577 }
5578
5579 #[inline]
5582 pub(crate) fn cell_vertex_mut(&mut self, addr: &CellRef) -> Option<VertexId> {
5583 if let Some(&v) = self.cell_to_vertex.get(addr) {
5584 return Some(v);
5585 }
5586 let m = self.vertex_formulas.virtual_members().by_cell(
5587 addr.sheet_id,
5588 addr.coord.row(),
5589 addr.coord.col(),
5590 )?;
5591 self.materialize_vertex(m.vertex);
5592 Some(m.vertex)
5593 }
5594
5595 #[inline]
5597 pub(crate) fn is_virtual_member(&self, v: VertexId) -> bool {
5598 !self.vertex_formulas.virtual_members().is_empty() && self.store.is_virtual(v)
5599 }
5600
5601 pub(crate) fn materialize_vertex(&mut self, v: VertexId) -> bool {
5605 if !self.is_virtual_member(v) {
5606 return false;
5607 }
5608 let Some(m) = self.vertex_formulas.materialize(v) else {
5609 return false;
5610 };
5611 self.store.ensure_dense(v, 1);
5612 self.store.set_virtual(v, false);
5613 let addr = CellRef::new(m.sheet, Coord::new(m.row, m.col, true, true));
5614 self.cell_to_vertex.insert(addr, v);
5615 self.sheet_index_mut(m.sheet)
5616 .add_vertex(GridAddr::new(m.row, m.col), v);
5617 true
5618 }
5619
5620 pub(crate) fn materialize_sheet(&mut self, sheet: SheetId) {
5622 let runs = self
5623 .vertex_formulas
5624 .virtual_members_mut()
5625 .drain_sheet(sheet);
5626 self.restore_runs(runs);
5627 }
5628
5629 pub(crate) fn materialize_all(&mut self) {
5632 if self.vertex_formulas.virtual_members().is_empty() {
5633 return;
5634 }
5635 let runs = self.vertex_formulas.virtual_members_mut().drain();
5636 self.restore_runs(runs);
5637 }
5638
5639 fn restore_runs(&mut self, runs: Vec<virtual_members::MemberRun>) {
5640 if runs.is_empty() {
5641 return;
5642 }
5643 let n: usize = runs.iter().map(|r| r.len as usize).sum();
5644 self.cell_to_vertex.reserve(n);
5645 self.vertex_formulas.reserve(n);
5646 let mut by_sheet: FxHashMap<SheetId, Vec<(GridAddr, VertexId)>> = FxHashMap::default();
5647 for r in &runs {
5648 self.store.ensure_dense(VertexId(r.first), r.len);
5649 let f = r.formula();
5650 let batch = by_sheet.entry(r.sheet).or_default();
5651 for (v, row) in r.members() {
5652 self.store.set_virtual(v, false);
5653 self.vertex_formulas.restore(v, f);
5654 self.cell_to_vertex
5655 .insert(CellRef::new(r.sheet, Coord::new(row, r.col, true, true)), v);
5656 batch.push((GridAddr::new(row, r.col), v));
5657 }
5658 }
5659 for (sheet, batch) in by_sheet {
5660 self.sheet_index_mut(sheet).add_vertices_batch(&batch);
5661 }
5662 }
5663
5664 pub(crate) fn virtual_members_in_cols(
5668 &self,
5669 sheet: SheetId,
5670 c0: u32,
5671 c1: u32,
5672 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
5673 self.vertex_formulas
5674 .virtual_members()
5675 .runs_in_cols(sheet, c0, c1)
5676 .flat_map(|r| {
5677 r.members()
5678 .map(move |(v, row)| (v, GridAddr::new(row, r.col)))
5679 })
5680 }
5681
5682 pub(crate) fn vertices_in_cols(&self, sheet: SheetId, c0: u32, c1: u32) -> Vec<VertexId> {
5686 match self.sheet_indexes.get(&sheet) {
5687 Some(index) => {
5688 let mut out = index.vertices_in_col_range(c0, c1);
5689 out.extend(self.virtual_members_in_cols(sheet, c0, c1).map(|(v, _)| v));
5690 out
5691 }
5692 None => self
5693 .grid_vertices_in_sheet(sheet)
5694 .filter(|(_, a)| a.col() >= c0 && a.col() <= c1)
5695 .map(|(v, _)| v)
5696 .collect(),
5697 }
5698 }
5699
5700 pub(crate) fn vertices_in_rows(&self, sheet: SheetId, r0: u32, r1: u32) -> Vec<VertexId> {
5703 match self.sheet_indexes.get(&sheet) {
5704 Some(index) => {
5705 let mut out = index.vertices_in_row_range(r0, r1);
5706 for r in self.vertex_formulas.virtual_members().runs_in_sheet(sheet) {
5707 let lo = r.row0.max(r0);
5708 let hi = (r.row0 + r.len - 1).min(r1);
5709 if lo <= hi {
5710 out.extend((lo..=hi).map(|row| VertexId(r.first + (row - r.row0))));
5711 }
5712 }
5713 out
5714 }
5715 None => self
5716 .grid_vertices_in_sheet(sheet)
5717 .filter(|(_, a)| a.row() >= r0 && a.row() <= r1)
5718 .map(|(v, _)| v)
5719 .collect(),
5720 }
5721 }
5722
5723 pub(crate) fn virtualize_family_members(&mut self) -> usize {
5735 let mut cand: Vec<(u32, AstNodeId, (u32, u32))> = self
5736 .vertex_formulas
5737 .map_iter()
5738 .filter_map(|(v, f)| match f {
5739 FormulaRef::Member { template, anchor } => Some((v.0, template, anchor)),
5740 FormulaRef::Own(_) => None,
5741 })
5742 .collect();
5743 if cand.len() < 2 {
5744 return 0;
5745 }
5746 cand.sort_unstable_by_key(|c| c.0);
5747 let runs = self.member_runs_in_id_order(&cand);
5748 drop(cand);
5749 self.install_virtual_runs(runs)
5750 }
5751
5752 fn member_runs_in_id_order(
5756 &self,
5757 cand: &[(u32, AstNodeId, (u32, u32))],
5758 ) -> Vec<virtual_members::MemberRun> {
5759 let mut runs: Vec<virtual_members::MemberRun> = Vec::new();
5760 let mut cur: Option<virtual_members::MemberRun> = None;
5761 let close = |cur: &mut Option<virtual_members::MemberRun>,
5762 runs: &mut Vec<virtual_members::MemberRun>| {
5763 if let Some(r) = cur.take()
5764 && r.len >= 2
5765 {
5766 runs.push(r);
5767 }
5768 };
5769 for &(v, template, anchor) in cand {
5770 let vid = VertexId(v);
5771 if !self.virtualizable(vid) {
5772 close(&mut cur, &mut runs);
5773 continue;
5774 }
5775 let Some(addr) = self.store.grid_addr(vid) else {
5776 close(&mut cur, &mut runs);
5777 continue;
5778 };
5779 let sheet = self.store.sheet_id(vid);
5780 if let Some(r) = cur.as_mut()
5781 && r.sheet == sheet
5782 && r.col == addr.col()
5783 && r.first + r.len == v
5784 && r.row0 + r.len == addr.row()
5785 && r.template == template
5786 && r.anchor == anchor
5787 {
5788 r.len += 1;
5789 continue;
5790 }
5791 close(&mut cur, &mut runs);
5792 cur = Some(virtual_members::MemberRun {
5793 sheet,
5794 col: addr.col(),
5795 row0: addr.row(),
5796 len: 1,
5797 first: v,
5798 template,
5799 anchor,
5800 });
5801 }
5802 close(&mut cur, &mut runs);
5803 runs
5804 }
5805
5806 fn install_virtual_runs(&mut self, runs: Vec<virtual_members::MemberRun>) -> usize {
5809 if runs.is_empty() {
5810 return 0;
5811 }
5812 let mut made = 0usize;
5813 let mut sheets: Vec<SheetId> = Vec::new();
5814 for &r in &runs {
5815 for (v, _) in r.members() {
5816 self.store.set_virtual(v, true);
5817 }
5818 sheets.push(r.sheet);
5819 made += r.len as usize;
5820 self.vertex_formulas.virtual_members_mut().insert(r);
5821 }
5822 let store = &self.store;
5828 let mut removed: Vec<u32> = Vec::with_capacity(made);
5829 self.cell_to_vertex.retain(|c, v| {
5830 let mine = store.is_virtual(*v)
5831 && store.sheet_id(*v) == c.sheet_id
5832 && store.grid_addr(*v) == Some(GridAddr::new(c.coord.row(), c.coord.col()));
5833 if mine {
5834 removed.push(v.0);
5835 }
5836 !mine
5837 });
5838 self.cell_to_vertex.shrink_to_fit();
5839 let store = &self.store;
5840 self.vertex_formulas
5841 .drop_virtual_from_map(|v| store.is_virtual(v));
5842 if removed.len() != made {
5843 removed.sort_unstable();
5847 let orphans: Vec<VertexId> = runs
5848 .iter()
5849 .flat_map(|r| r.members().map(|(v, _)| v))
5850 .filter(|v| removed.binary_search(&v.0).is_err())
5851 .collect();
5852 for v in orphans {
5853 if self.vertex_formulas.materialize(v).is_some() {
5854 self.store.set_virtual(v, false);
5855 made -= 1;
5856 }
5857 }
5858 }
5859 sheets.sort_unstable();
5860 sheets.dedup();
5861 self.rebuild_sheet_indexes(&sheets);
5862 self.virtualize_member_pages();
5863 made
5864 }
5865
5866 pub(crate) fn virtualize_member_pages(&mut self) -> usize {
5869 let runs: Vec<virtual_members::MemberRun> = self
5870 .vertex_formulas
5871 .virtual_members()
5872 .runs()
5873 .filter(|r| r.len as usize >= 1024)
5874 .copied()
5875 .collect();
5876 runs.iter()
5877 .map(|r| {
5878 self.store
5879 .virtualize_member_span(VertexId(r.first), r.len, r.sheet, r.col, r.row0)
5880 })
5881 .sum()
5882 }
5883
5884 fn virtualizable(&self, v: VertexId) -> bool {
5886 let flags = self.store.flags(v);
5887 if flags & (0x02 | 0x04 | 0x08 | crate::engine::vertex_store::VIRTUAL_FLAG) != 0
5889 || self.store.kind(v) != VertexKind::FormulaScalar
5890 {
5891 return false;
5892 }
5893 let absent = |empty: bool, contains: &dyn Fn() -> bool| empty || !contains();
5894 absent(self.ref_error_vertices.is_empty(), &|| {
5895 self.ref_error_vertices.contains(&v)
5896 }) && absent(self.vertex_values.is_empty(), &|| {
5897 self.vertex_values.contains_key(&v)
5898 }) && absent(self.vertex_to_pending_names.is_empty(), &|| {
5899 self.vertex_to_pending_names.contains_key(&v)
5900 }) && absent(self.spill_anchor_to_cells.is_empty(), &|| {
5901 self.spill_anchor_to_cells.contains_key(&v)
5902 }) && absent(self.name_vertex_lookup.is_empty(), &|| {
5903 self.name_vertex_lookup.contains_key(&v)
5904 }) && absent(self.volatile_vertices.is_empty(), &|| {
5905 self.volatile_vertices.contains(&v)
5906 })
5907 }
5908
5909 fn rebuild_sheet_indexes(&mut self, sheets: &[SheetId]) {
5913 let store = &self.store;
5914 for sheet in sheets {
5915 if let Some(index) = self.sheet_indexes.get_mut(sheet) {
5916 index.retain_vertices(|v| !store.is_virtual(v));
5917 }
5918 }
5919 }
5920
5921 pub(crate) fn virtual_vertex_pages(&self) -> usize {
5924 self.store.virtual_pages()
5925 }
5926
5927 pub(crate) fn virtual_member_counts(&self) -> (usize, usize) {
5928 let m = self.vertex_formulas.virtual_members();
5929 (m.len(), m.run_count())
5930 }
5931
5932 pub fn get_grid_addr(&self, id: VertexId) -> Option<GridAddr> {
5937 self.store.grid_addr(id)
5938 }
5939
5940 pub fn get_sheet_id(&self, id: VertexId) -> SheetId {
5942 self.store.sheet_id(id)
5943 }
5944
5945 pub fn grid_vertices_in_sheet(
5952 &self,
5953 sheet_id: SheetId,
5954 ) -> impl Iterator<Item = (VertexId, GridAddr)> + '_ {
5955 self.store.all_vertices().filter_map(move |id| {
5956 if !self.vertex_exists(id) || self.store.sheet_id(id) != sheet_id {
5957 return None;
5958 }
5959 if !self.retired_id_set.is_empty()
5960 && self.retired_id_set.contains(&id)
5961 && self.store.is_deleted(id)
5962 {
5963 return None;
5964 }
5965 self.store.grid_addr(id).map(|addr| (id, addr))
5966 })
5967 }
5968
5969 pub fn vertex_has_formula(&self, id: VertexId) -> bool {
5971 self.vertex_formulas.contains_key(&id)
5972 }
5973
5974 pub fn vertices_with_formulas(&self) -> impl Iterator<Item = VertexId> + '_ {
5976 self.vertex_formulas.keys()
5977 }
5978
5979 pub fn update_vertex_formula(&mut self, id: VertexId, ast: ASTNode) -> Result<(), ExcelError> {
5981 self.materialize_vertex(id);
5982 let sheet_id = self.store.sheet_id(id);
5984
5985 let (
5987 new_dependencies,
5988 new_range_dependencies,
5989 vertexless,
5990 named_dependencies,
5991 unresolved_names,
5992 ) = self.extract_dependencies_with_pending_names(&ast, sheet_id)?;
5993
5994 let old_kind = self.store.kind(id);
5995
5996 self.remove_dependent_edges(id);
5998 self.detach_vertex_from_names(id);
5999 self.clear_pending_name_references(id);
6000
6001 let ast_id = self.data_store.store_ast(&ast, &self.sheet_reg);
6003 self.vertex_formulas.insert(id, ast_id);
6004
6005 self.add_dependent_edges(id, &new_dependencies);
6007 self.note_vertexless_deps(
6008 id,
6009 vertexless
6010 .iter()
6011 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6012 );
6013 self.add_range_dependent_edges(id, &new_range_dependencies, sheet_id);
6014
6015 if !named_dependencies.is_empty() {
6016 self.attach_vertex_to_names(id, &named_dependencies);
6017 }
6018 for unresolved_name in &unresolved_names {
6019 self.record_pending_name_reference(sheet_id, unresolved_name, id);
6020 }
6021
6022 self.ref_error_vertices.remove(&id);
6025 self.vertex_values.remove(&id);
6026
6027 self.store.set_kind(
6029 id,
6030 if old_kind == VertexKind::FormulaArray {
6031 VertexKind::FormulaArray
6032 } else {
6033 VertexKind::FormulaScalar
6034 },
6035 );
6036
6037 Ok(())
6038 }
6039
6040 pub fn mark_vertex_dirty(&mut self, vertex_id: VertexId) {
6042 self.store.set_dirty(vertex_id, true);
6043 self.formula_dirty.legacy_insert(vertex_id);
6044 }
6045
6046 pub fn mark_vertices_dirty_batch(&mut self, vertices: &[VertexId]) {
6048 self.formula_dirty.legacy_reserve(vertices.len());
6049 for &vertex_id in vertices {
6050 self.store.set_dirty(vertex_id, true);
6051 }
6052 self.formula_dirty.legacy_extend(vertices.iter().copied());
6053 }
6054
6055 pub fn update_cell_mapping(
6057 &mut self,
6058 id: VertexId,
6059 old_addr: Option<CellRef>,
6060 new_addr: CellRef,
6061 ) {
6062 self.materialize_vertex(id);
6063 if let Some(old) = old_addr {
6064 self.cell_vertex_mut(&old);
6065 }
6066 self.cell_vertex_mut(&new_addr);
6067 if let Some(old) = old_addr {
6069 self.cell_to_vertex.remove(&old);
6070 }
6071 self.cell_to_vertex.insert(new_addr, id);
6073 }
6074
6075 pub fn remove_cell_mapping(&mut self, addr: &CellRef) {
6077 self.cell_vertex_mut(addr);
6078 self.cell_to_vertex.remove(addr);
6079 }
6080
6081 pub(crate) fn revive_vertex(&mut self, id: VertexId, sheet: SheetId, coord: GridAddr) -> bool {
6086 if !self.store.vertex_exists(id)
6087 || !self.store.is_deleted(id)
6088 || self.store.grid_addr(id).is_none()
6089 || self.store.sheet_id(id) != sheet
6090 {
6091 return false;
6092 }
6093 let cell = CellRef::new(sheet, Coord::new(coord.row(), coord.col(), true, true));
6094 let mut placeholder = None;
6099 if let Some(x) = self.cell_vertex(&cell)
6100 && !self.store.is_deleted(x)
6101 && self.store.grid_addr(x) == Some(coord)
6102 {
6103 if !self.is_pure_placeholder(x) {
6104 return false;
6105 }
6106 placeholder = Some(x);
6107 }
6108 #[cfg(any(test, feature = "legacy_oracle"))]
6109 let readers = placeholder
6110 .map(|x| self.get_dependents(x))
6111 .unwrap_or_default();
6112 if let Some(x) = placeholder {
6113 self.cell_to_vertex.remove(&cell);
6114 if let Some(index) = self.sheet_indexes.get_mut(&sheet) {
6115 index.remove_vertex(coord, x);
6116 }
6117 self.remove_all_edges(x);
6118 self.store.mark_deleted(x, true);
6119 }
6120 self.retired_id_set.remove(&id);
6121 self.store.mark_deleted(id, false);
6122 self.store.set_addr(id, VertexAddr::grid(coord));
6123 #[cfg(any(test, feature = "legacy_oracle"))]
6124 self.edges.update_addr(id, VertexAddr::grid(coord));
6125 self.store.set_kind(id, VertexKind::Empty);
6126 self.store.set_dynamic(id, false);
6127 self.store.set_volatile(id, false);
6128 self.cell_to_vertex.insert(cell, id);
6129 self.sheet_index_mut(sheet).add_vertex(coord, id);
6130 self.ref_error_vertices.remove(&id);
6131 #[cfg(any(test, feature = "legacy_oracle"))]
6134 for r in readers {
6135 if let Some(ast) = self.get_formula(r) {
6136 self.rebuild_formula_dependencies(r, &ast);
6137 }
6138 }
6139 let _ = self.mark_dirty(id);
6142 true
6143 }
6144
6145 fn is_pure_placeholder(&self, x: VertexId) -> bool {
6148 self.store.kind(x) == VertexKind::Empty
6149 && !self.vertex_formulas.contains_key(&x)
6150 && !self.vertex_values.contains_key(&x)
6151 && !self.ref_error_vertices.contains(&x)
6152 && !self.spill_anchor_to_cells.contains_key(&x)
6153 && !self.vertex_to_pending_names.contains_key(&x)
6154 && !self.name_vertex_lookup.contains_key(&x)
6155 }
6156
6157 pub(crate) fn journal_formula_left(&mut self, v: VertexId) {
6161 if !self.vertex_formulas.contains_key(&v) {
6162 return;
6163 }
6164 if let Some(cell) = self.get_cell_ref(v) {
6165 self.vertex_journal
6166 .retired((cell.sheet_id, cell.coord.row(), cell.coord.col()), v.0);
6167 }
6168 }
6169
6170 fn replay_formula_vertex(&mut self, addr: &CellRef) {
6176 if self.revive_retired_id(addr).is_some() {
6177 return;
6178 }
6179 let cell = (addr.sheet_id, addr.coord.row(), addr.coord.col());
6180 let Some(id) = self.vertex_journal.created(cell) else {
6181 return;
6182 };
6183 if self.cell_vertex(addr).is_none() {
6184 let coord = GridAddr::new(addr.coord.row(), addr.coord.col());
6185 self.revive_vertex(VertexId(id), addr.sheet_id, coord);
6186 }
6187 }
6188
6189 pub(crate) fn set_replay_mode(&mut self, mode: crate::engine::authority::history::Replay) {
6190 self.vertex_journal.set_mode(mode);
6191 }
6192
6193 pub fn get_cell_ref_for_vertex(&self, id: VertexId) -> Option<CellRef> {
6195 let coord = self.store.grid_addr(id)?;
6196 let sheet_id = self.store.sheet_id(id);
6197 let cell_ref = CellRef::new(sheet_id, Coord::new(coord.row(), coord.col(), true, true));
6199 if self.cell_vertex(&cell_ref) == Some(id) {
6201 Some(cell_ref)
6202 } else {
6203 None
6204 }
6205 }
6206
6207 pub(crate) fn rebuild_formula_dependencies(&mut self, vertex_id: VertexId, ast: &ASTNode) {
6213 self.materialize_vertex(vertex_id);
6214 let sheet_id = self.store.sheet_id(vertex_id);
6215
6216 self.remove_dependent_edges(vertex_id);
6218 self.detach_vertex_from_names(vertex_id);
6219 self.clear_pending_name_references(vertex_id);
6220
6221 let (
6222 new_dependencies,
6223 new_range_dependencies,
6224 vertexless,
6225 named_dependencies,
6226 unresolved_names,
6227 ) = match self.extract_dependencies_with_pending_names(ast, sheet_id) {
6228 Ok(v) => v,
6229 Err(_) => {
6230 self.mark_as_ref_error(vertex_id);
6231 return;
6232 }
6233 };
6234
6235 if new_dependencies.contains(&vertex_id) && !self.config.cycle.allows_self_dependency() {
6238 self.mark_as_ref_error(vertex_id);
6239 return;
6240 }
6241
6242 for &name_vertex in &named_dependencies {
6243 let mut visited = FxHashSet::default();
6244 if self.name_depends_on_vertex(name_vertex, vertex_id, &mut visited) {
6245 self.mark_as_ref_error(vertex_id);
6246 return;
6247 }
6248 }
6249
6250 self.ref_error_vertices.remove(&vertex_id);
6252 self.vertex_values.remove(&vertex_id);
6253
6254 if !named_dependencies.is_empty() {
6255 self.attach_vertex_to_names(vertex_id, &named_dependencies);
6256 }
6257 for unresolved_name in &unresolved_names {
6258 self.record_pending_name_reference(sheet_id, unresolved_name, vertex_id);
6259 }
6260
6261 self.add_dependent_edges(vertex_id, &new_dependencies);
6262 self.note_vertexless_deps(
6263 vertex_id,
6264 vertexless
6265 .iter()
6266 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
6267 );
6268 self.add_range_dependent_edges(vertex_id, &new_range_dependencies, sheet_id);
6269 self.vertex_formulas.touch(vertex_id);
6270 let _ = self.mark_dirty(vertex_id);
6271 }
6272}
6273
6274type RetiredBatch = Vec<((SheetId, u32, u32), VertexId)>;
6279
6280pub(crate) fn same_cell(a: &CellRef, b: &CellRef) -> bool {
6282 a.sheet_id == b.sheet_id && a.coord.row() == b.coord.row() && a.coord.col() == b.coord.col()
6283}
6284
6285fn shift_key(
6290 op: &crate::engine::graph::editor::reference_adjuster::ShiftOperation,
6291) -> (u8, SheetId, u32, u32) {
6292 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6293 match *op {
6294 Op::InsertRows {
6295 sheet_id,
6296 before,
6297 count,
6298 } => (0, sheet_id, before, count),
6299 Op::DeleteRows {
6300 sheet_id,
6301 start,
6302 count,
6303 } => (1, sheet_id, start, count),
6304 Op::InsertColumns {
6305 sheet_id,
6306 before,
6307 count,
6308 } => (2, sheet_id, before, count),
6309 Op::DeleteColumns {
6310 sheet_id,
6311 start,
6312 count,
6313 } => (3, sheet_id, start, count),
6314 }
6315}
6316
6317pub(crate) fn parse_structural_description(
6318 description: &str,
6319) -> Option<crate::engine::graph::editor::reference_adjuster::ShiftOperation> {
6320 use crate::engine::graph::editor::reference_adjuster::ShiftOperation as Op;
6321 let mut parts = description.split_whitespace();
6322 let kind = parts.next()?;
6323 let mut field = |name: &str| -> Option<u32> {
6324 parts
6325 .next()?
6326 .strip_prefix(name)?
6327 .strip_prefix('=')?
6328 .parse()
6329 .ok()
6330 };
6331 let sheet_id = u16::try_from(field("sheet")?).ok()?;
6332 Some(match kind {
6333 "InsertRows" => {
6334 let before = field("before")?;
6335 Op::InsertRows {
6336 sheet_id,
6337 before,
6338 count: field("count")?,
6339 }
6340 }
6341 "DeleteRows" => {
6342 let start = field("start")?;
6343 Op::DeleteRows {
6344 sheet_id,
6345 start,
6346 count: field("count")?,
6347 }
6348 }
6349 "InsertColumns" => {
6350 let before = field("before")?;
6351 Op::InsertColumns {
6352 sheet_id,
6353 before,
6354 count: field("count")?,
6355 }
6356 }
6357 "DeleteColumns" => {
6358 let start = field("start")?;
6359 Op::DeleteColumns {
6360 sheet_id,
6361 start,
6362 count: field("count")?,
6363 }
6364 }
6365 _ => return None,
6366 })
6367}