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