Skip to main content

formualizer_eval/engine/graph/
tables.rs

1use crate::SheetId;
2use crate::engine::graph::DependencyGraph;
3use crate::engine::vertex::{VertexId, VertexKind};
4use crate::reference::RangeRef;
5use formualizer_common::{ExcelError, ExcelErrorKind};
6
7/// Native workbook table (Excel ListObject) metadata.
8#[derive(Debug, Clone)]
9pub struct TableEntry {
10    pub name: String,
11    pub range: RangeRef,
12    pub headers: Vec<String>,
13    pub totals_row: bool,
14    pub vertex: VertexId,
15}
16
17impl TableEntry {
18    pub fn sheet_id(&self) -> SheetId {
19        self.range.start.sheet_id
20    }
21
22    pub fn col_index(&self, header: &str) -> Option<usize> {
23        self.headers
24            .iter()
25            .position(|h| h.eq_ignore_ascii_case(header))
26    }
27}
28
29impl DependencyGraph {
30    pub fn resolve_table_entry(&self, name: &str) -> Option<&TableEntry> {
31        self.tables.get(name)
32    }
33
34    pub fn table_by_vertex(&self, vertex: VertexId) -> Option<&TableEntry> {
35        self.table_vertex_lookup
36            .get(&vertex)
37            .and_then(|name| self.tables.get(name))
38    }
39
40    pub fn define_table(
41        &mut self,
42        name: &str,
43        range: RangeRef,
44        headers: Vec<String>,
45        totals_row: bool,
46    ) -> Result<(), ExcelError> {
47        if name.is_empty() {
48            return Err(ExcelError::new(ExcelErrorKind::Name)
49                .with_message("Table name cannot be empty".to_string()));
50        }
51
52        if self.tables.contains_key(name) {
53            return Err(ExcelError::new(ExcelErrorKind::Name)
54                .with_message(format!("Table already defined: {name}")));
55        }
56
57        let anchor = range.start;
58        let sheet_id = anchor.sheet_id;
59        let packed_coord = formualizer_common::Coord::new(anchor.coord.row(), anchor.coord.col());
60        let vertex = self.store.allocate(packed_coord, sheet_id, 0x01);
61        self.edges.add_vertex(packed_coord, vertex.0);
62        self.sheet_index_mut(sheet_id)
63            .add_vertex(packed_coord, vertex);
64        self.store.set_kind(vertex, VertexKind::Table);
65
66        // Register stripes for the full table region so cell edits inside the table
67        // propagate to formulas that depend on the table.
68        self.register_table_range_deps(vertex, &range);
69
70        let entry = TableEntry {
71            name: name.to_string(),
72            range,
73            headers,
74            totals_row,
75            vertex,
76        };
77
78        self.tables.insert(name.to_string(), entry);
79        self.table_vertex_lookup.insert(vertex, name.to_string());
80        Ok(())
81    }
82
83    pub fn update_table(
84        &mut self,
85        name: &str,
86        new_range: RangeRef,
87        headers: Vec<String>,
88        totals_row: bool,
89    ) -> Result<(), ExcelError> {
90        let vertex = self.tables.get(name).map(|t| t.vertex).ok_or_else(|| {
91            ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown table: {name}"))
92        })?;
93
94        // Replace range deps (cleans old stripes).
95        self.remove_dependent_edges(vertex);
96        self.register_table_range_deps(vertex, &new_range);
97
98        if let Some(existing) = self.tables.get_mut(name) {
99            existing.range = new_range;
100            existing.headers = headers;
101            existing.totals_row = totals_row;
102        }
103
104        // Propagate to dependents.
105        self.mark_dirty(vertex);
106        Ok(())
107    }
108
109    pub fn delete_table(&mut self, name: &str) -> Result<(), ExcelError> {
110        let Some(entry) = self.tables.remove(name) else {
111            return Err(ExcelError::new(ExcelErrorKind::Name)
112                .with_message(format!("Unknown table: {name}")));
113        };
114
115        let vertex = entry.vertex;
116        self.table_vertex_lookup.remove(&vertex);
117
118        // Clean range deps / stripes.
119        self.remove_dependent_edges(vertex);
120
121        // Mark deleted for debuggability; edges already removed.
122        self.store.mark_deleted(vertex, true);
123        self.vertex_values.remove(&vertex);
124        self.vertex_formulas.remove(&vertex);
125        self.dirty_vertices.remove(&vertex);
126        self.volatile_vertices.remove(&vertex);
127
128        Ok(())
129    }
130
131    fn register_table_range_deps(&mut self, table_vertex: VertexId, range: &RangeRef) {
132        use crate::reference::SharedRangeRef;
133        use crate::reference::SharedSheetLocator;
134        use formualizer_common::AxisBound;
135
136        // Reuse the same range-deps machinery as formulas/names.
137        let sheet_loc = SharedSheetLocator::Id(range.start.sheet_id);
138        let sr = AxisBound::new(range.start.coord.row(), range.start.coord.row_abs());
139        let sc = AxisBound::new(range.start.coord.col(), range.start.coord.col_abs());
140        let er = AxisBound::new(range.end.coord.row(), range.end.coord.row_abs());
141        let ec = AxisBound::new(range.end.coord.col(), range.end.coord.col_abs());
142
143        if let Ok(r) = SharedRangeRef::from_parts(sheet_loc, Some(sr), Some(sc), Some(er), Some(ec))
144        {
145            self.add_range_dependent_edges(table_vertex, &[r.into_owned()], range.start.sheet_id);
146        }
147    }
148}