Skip to main content

formualizer_eval/engine/graph/
sources.rs

1use crate::SheetId;
2use crate::engine::graph::DependencyGraph;
3use crate::engine::named_range::NameScope;
4use crate::engine::vertex::{VertexId, VertexKind};
5use formualizer_common::{ExcelError, ExcelErrorKind};
6
7#[derive(Debug, Clone)]
8pub struct SourceScalarEntry {
9    pub name: String,
10    pub vertex: VertexId,
11    pub version: Option<u64>,
12}
13
14#[derive(Debug, Clone)]
15pub struct SourceTableEntry {
16    pub name: String,
17    pub vertex: VertexId,
18    pub version: Option<u64>,
19}
20
21impl DependencyGraph {
22    fn allocate_source_vertex(&mut self) -> VertexId {
23        // External sources are identified by name and have no position; the default sheet
24        // is recorded only as the scope their lookups answer for.
25        let scope_sheet_id: SheetId = self.default_sheet_id;
26        self.allocate_symbol_vertex(VertexKind::External, scope_sheet_id)
27    }
28
29    pub fn resolve_source_scalar_entry(&self, name: &str) -> Option<&SourceScalarEntry> {
30        self.source_scalars.get(name)
31    }
32
33    pub fn resolve_source_table_entry(&self, name: &str) -> Option<&SourceTableEntry> {
34        self.source_tables.get(name)
35    }
36
37    pub fn define_source_scalar(
38        &mut self,
39        name: &str,
40        version: Option<u64>,
41    ) -> Result<(), ExcelError> {
42        if name.is_empty() {
43            return Err(ExcelError::new(ExcelErrorKind::Name)
44                .with_message("Source name cannot be empty".to_string()));
45        }
46        if self.source_scalars.contains_key(name) || self.source_tables.contains_key(name) {
47            return Err(ExcelError::new(ExcelErrorKind::Name)
48                .with_message(format!("Source already defined: {name}")));
49        }
50
51        let vertex = self.allocate_source_vertex();
52        self.source_vertex_lookup.insert(vertex, name.to_string());
53        self.mark_volatile(vertex, version.is_none());
54
55        let entry = SourceScalarEntry {
56            name: name.to_string(),
57            vertex,
58            version,
59        };
60        self.source_scalars.insert(name.to_string(), entry);
61        // Logged before the pending readers re-bind: a sync they trigger
62        // gives the source its symbol row before extracting them.
63        self.authority_note_symbol(Some(vertex));
64        self.resolve_pending_name_references(NameScope::Workbook, name);
65        self.bump_symbol_revision();
66        Ok(())
67    }
68
69    pub fn define_source_table(
70        &mut self,
71        name: &str,
72        version: Option<u64>,
73    ) -> Result<(), ExcelError> {
74        if name.is_empty() {
75            return Err(ExcelError::new(ExcelErrorKind::Name)
76                .with_message("Source name cannot be empty".to_string()));
77        }
78        if self.source_tables.contains_key(name) || self.source_scalars.contains_key(name) {
79            return Err(ExcelError::new(ExcelErrorKind::Name)
80                .with_message(format!("Source already defined: {name}")));
81        }
82
83        let vertex = self.allocate_source_vertex();
84        self.source_vertex_lookup.insert(vertex, name.to_string());
85        self.mark_volatile(vertex, version.is_none());
86
87        let entry = SourceTableEntry {
88            name: name.to_string(),
89            vertex,
90            version,
91        };
92        self.source_tables.insert(name.to_string(), entry);
93        self.authority_note_symbol(Some(vertex));
94        self.bump_symbol_revision();
95        // Formulas written before the source table re-bind to it (the
96        // authority's rebuild used to bind them; see final-notes decision 19).
97        self.resolve_pending_symbol("table", name);
98        Ok(())
99    }
100
101    pub fn set_source_scalar_version(
102        &mut self,
103        name: &str,
104        version: Option<u64>,
105    ) -> Result<(), ExcelError> {
106        let vertex = {
107            let entry = self.source_scalars.get_mut(name).ok_or_else(|| {
108                ExcelError::new(ExcelErrorKind::Name)
109                    .with_message(format!("Unknown source: {name}"))
110            })?;
111
112            if entry.version == version {
113                return Ok(());
114            }
115
116            entry.version = version;
117            entry.vertex
118        };
119
120        self.mark_volatile(vertex, version.is_none());
121        self.mark_dirty(vertex);
122        Ok(())
123    }
124
125    pub fn set_source_table_version(
126        &mut self,
127        name: &str,
128        version: Option<u64>,
129    ) -> Result<(), ExcelError> {
130        let vertex = {
131            let entry = self.source_tables.get_mut(name).ok_or_else(|| {
132                ExcelError::new(ExcelErrorKind::Name)
133                    .with_message(format!("Unknown source: {name}"))
134            })?;
135
136            if entry.version == version {
137                return Ok(());
138            }
139
140            entry.version = version;
141            entry.vertex
142        };
143
144        self.mark_volatile(vertex, version.is_none());
145        self.mark_dirty(vertex);
146        Ok(())
147    }
148
149    pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
150        if let Some(s) = self.source_scalars.get(name) {
151            self.mark_dirty(s.vertex);
152            return Ok(());
153        }
154        if let Some(t) = self.source_tables.get(name) {
155            self.mark_dirty(t.vertex);
156            return Ok(());
157        }
158        Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown source: {name}")))
159    }
160}