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