formualizer_eval/engine/graph/
tables.rs1use crate::SheetId;
2use crate::engine::graph::DependencyGraph;
3use crate::engine::vertex::{VertexId, VertexKind};
4use crate::reference::RangeRef;
5use formualizer_common::{ExcelError, ExcelErrorKind};
6
7#[inline]
8fn normalize_table_key(name: &str) -> String {
9 name.to_lowercase()
10}
11
12#[derive(Debug, Clone)]
14pub(crate) struct TableEntry {
15 pub(crate) name: String,
16 pub(crate) range: RangeRef,
17 pub(crate) header_row: bool,
18 pub(crate) headers: Vec<String>,
19 pub(crate) totals_row: bool,
20 pub(crate) vertex: VertexId,
21}
22
23impl TableEntry {
24 pub fn sheet_id(&self) -> SheetId {
25 self.range.start.sheet_id
26 }
27
28 pub fn col_index(&self, header: &str) -> Option<usize> {
29 let header_key = header.to_lowercase();
30 self.headers
31 .iter()
32 .position(|h| h.to_lowercase() == header_key)
33 }
34}
35
36impl DependencyGraph {
37 pub(crate) fn table_intersects_spill(
39 &self,
40 anchor: crate::reference::CellRef,
41 end_row: u32,
42 end_col: u32,
43 ) -> bool {
44 self.tables.values().any(|t| {
45 t.sheet_id() == anchor.sheet_id
46 && t.range.start.coord.row() <= end_row
47 && anchor.coord.row() <= t.range.end.coord.row()
48 && t.range.start.coord.col() <= end_col
49 && anchor.coord.col() <= t.range.end.coord.col()
50 })
51 }
52
53 #[inline]
54 fn table_lookup_key(&self, name: &str) -> String {
55 if self.config.case_sensitive_tables {
56 name.to_string()
57 } else {
58 normalize_table_key(name)
59 }
60 }
61
62 fn canonical_table_name(&self, name: &str) -> Option<String> {
63 let key = self.table_lookup_key(name);
64 self.tables_lookup.get(&key).cloned()
65 }
66
67 pub(crate) fn resolve_table_entry(&self, name: &str) -> Option<&TableEntry> {
68 if self.config.case_sensitive_tables {
69 self.tables.get(name)
70 } else {
71 let key = self.table_lookup_key(name);
72 self.tables_lookup
73 .get(&key)
74 .and_then(|canon| self.tables.get(canon))
75 }
76 }
77
78 pub(crate) fn table_by_vertex(&self, vertex: VertexId) -> Option<&TableEntry> {
79 self.table_vertex_lookup
80 .get(&vertex)
81 .and_then(|name| self.tables.get(name))
82 }
83
84 pub fn table_names(&self) -> Vec<String> {
86 let mut names: Vec<String> = self.tables.values().map(|t| t.name.clone()).collect();
87 names.sort();
88 names
89 }
90
91 pub fn define_table(
92 &mut self,
93 name: &str,
94 range: RangeRef,
95 header_row: bool,
96 headers: Vec<String>,
97 totals_row: bool,
98 ) -> Result<(), ExcelError> {
99 if name.is_empty() {
100 return Err(ExcelError::new(ExcelErrorKind::Name)
101 .with_message("Table name cannot be empty".to_string()));
102 }
103
104 let key = self.table_lookup_key(name);
105 if let Some(existing) = self.tables_lookup.get(&key) {
106 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
107 "Table collision under normalization: '{name}' conflicts with '{existing}'"
108 )));
109 }
110
111 let sheet_id = range.start.sheet_id;
116 let vertex = self.allocate_symbol_vertex(VertexKind::Table, sheet_id);
117
118 self.register_table_range_deps(vertex, &range);
121
122 let entry = TableEntry {
123 name: name.to_string(),
124 range,
125 header_row,
126 headers,
127 totals_row,
128 vertex,
129 };
130
131 let original = name.to_string();
132 self.tables.insert(original.clone(), entry);
133 self.tables_lookup
134 .insert(self.table_lookup_key(&original), original.clone());
135 self.table_vertex_lookup.insert(vertex, original);
136 self.authority_note_symbol(Some(vertex));
137 self.bump_symbol_revision();
138 self.resolve_pending_symbol("table", name);
139 Ok(())
140 }
141
142 pub fn update_table(
143 &mut self,
144 name: &str,
145 new_range: RangeRef,
146 header_row: bool,
147 headers: Vec<String>,
148 totals_row: bool,
149 ) -> Result<(), ExcelError> {
150 let Some(canon) = self.canonical_table_name(name) else {
151 return Err(ExcelError::new(ExcelErrorKind::Name)
152 .with_message(format!("Unknown table: {name}")));
153 };
154
155 let vertex = self.tables.get(&canon).map(|t| t.vertex).ok_or_else(|| {
156 ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown table: {name}"))
157 })?;
158
159 self.remove_dependent_edges(vertex);
161 self.register_table_range_deps(vertex, &new_range);
162
163 if let Some(existing) = self.tables.get_mut(&canon) {
164 existing.range = new_range;
165 existing.header_row = header_row;
166 existing.headers = headers;
167 existing.totals_row = totals_row;
168 }
169
170 self.mark_dirty(vertex);
172 self.authority_note_symbol(Some(vertex));
173 self.bump_symbol_revision();
174 Ok(())
175 }
176
177 pub fn delete_table(&mut self, name: &str) -> Result<(), ExcelError> {
178 let Some(canon) = self.canonical_table_name(name) else {
179 return Err(ExcelError::new(ExcelErrorKind::Name)
180 .with_message(format!("Unknown table: {name}")));
181 };
182
183 let Some(entry) = self.tables.remove(&canon) else {
184 return Err(ExcelError::new(ExcelErrorKind::Name)
185 .with_message(format!("Unknown table: {name}")));
186 };
187
188 self.tables_lookup.remove(&self.table_lookup_key(&canon));
189
190 let vertex = entry.vertex;
191 self.table_vertex_lookup.remove(&vertex);
192
193 self.remove_dependent_edges(vertex);
195
196 self.store.mark_deleted(vertex, true);
198 self.vertex_values.remove(&vertex);
199 self.vertex_formulas.remove(&vertex);
200 self.clear_formula_vertex_dirty(vertex);
201 self.volatile_vertices.remove(&vertex);
202 self.authority_note_symbol(Some(vertex));
203 self.bump_symbol_revision();
204
205 Ok(())
206 }
207
208 fn register_table_range_deps(&mut self, table_vertex: VertexId, range: &RangeRef) {
209 use crate::reference::SharedRangeRef;
210 use crate::reference::SharedSheetLocator;
211 use formualizer_common::AxisBound;
212
213 let sheet_loc = SharedSheetLocator::Id(range.start.sheet_id);
215 let sr = AxisBound::new(range.start.coord.row(), range.start.coord.row_abs());
216 let sc = AxisBound::new(range.start.coord.col(), range.start.coord.col_abs());
217 let er = AxisBound::new(range.end.coord.row(), range.end.coord.row_abs());
218 let ec = AxisBound::new(range.end.coord.col(), range.end.coord.col_abs());
219
220 if let Ok(r) = SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
221 {
222 self.add_range_dependent_edges(table_vertex, &[r.into_owned()], range.start.sheet_id);
223 }
224 }
225}