Skip to main content

visi_core/core/engine/
column.rs

1//! Column storage: the typed value column and the per-column bundle of
2//! parallel vectors that a `Sheet` is made of.
3
4use crate::core::CompiledFormula;
5use crate::core::SharedVec;
6use serde::{Deserialize, Serialize};
7
8use super::bitmask::Bitmask;
9use super::cell::{CellType, generate_unique_id};
10use super::result_data::ResultData;
11
12/// A column of computed values, stored in whichever representation fits what
13/// it currently holds.
14///
15/// A column starts out as `Integer` and widens as needed: writing a float
16/// promotes it to `Float`, and writing anything that is neither demotes it to
17/// `Any`. It never narrows back. The two numeric representations keep a
18/// separate validity [`Bitmask`] so a blank cell is distinct from a zero.
19///
20/// This is a storage detail of [`DataColumn`], exposed for reading. The
21/// operations that change a column's length are crate-private, since they
22/// would desync it from the sibling vectors it must stay aligned with -- go
23/// through `Sheet` to edit cells.
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum ColumnData {
26    /// All-integer, or integer-and-blank.
27    Integer {
28        /// Which positions hold a value rather than a blank.
29        validity: Bitmask,
30        /// The values. Positions marked invalid hold a placeholder.
31        values: SharedVec<i64>,
32    },
33    /// Numeric with at least one non-integer, or integer-and-blank promoted.
34    Float {
35        /// Which positions hold a value rather than a blank.
36        validity: Bitmask,
37        /// The values. Positions marked invalid hold a placeholder.
38        values: SharedVec<f64>,
39    },
40    /// Mixed: anything the numeric representations cannot hold -- text,
41    /// booleans, errors.
42    Any(SharedVec<ResultData>),
43}
44
45impl ColumnData {
46    pub(crate) fn new(size: usize) -> Self {
47        Self::Integer {
48            validity: Bitmask::with_size(size),
49            values: vec![0; size].into(),
50        }
51    }
52
53    /// How many rows the column holds.
54    pub fn len(&self) -> usize {
55        match self {
56            Self::Integer { validity, .. } => validity.len,
57            Self::Float { validity, .. } => validity.len,
58            Self::Any(v) => v.len(),
59        }
60    }
61
62    /// Whether the column holds no rows at all.
63    pub fn is_empty(&self) -> bool {
64        self.len() == 0
65    }
66
67    pub(crate) fn push(&mut self, value: ResultData) {
68        let index = self.len();
69        self.insert(index, value);
70    }
71
72    /// The value at `index`, or `None` if that is past the end.
73    ///
74    /// A blank within the column's range reads as
75    /// `Some(ResultData::None)`, which is what distinguishes it from an
76    /// out-of-range index.
77    pub fn get(&self, index: usize) -> Option<ResultData> {
78        if index >= self.len() {
79            return None;
80        }
81        match self {
82            Self::Integer { validity, values } => {
83                if validity.get(index) {
84                    Some(ResultData::Integer(values[index]))
85                } else {
86                    Some(ResultData::None)
87                }
88            }
89            Self::Float { validity, values } => {
90                if validity.get(index) {
91                    Some(ResultData::Float(values[index]))
92                } else {
93                    Some(ResultData::None)
94                }
95            }
96            Self::Any(v) => Some(v[index].clone()),
97        }
98    }
99
100    pub(crate) fn demote_to_any(&mut self) {
101        let len = self.len();
102        let mut any = Vec::with_capacity(len);
103        for i in 0..len {
104            any.push(self.get(i).unwrap());
105        }
106        *self = Self::Any(any.into());
107    }
108
109    pub(crate) fn promote_to_float(&mut self) {
110        if let Self::Integer { validity, values } = self {
111            let float_values = values.iter().map(|&i| i as f64).collect();
112            *self = Self::Float {
113                validity: validity.clone(),
114                values: float_values,
115            };
116        }
117    }
118
119    pub(crate) fn resize(&mut self, size: usize) {
120        match self {
121            Self::Integer { validity, values } => {
122                values.resize(size, 0);
123                *validity = Bitmask::with_size(size);
124            }
125            Self::Float { validity, values } => {
126                values.resize(size, 0.0);
127                *validity = Bitmask::with_size(size);
128            }
129            Self::Any(v) => {
130                v.resize(size, ResultData::None);
131            }
132        }
133    }
134
135    pub(crate) fn set(&mut self, index: usize, value: ResultData) {
136        if index >= self.len() {
137            return;
138        }
139        match self {
140            Self::Integer { validity, values } => match value {
141                ResultData::Integer(i) => {
142                    validity.set(index, true);
143                    values[index] = i;
144                }
145                ResultData::Float(f) => {
146                    self.promote_to_float();
147                    self.set(index, ResultData::Float(f));
148                }
149                ResultData::None => {
150                    validity.set(index, false);
151                    values[index] = 0;
152                }
153                _ => {
154                    self.demote_to_any();
155                    if let Self::Any(v) = self {
156                        v[index] = value;
157                    }
158                }
159            },
160            Self::Float { validity, values } => match value {
161                ResultData::Float(f) => {
162                    validity.set(index, true);
163                    values[index] = f;
164                }
165                ResultData::Integer(i) => {
166                    validity.set(index, true);
167                    values[index] = i as f64;
168                }
169                ResultData::None => {
170                    validity.set(index, false);
171                    values[index] = 0.0;
172                }
173                _ => {
174                    self.demote_to_any();
175                    if let Self::Any(v) = self {
176                        v[index] = value;
177                    }
178                }
179            },
180            Self::Any(v) => {
181                v[index] = value;
182            }
183        }
184    }
185
186    pub(crate) fn insert(&mut self, index: usize, value: ResultData) {
187        match self {
188            Self::Integer { validity, values } => match value {
189                ResultData::Integer(i) => {
190                    validity.insert(index, true);
191                    values.insert(index, i);
192                }
193                ResultData::Float(f) => {
194                    self.promote_to_float();
195                    self.insert(index, ResultData::Float(f));
196                }
197                ResultData::None => {
198                    validity.insert(index, false);
199                    values.insert(index, 0);
200                }
201                _ => {
202                    self.demote_to_any();
203                    if let Self::Any(v) = self {
204                        v.insert(index, value);
205                    }
206                }
207            },
208            Self::Float { validity, values } => match value {
209                ResultData::Float(f) => {
210                    validity.insert(index, true);
211                    values.insert(index, f);
212                }
213                ResultData::Integer(i) => {
214                    validity.insert(index, true);
215                    values.insert(index, i as f64);
216                }
217                ResultData::None => {
218                    validity.insert(index, false);
219                    values.insert(index, 0.0);
220                }
221                _ => {
222                    self.demote_to_any();
223                    if let Self::Any(v) = self {
224                        v.insert(index, value);
225                    }
226                }
227            },
228            Self::Any(v) => {
229                v.insert(index, value);
230            }
231        }
232    }
233
234    pub(crate) fn remove(&mut self, index: usize) {
235        match self {
236            Self::Integer { validity, values } => {
237                validity.remove(index);
238                values.remove(index);
239            }
240            Self::Float { validity, values } => {
241                validity.remove(index);
242                values.remove(index);
243            }
244            Self::Any(v) => {
245                v.remove(index);
246            }
247        }
248    }
249
250    pub(crate) fn drain<R: std::ops::RangeBounds<usize> + Clone>(&mut self, range: R) {
251        match self {
252            Self::Integer { validity, values } => {
253                validity.drain(range.clone());
254                values.drain(range);
255            }
256            Self::Float { validity, values } => {
257                validity.drain(range.clone());
258                values.drain(range);
259            }
260            Self::Any(v) => {
261                v.drain(range);
262            }
263        }
264    }
265}
266
267impl Default for ColumnData {
268    fn default() -> Self {
269        Self::Integer {
270            validity: Bitmask::with_size(0),
271            values: SharedVec::new(),
272        }
273    }
274}
275
276/// One column of a sheet: the raw text, the computed values, the cell types,
277/// the compiled formulas and the styles, as parallel per-row vectors.
278///
279/// # Invariant
280///
281/// `src`, `data`, `cell_types`, `compiled_src` and `styles` must all stay the
282/// same length -- row `r` of the column is entry `r` of each. Nothing
283/// enforces this; the row and column insert/delete paths in `Sheet` maintain it
284/// by hand, and `Sheet::setup_after_deserialization` restores it after a load,
285/// since only `src` and `styles` are persisted. Mutating one of these vectors
286/// directly will break it.
287///
288/// `dirty_indices` is not part of that invariant -- it is a queue of rows
289/// awaiting recomputation, and is emptied by `Sheet::commit`.
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct DataColumn {
292    /// Identifier, stable across renames and repositioning. Compiled formulas
293    /// refer to a column by this rather than by name or position.
294    #[serde(default = "generate_unique_id")]
295    pub id: u64,
296    /// Display name, empty unless one was set.
297    #[serde(default)]
298    pub name: String,
299    /// The computed values. Rebuilt on load, so not persisted.
300    #[serde(skip, default)]
301    pub(crate) data: ColumnData,
302    /// The raw text of each cell, exactly as typed. The only representation
303    /// that is persisted, and the one everything else is rebuilt from.
304    pub(crate) src: SharedVec<String>,
305    /// Intrinsic cell data types, matching Excel / OpenXML representations.
306    #[serde(default)]
307    pub(crate) cell_types: SharedVec<CellType>,
308    /// Cached compile output for each cell. Rebuilt on load.
309    #[serde(skip, default)]
310    pub(crate) compiled_src: SharedVec<CompiledFormula>,
311    /// Rows awaiting recomputation. Drained by `Sheet::commit`.
312    #[serde(skip, default)]
313    pub(crate) dirty_indices: SharedVec<usize>,
314    /// Per-cell styling, `None` where a cell has none. Carries a date cell's
315    /// number format.
316    #[serde(default)]
317    pub(crate) styles: SharedVec<Option<crate::core::CellStyle>>,
318}
319
320pub(crate) struct ColumnPosition {
321    pub row: usize,
322    pub char_offset: usize,
323}
324
325impl DataColumn {
326    /// A column of `size` empty rows, with every parallel vector sized to
327    /// match and a freshly generated id.
328    pub fn new(size: usize) -> Self {
329        Self {
330            id: generate_unique_id(),
331            name: String::new(),
332            data: ColumnData::new(size),
333            src: vec![String::new(); size].into(),
334            cell_types: vec![CellType::Auto; size].into(),
335            compiled_src: vec![CompiledFormula::default(); size].into(),
336            dirty_indices: SharedVec::new(),
337            styles: vec![None; size].into(),
338        }
339    }
340
341    /// Rows in the column. Every parallel vector has this length.
342    pub fn len(&self) -> usize {
343        self.src.len()
344    }
345
346    /// Whether the column has no rows.
347    pub fn is_empty(&self) -> bool {
348        self.src.is_empty()
349    }
350
351    /// The raw text of a cell, exactly as typed, or `None` past the end.
352    pub fn src(&self, row: usize) -> Option<&str> {
353        self.src.get(row).map(String::as_str)
354    }
355
356    /// The computed value of a cell, or `None` past the end.
357    ///
358    /// Reflects the last `Sheet::commit`; a cell edited since then still
359    /// reads as its old value.
360    pub fn value(&self, row: usize) -> Option<ResultData> {
361        self.data.get(row)
362    }
363
364    /// The whole value column, for callers that want to work with the typed
365    /// representation rather than row by row.
366    pub fn values(&self) -> &ColumnData {
367        &self.data
368    }
369
370    /// The intrinsic data type of a cell, or `None` past the end.
371    pub fn cell_type(&self, row: usize) -> Option<CellType> {
372        self.cell_types.get(row).copied()
373    }
374
375    /// Sets the intrinsic data type of a cell at `row`.
376    pub fn set_cell_type(&mut self, row: usize, cell_type: CellType) {
377        if row < self.cell_types.len() {
378            self.cell_types[row] = cell_type;
379        }
380    }
381
382    /// A cell's compiled formula, or `None` past the end. A cell holding a
383    /// literal has an empty one rather than no entry.
384    pub fn compiled(&self, row: usize) -> Option<&CompiledFormula> {
385        self.compiled_src.get(row)
386    }
387
388    /// A cell's style, or `None` if it has none or is past the end.
389    pub fn style(&self, row: usize) -> Option<&crate::core::CellStyle> {
390        self.styles.get(row).and_then(Option::as_ref)
391    }
392
393    pub(crate) fn mark_dirty(&mut self, row: usize) {
394        if !self.dirty_indices.contains(&row) {
395            self.dirty_indices.push(row);
396        }
397    }
398
399    /// A named column holding `src`, with every parallel vector sized to
400    /// match.
401    ///
402    /// The values start empty -- `Sheet::commit` is what fills them in from
403    /// the source text. Test-only: production builds sheets through
404    /// `Sheet::new` and `ensure_capacity`.
405    #[cfg(test)]
406    pub(crate) fn from_src(name: impl Into<String>, src: Vec<String>) -> Self {
407        let mut col = Self::new(src.len());
408        col.name = name.into();
409        col.src = src.into();
410        col
411    }
412
413    /// Rebuilds what serialization drops, restoring the length invariant.
414    ///
415    /// Only `src`, `cell_types` and `styles` are persisted, and `styles`/`cell_types`
416    /// are optional, so a workbook saved without them loads with a length of 0. Everything
417    /// is sized back to `src`, which is the authoritative length.
418    pub(crate) fn rebuild_after_load(&mut self) {
419        let size = self.src.len();
420        self.data.resize(size);
421        self.cell_types.resize(size, CellType::Auto);
422        self.compiled_src = vec![CompiledFormula::default(); size].into();
423        self.styles.resize(size, None);
424    }
425
426    /// Appends an empty row to every parallel vector.
427    pub(crate) fn push_row(&mut self) {
428        self.src.push(String::new());
429        self.cell_types.push(CellType::Auto);
430        self.compiled_src.push(CompiledFormula::default());
431        self.data.push(ResultData::None);
432        self.styles.push(None);
433    }
434
435    /// Inserts an empty row at `index` in every parallel vector, shifting the
436    /// rows below it down. Appends if `index` is at or past the end.
437    pub(crate) fn insert_row(&mut self, index: usize) {
438        if index >= self.len() {
439            self.push_row();
440            return;
441        }
442        self.src.insert(index, String::new());
443        self.cell_types.insert(index, CellType::Auto);
444        self.compiled_src.insert(index, CompiledFormula::default());
445        self.data.insert(index, ResultData::None);
446        self.styles.insert(index, None);
447        self.shift_dirty_after_insert(index, 1);
448    }
449
450    /// Removes row `index` from every parallel vector, shifting the rows below
451    /// it up. Ignored if `index` is past the end.
452    pub(crate) fn remove_row(&mut self, index: usize) {
453        if index >= self.len() {
454            return;
455        }
456        self.src.remove(index);
457        self.cell_types.remove(index);
458        self.compiled_src.remove(index);
459        self.data.remove(index);
460        self.styles.remove(index);
461        self.drop_dirty_range(index, index + 1);
462    }
463
464    /// Removes a range of rows from every parallel vector.
465    ///
466    /// The range is clamped to the column's length, so an out-of-range end is
467    /// not an error.
468    pub(crate) fn drain_rows<R: std::ops::RangeBounds<usize>>(&mut self, range: R) {
469        let start = match range.start_bound() {
470            std::ops::Bound::Included(&n) => n,
471            std::ops::Bound::Excluded(&n) => n + 1,
472            std::ops::Bound::Unbounded => 0,
473        };
474        let end = match range.end_bound() {
475            std::ops::Bound::Included(&n) => n + 1,
476            std::ops::Bound::Excluded(&n) => n,
477            std::ops::Bound::Unbounded => self.len(),
478        };
479        let start = start.min(self.len());
480        let end = end.min(self.len());
481        if start >= end {
482            return;
483        }
484        self.src.drain(start..end);
485        self.cell_types.drain(start..end);
486        self.compiled_src.drain(start..end);
487        self.data.drain(start..end);
488        self.styles.drain(start..end);
489        self.drop_dirty_range(start, end);
490    }
491
492    /// Grows or shrinks every parallel vector to `len` rows, filling with
493    /// empties when growing.
494    pub(crate) fn resize_rows(&mut self, len: usize) {
495        while self.len() < len {
496            self.push_row();
497        }
498        if self.len() > len {
499            self.drain_rows(len..);
500        }
501    }
502
503    /// Drops queued rows in `start..end` and rebases those below it.
504    fn drop_dirty_range(&mut self, start: usize, end: usize) {
505        let removed = end - start;
506        self.dirty_indices.retain(|&i| i < start || i >= end);
507        for i in self.dirty_indices.iter_mut() {
508            if *i >= end {
509                *i -= removed;
510            }
511        }
512    }
513
514    /// Rebases queued rows at or below `index` after an insert.
515    fn shift_dirty_after_insert(&mut self, index: usize, count: usize) {
516        for i in self.dirty_indices.iter_mut() {
517            if *i >= index {
518                *i += count;
519            }
520        }
521    }
522
523    /// Row is absolutely referenced
524    pub(crate) fn insert(&mut self, position: ColumnPosition, input: &str) {
525        let ColumnPosition { row, char_offset } = position;
526        let index = row;
527        if index < self.src.len() {
528            if self.src[index].is_empty() {
529                self.src[index].push_str(input);
530            } else {
531                self.src[index].insert_str(char_offset, input);
532            }
533        } else {
534            // Grow to cover `index`, then write into the new last row.
535            self.resize_rows(index + 1);
536            self.src[index] = input.to_string();
537        }
538    }
539}