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_ascii_key(name: &str) -> String {
9 name.to_ascii_lowercase()
10}
11
12#[derive(Debug, Clone)]
14pub struct TableEntry {
15 pub name: String,
16 pub range: RangeRef,
17 pub header_row: bool,
18 pub headers: Vec<String>,
19 pub totals_row: bool,
20 pub 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 self.headers
30 .iter()
31 .position(|h| h.eq_ignore_ascii_case(header))
32 }
33}
34
35impl DependencyGraph {
36 #[inline]
37 fn table_lookup_key(&self, name: &str) -> String {
38 if self.config.case_sensitive_tables {
39 name.to_string()
40 } else {
41 normalize_ascii_key(name)
42 }
43 }
44
45 fn canonical_table_name(&self, name: &str) -> Option<String> {
46 let key = self.table_lookup_key(name);
47 self.tables_lookup.get(&key).cloned()
48 }
49
50 pub fn resolve_table_entry(&self, name: &str) -> Option<&TableEntry> {
51 if self.config.case_sensitive_tables {
52 self.tables.get(name)
53 } else {
54 let key = self.table_lookup_key(name);
55 self.tables_lookup
56 .get(&key)
57 .and_then(|canon| self.tables.get(canon))
58 }
59 }
60
61 pub fn table_by_vertex(&self, vertex: VertexId) -> Option<&TableEntry> {
62 self.table_vertex_lookup
63 .get(&vertex)
64 .and_then(|name| self.tables.get(name))
65 }
66
67 pub fn define_table(
68 &mut self,
69 name: &str,
70 range: RangeRef,
71 header_row: bool,
72 headers: Vec<String>,
73 totals_row: bool,
74 ) -> Result<(), ExcelError> {
75 if name.is_empty() {
76 return Err(ExcelError::new(ExcelErrorKind::Name)
77 .with_message("Table name cannot be empty".to_string()));
78 }
79
80 let key = self.table_lookup_key(name);
81 if let Some(existing) = self.tables_lookup.get(&key) {
82 return Err(ExcelError::new(ExcelErrorKind::Name).with_message(format!(
83 "Table collision under normalization: '{name}' conflicts with '{existing}'"
84 )));
85 }
86
87 let anchor = range.start;
88 let sheet_id = anchor.sheet_id;
89 let packed_coord = formualizer_common::Coord::new(anchor.coord.row(), anchor.coord.col());
90 let vertex = self.store.allocate(packed_coord, sheet_id, 0x01);
91 self.edges.add_vertex(packed_coord, vertex.0);
92 self.sheet_index_mut(sheet_id)
93 .add_vertex(packed_coord, vertex);
94 self.store.set_kind(vertex, VertexKind::Table);
95
96 self.register_table_range_deps(vertex, &range);
99
100 let entry = TableEntry {
101 name: name.to_string(),
102 range,
103 header_row,
104 headers,
105 totals_row,
106 vertex,
107 };
108
109 let original = name.to_string();
110 self.tables.insert(original.clone(), entry);
111 self.tables_lookup
112 .insert(self.table_lookup_key(&original), original.clone());
113 self.table_vertex_lookup.insert(vertex, original);
114 Ok(())
115 }
116
117 pub fn update_table(
118 &mut self,
119 name: &str,
120 new_range: RangeRef,
121 header_row: bool,
122 headers: Vec<String>,
123 totals_row: bool,
124 ) -> Result<(), ExcelError> {
125 let Some(canon) = self.canonical_table_name(name) else {
126 return Err(ExcelError::new(ExcelErrorKind::Name)
127 .with_message(format!("Unknown table: {name}")));
128 };
129
130 let vertex = self.tables.get(&canon).map(|t| t.vertex).ok_or_else(|| {
131 ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown table: {name}"))
132 })?;
133
134 self.remove_dependent_edges(vertex);
136 self.register_table_range_deps(vertex, &new_range);
137
138 if let Some(existing) = self.tables.get_mut(&canon) {
139 existing.range = new_range;
140 existing.header_row = header_row;
141 existing.headers = headers;
142 existing.totals_row = totals_row;
143 }
144
145 self.mark_dirty(vertex);
147 Ok(())
148 }
149
150 pub fn delete_table(&mut self, name: &str) -> Result<(), ExcelError> {
151 let Some(canon) = self.canonical_table_name(name) else {
152 return Err(ExcelError::new(ExcelErrorKind::Name)
153 .with_message(format!("Unknown table: {name}")));
154 };
155
156 let Some(entry) = self.tables.remove(&canon) else {
157 return Err(ExcelError::new(ExcelErrorKind::Name)
158 .with_message(format!("Unknown table: {name}")));
159 };
160
161 self.tables_lookup.remove(&self.table_lookup_key(&canon));
162
163 let vertex = entry.vertex;
164 self.table_vertex_lookup.remove(&vertex);
165
166 self.remove_dependent_edges(vertex);
168
169 self.store.mark_deleted(vertex, true);
171 self.vertex_values.remove(&vertex);
172 self.vertex_formulas.remove(&vertex);
173 self.dirty_vertices.remove(&vertex);
174 self.volatile_vertices.remove(&vertex);
175
176 Ok(())
177 }
178
179 fn register_table_range_deps(&mut self, table_vertex: VertexId, range: &RangeRef) {
180 use crate::reference::SharedRangeRef;
181 use crate::reference::SharedSheetLocator;
182 use formualizer_common::AxisBound;
183
184 let sheet_loc = SharedSheetLocator::Id(range.start.sheet_id);
186 let sr = AxisBound::new(range.start.coord.row(), range.start.coord.row_abs());
187 let sc = AxisBound::new(range.start.coord.col(), range.start.coord.col_abs());
188 let er = AxisBound::new(range.end.coord.row(), range.end.coord.row_abs());
189 let ec = AxisBound::new(range.end.coord.col(), range.end.coord.col_abs());
190
191 if let Ok(r) = SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
192 {
193 self.add_range_dependent_edges(table_vertex, &[r.into_owned()], range.start.sheet_id);
194 }
195 }
196}