formualizer_eval/engine/graph/
sheets.rs1use super::ast_utils::update_internal_sheet_references;
2use super::*;
3use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
4
5const TOMBSTONE_SHEET_PREFIX: &str = "__FZ_MISSING_SHEET__";
6
7impl DependencyGraph {
8 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
13 if let Some(id) = self.sheet_reg.get_id(name) {
14 return Ok(id);
15 }
16
17 let sheet_id = self.sheet_reg.id_for(name);
18 self.sheet_indexes.entry(sheet_id).or_default();
19
20 self.heal_orphaned_formulas(name);
22 self.resolve_pending_symbol("sheet", name);
23 Ok(sheet_id)
24 }
25
26 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
28 let result = self.remove_sheet_impl(sheet_id);
29 if result.is_ok() {
30 self.drop_retired_ids_of_sheet(sheet_id);
31 }
32 self.authority_end_structural();
33 result
34 }
35
36 fn formulas_referencing_sheet(&self, sheet_id: SheetId) -> Vec<VertexId> {
39 use crate::engine::refs::{self, SemanticReference};
40 struct Probe<'a> {
41 graph: &'a DependencyGraph,
42 sheet_id: SheetId,
43 hit: bool,
44 }
45 fn visit(
46 p: &mut Probe<'_>,
47 r: SemanticReference<'_>,
48 key: Option<SheetId>,
49 ) -> Result<(), ExcelError> {
50 let name = match &r {
51 SemanticReference::Cell(c) => c.sheet.name(),
52 SemanticReference::FiniteRange(rg) | SemanticReference::OpenRange(rg) => {
53 rg.sheet.name()
54 }
55 _ => return Ok(()),
56 };
57 let id = match (key, name) {
58 (Some(id), _) => Some(id),
59 (None, Some(n)) => p.graph.sheet_id(n),
60 (None, None) => None,
61 };
62 if id == Some(p.sheet_id) {
63 p.hit = true;
64 }
65 Ok(())
66 }
67 let mut out = Vec::new();
68 for (v, f) in self.vertex_formulas.iter() {
69 let ast = f.root();
72 let mut probe = Probe {
73 graph: self,
74 sheet_id,
75 hit: false,
76 };
77 let _ = refs::visit_arena_references_keyed(
78 ast,
79 &mut probe,
80 |p| p.graph.data_store(),
81 |p| p.graph.sheet_reg(),
82 visit,
83 );
84 if probe.hit {
85 out.push(v);
86 }
87 }
88 out.sort_unstable();
89 out
90 }
91
92 fn remove_sheet_impl(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
93 self.authority_note_structural(true);
94 let old_name = self.sheet_reg.name(sheet_id).to_string();
95 if old_name.is_empty() {
96 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
97 }
98
99 let sheet_count = self.sheet_reg.all_sheets().len();
100 if sheet_count <= 1 {
101 return Err(
102 ExcelError::new(ExcelErrorKind::Value).with_message("Cannot remove the last sheet")
103 );
104 }
105
106 self.begin_batch();
107
108 let vertices_to_delete: Vec<VertexId> = self
112 .grid_vertices_in_sheet(sheet_id)
113 .map(|(id, _)| id)
114 .collect();
115
116 let formulas_to_update = self.formulas_referencing_sheet(sheet_id);
120
121 for &formula_id in &formulas_to_update {
122 self.tombstone_registry
123 .add_orphan(old_name.clone(), formula_id);
124 self.rewrite_formula_sheet_to_tombstone(formula_id, &old_name);
125 }
126
127 for formula_id in formulas_to_update {
128 self.mark_as_ref_error(formula_id);
129 }
130
131 let ref_err = LiteralValue::Error(ExcelError::new(ExcelErrorKind::Ref));
137 let mut name_vertices_to_update: Vec<VertexId> = Vec::new();
138 let mut dirty_vertices: Vec<VertexId> = Vec::new();
139
140 for nr in self.named_ranges.values_mut() {
141 match &nr.definition {
142 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
143 nr.definition = NamedDefinition::Literal(ref_err.clone());
144 name_vertices_to_update.push(nr.vertex);
145 dirty_vertices.push(nr.vertex);
146 }
147 NamedDefinition::Range(r)
148 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
149 {
150 nr.definition = NamedDefinition::Literal(ref_err.clone());
151 name_vertices_to_update.push(nr.vertex);
152 dirty_vertices.push(nr.vertex);
153 }
154 _ => {}
155 }
156 }
157 for nr in self.sheet_named_ranges.values_mut() {
158 match &nr.definition {
159 NamedDefinition::Cell(c) if c.sheet_id == sheet_id => {
160 nr.definition = NamedDefinition::Literal(ref_err.clone());
161 name_vertices_to_update.push(nr.vertex);
162 dirty_vertices.push(nr.vertex);
163 }
164 NamedDefinition::Range(r)
165 if r.start.sheet_id == sheet_id || r.end.sheet_id == sheet_id =>
166 {
167 nr.definition = NamedDefinition::Literal(ref_err.clone());
168 name_vertices_to_update.push(nr.vertex);
169 dirty_vertices.push(nr.vertex);
170 }
171 _ => {}
172 }
173 }
174
175 for vid in name_vertices_to_update {
177 self.update_vertex_value_ref(vid, &ref_err);
178 }
179 for &vid in &dirty_vertices {
180 self.mark_vertex_dirty(vid);
181 }
182 self.mark_dirty_many(&dirty_vertices);
184
185 for vertex_id in vertices_to_delete {
186 if let Some(cell_ref) = self.get_cell_ref_for_vertex(vertex_id) {
187 self.cell_to_vertex.remove(&cell_ref);
188 }
189
190 self.remove_all_edges(vertex_id);
191
192 if let Some(coord) = self.store.grid_addr(vertex_id)
193 && let Some(index) = self.sheet_indexes.get_mut(&sheet_id)
194 {
195 index.remove_vertex(coord, vertex_id);
196 }
197
198 self.clear_pending_name_references(vertex_id);
199 self.vertex_formulas.remove(&vertex_id);
200 self.vertex_values.remove(&vertex_id);
201
202 self.mark_deleted(vertex_id, true);
203 }
204
205 let sheet_names_to_remove: Vec<(SheetId, String)> = self
206 .sheet_named_ranges
207 .keys()
208 .filter(|(sid, _)| *sid == sheet_id)
209 .cloned()
210 .collect();
211
212 for key in sheet_names_to_remove {
213 if let Some(named_range) = self.sheet_named_ranges.remove(&key) {
214 if !self.config.case_sensitive_names {
215 let normalized = key.1.to_lowercase();
216 self.sheet_named_ranges_lookup
217 .remove(&(sheet_id, normalized));
218 } else {
219 self.sheet_named_ranges_lookup.remove(&key);
220 }
221 self.mark_named_vertex_deleted(&named_range);
222 }
223 }
224
225 self.sheet_indexes.remove(&sheet_id);
226
227 if self.default_sheet_id == sheet_id
228 && let Some(&new_default) = self.sheet_indexes.keys().next()
229 {
230 self.default_sheet_id = new_default;
231 }
232
233 self.sheet_reg.remove(sheet_id)?;
234 self.end_batch();
235
236 Ok(())
237 }
238
239 fn tombstone_marker(sheet_name: &str) -> String {
240 format!("{TOMBSTONE_SHEET_PREFIX}{sheet_name}")
241 }
242
243 pub(crate) fn is_tombstone_sheet(sheet_name: &str) -> bool {
249 sheet_name.starts_with(TOMBSTONE_SHEET_PREFIX)
250 }
251
252 fn rewrite_formula_sheet_to_tombstone(&mut self, vertex_id: VertexId, sheet_name: &str) {
253 let Some(ast) = self.get_formula(vertex_id) else {
254 return;
255 };
256
257 let marker = Self::tombstone_marker(sheet_name);
258 let mut updated_ast = ast.clone();
259 updated_ast.update_sheet_references(Some(sheet_name), &marker);
260
261 if updated_ast != ast {
262 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
263 self.materialize_vertex(vertex_id);
264 self.vertex_formulas.insert(vertex_id, updated_ast_id);
265 }
266 }
267
268 fn heal_orphaned_formulas(&mut self, sheet_name: &str) {
269 let orphans = self.tombstone_registry.take_orphans(sheet_name);
270 let marker = Self::tombstone_marker(sheet_name);
271
272 for vertex_id in orphans {
273 let Some(ast) = self.get_formula(vertex_id) else {
274 continue;
275 };
276
277 if !self.is_ref_error(vertex_id) {
280 continue;
281 }
282
283 let mut updated_ast = ast.clone();
285 updated_ast.update_sheet_references(Some(&marker), sheet_name);
286
287 if updated_ast == ast {
288 continue;
290 }
291
292 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
293 self.materialize_vertex(vertex_id);
294 self.vertex_formulas.insert(vertex_id, updated_ast_id);
295 self.rebuild_formula_dependencies(vertex_id, &updated_ast);
296 }
297 }
298 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
300 let result = self.rename_sheet_impl(sheet_id, new_name);
301 self.authority_end_structural();
302 result
303 }
304
305 fn rename_sheet_impl(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
306 self.authority_note_structural(true);
307 if new_name.is_empty() || new_name.len() > 255 {
308 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
309 }
310
311 let old_name = self.sheet_reg.name(sheet_id).to_string();
312
313 if old_name.is_empty() {
314 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Sheet does not exist"));
315 }
316
317 if let Some(existing_id) = self.sheet_reg.get_id(new_name) {
318 if existing_id != sheet_id {
319 return Err(ExcelError::new(ExcelErrorKind::Value)
320 .with_message(format!("Sheet '{new_name}' already exists")));
321 }
322 return Ok(());
323 }
324
325 self.sheet_reg.rename(sheet_id, new_name)?;
326 let old_key = old_name.to_ascii_lowercase();
331 self.renamed_sheet_aliases
332 .retain(|k, _| *k != new_name.to_ascii_lowercase());
333 self.renamed_sheet_aliases.insert(old_key, sheet_id);
334
335 self.begin_batch();
336
337 self.heal_orphaned_formulas(new_name);
339
340 let formulas_to_update: Vec<VertexId> = self.vertex_formulas.keys().collect();
342 for formula_id in formulas_to_update {
343 if let Some(ast) = self.get_formula(formula_id) {
344 let mut updated_ast = ast.clone();
345 updated_ast.update_sheet_references(Some(&old_name), new_name);
346
347 if ast != updated_ast {
348 self.rebuild_formula_dependencies(formula_id, &updated_ast);
349 let updated_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
350 self.vertex_formulas.insert(formula_id, updated_ast_id);
351 }
352 }
353 }
354
355 self.end_batch();
356 Ok(())
357 }
358
359 pub fn duplicate_sheet(
361 &mut self,
362 source_sheet_id: SheetId,
363 new_name: &str,
364 ) -> Result<SheetId, ExcelError> {
365 let result = self.duplicate_sheet_impl(source_sheet_id, new_name);
366 self.authority_end_structural();
367 result
368 }
369
370 fn duplicate_sheet_impl(
371 &mut self,
372 source_sheet_id: SheetId,
373 new_name: &str,
374 ) -> Result<SheetId, ExcelError> {
375 self.authority_note_structural(true);
376 if new_name.is_empty() || new_name.len() > 255 {
377 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
378 }
379
380 let source_name = self.sheet_reg.name(source_sheet_id).to_string();
381 if source_name.is_empty() {
382 return Err(
383 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
384 );
385 }
386
387 if self.sheet_reg.get_id(new_name).is_some() {
388 return Err(ExcelError::new(ExcelErrorKind::Value)
389 .with_message(format!("Sheet '{new_name}' already exists")));
390 }
391
392 let new_sheet_id = self.add_sheet(new_name)?;
393
394 self.begin_batch();
395
396 let source_vertices: Vec<(VertexId, GridAddr)> =
397 self.grid_vertices_in_sheet(source_sheet_id).collect();
398
399 let mut vertex_mapping = FxHashMap::default();
400
401 for (old_id, coord) in &source_vertices {
402 let row = coord.row();
403 let col = coord.col();
404 let kind = self.store.kind(*old_id);
405
406 let new_id = self
407 .store
408 .allocate(VertexAddr::grid(*coord), new_sheet_id, 0x01);
409 #[cfg(any(test, feature = "legacy_oracle"))]
410 {
411 self.edges.add_vertex(VertexAddr::grid(*coord), new_id.0);
412 self.oracle_cell_vertex_created((new_sheet_id, coord.row(), coord.col()), new_id);
413 }
414 self.sheet_index_mut(new_sheet_id)
415 .add_vertex(*coord, new_id);
416
417 self.store.set_kind(new_id, kind);
418
419 if let Some(&value_ref) = self.vertex_values.get(old_id) {
420 self.vertex_values.insert(new_id, value_ref);
421 }
422
423 vertex_mapping.insert(*old_id, new_id);
424
425 let cell_ref = CellRef::new(new_sheet_id, Coord::new(row, col, true, true));
426 self.cell_to_vertex.insert(cell_ref, new_id);
427 }
428
429 let sheet_names: Vec<(String, NamedRange)> = self
430 .sheet_named_ranges
431 .iter()
432 .filter(|((sid, _), _)| *sid == source_sheet_id)
433 .map(|((_, name), range)| (name.clone(), range.clone()))
434 .collect();
435
436 for (name, mut named_range) in sheet_names {
437 named_range.scope = NameScope::Sheet(new_sheet_id);
438
439 match &mut named_range.definition {
440 NamedDefinition::Cell(cell_ref) if cell_ref.sheet_id == source_sheet_id => {
441 cell_ref.sheet_id = new_sheet_id;
442 }
443 NamedDefinition::Range(range_ref) => {
444 if range_ref.start.sheet_id == source_sheet_id {
445 range_ref.start.sheet_id = new_sheet_id;
446 range_ref.end.sheet_id = new_sheet_id;
447 }
448 }
449 _ => {}
450 }
451
452 #[cfg(any(test, feature = "legacy_oracle"))]
453 named_range.dependents.clear();
454 let name_vertex = self.allocate_name_vertex(named_range.scope);
455 if matches!(named_range.definition, NamedDefinition::Range(_)) {
456 self.store.set_kind(name_vertex, VertexKind::NamedArray);
457 } else {
458 self.store.set_kind(name_vertex, VertexKind::NamedScalar);
459 }
460 named_range.vertex = name_vertex;
461
462 let referenced_names = self.rebuild_name_dependencies(
463 name_vertex,
464 &named_range.definition,
465 named_range.scope,
466 )?;
467 if !referenced_names.is_empty() {
468 self.attach_vertex_to_names(name_vertex, &referenced_names);
469 }
470
471 self.sheet_named_ranges
472 .insert((new_sheet_id, name.clone()), named_range);
473 self.sheet_named_ranges_lookup
474 .insert((new_sheet_id, self.name_lookup_key(&name)), name.clone());
475 self.name_vertex_lookup
476 .insert(name_vertex, (NameScope::Sheet(new_sheet_id), name));
477 }
478
479 for (old_id, _) in &source_vertices {
480 if let Some(&new_id) = vertex_mapping.get(old_id)
481 && let Some(ast) = self.get_formula(*old_id)
482 {
483 let updated_ast = update_internal_sheet_references(
484 &ast,
485 &source_name,
486 new_name,
487 source_sheet_id,
488 new_sheet_id,
489 );
490
491 let new_ast_id = self.data_store.store_ast(&updated_ast, &self.sheet_reg);
492 self.vertex_formulas.insert(new_id, new_ast_id);
493
494 if let Ok((deps, range_deps, vertexless, name_vertices)) =
495 self.extract_dependencies(&updated_ast, new_sheet_id)
496 {
497 let mapped_deps: Vec<VertexId> = deps
498 .iter()
499 .map(|&dep_id| vertex_mapping.get(&dep_id).copied().unwrap_or(dep_id))
500 .collect();
501
502 self.add_dependent_edges(new_id, &mapped_deps);
503 self.note_vertexless_deps(
504 new_id,
505 vertexless
506 .iter()
507 .map(|c| (c.sheet_id, c.coord.row(), c.coord.col())),
508 );
509 self.add_range_dependent_edges(new_id, &range_deps, new_sheet_id);
510
511 if !name_vertices.is_empty() {
512 self.attach_vertex_to_names(new_id, &name_vertices);
513 }
514 }
515 }
516 }
517
518 self.end_batch();
519
520 Ok(new_sheet_id)
521 }
522}