Skip to main content

formualizer_eval/arrow_store/
mod.rs

1use arrow_array::Array;
2use arrow_array::new_null_array;
3use arrow_schema::DataType;
4use std::sync::Arc;
5
6use arrow_array::builder::{BooleanBuilder, Float64Builder, StringBuilder, UInt8Builder};
7use arrow_array::{ArrayRef, BooleanArray, Float64Array, StringArray, UInt8Array, UInt32Array};
8use once_cell::sync::OnceCell;
9
10use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
11use rustc_hash::FxHashMap;
12use std::collections::{BTreeMap, HashMap};
13
14/// Compact type tag per row (UInt8 backing)
15#[repr(u8)]
16#[derive(Copy, Clone, Debug, Eq, PartialEq)]
17pub enum TypeTag {
18    Empty = 0,
19    Number = 1,
20    Boolean = 2,
21    Text = 3,
22    Error = 4,
23    DateTime = 5, // reserved for future temporal lanes
24    Duration = 6, // reserved
25    Pending = 7,
26}
27
28impl TypeTag {
29    fn from_value(v: &LiteralValue) -> Self {
30        match v {
31            LiteralValue::Empty => TypeTag::Empty,
32            LiteralValue::Int(_) | LiteralValue::Number(_) => TypeTag::Number,
33            LiteralValue::Boolean(_) => TypeTag::Boolean,
34            LiteralValue::Text(_) => TypeTag::Text,
35            LiteralValue::Error(_) => TypeTag::Error,
36            LiteralValue::Date(_) | LiteralValue::DateTime(_) | LiteralValue::Time(_) => {
37                TypeTag::DateTime
38            }
39            LiteralValue::Duration(_) => TypeTag::Duration,
40            LiteralValue::Pending => TypeTag::Pending,
41            LiteralValue::Array(_) => TypeTag::Error, // arrays not storable in a single cell lane
42        }
43    }
44}
45
46impl TypeTag {
47    #[inline]
48    pub fn from_u8(b: u8) -> Self {
49        match b {
50            x if x == TypeTag::Empty as u8 => TypeTag::Empty,
51            x if x == TypeTag::Number as u8 => TypeTag::Number,
52            x if x == TypeTag::Boolean as u8 => TypeTag::Boolean,
53            x if x == TypeTag::Text as u8 => TypeTag::Text,
54            x if x == TypeTag::Error as u8 => TypeTag::Error,
55            x if x == TypeTag::DateTime as u8 => TypeTag::DateTime,
56            x if x == TypeTag::Duration as u8 => TypeTag::Duration,
57            x if x == TypeTag::Pending as u8 => TypeTag::Pending,
58            _ => TypeTag::Empty,
59        }
60    }
61}
62
63#[derive(Debug, Clone, Copy, Default)]
64pub struct ColumnChunkMeta {
65    pub len: usize,
66    pub non_null_num: usize,
67    pub non_null_bool: usize,
68    pub non_null_text: usize,
69    pub non_null_err: usize,
70}
71
72#[derive(Debug, Clone)]
73pub struct ColumnChunk {
74    pub numbers: Option<Arc<Float64Array>>,
75    pub booleans: Option<Arc<BooleanArray>>,
76    pub text: Option<ArrayRef>,          // Utf8 for Phase A
77    pub errors: Option<Arc<UInt8Array>>, // compact error code (UInt8)
78    pub type_tag: Arc<UInt8Array>,
79    pub formula_id: Option<Arc<UInt32Array>>, // reserved for Phase A+
80    pub meta: ColumnChunkMeta,
81    // Lazy null providers (per-chunk)
82    lazy_null_numbers: OnceCell<Arc<Float64Array>>,
83    lazy_null_booleans: OnceCell<Arc<BooleanArray>>,
84    lazy_null_text: OnceCell<ArrayRef>,
85    lazy_null_errors: OnceCell<Arc<UInt8Array>>,
86    // Cache: lowered text lane, nulls preserved
87    lowered_text: OnceCell<ArrayRef>,
88    // Phase C: per-chunk overlay (delta edits since last compaction)
89    pub overlay: Overlay,
90    // Phase 0/1: separate computed overlay (formula/spill outputs)
91    pub computed_overlay: Overlay,
92}
93
94impl ColumnChunk {
95    #[inline]
96    pub fn len(&self) -> usize {
97        self.type_tag.len()
98    }
99    #[inline]
100    pub fn is_empty(&self) -> bool {
101        self.len() == 0
102    }
103    #[inline]
104    pub fn numbers_or_null(&self) -> Arc<Float64Array> {
105        if let Some(a) = &self.numbers {
106            return a.clone();
107        }
108        self.lazy_null_numbers
109            .get_or_init(|| {
110                let arr = new_null_array(&DataType::Float64, self.len());
111                Arc::new(arr.as_any().downcast_ref::<Float64Array>().unwrap().clone())
112            })
113            .clone()
114    }
115    #[inline]
116    pub fn booleans_or_null(&self) -> Arc<BooleanArray> {
117        if let Some(a) = &self.booleans {
118            return a.clone();
119        }
120        self.lazy_null_booleans
121            .get_or_init(|| {
122                let arr = new_null_array(&DataType::Boolean, self.len());
123                Arc::new(arr.as_any().downcast_ref::<BooleanArray>().unwrap().clone())
124            })
125            .clone()
126    }
127    #[inline]
128    pub fn errors_or_null(&self) -> Arc<UInt8Array> {
129        if let Some(a) = &self.errors {
130            return a.clone();
131        }
132        self.lazy_null_errors
133            .get_or_init(|| {
134                let arr = new_null_array(&DataType::UInt8, self.len());
135                Arc::new(arr.as_any().downcast_ref::<UInt8Array>().unwrap().clone())
136            })
137            .clone()
138    }
139    #[inline]
140    pub fn text_or_null(&self) -> ArrayRef {
141        if let Some(a) = &self.text {
142            return a.clone();
143        }
144        self.lazy_null_text
145            .get_or_init(|| new_null_array(&DataType::Utf8, self.len()))
146            .clone()
147    }
148
149    /// Lowercased text lane, with nulls preserved. Cached per chunk.
150    pub fn text_lower_or_null(&self) -> ArrayRef {
151        if let Some(a) = self.lowered_text.get() {
152            return a.clone();
153        }
154        // Lowercase when text present; else return null Utf8
155        let out: ArrayRef = if let Some(txt) = &self.text {
156            let sa = txt.as_any().downcast_ref::<StringArray>().unwrap();
157            let mut b = arrow_array::builder::StringBuilder::with_capacity(sa.len(), sa.len() * 8);
158            for i in 0..sa.len() {
159                if sa.is_null(i) {
160                    b.append_null();
161                } else {
162                    b.append_value(sa.value(i).to_lowercase());
163                }
164            }
165            let lowered = b.finish();
166            Arc::new(lowered)
167        } else {
168            new_null_array(&DataType::Utf8, self.len())
169        };
170        self.lowered_text.get_or_init(|| out.clone());
171        out
172    }
173
174    /// Grow this chunk's logical length to `new_len` (padding with empty/null values).
175    ///
176    /// This is used to keep already-materialized chunks consistent when `ArrowSheet::nrows`
177    /// grows incrementally inside the current last chunk.
178    pub fn grow_len_to(&mut self, new_len: usize) {
179        let old_len = self.len();
180        if new_len <= old_len {
181            return;
182        }
183
184        // Grow type tags (pad with Empty).
185        let mut tags: Vec<u8> = self.type_tag.values().to_vec();
186        tags.resize(new_len, TypeTag::Empty as u8);
187        self.type_tag = Arc::new(UInt8Array::from(tags));
188
189        // Grow lanes when present; append nulls for new rows.
190        if let Some(a) = &self.numbers {
191            use arrow_array::builder::Float64Builder;
192            let mut b = Float64Builder::with_capacity(new_len);
193            for i in 0..old_len {
194                if a.is_null(i) {
195                    b.append_null();
196                } else {
197                    b.append_value(a.value(i));
198                }
199            }
200            for _ in old_len..new_len {
201                b.append_null();
202            }
203            self.numbers = Some(Arc::new(b.finish()));
204        }
205        if let Some(a) = &self.booleans {
206            use arrow_array::builder::BooleanBuilder;
207            let mut b = BooleanBuilder::with_capacity(new_len);
208            for i in 0..old_len {
209                if a.is_null(i) {
210                    b.append_null();
211                } else {
212                    b.append_value(a.value(i));
213                }
214            }
215            for _ in old_len..new_len {
216                b.append_null();
217            }
218            self.booleans = Some(Arc::new(b.finish()));
219        }
220        if let Some(a) = &self.errors {
221            use arrow_array::builder::UInt8Builder;
222            let mut b = UInt8Builder::with_capacity(new_len);
223            for i in 0..old_len {
224                if a.is_null(i) {
225                    b.append_null();
226                } else {
227                    b.append_value(a.value(i));
228                }
229            }
230            for _ in old_len..new_len {
231                b.append_null();
232            }
233            self.errors = Some(Arc::new(b.finish()));
234        }
235        if let Some(a) = &self.text {
236            use arrow_array::builder::StringBuilder;
237            let sa = a.as_any().downcast_ref::<StringArray>().unwrap();
238            let mut b = StringBuilder::with_capacity(new_len, 0);
239            for i in 0..old_len {
240                if sa.is_null(i) {
241                    b.append_null();
242                } else {
243                    b.append_value(sa.value(i));
244                }
245            }
246            for _ in old_len..new_len {
247                b.append_null();
248            }
249            self.text = Some(Arc::new(b.finish()) as ArrayRef);
250        }
251
252        // Length-dependent caches must be dropped.
253        self.lazy_null_numbers = OnceCell::new();
254        self.lazy_null_booleans = OnceCell::new();
255        self.lazy_null_text = OnceCell::new();
256        self.lazy_null_errors = OnceCell::new();
257        self.lowered_text = OnceCell::new();
258
259        self.meta.len = new_len;
260    }
261}
262
263#[derive(Debug, Clone)]
264pub struct ArrowColumn {
265    pub chunks: Vec<ColumnChunk>,
266    pub sparse_chunks: FxHashMap<usize, ColumnChunk>,
267    pub index: u32,
268}
269
270impl ArrowColumn {
271    #[inline]
272    pub fn chunk(&self, idx: usize) -> Option<&ColumnChunk> {
273        if idx < self.chunks.len() {
274            Some(&self.chunks[idx])
275        } else {
276            self.sparse_chunks.get(&idx)
277        }
278    }
279
280    #[inline]
281    pub fn chunk_mut(&mut self, idx: usize) -> Option<&mut ColumnChunk> {
282        if idx < self.chunks.len() {
283            Some(&mut self.chunks[idx])
284        } else {
285            self.sparse_chunks.get_mut(&idx)
286        }
287    }
288
289    #[inline]
290    pub fn has_sparse_chunks(&self) -> bool {
291        !self.sparse_chunks.is_empty()
292    }
293
294    #[inline]
295    pub fn total_chunk_count(&self) -> usize {
296        self.chunks.len() + self.sparse_chunks.len()
297    }
298}
299
300#[derive(Debug, Clone)]
301pub struct ArrowSheet {
302    pub name: Arc<str>,
303    /// Serial encoding used by every temporal lane in this sheet.
304    ///
305    /// Changing this value requires re-encoding all temporal cells; engine and
306    /// workbook constructors therefore set it once when the sheet is created.
307    pub date_system: crate::engine::DateSystem,
308    pub columns: Vec<ArrowColumn>,
309    pub nrows: u32,
310    pub chunk_starts: Vec<usize>,
311    /// Preferred chunk size (rows) for capacity growth operations.
312    ///
313    /// For Arrow-ingested sheets this matches the ingest `chunk_rows`. For sparse/overlay-created
314    /// sheets this defaults to 32k to avoid creating thousands of tiny chunks during growth.
315    pub chunk_rows: usize,
316}
317
318#[derive(Debug, Default, Clone)]
319pub struct SheetStore {
320    pub sheets: Vec<ArrowSheet>,
321}
322
323impl SheetStore {
324    pub fn sheet(&self, name: &str) -> Option<&ArrowSheet> {
325        self.sheets.iter().find(|s| s.name.as_ref() == name)
326    }
327    pub fn sheet_mut(&mut self, name: &str) -> Option<&mut ArrowSheet> {
328        self.sheets.iter_mut().find(|s| s.name.as_ref() == name)
329    }
330}
331
332/// Ingestion builder that writes per-column Arrow arrays with a lane/tag design.
333pub struct IngestBuilder {
334    name: Arc<str>,
335    ncols: usize,
336    chunk_rows: usize,
337    date_system: crate::engine::DateSystem,
338
339    // Per-column active builders for current chunk
340    num_builders: Vec<Float64Builder>,
341    bool_builders: Vec<BooleanBuilder>,
342    text_builders: Vec<StringBuilder>,
343    err_builders: Vec<UInt8Builder>,
344    tag_builders: Vec<UInt8Builder>,
345
346    // Per-column per-lane non-null counters for current chunk
347    lane_counts: Vec<LaneCounts>,
348
349    // Accumulated chunks
350    chunks: Vec<Vec<ColumnChunk>>, // indexed by col
351    row_in_chunk: usize,
352    total_rows: u32,
353}
354
355#[derive(Debug, Clone, Copy, Default)]
356struct LaneCounts {
357    n_num: usize,
358    n_bool: usize,
359    n_text: usize,
360    n_err: usize,
361}
362
363impl IngestBuilder {
364    pub fn new(
365        sheet_name: &str,
366        ncols: usize,
367        chunk_rows: usize,
368        date_system: crate::engine::DateSystem,
369    ) -> Self {
370        let mut chunks = Vec::with_capacity(ncols);
371        chunks.resize_with(ncols, Vec::new);
372        Self {
373            name: Arc::from(sheet_name.to_string()),
374            ncols,
375            chunk_rows: chunk_rows.max(1),
376            date_system,
377            num_builders: (0..ncols)
378                .map(|_| Float64Builder::with_capacity(chunk_rows))
379                .collect(),
380            bool_builders: (0..ncols)
381                .map(|_| BooleanBuilder::with_capacity(chunk_rows))
382                .collect(),
383            text_builders: (0..ncols)
384                .map(|_| StringBuilder::with_capacity(chunk_rows, chunk_rows * 12))
385                .collect(),
386            err_builders: (0..ncols)
387                .map(|_| UInt8Builder::with_capacity(chunk_rows))
388                .collect(),
389            tag_builders: (0..ncols)
390                .map(|_| UInt8Builder::with_capacity(chunk_rows))
391                .collect(),
392            lane_counts: vec![LaneCounts::default(); ncols],
393            chunks,
394            row_in_chunk: 0,
395            total_rows: 0,
396        }
397    }
398
399    /// Zero-allocation row append from typed cell tokens (no LiteralValue).
400    /// Text borrows are copied into the internal StringBuilder.
401    pub fn append_row_cells<'a>(&mut self, row: &[CellIngest<'a>]) -> Result<(), ExcelError> {
402        assert_eq!(row.len(), self.ncols, "row width mismatch");
403        for (c, cell) in row.iter().enumerate() {
404            match cell {
405                CellIngest::Empty => {
406                    self.tag_builders[c].append_value(TypeTag::Empty as u8);
407                    self.num_builders[c].append_null();
408                    self.bool_builders[c].append_null();
409                    self.text_builders[c].append_null();
410                    self.err_builders[c].append_null();
411                }
412                CellIngest::Number(n) => {
413                    self.tag_builders[c].append_value(TypeTag::Number as u8);
414                    self.num_builders[c].append_value(*n);
415                    self.lane_counts[c].n_num += 1;
416                    self.bool_builders[c].append_null();
417                    self.text_builders[c].append_null();
418                    self.err_builders[c].append_null();
419                }
420                CellIngest::Boolean(b) => {
421                    self.tag_builders[c].append_value(TypeTag::Boolean as u8);
422                    self.num_builders[c].append_null();
423                    self.bool_builders[c].append_value(*b);
424                    self.lane_counts[c].n_bool += 1;
425                    self.text_builders[c].append_null();
426                    self.err_builders[c].append_null();
427                }
428                CellIngest::Text(s) => {
429                    self.tag_builders[c].append_value(TypeTag::Text as u8);
430                    self.num_builders[c].append_null();
431                    self.bool_builders[c].append_null();
432                    self.text_builders[c].append_value(s);
433                    self.lane_counts[c].n_text += 1;
434                    self.err_builders[c].append_null();
435                }
436                CellIngest::ErrorCode(code) => {
437                    self.tag_builders[c].append_value(TypeTag::Error as u8);
438                    self.num_builders[c].append_null();
439                    self.bool_builders[c].append_null();
440                    self.text_builders[c].append_null();
441                    self.err_builders[c].append_value(*code);
442                    self.lane_counts[c].n_err += 1;
443                }
444                CellIngest::DateSerial(serial) => {
445                    self.tag_builders[c].append_value(TypeTag::DateTime as u8);
446                    self.num_builders[c].append_value(*serial);
447                    self.lane_counts[c].n_num += 1;
448                    self.bool_builders[c].append_null();
449                    self.text_builders[c].append_null();
450                    self.err_builders[c].append_null();
451                }
452                CellIngest::Pending => {
453                    self.tag_builders[c].append_value(TypeTag::Pending as u8);
454                    self.num_builders[c].append_null();
455                    self.bool_builders[c].append_null();
456                    self.text_builders[c].append_null();
457                    self.err_builders[c].append_null();
458                }
459            }
460        }
461        self.row_in_chunk += 1;
462        self.total_rows += 1;
463        if self.row_in_chunk >= self.chunk_rows {
464            self.finish_chunk();
465        }
466        Ok(())
467    }
468
469    /// Streaming row append from an iterator of typed cell tokens.
470    /// Requires an `ExactSizeIterator` to validate row width without materializing a Vec.
471    pub fn append_row_cells_iter<'a, I>(&mut self, iter: I) -> Result<(), ExcelError>
472    where
473        I: ExactSizeIterator<Item = CellIngest<'a>>,
474    {
475        assert_eq!(iter.len(), self.ncols, "row width mismatch");
476        for (c, cell) in iter.enumerate() {
477            match cell {
478                CellIngest::Empty => {
479                    self.tag_builders[c].append_value(TypeTag::Empty as u8);
480                    self.num_builders[c].append_null();
481                    self.bool_builders[c].append_null();
482                    self.text_builders[c].append_null();
483                    self.err_builders[c].append_null();
484                }
485                CellIngest::Number(n) => {
486                    self.tag_builders[c].append_value(TypeTag::Number as u8);
487                    self.num_builders[c].append_value(n);
488                    self.lane_counts[c].n_num += 1;
489                    self.bool_builders[c].append_null();
490                    self.text_builders[c].append_null();
491                    self.err_builders[c].append_null();
492                }
493                CellIngest::Boolean(b) => {
494                    self.tag_builders[c].append_value(TypeTag::Boolean as u8);
495                    self.num_builders[c].append_null();
496                    self.bool_builders[c].append_value(b);
497                    self.lane_counts[c].n_bool += 1;
498                    self.text_builders[c].append_null();
499                    self.err_builders[c].append_null();
500                }
501                CellIngest::Text(s) => {
502                    self.tag_builders[c].append_value(TypeTag::Text as u8);
503                    self.num_builders[c].append_null();
504                    self.bool_builders[c].append_null();
505                    self.text_builders[c].append_value(s);
506                    self.lane_counts[c].n_text += 1;
507                    self.err_builders[c].append_null();
508                }
509                CellIngest::ErrorCode(code) => {
510                    self.tag_builders[c].append_value(TypeTag::Error as u8);
511                    self.num_builders[c].append_null();
512                    self.bool_builders[c].append_null();
513                    self.text_builders[c].append_null();
514                    self.err_builders[c].append_value(code);
515                    self.lane_counts[c].n_err += 1;
516                }
517                CellIngest::DateSerial(serial) => {
518                    self.tag_builders[c].append_value(TypeTag::DateTime as u8);
519                    self.num_builders[c].append_value(serial);
520                    self.lane_counts[c].n_num += 1;
521                    self.bool_builders[c].append_null();
522                    self.text_builders[c].append_null();
523                    self.err_builders[c].append_null();
524                }
525                CellIngest::Pending => {
526                    self.tag_builders[c].append_value(TypeTag::Pending as u8);
527                    self.num_builders[c].append_null();
528                    self.bool_builders[c].append_null();
529                    self.text_builders[c].append_null();
530                    self.err_builders[c].append_null();
531                }
532            }
533        }
534        self.row_in_chunk += 1;
535        self.total_rows += 1;
536        if self.row_in_chunk >= self.chunk_rows {
537            self.finish_chunk();
538        }
539        Ok(())
540    }
541
542    /// Append a single row of values. Length must match `ncols`.
543    pub fn append_row(&mut self, row: &[LiteralValue]) -> Result<(), ExcelError> {
544        assert_eq!(row.len(), self.ncols, "row width mismatch");
545
546        for (c, v) in row.iter().enumerate() {
547            let tag = TypeTag::from_value(v) as u8;
548            self.tag_builders[c].append_value(tag);
549
550            match v {
551                LiteralValue::Empty => {
552                    self.num_builders[c].append_null();
553                    self.bool_builders[c].append_null();
554                    self.text_builders[c].append_null();
555                    self.err_builders[c].append_null();
556                }
557                LiteralValue::Int(i) => {
558                    self.num_builders[c].append_value(*i as f64);
559                    self.lane_counts[c].n_num += 1;
560                    self.bool_builders[c].append_null();
561                    self.text_builders[c].append_null();
562                    self.err_builders[c].append_null();
563                }
564                LiteralValue::Number(n) => {
565                    self.num_builders[c].append_value(*n);
566                    self.lane_counts[c].n_num += 1;
567                    self.bool_builders[c].append_null();
568                    self.text_builders[c].append_null();
569                    self.err_builders[c].append_null();
570                }
571                LiteralValue::Boolean(b) => {
572                    self.num_builders[c].append_null();
573                    self.bool_builders[c].append_value(*b);
574                    self.lane_counts[c].n_bool += 1;
575                    self.text_builders[c].append_null();
576                    self.err_builders[c].append_null();
577                }
578                LiteralValue::Text(s) => {
579                    self.num_builders[c].append_null();
580                    self.bool_builders[c].append_null();
581                    self.text_builders[c].append_value(s);
582                    self.lane_counts[c].n_text += 1;
583                    self.err_builders[c].append_null();
584                }
585                LiteralValue::Error(e) => {
586                    self.num_builders[c].append_null();
587                    self.bool_builders[c].append_null();
588                    self.text_builders[c].append_null();
589                    self.err_builders[c].append_value(map_error_code(e.kind));
590                    self.lane_counts[c].n_err += 1;
591                }
592                // Phase A: coerce temporal to serials in numeric lane with DateTime tag
593                LiteralValue::Date(d) => {
594                    let dt = d.and_hms_opt(0, 0, 0).unwrap();
595                    let serial = formualizer_common::datetime_to_serial_for(self.date_system, &dt);
596                    self.num_builders[c].append_value(serial);
597                    self.lane_counts[c].n_num += 1;
598                    self.bool_builders[c].append_null();
599                    self.text_builders[c].append_null();
600                    self.err_builders[c].append_null();
601                }
602                LiteralValue::DateTime(dt) => {
603                    let serial = formualizer_common::datetime_to_serial_for(self.date_system, dt);
604                    self.num_builders[c].append_value(serial);
605                    self.lane_counts[c].n_num += 1;
606                    self.bool_builders[c].append_null();
607                    self.text_builders[c].append_null();
608                    self.err_builders[c].append_null();
609                }
610                LiteralValue::Time(t) => {
611                    let serial = formualizer_common::time_to_fraction(t);
612                    self.num_builders[c].append_value(serial);
613                    self.lane_counts[c].n_num += 1;
614                    self.bool_builders[c].append_null();
615                    self.text_builders[c].append_null();
616                    self.err_builders[c].append_null();
617                }
618                LiteralValue::Duration(dur) => {
619                    let serial = dur.num_seconds() as f64 / 86_400.0;
620                    self.num_builders[c].append_value(serial);
621                    self.lane_counts[c].n_num += 1;
622                    self.bool_builders[c].append_null();
623                    self.text_builders[c].append_null();
624                    self.err_builders[c].append_null();
625                }
626                LiteralValue::Array(_) => {
627                    // Not allowed as a stored scalar; mark as error kind VALUE
628                    self.num_builders[c].append_null();
629                    self.bool_builders[c].append_null();
630                    self.text_builders[c].append_null();
631                    self.err_builders[c].append_value(map_error_code(ExcelErrorKind::Value));
632                    self.lane_counts[c].n_err += 1;
633                }
634                LiteralValue::Pending => {
635                    // Pending: tag only; all lanes remain null (no error)
636                    self.num_builders[c].append_null();
637                    self.bool_builders[c].append_null();
638                    self.text_builders[c].append_null();
639                    self.err_builders[c].append_null();
640                }
641            }
642        }
643
644        self.row_in_chunk += 1;
645        self.total_rows += 1;
646
647        if self.row_in_chunk >= self.chunk_rows {
648            self.finish_chunk();
649        }
650
651        Ok(())
652    }
653
654    fn finish_chunk(&mut self) {
655        if self.row_in_chunk == 0 {
656            return;
657        }
658        for c in 0..self.ncols {
659            let len = self.row_in_chunk;
660            let numbers_arc: Option<Arc<Float64Array>> = if self.lane_counts[c].n_num == 0 {
661                None
662            } else {
663                Some(Arc::new(self.num_builders[c].finish()))
664            };
665            let booleans_arc: Option<Arc<BooleanArray>> = if self.lane_counts[c].n_bool == 0 {
666                None
667            } else {
668                Some(Arc::new(self.bool_builders[c].finish()))
669            };
670            let text_ref: Option<ArrayRef> = if self.lane_counts[c].n_text == 0 {
671                None
672            } else {
673                Some(Arc::new(self.text_builders[c].finish()))
674            };
675            let errors_arc: Option<Arc<UInt8Array>> = if self.lane_counts[c].n_err == 0 {
676                None
677            } else {
678                Some(Arc::new(self.err_builders[c].finish()))
679            };
680            let tags: UInt8Array = self.tag_builders[c].finish();
681
682            let chunk = ColumnChunk {
683                numbers: numbers_arc,
684                booleans: booleans_arc,
685                text: text_ref,
686                errors: errors_arc,
687                type_tag: Arc::new(tags),
688                formula_id: None,
689                meta: ColumnChunkMeta {
690                    len,
691                    non_null_num: self.lane_counts[c].n_num,
692                    non_null_bool: self.lane_counts[c].n_bool,
693                    non_null_text: self.lane_counts[c].n_text,
694                    non_null_err: self.lane_counts[c].n_err,
695                },
696                lazy_null_numbers: OnceCell::new(),
697                lazy_null_booleans: OnceCell::new(),
698                lazy_null_text: OnceCell::new(),
699                lazy_null_errors: OnceCell::new(),
700                lowered_text: OnceCell::new(),
701                overlay: Overlay::new(),
702                computed_overlay: Overlay::new(),
703            };
704            self.chunks[c].push(chunk);
705
706            // re-init builders for next chunk
707            self.num_builders[c] = Float64Builder::with_capacity(self.chunk_rows);
708            self.bool_builders[c] = BooleanBuilder::with_capacity(self.chunk_rows);
709            self.text_builders[c] =
710                StringBuilder::with_capacity(self.chunk_rows, self.chunk_rows * 12);
711            self.err_builders[c] = UInt8Builder::with_capacity(self.chunk_rows);
712            self.tag_builders[c] = UInt8Builder::with_capacity(self.chunk_rows);
713            self.lane_counts[c] = LaneCounts::default();
714        }
715        self.row_in_chunk = 0;
716    }
717
718    pub fn finish(mut self) -> ArrowSheet {
719        // flush partial chunk
720        if self.row_in_chunk > 0 {
721            self.finish_chunk();
722        }
723
724        let mut columns = Vec::with_capacity(self.ncols);
725        for (idx, chunks) in self.chunks.into_iter().enumerate() {
726            columns.push(ArrowColumn {
727                chunks,
728                sparse_chunks: FxHashMap::default(),
729                index: idx as u32,
730            });
731        }
732        // Precompute chunk starts from first column and enforce alignment across columns
733        let mut chunk_starts: Vec<usize> = Vec::new();
734        if let Some(col0) = columns.first() {
735            let chunks_len0 = col0.chunks.len();
736            for (ci, col) in columns.iter().enumerate() {
737                if col.chunks.len() != chunks_len0 {
738                    panic!(
739                        "ArrowSheet chunk misalignment: column {} chunks={} != {}",
740                        ci,
741                        col.chunks.len(),
742                        chunks_len0
743                    );
744                }
745            }
746            let mut cur = 0usize;
747            for i in 0..chunks_len0 {
748                let len_i = col0.chunks[i].type_tag.len();
749                for (ci, col) in columns.iter().enumerate() {
750                    let got = col.chunks[i].type_tag.len();
751                    if got != len_i {
752                        panic!(
753                            "ArrowSheet chunk row-length misalignment at chunk {i}: col {ci} len={got} != {len_i}"
754                        );
755                    }
756                }
757                chunk_starts.push(cur);
758                cur += len_i;
759            }
760        }
761        ArrowSheet {
762            name: self.name,
763            date_system: self.date_system,
764            columns,
765            nrows: self.total_rows,
766            chunk_starts,
767            chunk_rows: self.chunk_rows,
768        }
769    }
770}
771
772pub fn map_error_code(kind: ExcelErrorKind) -> u8 {
773    match kind {
774        ExcelErrorKind::Null => 1,
775        ExcelErrorKind::Ref => 2,
776        ExcelErrorKind::Name => 3,
777        ExcelErrorKind::Value => 4,
778        ExcelErrorKind::Div => 5,
779        ExcelErrorKind::Na => 6,
780        ExcelErrorKind::Num => 7,
781        ExcelErrorKind::Error => 8,
782        ExcelErrorKind::NImpl => 9,
783        ExcelErrorKind::Spill => 10,
784        ExcelErrorKind::Calc => 11,
785        ExcelErrorKind::Circ => 12,
786        ExcelErrorKind::Cancelled => 13,
787        _ => 8,
788    }
789}
790
791pub fn unmap_error_code(code: u8) -> ExcelErrorKind {
792    match code {
793        1 => ExcelErrorKind::Null,
794        2 => ExcelErrorKind::Ref,
795        3 => ExcelErrorKind::Name,
796        4 => ExcelErrorKind::Value,
797        5 => ExcelErrorKind::Div,
798        6 => ExcelErrorKind::Na,
799        7 => ExcelErrorKind::Num,
800        8 => ExcelErrorKind::Error,
801        9 => ExcelErrorKind::NImpl,
802        10 => ExcelErrorKind::Spill,
803        11 => ExcelErrorKind::Calc,
804        12 => ExcelErrorKind::Circ,
805        13 => ExcelErrorKind::Cancelled,
806        _ => ExcelErrorKind::Error,
807    }
808}
809
810// ─────────────────────────── Overlay (Phase C) ────────────────────────────
811
812/// Zero-allocation cell token for ingestion.
813pub enum CellIngest<'a> {
814    Empty,
815    Number(f64),
816    Boolean(bool),
817    Text(&'a str),
818    ErrorCode(u8),
819    DateSerial(f64),
820    Pending,
821}
822
823#[derive(Debug, Clone, PartialEq)]
824pub enum OverlayValue {
825    Empty,
826    Number(f64),
827    /// Date/Time/DateTime stored as an Excel serial in the numeric lane.
828    DateTime(f64),
829    /// Duration stored as an Excel-style day-fraction in the numeric lane.
830    Duration(f64),
831    Boolean(bool),
832    Text(Arc<str>),
833    Error(u8),
834    Pending,
835}
836
837impl OverlayValue {
838    pub fn from_literal_value(
839        value: &LiteralValue,
840        date_system: crate::engine::DateSystem,
841    ) -> Self {
842        match value {
843            LiteralValue::Empty => OverlayValue::Empty,
844            LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
845            LiteralValue::Number(n) => OverlayValue::Number(*n),
846            LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
847            LiteralValue::Text(s) => OverlayValue::Text(Arc::from(s.clone())),
848            LiteralValue::Error(e) => OverlayValue::Error(map_error_code(e.kind)),
849            LiteralValue::Date(d) => {
850                let dt = d.and_hms_opt(0, 0, 0).unwrap();
851                OverlayValue::DateTime(formualizer_common::datetime_to_serial_for(date_system, &dt))
852            }
853            LiteralValue::DateTime(dt) => {
854                OverlayValue::DateTime(formualizer_common::datetime_to_serial_for(date_system, dt))
855            }
856            LiteralValue::Time(t) => {
857                OverlayValue::DateTime(formualizer_common::time_to_fraction(t))
858            }
859            LiteralValue::Duration(d) => OverlayValue::Duration(d.num_seconds() as f64 / 86_400.0),
860            LiteralValue::Pending => OverlayValue::Pending,
861            LiteralValue::Array(_) => OverlayValue::Error(map_error_code(ExcelErrorKind::Value)),
862        }
863    }
864
865    #[inline]
866    pub(crate) fn estimated_payload_bytes(&self) -> usize {
867        match self {
868            OverlayValue::Empty | OverlayValue::Pending => 0,
869            OverlayValue::Number(_) | OverlayValue::DateTime(_) | OverlayValue::Duration(_) => {
870                core::mem::size_of::<f64>()
871            }
872            OverlayValue::Boolean(_) => core::mem::size_of::<bool>(),
873            OverlayValue::Error(_) => core::mem::size_of::<u8>(),
874            // Deterministic estimate: count string bytes only.
875            OverlayValue::Text(s) => s.len(),
876        }
877    }
878
879    #[inline]
880    pub(crate) fn type_tag(&self) -> TypeTag {
881        match self {
882            OverlayValue::Empty => TypeTag::Empty,
883            OverlayValue::Number(_) => TypeTag::Number,
884            OverlayValue::DateTime(_) => TypeTag::DateTime,
885            OverlayValue::Duration(_) => TypeTag::Duration,
886            OverlayValue::Boolean(_) => TypeTag::Boolean,
887            OverlayValue::Text(_) => TypeTag::Text,
888            OverlayValue::Error(_) => TypeTag::Error,
889            OverlayValue::Pending => TypeTag::Pending,
890        }
891    }
892
893    #[inline]
894    pub(crate) fn numeric_lane_value(&self) -> Option<f64> {
895        match self {
896            OverlayValue::Number(n) | OverlayValue::DateTime(n) | OverlayValue::Duration(n) => {
897                Some(*n)
898            }
899            _ => None,
900        }
901    }
902
903    #[inline]
904    pub(crate) fn boolean_lane_value(&self) -> Option<bool> {
905        match self {
906            OverlayValue::Boolean(b) => Some(*b),
907            _ => None,
908        }
909    }
910
911    #[inline]
912    pub(crate) fn text_lane_value(&self) -> Option<&str> {
913        match self {
914            OverlayValue::Text(s) => Some(s.as_ref()),
915            _ => None,
916        }
917    }
918
919    #[inline]
920    pub(crate) fn error_lane_value(&self) -> Option<u8> {
921        match self {
922            OverlayValue::Error(code) => Some(*code),
923            _ => None,
924        }
925    }
926
927    pub(crate) fn lowered_text_value(&self) -> Option<String> {
928        match self {
929            OverlayValue::Text(s) => Some(s.to_lowercase()),
930            OverlayValue::Number(n) | OverlayValue::DateTime(n) | OverlayValue::Duration(n) => {
931                Some(n.to_string())
932            }
933            OverlayValue::Boolean(b) => Some(if *b { "true" } else { "false" }.to_string()),
934            OverlayValue::Empty | OverlayValue::Error(_) | OverlayValue::Pending => None,
935        }
936    }
937
938    pub(crate) fn to_literal_for(&self, date_system: crate::engine::DateSystem) -> LiteralValue {
939        match self {
940            OverlayValue::Empty => LiteralValue::Empty,
941            OverlayValue::Number(n) => LiteralValue::Number(*n),
942            OverlayValue::DateTime(serial) => {
943                LiteralValue::try_from_serial_number_for(date_system, *serial)
944                    .unwrap_or_else(LiteralValue::Error)
945            }
946            OverlayValue::Duration(serial) => {
947                let nanos_f = *serial * 86_400.0 * 1_000_000_000.0;
948                let nanos = nanos_f.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
949                LiteralValue::Duration(chrono::Duration::nanoseconds(nanos))
950            }
951            OverlayValue::Boolean(b) => LiteralValue::Boolean(*b),
952            OverlayValue::Text(s) => LiteralValue::Text((**s).to_string()),
953            OverlayValue::Error(code) => {
954                LiteralValue::Error(ExcelError::new(unmap_error_code(*code)))
955            }
956            OverlayValue::Pending => LiteralValue::Pending,
957        }
958    }
959
960    #[cfg(test)]
961    pub(crate) fn to_literal(&self) -> LiteralValue {
962        self.to_literal_for(crate::engine::DateSystem::Excel1900)
963    }
964}
965
966#[derive(Debug, Clone)]
967pub(crate) enum OverlayScalar<'a> {
968    Borrowed(&'a OverlayValue),
969    Owned(OverlayValue),
970}
971
972impl<'a> OverlayScalar<'a> {
973    #[inline]
974    fn as_value(&self) -> &OverlayValue {
975        match self {
976            OverlayScalar::Borrowed(value) => value,
977            OverlayScalar::Owned(value) => value,
978        }
979    }
980
981    #[inline]
982    pub(crate) fn to_overlay_value(&self) -> OverlayValue {
983        self.as_value().clone()
984    }
985
986    #[inline]
987    pub(crate) fn type_tag(&self) -> TypeTag {
988        self.as_value().type_tag()
989    }
990
991    #[inline]
992    pub(crate) fn numeric_lane_value(&self) -> Option<f64> {
993        self.as_value().numeric_lane_value()
994    }
995
996    #[inline]
997    pub(crate) fn boolean_lane_value(&self) -> Option<bool> {
998        self.as_value().boolean_lane_value()
999    }
1000
1001    #[inline]
1002    pub(crate) fn text_lane_value(&self) -> Option<&str> {
1003        self.as_value().text_lane_value()
1004    }
1005
1006    #[inline]
1007    pub(crate) fn error_lane_value(&self) -> Option<u8> {
1008        self.as_value().error_lane_value()
1009    }
1010
1011    pub(crate) fn lowered_text_value(&self) -> Option<String> {
1012        self.as_value().lowered_text_value()
1013    }
1014
1015    pub(crate) fn to_literal_for(&self, date_system: crate::engine::DateSystem) -> LiteralValue {
1016        self.as_value().to_literal_for(date_system)
1017    }
1018
1019    #[cfg(test)]
1020    pub(crate) fn to_literal(&self) -> LiteralValue {
1021        self.to_literal_for(crate::engine::DateSystem::Excel1900)
1022    }
1023}
1024
1025const OVERLAY_ENTRY_BASE_BYTES: usize = 32;
1026const OVERLAY_FRAGMENT_BASE_BYTES: usize = 48;
1027
1028#[allow(dead_code)]
1029#[derive(Debug, Clone)]
1030pub(crate) struct OverlayFragmentPayload {
1031    type_tags: Arc<UInt8Array>,
1032    numbers: Option<Arc<Float64Array>>,
1033    booleans: Option<Arc<BooleanArray>>,
1034    text: Option<ArrayRef>,
1035    errors: Option<Arc<UInt8Array>>,
1036    estimated_bytes: usize,
1037}
1038
1039impl OverlayFragmentPayload {
1040    fn from_values(values: Vec<OverlayValue>) -> Self {
1041        let len = values.len();
1042        let mut tag_b = UInt8Builder::with_capacity(len);
1043        let mut nb = Float64Builder::with_capacity(len);
1044        let mut bb = BooleanBuilder::with_capacity(len);
1045        let mut sb = StringBuilder::with_capacity(len, len.saturating_mul(8));
1046        let mut eb = UInt8Builder::with_capacity(len);
1047        let mut non_num = 0usize;
1048        let mut non_bool = 0usize;
1049        let mut non_text = 0usize;
1050        let mut non_err = 0usize;
1051
1052        for value in &values {
1053            append_overlay_value_to_lane_builders(
1054                value,
1055                &mut tag_b,
1056                &mut nb,
1057                &mut bb,
1058                &mut sb,
1059                &mut eb,
1060                &mut non_num,
1061                &mut non_bool,
1062                &mut non_text,
1063                &mut non_err,
1064            );
1065        }
1066
1067        let type_tags = Arc::new(tag_b.finish());
1068        let numbers = {
1069            let a = nb.finish();
1070            (non_num > 0).then(|| Arc::new(a))
1071        };
1072        let booleans = {
1073            let a = bb.finish();
1074            (non_bool > 0).then(|| Arc::new(a))
1075        };
1076        let text = {
1077            let a = sb.finish();
1078            (non_text > 0).then(|| Arc::new(a) as ArrayRef)
1079        };
1080        let errors = {
1081            let a = eb.finish();
1082            (non_err > 0).then(|| Arc::new(a))
1083        };
1084
1085        let estimated_bytes = type_tags
1086            .get_array_memory_size()
1087            .saturating_add(
1088                numbers
1089                    .as_ref()
1090                    .map(|a| a.get_array_memory_size())
1091                    .unwrap_or(0),
1092            )
1093            .saturating_add(
1094                booleans
1095                    .as_ref()
1096                    .map(|a| a.get_array_memory_size())
1097                    .unwrap_or(0),
1098            )
1099            .saturating_add(
1100                text.as_ref()
1101                    .map(|a| a.get_array_memory_size())
1102                    .unwrap_or(0),
1103            )
1104            .saturating_add(
1105                errors
1106                    .as_ref()
1107                    .map(|a| a.get_array_memory_size())
1108                    .unwrap_or(0),
1109            );
1110
1111        Self {
1112            type_tags,
1113            numbers,
1114            booleans,
1115            text,
1116            errors,
1117            estimated_bytes,
1118        }
1119    }
1120
1121    fn overlay_value(&self, idx: usize) -> Option<OverlayValue> {
1122        if idx >= self.type_tags.len() || self.type_tags.is_null(idx) {
1123            return None;
1124        }
1125        match TypeTag::from_u8(self.type_tags.value(idx)) {
1126            TypeTag::Empty => Some(OverlayValue::Empty),
1127            TypeTag::Number => Some(OverlayValue::Number(self.number_at(idx)?)),
1128            TypeTag::DateTime => Some(OverlayValue::DateTime(self.number_at(idx)?)),
1129            TypeTag::Duration => Some(OverlayValue::Duration(self.number_at(idx)?)),
1130            TypeTag::Boolean => Some(OverlayValue::Boolean(self.boolean_at(idx)?)),
1131            TypeTag::Text => Some(OverlayValue::Text(Arc::from(self.text_at(idx)?))),
1132            TypeTag::Error => Some(OverlayValue::Error(self.error_at(idx)?)),
1133            TypeTag::Pending => Some(OverlayValue::Pending),
1134        }
1135    }
1136
1137    #[inline]
1138    fn get_scalar(&self, idx: usize) -> Option<OverlayScalar<'_>> {
1139        self.overlay_value(idx).map(OverlayScalar::Owned)
1140    }
1141
1142    #[inline]
1143    fn number_at(&self, idx: usize) -> Option<f64> {
1144        let arr = self.numbers.as_ref()?;
1145        (!arr.is_null(idx)).then(|| arr.value(idx))
1146    }
1147
1148    #[inline]
1149    fn boolean_at(&self, idx: usize) -> Option<bool> {
1150        let arr = self.booleans.as_ref()?;
1151        (!arr.is_null(idx)).then(|| arr.value(idx))
1152    }
1153
1154    #[inline]
1155    fn text_at(&self, idx: usize) -> Option<&str> {
1156        let arr = self.text.as_ref()?;
1157        let arr = arr.as_any().downcast_ref::<StringArray>()?;
1158        (!arr.is_null(idx)).then(|| arr.value(idx))
1159    }
1160
1161    #[inline]
1162    fn error_at(&self, idx: usize) -> Option<u8> {
1163        let arr = self.errors.as_ref()?;
1164        (!arr.is_null(idx)).then(|| arr.value(idx))
1165    }
1166
1167    #[inline]
1168    fn values_slice(&self, start: usize, len: usize) -> Vec<OverlayValue> {
1169        (start..start.saturating_add(len))
1170            .filter_map(|idx| self.overlay_value(idx))
1171            .collect()
1172    }
1173
1174    #[inline]
1175    fn estimated_bytes(&self) -> usize {
1176        self.estimated_bytes
1177    }
1178}
1179#[derive(Debug, Clone)]
1180pub(crate) enum OverlayFragment {
1181    SparseOffsets {
1182        offsets: Vec<u32>,
1183        payload: OverlayFragmentPayload,
1184    },
1185    DenseRange {
1186        start: u32,
1187        len: u32,
1188        payload: OverlayFragmentPayload,
1189    },
1190    RunRange {
1191        start: u32,
1192        len: u32,
1193        run_ends: Vec<u32>,
1194        payload: OverlayFragmentPayload,
1195    },
1196}
1197
1198impl OverlayFragment {
1199    const MAX_SPLIT_SEGMENTS_BEFORE_SPARSE_FALLBACK: usize = 128;
1200
1201    pub(crate) fn sparse_offsets(items: Vec<(usize, OverlayValue)>) -> Option<Self> {
1202        let mut by_offset: BTreeMap<usize, OverlayValue> = BTreeMap::new();
1203        for (offset, value) in items {
1204            by_offset.insert(offset, value);
1205        }
1206        if by_offset.is_empty() {
1207            return None;
1208        }
1209
1210        let mut offsets = Vec::with_capacity(by_offset.len());
1211        let mut values = Vec::with_capacity(by_offset.len());
1212        for (offset, value) in by_offset {
1213            offsets.push(u32::try_from(offset).expect("overlay offset fits in u32"));
1214            values.push(value);
1215        }
1216
1217        Some(Self::SparseOffsets {
1218            offsets,
1219            payload: OverlayFragmentPayload::from_values(values),
1220        })
1221    }
1222
1223    pub(crate) fn sparse_offsets_if_estimated_smaller_than_points(
1224        items: Vec<(usize, OverlayValue)>,
1225        point_estimate: usize,
1226    ) -> Option<Result<Self, Vec<(usize, OverlayValue)>>> {
1227        let fragment = Self::sparse_offsets(items)?;
1228        if fragment.estimated_bytes() < point_estimate {
1229            Some(Ok(fragment))
1230        } else {
1231            Some(Err(fragment.cells()))
1232        }
1233    }
1234
1235    pub(crate) fn dense_range(start: usize, values: Vec<OverlayValue>) -> Option<Self> {
1236        let len = values.len();
1237        if len == 0 {
1238            return None;
1239        }
1240        Some(Self::DenseRange {
1241            start: u32::try_from(start).expect("overlay start fits in u32"),
1242            len: u32::try_from(len).expect("overlay length fits in u32"),
1243            payload: OverlayFragmentPayload::from_values(values),
1244        })
1245    }
1246
1247    pub(crate) fn run_range(start: usize, values: Vec<OverlayValue>) -> Option<Self> {
1248        if values.is_empty() {
1249            return None;
1250        }
1251
1252        let mut run_ends = Vec::new();
1253        let mut run_values = Vec::new();
1254        let mut current = values[0].clone();
1255        for (idx, value) in values.iter().enumerate().skip(1) {
1256            if *value != current {
1257                run_ends.push(idx);
1258                run_values.push(current);
1259                current = value.clone();
1260            }
1261        }
1262        run_ends.push(values.len());
1263        run_values.push(current);
1264
1265        Self::run_range_from_parts(start, values.len(), run_ends, run_values)
1266    }
1267
1268    fn run_range_from_parts(
1269        start: usize,
1270        len: usize,
1271        run_ends: Vec<usize>,
1272        values: Vec<OverlayValue>,
1273    ) -> Option<Self> {
1274        if len == 0 || run_ends.is_empty() || run_ends.len() != values.len() {
1275            return None;
1276        }
1277
1278        let mut merged_ends: Vec<u32> = Vec::with_capacity(run_ends.len());
1279        let mut merged_values: Vec<OverlayValue> = Vec::with_capacity(values.len());
1280        let mut prev_end = 0usize;
1281        for (end, value) in run_ends.into_iter().zip(values.into_iter()) {
1282            if end <= prev_end || end > len {
1283                return None;
1284            }
1285            if merged_values.last().is_some_and(|last| *last == value) {
1286                if let Some(last_end) = merged_ends.last_mut() {
1287                    *last_end = u32::try_from(end).expect("run end fits in u32");
1288                }
1289            } else {
1290                merged_ends.push(u32::try_from(end).expect("run end fits in u32"));
1291                merged_values.push(value);
1292            }
1293            prev_end = end;
1294        }
1295
1296        if prev_end != len || merged_ends.last().copied() != Some(len as u32) {
1297            return None;
1298        }
1299
1300        Some(Self::RunRange {
1301            start: u32::try_from(start).expect("overlay start fits in u32"),
1302            len: u32::try_from(len).expect("overlay length fits in u32"),
1303            run_ends: merged_ends,
1304            payload: OverlayFragmentPayload::from_values(merged_values),
1305        })
1306    }
1307
1308    #[inline]
1309    fn estimated_bytes(&self) -> usize {
1310        match self {
1311            OverlayFragment::SparseOffsets { offsets, payload } => OVERLAY_FRAGMENT_BASE_BYTES
1312                .saturating_add(offsets.len().saturating_mul(core::mem::size_of::<u32>()))
1313                .saturating_add(payload.estimated_bytes()),
1314            OverlayFragment::DenseRange { payload, .. } => {
1315                OVERLAY_FRAGMENT_BASE_BYTES.saturating_add(payload.estimated_bytes())
1316            }
1317            OverlayFragment::RunRange {
1318                run_ends, payload, ..
1319            } => OVERLAY_FRAGMENT_BASE_BYTES
1320                .saturating_add(run_ends.len().saturating_mul(core::mem::size_of::<u32>()))
1321                .saturating_add(payload.estimated_bytes()),
1322        }
1323    }
1324
1325    #[inline]
1326    fn coverage_len(&self) -> usize {
1327        match self {
1328            OverlayFragment::SparseOffsets { offsets, .. } => offsets.len(),
1329            OverlayFragment::DenseRange { len, .. } | OverlayFragment::RunRange { len, .. } => {
1330                *len as usize
1331            }
1332        }
1333    }
1334
1335    pub(crate) fn max_covered_offset(&self) -> usize {
1336        match self {
1337            OverlayFragment::SparseOffsets { offsets, .. } => {
1338                offsets.iter().copied().max().unwrap_or(0) as usize
1339            }
1340            OverlayFragment::DenseRange { start, len, .. }
1341            | OverlayFragment::RunRange { start, len, .. } => (*start as usize)
1342                .saturating_add(*len as usize)
1343                .saturating_sub(1),
1344        }
1345    }
1346
1347    fn interval_coverage(&self) -> Option<core::ops::Range<usize>> {
1348        match self {
1349            OverlayFragment::DenseRange { start, len, .. }
1350            | OverlayFragment::RunRange { start, len, .. } => {
1351                let start = *start as usize;
1352                Some(start..start.saturating_add(*len as usize))
1353            }
1354            OverlayFragment::SparseOffsets { .. } => None,
1355        }
1356    }
1357
1358    fn sparse_offsets_slice(&self) -> Option<&[u32]> {
1359        match self {
1360            OverlayFragment::SparseOffsets { offsets, .. } => Some(offsets.as_slice()),
1361            _ => None,
1362        }
1363    }
1364
1365    fn has_any_in_range(&self, range: core::ops::Range<usize>) -> bool {
1366        if range.is_empty() {
1367            return false;
1368        }
1369        match self {
1370            OverlayFragment::SparseOffsets { offsets, .. } => {
1371                let start = u32::try_from(range.start).unwrap_or(u32::MAX);
1372                let idx = offsets.partition_point(|off| *off < start);
1373                offsets
1374                    .get(idx)
1375                    .is_some_and(|off| (*off as usize) < range.end)
1376            }
1377            OverlayFragment::DenseRange { .. } | OverlayFragment::RunRange { .. } => self
1378                .interval_coverage()
1379                .is_some_and(|r| r.start < range.end && range.start < r.end),
1380        }
1381    }
1382
1383    fn intersects_fragment_exact(&self, replacement: &OverlayFragment) -> bool {
1384        if let Some(offsets) = replacement.sparse_offsets_slice() {
1385            self.intersects_sparse_offsets(offsets)
1386        } else if let Some(range) = replacement.interval_coverage() {
1387            self.intersects_interval(range)
1388        } else {
1389            false
1390        }
1391    }
1392
1393    fn intersects_interval(&self, range: core::ops::Range<usize>) -> bool {
1394        if range.is_empty() {
1395            return false;
1396        }
1397        match self {
1398            OverlayFragment::SparseOffsets { offsets, .. } => {
1399                let start = u32::try_from(range.start).unwrap_or(u32::MAX);
1400                let idx = offsets.partition_point(|off| *off < start);
1401                offsets
1402                    .get(idx)
1403                    .is_some_and(|off| (*off as usize) < range.end)
1404            }
1405            OverlayFragment::DenseRange { .. } | OverlayFragment::RunRange { .. } => self
1406                .interval_coverage()
1407                .is_some_and(|own| own.start < range.end && range.start < own.end),
1408        }
1409    }
1410
1411    fn intersects_sparse_offsets(&self, replacement_offsets: &[u32]) -> bool {
1412        if replacement_offsets.is_empty() {
1413            return false;
1414        }
1415        match self {
1416            OverlayFragment::SparseOffsets { offsets, .. } => {
1417                Self::sorted_offsets_intersect(offsets, replacement_offsets)
1418            }
1419            OverlayFragment::DenseRange { .. } | OverlayFragment::RunRange { .. } => {
1420                self.interval_coverage().is_some_and(|range| {
1421                    let start = u32::try_from(range.start).unwrap_or(u32::MAX);
1422                    let idx = replacement_offsets.partition_point(|off| *off < start);
1423                    replacement_offsets
1424                        .get(idx)
1425                        .is_some_and(|off| (*off as usize) < range.end)
1426                })
1427            }
1428        }
1429    }
1430
1431    fn sorted_offsets_intersect(a: &[u32], b: &[u32]) -> bool {
1432        let mut ai = 0usize;
1433        let mut bi = 0usize;
1434        while ai < a.len() && bi < b.len() {
1435            match a[ai].cmp(&b[bi]) {
1436                core::cmp::Ordering::Equal => return true,
1437                core::cmp::Ordering::Less => ai += 1,
1438                core::cmp::Ordering::Greater => bi += 1,
1439            }
1440        }
1441        false
1442    }
1443
1444    fn covers_offset(&self, off: usize) -> bool {
1445        self.get_scalar(off).is_some()
1446    }
1447
1448    fn get_scalar(&self, off: usize) -> Option<OverlayScalar<'_>> {
1449        match self {
1450            OverlayFragment::SparseOffsets { offsets, payload } => {
1451                let off = u32::try_from(off).ok()?;
1452                let idx = offsets.binary_search(&off).ok()?;
1453                payload.get_scalar(idx)
1454            }
1455            OverlayFragment::DenseRange {
1456                start,
1457                len,
1458                payload,
1459            } => {
1460                let start = *start as usize;
1461                let rel = off.checked_sub(start)?;
1462                if rel >= *len as usize {
1463                    return None;
1464                }
1465                payload.get_scalar(rel)
1466            }
1467            OverlayFragment::RunRange {
1468                start,
1469                len,
1470                run_ends,
1471                payload,
1472            } => {
1473                let start = *start as usize;
1474                let rel = off.checked_sub(start)?;
1475                if rel >= *len as usize {
1476                    return None;
1477                }
1478                let rel_u32 = u32::try_from(rel).ok()?;
1479                let run_idx = run_ends.partition_point(|end| *end <= rel_u32);
1480                payload.get_scalar(run_idx)
1481            }
1482        }
1483    }
1484
1485    fn subtract_fragment(&self, replacement: &OverlayFragment) -> Vec<OverlayFragment> {
1486        if let Some(offsets) = replacement.sparse_offsets_slice() {
1487            self.subtract_sparse_offsets(offsets)
1488        } else if let Some(range) = replacement.interval_coverage() {
1489            self.subtract_interval(range)
1490        } else {
1491            vec![self.clone()]
1492        }
1493    }
1494
1495    fn subtract_offset(&self, off: usize) -> Vec<OverlayFragment> {
1496        match self {
1497            OverlayFragment::SparseOffsets { .. } => {
1498                let Ok(off) = u32::try_from(off) else {
1499                    return vec![self.clone()];
1500                };
1501                self.subtract_sparse_offsets(core::slice::from_ref(&off))
1502            }
1503            OverlayFragment::DenseRange { .. } | OverlayFragment::RunRange { .. } => {
1504                self.subtract_interval(off..off.saturating_add(1))
1505            }
1506        }
1507    }
1508
1509    fn subtract_interval(&self, replacement: core::ops::Range<usize>) -> Vec<OverlayFragment> {
1510        if replacement.is_empty() {
1511            return vec![self.clone()];
1512        }
1513
1514        match self {
1515            OverlayFragment::SparseOffsets { offsets, payload } => {
1516                let cells: Vec<_> = offsets
1517                    .iter()
1518                    .enumerate()
1519                    .filter_map(|(idx, off)| {
1520                        let off_usize = *off as usize;
1521                        (!replacement.contains(&off_usize))
1522                            .then(|| payload.overlay_value(idx).map(|value| (off_usize, value)))?
1523                    })
1524                    .collect();
1525                OverlayFragment::sparse_offsets(cells).into_iter().collect()
1526            }
1527            OverlayFragment::DenseRange { .. } => {
1528                let Some(own) = self.interval_coverage() else {
1529                    return vec![self.clone()];
1530                };
1531                if own.end <= replacement.start || replacement.end <= own.start {
1532                    return vec![self.clone()];
1533                }
1534                let cut_start = replacement.start.max(own.start);
1535                let cut_end = replacement.end.min(own.end);
1536                let mut out = Vec::with_capacity(2);
1537                if own.start < cut_start
1538                    && let Some(left) =
1539                        self.dense_segment_with_start(own.start, own.start, cut_start)
1540                {
1541                    out.push(left);
1542                }
1543                if cut_end < own.end
1544                    && let Some(right) = self.dense_segment_with_start(cut_end, cut_end, own.end)
1545                {
1546                    out.push(right);
1547                }
1548                out
1549            }
1550            OverlayFragment::RunRange { .. } => {
1551                let Some(own) = self.interval_coverage() else {
1552                    return vec![self.clone()];
1553                };
1554                if own.end <= replacement.start || replacement.end <= own.start {
1555                    return vec![self.clone()];
1556                }
1557                let cut_start = replacement.start.max(own.start);
1558                let cut_end = replacement.end.min(own.end);
1559                let mut out = Vec::with_capacity(2);
1560                if own.start < cut_start
1561                    && let Some(left) = self.run_segment_with_start(own.start, own.start, cut_start)
1562                {
1563                    out.push(left);
1564                }
1565                if cut_end < own.end
1566                    && let Some(right) = self.run_segment_with_start(cut_end, cut_end, own.end)
1567                {
1568                    out.push(right);
1569                }
1570                out
1571            }
1572        }
1573    }
1574
1575    fn subtract_sparse_offsets(&self, replacement_offsets: &[u32]) -> Vec<OverlayFragment> {
1576        if replacement_offsets.is_empty() {
1577            return vec![self.clone()];
1578        }
1579
1580        match self {
1581            OverlayFragment::SparseOffsets { offsets, payload } => {
1582                let cells: Vec<_> = offsets
1583                    .iter()
1584                    .enumerate()
1585                    .filter_map(|(idx, off)| {
1586                        replacement_offsets.binary_search(off).is_err().then(|| {
1587                            payload
1588                                .overlay_value(idx)
1589                                .map(|value| (*off as usize, value))
1590                        })?
1591                    })
1592                    .collect();
1593                OverlayFragment::sparse_offsets(cells).into_iter().collect()
1594            }
1595            OverlayFragment::DenseRange { .. } => {
1596                self.subtract_sparse_offsets_from_dense(replacement_offsets)
1597            }
1598            OverlayFragment::RunRange { .. } => {
1599                self.subtract_sparse_offsets_from_run(replacement_offsets)
1600            }
1601        }
1602    }
1603
1604    fn sparse_holes_in_interval(offsets: &[u32], range: core::ops::Range<usize>) -> Vec<usize> {
1605        if range.is_empty() {
1606            return Vec::new();
1607        }
1608        let start = u32::try_from(range.start).unwrap_or(u32::MAX);
1609        let mut idx = offsets.partition_point(|off| *off < start);
1610        let mut holes = Vec::new();
1611        let mut last = None;
1612        while let Some(off) = offsets.get(idx).copied() {
1613            let off_usize = off as usize;
1614            if off_usize >= range.end {
1615                break;
1616            }
1617            if last != Some(off_usize) {
1618                holes.push(off_usize);
1619                last = Some(off_usize);
1620            }
1621            idx += 1;
1622        }
1623        holes
1624    }
1625
1626    fn subtract_sparse_offsets_from_dense(
1627        &self,
1628        replacement_offsets: &[u32],
1629    ) -> Vec<OverlayFragment> {
1630        let Some(own) = self.interval_coverage() else {
1631            return vec![self.clone()];
1632        };
1633        let holes = Self::sparse_holes_in_interval(replacement_offsets, own.clone());
1634        if holes.is_empty() {
1635            return vec![self.clone()];
1636        }
1637        if holes.len().saturating_add(1) > Self::MAX_SPLIT_SEGMENTS_BEFORE_SPARSE_FALLBACK {
1638            return self.sparse_remainder_excluding_offsets(&holes);
1639        }
1640
1641        let mut out = Vec::with_capacity(holes.len().saturating_add(1));
1642        let mut seg_start = own.start;
1643        for hole in holes {
1644            if seg_start < hole
1645                && let Some(segment) = self.dense_segment_with_start(seg_start, seg_start, hole)
1646            {
1647                out.push(segment);
1648            }
1649            seg_start = hole.saturating_add(1);
1650        }
1651        if seg_start < own.end
1652            && let Some(segment) = self.dense_segment_with_start(seg_start, seg_start, own.end)
1653        {
1654            out.push(segment);
1655        }
1656        out
1657    }
1658
1659    fn subtract_sparse_offsets_from_run(
1660        &self,
1661        replacement_offsets: &[u32],
1662    ) -> Vec<OverlayFragment> {
1663        let Some(own) = self.interval_coverage() else {
1664            return vec![self.clone()];
1665        };
1666        let holes = Self::sparse_holes_in_interval(replacement_offsets, own.clone());
1667        if holes.is_empty() {
1668            return vec![self.clone()];
1669        }
1670        if holes.len().saturating_add(1) > Self::MAX_SPLIT_SEGMENTS_BEFORE_SPARSE_FALLBACK {
1671            return self.sparse_remainder_excluding_offsets(&holes);
1672        }
1673
1674        let mut out = Vec::with_capacity(holes.len().saturating_add(1));
1675        let mut seg_start = own.start;
1676        for hole in holes {
1677            if seg_start < hole
1678                && let Some(segment) = self.run_segment_with_start(seg_start, seg_start, hole)
1679            {
1680                out.push(segment);
1681            }
1682            seg_start = hole.saturating_add(1);
1683        }
1684        if seg_start < own.end
1685            && let Some(segment) = self.run_segment_with_start(seg_start, seg_start, own.end)
1686        {
1687            out.push(segment);
1688        }
1689        out
1690    }
1691
1692    fn sparse_remainder_excluding_offsets(&self, sorted_holes: &[usize]) -> Vec<OverlayFragment> {
1693        let cells: Vec<_> = self
1694            .cells()
1695            .into_iter()
1696            .filter(|(off, _)| sorted_holes.binary_search(off).is_err())
1697            .collect();
1698        OverlayFragment::sparse_offsets(cells).into_iter().collect()
1699    }
1700
1701    fn dense_segment_with_start(
1702        &self,
1703        new_start: usize,
1704        abs_start: usize,
1705        abs_end: usize,
1706    ) -> Option<OverlayFragment> {
1707        match self {
1708            OverlayFragment::DenseRange { start, payload, .. } => {
1709                if abs_start >= abs_end {
1710                    return None;
1711                }
1712                let base = *start as usize;
1713                let rel_start = abs_start.checked_sub(base)?;
1714                let len = abs_end.saturating_sub(abs_start);
1715                OverlayFragment::dense_range(new_start, payload.values_slice(rel_start, len))
1716            }
1717            _ => None,
1718        }
1719    }
1720
1721    fn run_segment_with_start(
1722        &self,
1723        new_start: usize,
1724        abs_start: usize,
1725        abs_end: usize,
1726    ) -> Option<OverlayFragment> {
1727        let OverlayFragment::RunRange {
1728            start,
1729            len,
1730            run_ends,
1731            payload,
1732        } = self
1733        else {
1734            return None;
1735        };
1736        if abs_start >= abs_end {
1737            return None;
1738        }
1739        let base = *start as usize;
1740        let frag_end = base.saturating_add(*len as usize);
1741        if abs_start < base || abs_end > frag_end {
1742            return None;
1743        }
1744
1745        let rel_start = abs_start - base;
1746        let rel_end = abs_end - base;
1747        let mut new_run_ends = Vec::new();
1748        let mut new_values = Vec::new();
1749        let mut prev_end = 0usize;
1750
1751        for (run_idx, end) in run_ends.iter().enumerate() {
1752            let run_start = prev_end;
1753            let run_end = *end as usize;
1754            let inter_start = run_start.max(rel_start);
1755            let inter_end = run_end.min(rel_end);
1756            if inter_start < inter_end {
1757                new_run_ends.push(inter_end - rel_start);
1758                if let Some(value) = payload.overlay_value(run_idx) {
1759                    new_values.push(value);
1760                }
1761            }
1762            prev_end = run_end;
1763            if prev_end >= rel_end {
1764                break;
1765            }
1766        }
1767
1768        OverlayFragment::run_range_from_parts(
1769            new_start,
1770            abs_end.saturating_sub(abs_start),
1771            new_run_ends,
1772            new_values,
1773        )
1774    }
1775
1776    fn cells(&self) -> Vec<(usize, OverlayValue)> {
1777        match self {
1778            OverlayFragment::SparseOffsets { offsets, payload } => offsets
1779                .iter()
1780                .enumerate()
1781                .filter_map(|(idx, off)| {
1782                    payload
1783                        .overlay_value(idx)
1784                        .map(|value| (*off as usize, value))
1785                })
1786                .collect(),
1787            OverlayFragment::DenseRange {
1788                start,
1789                len,
1790                payload,
1791            } => {
1792                let start = *start as usize;
1793                (0..*len as usize)
1794                    .filter_map(|idx| {
1795                        payload
1796                            .overlay_value(idx)
1797                            .map(|value| (start.saturating_add(idx), value))
1798                    })
1799                    .collect()
1800            }
1801            OverlayFragment::RunRange { start, len, .. } => {
1802                let start = *start as usize;
1803                (0..*len as usize)
1804                    .filter_map(|idx| {
1805                        self.get_scalar(start.saturating_add(idx))
1806                            .map(|value| (start.saturating_add(idx), value.to_overlay_value()))
1807                    })
1808                    .collect()
1809            }
1810        }
1811    }
1812
1813    fn slice(&self, off: usize, len: usize) -> Option<OverlayFragment> {
1814        let end = off.saturating_add(len);
1815        if len == 0 {
1816            return None;
1817        }
1818
1819        match self {
1820            OverlayFragment::SparseOffsets { offsets, payload } => {
1821                let start = u32::try_from(off).unwrap_or(u32::MAX);
1822                let lo = offsets.partition_point(|candidate| *candidate < start);
1823                let hi = offsets.partition_point(|candidate| (*candidate as usize) < end);
1824                let cells: Vec<_> = (lo..hi)
1825                    .filter_map(|idx| {
1826                        let rebased = (offsets[idx] as usize).saturating_sub(off);
1827                        payload.overlay_value(idx).map(|value| (rebased, value))
1828                    })
1829                    .collect();
1830                OverlayFragment::sparse_offsets(cells)
1831            }
1832            OverlayFragment::DenseRange { .. } => {
1833                let own = self.interval_coverage()?;
1834                let seg_start = own.start.max(off);
1835                let seg_end = own.end.min(end);
1836                if seg_start >= seg_end {
1837                    return None;
1838                }
1839                self.dense_segment_with_start(seg_start - off, seg_start, seg_end)
1840            }
1841            OverlayFragment::RunRange { .. } => {
1842                let own = self.interval_coverage()?;
1843                let seg_start = own.start.max(off);
1844                let seg_end = own.end.min(end);
1845                if seg_start >= seg_end {
1846                    return None;
1847                }
1848                self.run_segment_with_start(seg_start - off, seg_start, seg_end)
1849            }
1850        }
1851    }
1852}
1853#[derive(Debug, Default, Clone)]
1854pub struct Overlay {
1855    points: HashMap<usize, OverlayValue>,
1856    fragments: Vec<OverlayFragment>,
1857    // Deterministic (and intentionally approximate) accounting of overlay memory.
1858    // This is used for budget enforcement/observability; it does not attempt to reflect
1859    // the allocator's exact overhead.
1860    estimated_bytes: usize,
1861}
1862
1863impl Overlay {
1864    // Deterministic estimate per entry to keep budget enforcement stable across platforms.
1865    // Includes key + map/node overhead (approx) and value payload bytes.
1866    const ENTRY_BASE_BYTES: usize = OVERLAY_ENTRY_BASE_BYTES;
1867
1868    pub fn new() -> Self {
1869        Self {
1870            points: HashMap::new(),
1871            fragments: Vec::new(),
1872            estimated_bytes: 0,
1873        }
1874    }
1875
1876    #[inline]
1877    fn point_estimate(v: &OverlayValue) -> usize {
1878        Self::ENTRY_BASE_BYTES + v.estimated_payload_bytes()
1879    }
1880
1881    #[inline]
1882    fn adjust_estimated_bytes(&mut self, delta: isize) {
1883        if delta >= 0 {
1884            self.estimated_bytes = self.estimated_bytes.saturating_add(delta as usize);
1885        } else {
1886            self.estimated_bytes = self.estimated_bytes.saturating_sub((-delta) as usize);
1887        }
1888    }
1889
1890    #[inline]
1891    pub(crate) fn get_scalar(&self, off: usize) -> Option<OverlayScalar<'_>> {
1892        self.points
1893            .get(&off)
1894            .map(OverlayScalar::Borrowed)
1895            .or_else(|| self.fragments.iter().rev().find_map(|f| f.get_scalar(off)))
1896    }
1897
1898    #[inline]
1899    pub fn get(&self, off: usize) -> Option<OverlayValue> {
1900        self.get_scalar(off).map(|value| value.to_overlay_value())
1901    }
1902
1903    #[inline]
1904    pub(crate) fn set_scalar(&mut self, off: usize, v: OverlayValue) -> isize {
1905        let removed = self.remove_scalar(off);
1906        let new_est = Self::point_estimate(&v);
1907        self.points.insert(off, v);
1908        self.adjust_estimated_bytes(new_est as isize);
1909        removed.saturating_add(new_est as isize)
1910    }
1911
1912    #[inline]
1913    pub fn set(&mut self, off: usize, v: OverlayValue) -> isize {
1914        self.set_scalar(off, v)
1915    }
1916
1917    pub(crate) fn apply_fragment(&mut self, fragment: OverlayFragment) -> isize {
1918        let mut delta = self.remove_points_covered_by_fragment(&fragment);
1919        delta = delta.saturating_add(self.remove_fragments_covered_by_fragment(&fragment));
1920
1921        let fragment_est = fragment.estimated_bytes();
1922        self.fragments.push(fragment);
1923        self.adjust_estimated_bytes(fragment_est as isize);
1924        delta.saturating_add(fragment_est as isize)
1925    }
1926
1927    fn remove_points_covered_by_fragment(&mut self, fragment: &OverlayFragment) -> isize {
1928        let mut removed = 0usize;
1929        match fragment {
1930            OverlayFragment::SparseOffsets { offsets, .. } => {
1931                for off in offsets.iter().copied() {
1932                    if let Some(old) = self.points.remove(&(off as usize)) {
1933                        removed = removed.saturating_add(Self::point_estimate(&old));
1934                    }
1935                }
1936            }
1937            OverlayFragment::DenseRange { .. } | OverlayFragment::RunRange { .. } => {
1938                if let Some(range) = fragment.interval_coverage() {
1939                    let keys: Vec<_> = self
1940                        .points
1941                        .keys()
1942                        .copied()
1943                        .filter(|off| range.contains(off))
1944                        .collect();
1945                    for off in keys {
1946                        if let Some(old) = self.points.remove(&off) {
1947                            removed = removed.saturating_add(Self::point_estimate(&old));
1948                        }
1949                    }
1950                }
1951            }
1952        }
1953        self.estimated_bytes = self.estimated_bytes.saturating_sub(removed);
1954        -(removed as isize)
1955    }
1956
1957    fn remove_fragments_covered_by_fragment(&mut self, replacement: &OverlayFragment) -> isize {
1958        if self.fragments.is_empty() {
1959            return 0;
1960        }
1961
1962        let mut delta: isize = 0;
1963        let mut fragments = Vec::with_capacity(self.fragments.len());
1964        for fragment in self.fragments.drain(..) {
1965            if !fragment.intersects_fragment_exact(replacement) {
1966                fragments.push(fragment);
1967                continue;
1968            }
1969
1970            let old_est = fragment.estimated_bytes();
1971            let replacements = fragment.subtract_fragment(replacement);
1972            let new_est = replacements
1973                .iter()
1974                .map(OverlayFragment::estimated_bytes)
1975                .fold(0usize, usize::saturating_add);
1976            fragments.extend(replacements);
1977            delta = delta.saturating_add(new_est as isize - old_est as isize);
1978        }
1979        self.fragments = fragments;
1980        self.adjust_estimated_bytes(delta);
1981        delta
1982    }
1983
1984    #[inline]
1985    pub(crate) fn remove_scalar(&mut self, off: usize) -> isize {
1986        let mut delta = 0isize;
1987        if let Some(old) = self.points.remove(&off) {
1988            let old_est = Self::point_estimate(&old);
1989            self.estimated_bytes = self.estimated_bytes.saturating_sub(old_est);
1990            delta = delta.saturating_sub(old_est as isize);
1991        }
1992
1993        if !self.fragments.is_empty() {
1994            let mut fragments = Vec::with_capacity(self.fragments.len());
1995            for fragment in self.fragments.drain(..) {
1996                if fragment.get_scalar(off).is_none() {
1997                    fragments.push(fragment);
1998                    continue;
1999                }
2000
2001                let old_est = fragment.estimated_bytes();
2002                let replacements = fragment.subtract_offset(off);
2003                let new_est = replacements
2004                    .iter()
2005                    .map(OverlayFragment::estimated_bytes)
2006                    .fold(0usize, usize::saturating_add);
2007                fragments.extend(replacements);
2008                delta = delta.saturating_add(new_est as isize - old_est as isize);
2009            }
2010            self.fragments = fragments;
2011            self.adjust_estimated_bytes(delta);
2012        }
2013
2014        delta
2015    }
2016
2017    #[inline]
2018    pub fn remove(&mut self, off: usize) -> isize {
2019        self.remove_scalar(off)
2020    }
2021
2022    pub(crate) fn remove_range(&mut self, range: core::ops::Range<usize>) -> isize {
2023        if range.is_empty() {
2024            return 0;
2025        }
2026
2027        let mut delta = 0isize;
2028        let removed_points: Vec<_> = self
2029            .points
2030            .keys()
2031            .copied()
2032            .filter(|off| range.contains(off))
2033            .collect();
2034        for off in removed_points {
2035            if let Some(old) = self.points.remove(&off) {
2036                let old_est = Self::point_estimate(&old);
2037                self.estimated_bytes = self.estimated_bytes.saturating_sub(old_est);
2038                delta = delta.saturating_sub(old_est as isize);
2039            }
2040        }
2041
2042        if !self.fragments.is_empty() {
2043            let mut fragment_delta = 0isize;
2044            let mut fragments = Vec::with_capacity(self.fragments.len());
2045            for fragment in self.fragments.drain(..) {
2046                let old_est = fragment.estimated_bytes();
2047                let replacements = fragment.subtract_interval(range.clone());
2048                let new_est = replacements
2049                    .iter()
2050                    .map(OverlayFragment::estimated_bytes)
2051                    .fold(0usize, usize::saturating_add);
2052                fragments.extend(replacements);
2053                fragment_delta = fragment_delta.saturating_add(new_est as isize - old_est as isize);
2054            }
2055            self.fragments = fragments;
2056            self.adjust_estimated_bytes(fragment_delta);
2057            delta = delta.saturating_add(fragment_delta);
2058        }
2059
2060        delta
2061    }
2062
2063    #[inline]
2064    pub(crate) fn clear_all(&mut self) -> usize {
2065        let freed = self.estimated_bytes;
2066        self.points.clear();
2067        self.fragments.clear();
2068        self.estimated_bytes = 0;
2069        freed
2070    }
2071
2072    #[inline]
2073    pub fn clear(&mut self) -> usize {
2074        self.clear_all()
2075    }
2076
2077    #[inline]
2078    pub fn len(&self) -> usize {
2079        self.points.len().saturating_add(
2080            self.fragments
2081                .iter()
2082                .map(OverlayFragment::coverage_len)
2083                .sum(),
2084        )
2085    }
2086
2087    #[inline]
2088    pub fn estimated_bytes(&self) -> usize {
2089        self.estimated_bytes
2090    }
2091
2092    #[inline]
2093    pub fn is_empty(&self) -> bool {
2094        self.points.is_empty() && self.fragments.is_empty()
2095    }
2096
2097    #[inline]
2098    pub(crate) fn has_any_in_range(&self, range: core::ops::Range<usize>) -> bool {
2099        self.points.keys().any(|k| range.contains(k))
2100            || self
2101                .fragments
2102                .iter()
2103                .any(|fragment| fragment.has_any_in_range(range.clone()))
2104    }
2105
2106    #[inline]
2107    pub fn any_in_range(&self, range: core::ops::Range<usize>) -> bool {
2108        self.has_any_in_range(range)
2109    }
2110
2111    pub(crate) fn slice(&self, off: usize, len: usize) -> Overlay {
2112        let mut out = Overlay::new();
2113        let end = off.saturating_add(len);
2114        for fragment in &self.fragments {
2115            if let Some(sliced) = fragment.slice(off, len) {
2116                let _ = out.apply_fragment(sliced);
2117            }
2118        }
2119        for (k, v) in self.points.iter() {
2120            if *k >= off && *k < end {
2121                let _ = out.set_scalar(*k - off, v.clone());
2122            }
2123        }
2124        out
2125    }
2126
2127    /// Iterate over logical `(offset, value)` pairs in the overlay.
2128    pub fn iter(&self) -> impl Iterator<Item = (usize, OverlayValue)> {
2129        let mut cells = BTreeMap::new();
2130        for fragment in &self.fragments {
2131            for (off, value) in fragment.cells() {
2132                cells.insert(off, value);
2133            }
2134        }
2135        for (off, value) in &self.points {
2136            cells.insert(*off, value.clone());
2137        }
2138        cells.into_iter()
2139    }
2140
2141    /// Iterate over physical point entries only.
2142    pub(crate) fn iter_points(&self) -> impl Iterator<Item = (&usize, &OverlayValue)> {
2143        self.points.iter()
2144    }
2145}
2146
2147#[cfg(test)]
2148#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
2149pub(crate) struct OverlayDebugStats {
2150    pub(crate) points: usize,
2151    pub(crate) sparse_fragments: usize,
2152    pub(crate) dense_fragments: usize,
2153    pub(crate) run_fragments: usize,
2154    pub(crate) covered_len: usize,
2155}
2156
2157#[cfg(test)]
2158impl Overlay {
2159    pub(crate) fn debug_stats(&self) -> OverlayDebugStats {
2160        let mut stats = OverlayDebugStats {
2161            points: self.points.len(),
2162            covered_len: self.len(),
2163            ..OverlayDebugStats::default()
2164        };
2165        for fragment in &self.fragments {
2166            match fragment {
2167                OverlayFragment::SparseOffsets { .. } => stats.sparse_fragments += 1,
2168                OverlayFragment::DenseRange { .. } => stats.dense_fragments += 1,
2169                OverlayFragment::RunRange { .. } => stats.run_fragments += 1,
2170            }
2171        }
2172        stats
2173    }
2174
2175    pub(crate) fn debug_is_normalized(&self) -> bool {
2176        let mut covered = std::collections::HashSet::new();
2177        for off in self.points.keys().copied() {
2178            if !covered.insert(off) {
2179                return false;
2180            }
2181        }
2182        for fragment in &self.fragments {
2183            for (off, _) in fragment.cells() {
2184                if !covered.insert(off) {
2185                    return false;
2186                }
2187            }
2188        }
2189        covered.len() == self.len()
2190    }
2191
2192    pub(crate) fn debug_recomputed_estimated_bytes(&self) -> usize {
2193        let point_bytes = self
2194            .points
2195            .values()
2196            .map(Self::point_estimate)
2197            .fold(0usize, usize::saturating_add);
2198        let fragment_bytes = self
2199            .fragments
2200            .iter()
2201            .map(OverlayFragment::estimated_bytes)
2202            .fold(0usize, usize::saturating_add);
2203        point_bytes.saturating_add(fragment_bytes)
2204    }
2205}
2206
2207#[derive(Debug, Clone, Copy, Default)]
2208#[cfg_attr(test, derive(serde::Serialize))]
2209pub(crate) struct OverlaySelectStats {
2210    pub(crate) zip_select_calls: usize,
2211    pub(crate) direct_dense_slices: usize,
2212    pub(crate) direct_run_materializations: usize,
2213    pub(crate) partial_sparse_intersections: usize,
2214    pub(crate) partial_dense_intersections: usize,
2215    pub(crate) partial_run_intersections: usize,
2216    pub(crate) partial_overlay_builds: usize,
2217    pub(crate) row_scalar_fallbacks: usize,
2218    pub(crate) point_entries_applied: usize,
2219    pub(crate) fragment_intersections: usize,
2220}
2221
2222#[cfg(test)]
2223thread_local! {
2224    static OVERLAY_SELECT_STATS: std::cell::RefCell<OverlaySelectStats> =
2225        std::cell::RefCell::new(OverlaySelectStats::default());
2226}
2227
2228#[cfg(test)]
2229pub(crate) fn reset_overlay_select_stats() {
2230    OVERLAY_SELECT_STATS.with(|stats| *stats.borrow_mut() = OverlaySelectStats::default());
2231}
2232
2233#[cfg(test)]
2234pub(crate) fn snapshot_overlay_select_stats() -> OverlaySelectStats {
2235    OVERLAY_SELECT_STATS.with(|stats| *stats.borrow())
2236}
2237
2238#[cfg(test)]
2239fn record_overlay_select_stats(f: impl FnOnce(&mut OverlaySelectStats)) {
2240    OVERLAY_SELECT_STATS.with(|stats| f(&mut stats.borrow_mut()));
2241}
2242
2243#[cfg(not(test))]
2244#[inline]
2245fn record_overlay_select_stats(_f: impl FnOnce(&mut OverlaySelectStats)) {}
2246
2247#[derive(Debug, Clone, Copy, Eq, PartialEq)]
2248enum OverlayFragmentShape {
2249    Sparse,
2250    Dense,
2251    Run,
2252}
2253
2254struct OverlaySlots<T> {
2255    present: Vec<bool>,
2256    values: Vec<Option<T>>,
2257    any_present: bool,
2258}
2259
2260impl<T> OverlaySlots<T> {
2261    fn new(len: usize) -> Self {
2262        Self {
2263            present: vec![false; len],
2264            values: (0..len).map(|_| None).collect(),
2265            any_present: false,
2266        }
2267    }
2268
2269    #[inline]
2270    fn set(&mut self, idx: usize, value: Option<T>) {
2271        if idx >= self.present.len() {
2272            return;
2273        }
2274        self.present[idx] = true;
2275        self.values[idx] = value;
2276        self.any_present = true;
2277    }
2278
2279    #[inline]
2280    fn any_present(&self) -> bool {
2281        self.any_present
2282    }
2283}
2284
2285pub(crate) struct OverlayCascade<'a> {
2286    user: &'a Overlay,
2287    computed: &'a Overlay,
2288}
2289
2290impl<'a> OverlayCascade<'a> {
2291    #[inline]
2292    pub(crate) fn new(user: &'a Overlay, computed: &'a Overlay) -> Self {
2293        Self { user, computed }
2294    }
2295
2296    #[inline]
2297    pub(crate) fn get_scalar(&self, off: usize) -> Option<OverlayScalar<'a>> {
2298        self.user
2299            .get_scalar(off)
2300            .or_else(|| self.computed.get_scalar(off))
2301    }
2302
2303    #[inline]
2304    pub(crate) fn has_any_in_range(&self, range: core::ops::Range<usize>) -> bool {
2305        self.user.has_any_in_range(range.clone()) || self.computed.has_any_in_range(range)
2306    }
2307
2308    pub(crate) fn select_numbers(
2309        &self,
2310        range: core::ops::Range<usize>,
2311        base: &Float64Array,
2312    ) -> Arc<Float64Array> {
2313        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2314            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2315            return Self::dense_numbers(fragment, range);
2316        }
2317        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2318            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2319            return Self::run_numbers(fragment, range);
2320        }
2321        if !self.user.has_any_in_range(range.clone()) {
2322            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2323                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2324                return Self::dense_numbers(fragment, range);
2325            }
2326            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2327                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2328                return Self::run_numbers(fragment, range);
2329            }
2330        }
2331
2332        if !self.has_any_in_range(range.clone()) {
2333            return Arc::new(base.clone());
2334        }
2335
2336        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2337        let len = range.end.saturating_sub(range.start);
2338        let mut slots = OverlaySlots::<f64>::new(len);
2339        Self::apply_number_layer(self.computed, range.clone(), &mut slots);
2340        Self::apply_number_layer(self.user, range.clone(), &mut slots);
2341        if !slots.any_present() {
2342            return Arc::new(base.clone());
2343        }
2344
2345        let mut mask_b = BooleanBuilder::with_capacity(len);
2346        let mut values_b = Float64Builder::with_capacity(len);
2347        for idx in 0..len {
2348            mask_b.append_value(slots.present[idx]);
2349            match slots.values[idx] {
2350                Some(value) => values_b.append_value(value),
2351                None => values_b.append_null(),
2352            }
2353        }
2354        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2355        let mask = mask_b.finish();
2356        let values = values_b.finish();
2357        let zipped =
2358            crate::compute_prelude::zip_select(&mask, &values, base).expect("zip numeric overlay");
2359        Arc::new(
2360            zipped
2361                .as_any()
2362                .downcast_ref::<Float64Array>()
2363                .expect("numeric overlay zip type")
2364                .clone(),
2365        )
2366    }
2367
2368    pub(crate) fn select_booleans(
2369        &self,
2370        range: core::ops::Range<usize>,
2371        base: &BooleanArray,
2372    ) -> Arc<BooleanArray> {
2373        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2374            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2375            return Self::dense_booleans(fragment, range);
2376        }
2377        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2378            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2379            return Self::run_booleans(fragment, range);
2380        }
2381        if !self.user.has_any_in_range(range.clone()) {
2382            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2383                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2384                return Self::dense_booleans(fragment, range);
2385            }
2386            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2387                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2388                return Self::run_booleans(fragment, range);
2389            }
2390        }
2391
2392        if !self.has_any_in_range(range.clone()) {
2393            return Arc::new(base.clone());
2394        }
2395
2396        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2397        let len = range.end.saturating_sub(range.start);
2398        let mut slots = OverlaySlots::<bool>::new(len);
2399        Self::apply_boolean_layer(self.computed, range.clone(), &mut slots);
2400        Self::apply_boolean_layer(self.user, range.clone(), &mut slots);
2401        if !slots.any_present() {
2402            return Arc::new(base.clone());
2403        }
2404
2405        let mut mask_b = BooleanBuilder::with_capacity(len);
2406        let mut values_b = BooleanBuilder::with_capacity(len);
2407        for idx in 0..len {
2408            mask_b.append_value(slots.present[idx]);
2409            match slots.values[idx] {
2410                Some(value) => values_b.append_value(value),
2411                None => values_b.append_null(),
2412            }
2413        }
2414        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2415        let mask = mask_b.finish();
2416        let values = values_b.finish();
2417        let zipped =
2418            crate::compute_prelude::zip_select(&mask, &values, base).expect("zip boolean overlay");
2419        Arc::new(
2420            zipped
2421                .as_any()
2422                .downcast_ref::<BooleanArray>()
2423                .expect("boolean overlay zip type")
2424                .clone(),
2425        )
2426    }
2427
2428    pub(crate) fn select_text(
2429        &self,
2430        range: core::ops::Range<usize>,
2431        base: &StringArray,
2432    ) -> ArrayRef {
2433        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2434            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2435            return Self::dense_text(fragment, range);
2436        }
2437        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2438            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2439            return Self::run_text(fragment, range);
2440        }
2441        if !self.user.has_any_in_range(range.clone()) {
2442            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2443                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2444                return Self::dense_text(fragment, range);
2445            }
2446            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2447                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2448                return Self::run_text(fragment, range);
2449            }
2450        }
2451
2452        if !self.has_any_in_range(range.clone()) {
2453            return Arc::new(base.clone()) as ArrayRef;
2454        }
2455
2456        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2457        let len = range.end.saturating_sub(range.start);
2458        let mut slots = OverlaySlots::<String>::new(len);
2459        Self::apply_text_layer(self.computed, range.clone(), &mut slots);
2460        Self::apply_text_layer(self.user, range.clone(), &mut slots);
2461        if !slots.any_present() {
2462            return Arc::new(base.clone()) as ArrayRef;
2463        }
2464
2465        let mut mask_b = BooleanBuilder::with_capacity(len);
2466        let mut values_b = StringBuilder::with_capacity(len, len.saturating_mul(8));
2467        for idx in 0..len {
2468            mask_b.append_value(slots.present[idx]);
2469            match &slots.values[idx] {
2470                Some(value) => values_b.append_value(value),
2471                None => values_b.append_null(),
2472            }
2473        }
2474        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2475        let mask = mask_b.finish();
2476        let values = values_b.finish();
2477        crate::compute_prelude::zip_select(&mask, &values, base).expect("zip text overlay")
2478    }
2479
2480    pub(crate) fn select_errors(
2481        &self,
2482        range: core::ops::Range<usize>,
2483        base: &UInt8Array,
2484    ) -> Arc<UInt8Array> {
2485        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2486            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2487            return Self::dense_errors(fragment, range);
2488        }
2489        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2490            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2491            return Self::run_errors(fragment, range);
2492        }
2493        if !self.user.has_any_in_range(range.clone()) {
2494            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2495                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2496                return Self::dense_errors(fragment, range);
2497            }
2498            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2499                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2500                return Self::run_errors(fragment, range);
2501            }
2502        }
2503
2504        if !self.has_any_in_range(range.clone()) {
2505            return Arc::new(base.clone());
2506        }
2507
2508        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2509        let len = range.end.saturating_sub(range.start);
2510        let mut slots = OverlaySlots::<u8>::new(len);
2511        Self::apply_error_layer(self.computed, range.clone(), &mut slots);
2512        Self::apply_error_layer(self.user, range.clone(), &mut slots);
2513        if !slots.any_present() {
2514            return Arc::new(base.clone());
2515        }
2516
2517        let mut mask_b = BooleanBuilder::with_capacity(len);
2518        let mut values_b = UInt8Builder::with_capacity(len);
2519        for idx in 0..len {
2520            mask_b.append_value(slots.present[idx]);
2521            match slots.values[idx] {
2522                Some(value) => values_b.append_value(value),
2523                None => values_b.append_null(),
2524            }
2525        }
2526        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2527        let mask = mask_b.finish();
2528        let values = values_b.finish();
2529        let zipped =
2530            crate::compute_prelude::zip_select(&mask, &values, base).expect("zip error overlay");
2531        Arc::new(
2532            zipped
2533                .as_any()
2534                .downcast_ref::<UInt8Array>()
2535                .expect("error overlay zip type")
2536                .clone(),
2537        )
2538    }
2539
2540    pub(crate) fn select_type_tags(
2541        &self,
2542        range: core::ops::Range<usize>,
2543        base: &UInt8Array,
2544    ) -> Arc<UInt8Array> {
2545        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2546            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2547            return Self::dense_type_tags(fragment, range);
2548        }
2549        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2550            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2551            return Self::run_type_tags(fragment, range);
2552        }
2553        if !self.user.has_any_in_range(range.clone()) {
2554            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2555                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2556                return Self::dense_type_tags(fragment, range);
2557            }
2558            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2559                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2560                return Self::run_type_tags(fragment, range);
2561            }
2562        }
2563
2564        if !self.has_any_in_range(range.clone()) {
2565            return Arc::new(base.clone());
2566        }
2567
2568        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2569        let len = range.end.saturating_sub(range.start);
2570        let mut slots = OverlaySlots::<u8>::new(len);
2571        Self::apply_type_tag_layer(self.computed, range.clone(), &mut slots);
2572        Self::apply_type_tag_layer(self.user, range.clone(), &mut slots);
2573        if !slots.any_present() {
2574            return Arc::new(base.clone());
2575        }
2576
2577        let mut mask_b = BooleanBuilder::with_capacity(len);
2578        let mut values_b = UInt8Builder::with_capacity(len);
2579        for idx in 0..len {
2580            mask_b.append_value(slots.present[idx]);
2581            match slots.values[idx] {
2582                Some(value) => values_b.append_value(value),
2583                None => values_b.append_null(),
2584            }
2585        }
2586        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2587        let mask = mask_b.finish();
2588        let values = values_b.finish();
2589        let zipped =
2590            crate::compute_prelude::zip_select(&mask, &values, base).expect("zip type-tag overlay");
2591        Arc::new(
2592            zipped
2593                .as_any()
2594                .downcast_ref::<UInt8Array>()
2595                .expect("type-tag overlay zip type")
2596                .clone(),
2597        )
2598    }
2599
2600    pub(crate) fn select_lowered_text(
2601        &self,
2602        range: core::ops::Range<usize>,
2603        base: &StringArray,
2604    ) -> Arc<StringArray> {
2605        if let Some(fragment) = self.user.full_cover_dense_fragment(range.clone()) {
2606            record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2607            return Self::dense_lowered_text(fragment, range);
2608        }
2609        if let Some(fragment) = self.user.full_cover_run_fragment(range.clone()) {
2610            record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2611            return Self::run_lowered_text(fragment, range);
2612        }
2613        if !self.user.has_any_in_range(range.clone()) {
2614            if let Some(fragment) = self.computed.full_cover_dense_fragment(range.clone()) {
2615                record_overlay_select_stats(|stats| stats.direct_dense_slices += 1);
2616                return Self::dense_lowered_text(fragment, range);
2617            }
2618            if let Some(fragment) = self.computed.full_cover_run_fragment(range.clone()) {
2619                record_overlay_select_stats(|stats| stats.direct_run_materializations += 1);
2620                return Self::run_lowered_text(fragment, range);
2621            }
2622        }
2623
2624        if !self.has_any_in_range(range.clone()) {
2625            return Arc::new(base.clone());
2626        }
2627        if self.user.fragments.is_empty() && self.computed.fragments.is_empty() {
2628            return self.select_lowered_text_point_scalar(range, base);
2629        }
2630
2631        record_overlay_select_stats(|stats| stats.partial_overlay_builds += 1);
2632        let len = range.end.saturating_sub(range.start);
2633        let mut slots = OverlaySlots::<String>::new(len);
2634        Self::apply_lowered_text_layer(self.computed, range.clone(), &mut slots);
2635        Self::apply_lowered_text_layer(self.user, range.clone(), &mut slots);
2636        if !slots.any_present() {
2637            return Arc::new(base.clone());
2638        }
2639
2640        let mut mask_b = BooleanBuilder::with_capacity(len);
2641        let mut values_b = StringBuilder::with_capacity(len, len.saturating_mul(8));
2642        for idx in 0..len {
2643            mask_b.append_value(slots.present[idx]);
2644            match &slots.values[idx] {
2645                Some(value) => values_b.append_value(value),
2646                None => values_b.append_null(),
2647            }
2648        }
2649        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2650        let mask = mask_b.finish();
2651        let values = values_b.finish();
2652        let zipped = crate::compute_prelude::zip_select(&mask, &values, base)
2653            .expect("zip lowered text overlay");
2654        Arc::new(
2655            zipped
2656                .as_any()
2657                .downcast_ref::<StringArray>()
2658                .expect("lowered text overlay zip type")
2659                .clone(),
2660        )
2661    }
2662
2663    fn select_lowered_text_point_scalar(
2664        &self,
2665        range: core::ops::Range<usize>,
2666        base: &StringArray,
2667    ) -> Arc<StringArray> {
2668        let len = range.end.saturating_sub(range.start);
2669        let mut mask_b = BooleanBuilder::with_capacity(len);
2670        let mut values_b = StringBuilder::with_capacity(len, len.saturating_mul(8));
2671        record_overlay_select_stats(|stats| stats.row_scalar_fallbacks += len);
2672        for off in range {
2673            if let Some(value) = self.get_scalar(off) {
2674                mask_b.append_value(true);
2675                if let Some(s) = value.lowered_text_value() {
2676                    values_b.append_value(&s);
2677                } else {
2678                    values_b.append_null();
2679                }
2680                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
2681            } else {
2682                mask_b.append_value(false);
2683                values_b.append_null();
2684            }
2685        }
2686        record_overlay_select_stats(|stats| stats.zip_select_calls += 1);
2687        let mask = mask_b.finish();
2688        let values = values_b.finish();
2689        let zipped = crate::compute_prelude::zip_select(&mask, &values, base)
2690            .expect("zip lowered text overlay");
2691        Arc::new(
2692            zipped
2693                .as_any()
2694                .downcast_ref::<StringArray>()
2695                .expect("lowered text overlay zip type")
2696                .clone(),
2697        )
2698    }
2699
2700    fn dense_numbers(
2701        fragment: &OverlayFragment,
2702        range: core::ops::Range<usize>,
2703    ) -> Arc<Float64Array> {
2704        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2705        Self::payload_numbers_slice(payload, rel_start, len)
2706    }
2707
2708    fn dense_booleans(
2709        fragment: &OverlayFragment,
2710        range: core::ops::Range<usize>,
2711    ) -> Arc<BooleanArray> {
2712        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2713        Self::payload_booleans_slice(payload, rel_start, len)
2714    }
2715
2716    fn dense_text(fragment: &OverlayFragment, range: core::ops::Range<usize>) -> ArrayRef {
2717        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2718        Self::payload_text_slice(payload, rel_start, len)
2719    }
2720
2721    fn dense_errors(fragment: &OverlayFragment, range: core::ops::Range<usize>) -> Arc<UInt8Array> {
2722        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2723        Self::payload_errors_slice(payload, rel_start, len)
2724    }
2725
2726    fn dense_type_tags(
2727        fragment: &OverlayFragment,
2728        range: core::ops::Range<usize>,
2729    ) -> Arc<UInt8Array> {
2730        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2731        Self::payload_type_tags_slice(payload, rel_start, len)
2732    }
2733
2734    fn dense_lowered_text(
2735        fragment: &OverlayFragment,
2736        range: core::ops::Range<usize>,
2737    ) -> Arc<StringArray> {
2738        let (rel_start, len, payload) = Self::dense_payload_window(fragment, range);
2739        Self::payload_lowered_text_materialize(payload, rel_start, len)
2740    }
2741
2742    fn dense_payload_window(
2743        fragment: &OverlayFragment,
2744        range: core::ops::Range<usize>,
2745    ) -> (usize, usize, &OverlayFragmentPayload) {
2746        let OverlayFragment::DenseRange { start, payload, .. } = fragment else {
2747            unreachable!("dense payload window requires DenseRange")
2748        };
2749        let rel_start = range.start.saturating_sub(*start as usize);
2750        (rel_start, range.end.saturating_sub(range.start), payload)
2751    }
2752
2753    fn run_numbers(
2754        fragment: &OverlayFragment,
2755        range: core::ops::Range<usize>,
2756    ) -> Arc<Float64Array> {
2757        let mut b = Float64Builder::with_capacity(range.end.saturating_sub(range.start));
2758        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2759            if let Some(value) = payload.number_at(run_idx) {
2760                for _ in 0..repeat {
2761                    b.append_value(value);
2762                }
2763            } else {
2764                for _ in 0..repeat {
2765                    b.append_null();
2766                }
2767            }
2768        });
2769        Arc::new(b.finish())
2770    }
2771
2772    fn run_booleans(
2773        fragment: &OverlayFragment,
2774        range: core::ops::Range<usize>,
2775    ) -> Arc<BooleanArray> {
2776        let mut b = BooleanBuilder::with_capacity(range.end.saturating_sub(range.start));
2777        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2778            if let Some(value) = payload.boolean_at(run_idx) {
2779                for _ in 0..repeat {
2780                    b.append_value(value);
2781                }
2782            } else {
2783                for _ in 0..repeat {
2784                    b.append_null();
2785                }
2786            }
2787        });
2788        Arc::new(b.finish())
2789    }
2790
2791    fn run_text(fragment: &OverlayFragment, range: core::ops::Range<usize>) -> ArrayRef {
2792        let mut b = StringBuilder::with_capacity(
2793            range.end.saturating_sub(range.start),
2794            range.end.saturating_sub(range.start).saturating_mul(8),
2795        );
2796        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2797            if let Some(value) = payload.text_at(run_idx) {
2798                for _ in 0..repeat {
2799                    b.append_value(value);
2800                }
2801            } else {
2802                for _ in 0..repeat {
2803                    b.append_null();
2804                }
2805            }
2806        });
2807        Arc::new(b.finish()) as ArrayRef
2808    }
2809
2810    fn run_errors(fragment: &OverlayFragment, range: core::ops::Range<usize>) -> Arc<UInt8Array> {
2811        let mut b = UInt8Builder::with_capacity(range.end.saturating_sub(range.start));
2812        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2813            if let Some(value) = payload.error_at(run_idx) {
2814                for _ in 0..repeat {
2815                    b.append_value(value);
2816                }
2817            } else {
2818                for _ in 0..repeat {
2819                    b.append_null();
2820                }
2821            }
2822        });
2823        Arc::new(b.finish())
2824    }
2825
2826    fn run_type_tags(
2827        fragment: &OverlayFragment,
2828        range: core::ops::Range<usize>,
2829    ) -> Arc<UInt8Array> {
2830        let mut b = UInt8Builder::with_capacity(range.end.saturating_sub(range.start));
2831        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2832            let tag = payload.type_tag_at(run_idx).unwrap_or(TypeTag::Empty) as u8;
2833            for _ in 0..repeat {
2834                b.append_value(tag);
2835            }
2836        });
2837        Arc::new(b.finish())
2838    }
2839
2840    fn run_lowered_text(
2841        fragment: &OverlayFragment,
2842        range: core::ops::Range<usize>,
2843    ) -> Arc<StringArray> {
2844        let mut b = StringBuilder::with_capacity(
2845            range.end.saturating_sub(range.start),
2846            range.end.saturating_sub(range.start).saturating_mul(8),
2847        );
2848        Self::for_each_run_payload_index(fragment, range, |payload, run_idx, repeat| {
2849            let value = Self::payload_lowered_text_at(payload, run_idx);
2850            if let Some(value) = value {
2851                for _ in 0..repeat {
2852                    b.append_value(&value);
2853                }
2854            } else {
2855                for _ in 0..repeat {
2856                    b.append_null();
2857                }
2858            }
2859        });
2860        Arc::new(b.finish())
2861    }
2862
2863    fn payload_numbers_slice(
2864        payload: &OverlayFragmentPayload,
2865        start: usize,
2866        len: usize,
2867    ) -> Arc<Float64Array> {
2868        if let Some(array) = &payload.numbers {
2869            let sliced = array.slice(start, len);
2870            Arc::new(
2871                sliced
2872                    .as_any()
2873                    .downcast_ref::<Float64Array>()
2874                    .unwrap()
2875                    .clone(),
2876            )
2877        } else {
2878            Self::null_numbers(len)
2879        }
2880    }
2881
2882    fn payload_booleans_slice(
2883        payload: &OverlayFragmentPayload,
2884        start: usize,
2885        len: usize,
2886    ) -> Arc<BooleanArray> {
2887        if let Some(array) = &payload.booleans {
2888            let sliced = array.slice(start, len);
2889            Arc::new(
2890                sliced
2891                    .as_any()
2892                    .downcast_ref::<BooleanArray>()
2893                    .unwrap()
2894                    .clone(),
2895            )
2896        } else {
2897            Self::null_booleans(len)
2898        }
2899    }
2900
2901    fn payload_text_slice(payload: &OverlayFragmentPayload, start: usize, len: usize) -> ArrayRef {
2902        if let Some(array) = &payload.text {
2903            array.slice(start, len)
2904        } else {
2905            new_null_array(&DataType::Utf8, len)
2906        }
2907    }
2908
2909    fn payload_errors_slice(
2910        payload: &OverlayFragmentPayload,
2911        start: usize,
2912        len: usize,
2913    ) -> Arc<UInt8Array> {
2914        if let Some(array) = &payload.errors {
2915            let sliced = array.slice(start, len);
2916            Arc::new(
2917                sliced
2918                    .as_any()
2919                    .downcast_ref::<UInt8Array>()
2920                    .unwrap()
2921                    .clone(),
2922            )
2923        } else {
2924            Self::null_errors(len)
2925        }
2926    }
2927
2928    fn payload_type_tags_slice(
2929        payload: &OverlayFragmentPayload,
2930        start: usize,
2931        len: usize,
2932    ) -> Arc<UInt8Array> {
2933        let sliced = payload.type_tags.slice(start, len);
2934        Arc::new(
2935            sliced
2936                .as_any()
2937                .downcast_ref::<UInt8Array>()
2938                .unwrap()
2939                .clone(),
2940        )
2941    }
2942
2943    fn payload_lowered_text_materialize(
2944        payload: &OverlayFragmentPayload,
2945        start: usize,
2946        len: usize,
2947    ) -> Arc<StringArray> {
2948        let mut b = StringBuilder::with_capacity(len, len.saturating_mul(8));
2949        for idx in start..start.saturating_add(len) {
2950            if let Some(value) = Self::payload_lowered_text_at(payload, idx) {
2951                b.append_value(&value);
2952            } else {
2953                b.append_null();
2954            }
2955        }
2956        Arc::new(b.finish())
2957    }
2958
2959    fn payload_lowered_text_at(payload: &OverlayFragmentPayload, idx: usize) -> Option<String> {
2960        match payload.type_tag_at(idx)? {
2961            TypeTag::Text => payload.text_at(idx).map(|value| value.to_lowercase()),
2962            TypeTag::Number | TypeTag::DateTime | TypeTag::Duration => {
2963                payload.number_at(idx).map(|value| value.to_string())
2964            }
2965            TypeTag::Boolean => payload
2966                .boolean_at(idx)
2967                .map(|value| if value { "true" } else { "false" }.to_string()),
2968            TypeTag::Empty | TypeTag::Error | TypeTag::Pending => None,
2969        }
2970    }
2971
2972    fn null_numbers(len: usize) -> Arc<Float64Array> {
2973        let arr = new_null_array(&DataType::Float64, len);
2974        Arc::new(arr.as_any().downcast_ref::<Float64Array>().unwrap().clone())
2975    }
2976
2977    fn null_booleans(len: usize) -> Arc<BooleanArray> {
2978        let arr = new_null_array(&DataType::Boolean, len);
2979        Arc::new(arr.as_any().downcast_ref::<BooleanArray>().unwrap().clone())
2980    }
2981
2982    fn null_errors(len: usize) -> Arc<UInt8Array> {
2983        let arr = new_null_array(&DataType::UInt8, len);
2984        Arc::new(arr.as_any().downcast_ref::<UInt8Array>().unwrap().clone())
2985    }
2986
2987    fn apply_number_layer(
2988        layer: &Overlay,
2989        range: core::ops::Range<usize>,
2990        slots: &mut OverlaySlots<f64>,
2991    ) {
2992        Self::apply_fragment_layer(layer, range.clone(), slots, |payload, idx| {
2993            payload.number_at(idx)
2994        });
2995        for (off, value) in layer.iter_points() {
2996            if range.contains(off) {
2997                slots.set(*off - range.start, value.numeric_lane_value());
2998                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
2999            }
3000        }
3001    }
3002
3003    fn apply_boolean_layer(
3004        layer: &Overlay,
3005        range: core::ops::Range<usize>,
3006        slots: &mut OverlaySlots<bool>,
3007    ) {
3008        Self::apply_fragment_layer(layer, range.clone(), slots, |payload, idx| {
3009            payload.boolean_at(idx)
3010        });
3011        for (off, value) in layer.iter_points() {
3012            if range.contains(off) {
3013                slots.set(*off - range.start, value.boolean_lane_value());
3014                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
3015            }
3016        }
3017    }
3018
3019    fn apply_text_layer(
3020        layer: &Overlay,
3021        range: core::ops::Range<usize>,
3022        slots: &mut OverlaySlots<String>,
3023    ) {
3024        Self::apply_fragment_layer(layer, range.clone(), slots, |payload, idx| {
3025            payload.text_at(idx).map(ToString::to_string)
3026        });
3027        for (off, value) in layer.iter_points() {
3028            if range.contains(off) {
3029                slots.set(
3030                    *off - range.start,
3031                    value.text_lane_value().map(ToString::to_string),
3032                );
3033                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
3034            }
3035        }
3036    }
3037
3038    fn apply_error_layer(
3039        layer: &Overlay,
3040        range: core::ops::Range<usize>,
3041        slots: &mut OverlaySlots<u8>,
3042    ) {
3043        Self::apply_fragment_layer(layer, range.clone(), slots, |payload, idx| {
3044            payload.error_at(idx)
3045        });
3046        for (off, value) in layer.iter_points() {
3047            if range.contains(off) {
3048                slots.set(*off - range.start, value.error_lane_value());
3049                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
3050            }
3051        }
3052    }
3053
3054    fn apply_type_tag_layer(
3055        layer: &Overlay,
3056        range: core::ops::Range<usize>,
3057        slots: &mut OverlaySlots<u8>,
3058    ) {
3059        Self::apply_fragment_layer(layer, range.clone(), slots, |payload, idx| {
3060            payload.type_tag_at(idx).map(|tag| tag as u8)
3061        });
3062        for (off, value) in layer.iter_points() {
3063            if range.contains(off) {
3064                slots.set(*off - range.start, Some(value.type_tag() as u8));
3065                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
3066            }
3067        }
3068    }
3069
3070    fn apply_lowered_text_layer(
3071        layer: &Overlay,
3072        range: core::ops::Range<usize>,
3073        slots: &mut OverlaySlots<String>,
3074    ) {
3075        Self::apply_fragment_layer(layer, range.clone(), slots, Self::payload_lowered_text_at);
3076        for (off, value) in layer.iter_points() {
3077            if range.contains(off) {
3078                slots.set(*off - range.start, value.lowered_text_value());
3079                record_overlay_select_stats(|stats| stats.point_entries_applied += 1);
3080            }
3081        }
3082    }
3083
3084    fn apply_fragment_layer<T>(
3085        layer: &Overlay,
3086        range: core::ops::Range<usize>,
3087        slots: &mut OverlaySlots<T>,
3088        mut value_at: impl FnMut(&OverlayFragmentPayload, usize) -> Option<T>,
3089    ) {
3090        for fragment in &layer.fragments {
3091            if !fragment.has_any_in_range(range.clone()) {
3092                continue;
3093            }
3094            Self::record_fragment_intersection(fragment);
3095            Self::for_each_fragment_payload_index(
3096                fragment,
3097                range.clone(),
3098                |out_idx, payload, payload_idx| {
3099                    slots.set(out_idx, value_at(payload, payload_idx));
3100                },
3101            );
3102        }
3103    }
3104
3105    fn record_fragment_intersection(fragment: &OverlayFragment) {
3106        let shape = match fragment {
3107            OverlayFragment::SparseOffsets { .. } => OverlayFragmentShape::Sparse,
3108            OverlayFragment::DenseRange { .. } => OverlayFragmentShape::Dense,
3109            OverlayFragment::RunRange { .. } => OverlayFragmentShape::Run,
3110        };
3111        record_overlay_select_stats(|stats| {
3112            stats.fragment_intersections += 1;
3113            match shape {
3114                OverlayFragmentShape::Sparse => stats.partial_sparse_intersections += 1,
3115                OverlayFragmentShape::Dense => stats.partial_dense_intersections += 1,
3116                OverlayFragmentShape::Run => stats.partial_run_intersections += 1,
3117            }
3118        });
3119    }
3120
3121    fn for_each_fragment_payload_index(
3122        fragment: &OverlayFragment,
3123        range: core::ops::Range<usize>,
3124        mut f: impl FnMut(usize, &OverlayFragmentPayload, usize),
3125    ) {
3126        if range.is_empty() {
3127            return;
3128        }
3129        match fragment {
3130            OverlayFragment::SparseOffsets { offsets, payload } => {
3131                let start = u32::try_from(range.start).unwrap_or(u32::MAX);
3132                let lo = offsets.partition_point(|off| *off < start);
3133                let hi = offsets.partition_point(|off| (*off as usize) < range.end);
3134                for (idx, off) in offsets.iter().enumerate().take(hi).skip(lo) {
3135                    let out_idx = (*off as usize).saturating_sub(range.start);
3136                    f(out_idx, payload, idx);
3137                }
3138            }
3139            OverlayFragment::DenseRange {
3140                start,
3141                len,
3142                payload,
3143            } => {
3144                let frag_start = *start as usize;
3145                let frag_end = frag_start.saturating_add(*len as usize);
3146                let inter_start = frag_start.max(range.start);
3147                let inter_end = frag_end.min(range.end);
3148                if inter_start >= inter_end {
3149                    return;
3150                }
3151                for abs in inter_start..inter_end {
3152                    f(abs - range.start, payload, abs - frag_start);
3153                }
3154            }
3155            OverlayFragment::RunRange {
3156                start,
3157                len,
3158                run_ends,
3159                payload,
3160            } => {
3161                let frag_start = *start as usize;
3162                let frag_end = frag_start.saturating_add(*len as usize);
3163                let inter_start = frag_start.max(range.start);
3164                let inter_end = frag_end.min(range.end);
3165                if inter_start >= inter_end {
3166                    return;
3167                }
3168                let mut prev_end = 0usize;
3169                for (run_idx, run_end) in run_ends.iter().enumerate() {
3170                    let run_start_abs = frag_start.saturating_add(prev_end);
3171                    let run_end_abs = frag_start.saturating_add(*run_end as usize);
3172                    let start_abs = run_start_abs.max(inter_start);
3173                    let end_abs = run_end_abs.min(inter_end);
3174                    if start_abs < end_abs {
3175                        for abs in start_abs..end_abs {
3176                            f(abs - range.start, payload, run_idx);
3177                        }
3178                    }
3179                    prev_end = *run_end as usize;
3180                    if run_end_abs >= inter_end {
3181                        break;
3182                    }
3183                }
3184            }
3185        }
3186    }
3187
3188    fn for_each_run_payload_index(
3189        fragment: &OverlayFragment,
3190        range: core::ops::Range<usize>,
3191        mut f: impl FnMut(&OverlayFragmentPayload, usize, usize),
3192    ) {
3193        let OverlayFragment::RunRange {
3194            start,
3195            len,
3196            run_ends,
3197            payload,
3198        } = fragment
3199        else {
3200            unreachable!("run payload iteration requires RunRange")
3201        };
3202        let frag_start = *start as usize;
3203        let frag_end = frag_start.saturating_add(*len as usize);
3204        let inter_start = frag_start.max(range.start);
3205        let inter_end = frag_end.min(range.end);
3206        if inter_start >= inter_end {
3207            return;
3208        }
3209        let mut prev_end = 0usize;
3210        for (run_idx, run_end) in run_ends.iter().enumerate() {
3211            let run_start_abs = frag_start.saturating_add(prev_end);
3212            let run_end_abs = frag_start.saturating_add(*run_end as usize);
3213            let start_abs = run_start_abs.max(inter_start);
3214            let end_abs = run_end_abs.min(inter_end);
3215            if start_abs < end_abs {
3216                f(payload, run_idx, end_abs - start_abs);
3217            }
3218            prev_end = *run_end as usize;
3219            if run_end_abs >= inter_end {
3220                break;
3221            }
3222        }
3223    }
3224}
3225
3226impl OverlayFragmentPayload {
3227    #[inline]
3228    fn type_tag_at(&self, idx: usize) -> Option<TypeTag> {
3229        if idx >= self.type_tags.len() || self.type_tags.is_null(idx) {
3230            return None;
3231        }
3232        Some(TypeTag::from_u8(self.type_tags.value(idx)))
3233    }
3234}
3235
3236impl Overlay {
3237    fn full_cover_dense_fragment(
3238        &self,
3239        range: core::ops::Range<usize>,
3240    ) -> Option<&OverlayFragment> {
3241        self.full_cover_single_fragment(range, OverlayFragmentShape::Dense)
3242    }
3243
3244    fn full_cover_run_fragment(&self, range: core::ops::Range<usize>) -> Option<&OverlayFragment> {
3245        self.full_cover_single_fragment(range, OverlayFragmentShape::Run)
3246    }
3247
3248    fn full_cover_single_fragment(
3249        &self,
3250        range: core::ops::Range<usize>,
3251        shape: OverlayFragmentShape,
3252    ) -> Option<&OverlayFragment> {
3253        if range.is_empty() || self.points.keys().any(|off| range.contains(off)) {
3254            return None;
3255        }
3256        let mut found = None;
3257        for fragment in &self.fragments {
3258            if !fragment.has_any_in_range(range.clone()) {
3259                continue;
3260            }
3261            let shape_matches = matches!(
3262                (shape, fragment),
3263                (
3264                    OverlayFragmentShape::Dense,
3265                    OverlayFragment::DenseRange { .. }
3266                ) | (OverlayFragmentShape::Run, OverlayFragment::RunRange { .. })
3267            );
3268            let covers = fragment
3269                .interval_coverage()
3270                .is_some_and(|own| own.start <= range.start && range.end <= own.end);
3271            if shape_matches && covers && found.is_none() {
3272                found = Some(fragment);
3273            } else {
3274                return None;
3275            }
3276        }
3277        found
3278    }
3279}
3280fn append_overlay_value_to_lane_builders(
3281    ov: &OverlayValue,
3282    tag_b: &mut UInt8Builder,
3283    nb: &mut Float64Builder,
3284    bb: &mut BooleanBuilder,
3285    sb: &mut StringBuilder,
3286    eb: &mut UInt8Builder,
3287    non_num: &mut usize,
3288    non_bool: &mut usize,
3289    non_text: &mut usize,
3290    non_err: &mut usize,
3291) {
3292    match ov {
3293        OverlayValue::Empty => {
3294            tag_b.append_value(TypeTag::Empty as u8);
3295            nb.append_null();
3296            bb.append_null();
3297            sb.append_null();
3298            eb.append_null();
3299        }
3300        OverlayValue::Number(n) => {
3301            tag_b.append_value(TypeTag::Number as u8);
3302            nb.append_value(*n);
3303            *non_num += 1;
3304            bb.append_null();
3305            sb.append_null();
3306            eb.append_null();
3307        }
3308        OverlayValue::DateTime(serial) => {
3309            tag_b.append_value(TypeTag::DateTime as u8);
3310            nb.append_value(*serial);
3311            *non_num += 1;
3312            bb.append_null();
3313            sb.append_null();
3314            eb.append_null();
3315        }
3316        OverlayValue::Duration(serial) => {
3317            tag_b.append_value(TypeTag::Duration as u8);
3318            nb.append_value(*serial);
3319            *non_num += 1;
3320            bb.append_null();
3321            sb.append_null();
3322            eb.append_null();
3323        }
3324        OverlayValue::Boolean(b) => {
3325            tag_b.append_value(TypeTag::Boolean as u8);
3326            nb.append_null();
3327            bb.append_value(*b);
3328            *non_bool += 1;
3329            sb.append_null();
3330            eb.append_null();
3331        }
3332        OverlayValue::Text(s) => {
3333            tag_b.append_value(TypeTag::Text as u8);
3334            nb.append_null();
3335            bb.append_null();
3336            sb.append_value(s);
3337            *non_text += 1;
3338            eb.append_null();
3339        }
3340        OverlayValue::Error(code) => {
3341            tag_b.append_value(TypeTag::Error as u8);
3342            nb.append_null();
3343            bb.append_null();
3344            sb.append_null();
3345            eb.append_value(*code);
3346            *non_err += 1;
3347        }
3348        OverlayValue::Pending => {
3349            tag_b.append_value(TypeTag::Pending as u8);
3350            nb.append_null();
3351            bb.append_null();
3352            sb.append_null();
3353            eb.append_null();
3354        }
3355    }
3356}
3357
3358impl ArrowSheet {
3359    /// Create a logical sheet whose cells are initially implicit empty values.
3360    ///
3361    /// Columns start with no materialized chunks; callers can populate only touched
3362    /// column/chunk pairs via `set_sparse_overlay_value`. Missing chunks remain
3363    /// observable as empty cells through scalar and range reads.
3364    pub fn new_sparse(sheet_name: &str, ncols: usize, nrows: usize, chunk_rows: usize) -> Self {
3365        Self::new_sparse_with_date_system(
3366            sheet_name,
3367            ncols,
3368            nrows,
3369            chunk_rows,
3370            crate::engine::DateSystem::Excel1900,
3371        )
3372    }
3373
3374    /// Create a sparse sheet using an explicitly selected workbook date system.
3375    pub fn new_sparse_with_date_system(
3376        sheet_name: &str,
3377        ncols: usize,
3378        nrows: usize,
3379        chunk_rows: usize,
3380        date_system: crate::engine::DateSystem,
3381    ) -> Self {
3382        let chunk_rows = chunk_rows.max(1);
3383        let columns = (0..ncols)
3384            .map(|idx| ArrowColumn {
3385                chunks: Vec::new(),
3386                sparse_chunks: FxHashMap::default(),
3387                index: idx as u32,
3388            })
3389            .collect();
3390        let mut sheet = Self {
3391            name: Arc::from(sheet_name.to_string()),
3392            date_system,
3393            columns,
3394            nrows: 0,
3395            chunk_starts: Vec::new(),
3396            chunk_rows,
3397        };
3398        sheet.ensure_row_capacity(nrows);
3399        sheet
3400    }
3401
3402    /// Populate a single sparse cell using the overlay cascade while preserving
3403    /// implicit-empty chunks elsewhere. This is intended for sparse initial ingest,
3404    /// not for user edits; no graph/changelog side effects are triggered here.
3405    pub fn set_sparse_overlay_value(
3406        &mut self,
3407        abs_row: usize,
3408        abs_col: usize,
3409        value: OverlayValue,
3410    ) -> isize {
3411        if abs_col >= self.columns.len() {
3412            let start = self.columns.len();
3413            self.columns
3414                .extend((start..=abs_col).map(|idx| ArrowColumn {
3415                    chunks: Vec::new(),
3416                    sparse_chunks: FxHashMap::default(),
3417                    index: idx as u32,
3418                }));
3419        }
3420        if abs_row >= self.nrows as usize {
3421            self.ensure_row_capacity(abs_row + 1);
3422        }
3423        let Some((ch_idx, in_off)) = self.chunk_of_row(abs_row) else {
3424            return 0;
3425        };
3426        let Some(ch) = self.ensure_column_chunk_mut(abs_col, ch_idx) else {
3427            return 0;
3428        };
3429        ch.overlay.set(in_off, value)
3430    }
3431
3432    /// Return a summary of each column's chunk counts, total rows, and lane presence.
3433    pub fn shape(&self) -> Vec<ColumnShape> {
3434        self.columns
3435            .iter()
3436            .map(|c| {
3437                let chunks = c.chunks.len();
3438                let rows = self.nrows as usize;
3439                let has_num = c.chunks.iter().any(|ch| ch.meta.non_null_num > 0);
3440                let has_bool = c.chunks.iter().any(|ch| ch.meta.non_null_bool > 0);
3441                let has_text = c.chunks.iter().any(|ch| ch.meta.non_null_text > 0);
3442                let has_err = c.chunks.iter().any(|ch| ch.meta.non_null_err > 0);
3443                ColumnShape {
3444                    index: c.index,
3445                    chunks,
3446                    rows,
3447                    has_num,
3448                    has_bool,
3449                    has_text,
3450                    has_err,
3451                }
3452            })
3453            .collect()
3454    }
3455
3456    pub fn range_view(
3457        &self,
3458        sr: usize,
3459        sc: usize,
3460        er: usize,
3461        ec: usize,
3462    ) -> crate::engine::range_view::RangeView<'_> {
3463        let r0 = er.checked_sub(sr).map(|d| d + 1).unwrap_or(0);
3464        let c0 = ec.checked_sub(sc).map(|d| d + 1).unwrap_or(0);
3465        let (rows, cols) = if r0 == 0 || c0 == 0 { (0, 0) } else { (r0, c0) };
3466        crate::engine::range_view::RangeView::new(
3467            crate::engine::range_view::RangeBacking::Borrowed(self),
3468            sr,
3469            sc,
3470            er,
3471            ec,
3472            rows,
3473            cols,
3474        )
3475    }
3476
3477    /// Fast single-cell read (0-based row/col) with overlay precedence.
3478    ///
3479    /// This avoids constructing a 1x1 RangeView and is intended for tight read loops.
3480    #[inline]
3481    pub fn get_cell_value(&self, abs_row: usize, abs_col: usize) -> LiteralValue {
3482        let sheet_rows = self.nrows as usize;
3483        if abs_row >= sheet_rows {
3484            return LiteralValue::Empty;
3485        }
3486        if abs_col >= self.columns.len() {
3487            return LiteralValue::Empty;
3488        }
3489        let Some((ch_idx, in_off)) = self.chunk_of_row(abs_row) else {
3490            return LiteralValue::Empty;
3491        };
3492        let col_ref = &self.columns[abs_col];
3493        let Some(ch) = col_ref.chunk(ch_idx) else {
3494            return LiteralValue::Empty;
3495        };
3496
3497        // Overlay takes precedence: user edits over computed over base.
3498        let cascade = OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
3499        if let Some(ov) = cascade.get_scalar(in_off) {
3500            return ov.to_literal_for(self.date_system);
3501        }
3502
3503        // Read tag and route to lane.
3504        let tag_u8 = ch.type_tag.value(in_off);
3505        match TypeTag::from_u8(tag_u8) {
3506            TypeTag::Empty => LiteralValue::Empty,
3507            TypeTag::Number => {
3508                if let Some(arr) = &ch.numbers {
3509                    if arr.is_null(in_off) {
3510                        return LiteralValue::Empty;
3511                    }
3512                    LiteralValue::Number(arr.value(in_off))
3513                } else {
3514                    LiteralValue::Empty
3515                }
3516            }
3517            TypeTag::DateTime => {
3518                if let Some(arr) = &ch.numbers {
3519                    if arr.is_null(in_off) {
3520                        return LiteralValue::Empty;
3521                    }
3522                    LiteralValue::try_from_serial_number_for(self.date_system, arr.value(in_off))
3523                        .unwrap_or_else(LiteralValue::Error)
3524                } else {
3525                    LiteralValue::Empty
3526                }
3527            }
3528            TypeTag::Duration => {
3529                if let Some(arr) = &ch.numbers {
3530                    if arr.is_null(in_off) {
3531                        return LiteralValue::Empty;
3532                    }
3533                    let serial = arr.value(in_off);
3534                    let nanos_f = serial * 86_400.0 * 1_000_000_000.0;
3535                    let nanos = nanos_f.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
3536                    LiteralValue::Duration(chrono::Duration::nanoseconds(nanos))
3537                } else {
3538                    LiteralValue::Empty
3539                }
3540            }
3541            TypeTag::Boolean => {
3542                if let Some(arr) = &ch.booleans {
3543                    if arr.is_null(in_off) {
3544                        return LiteralValue::Empty;
3545                    }
3546                    LiteralValue::Boolean(arr.value(in_off))
3547                } else {
3548                    LiteralValue::Empty
3549                }
3550            }
3551            TypeTag::Text => {
3552                if let Some(arr) = &ch.text {
3553                    if arr.is_null(in_off) {
3554                        return LiteralValue::Empty;
3555                    }
3556                    let sa = arr
3557                        .as_any()
3558                        .downcast_ref::<arrow_array::StringArray>()
3559                        .unwrap();
3560                    LiteralValue::Text(sa.value(in_off).to_string())
3561                } else {
3562                    LiteralValue::Empty
3563                }
3564            }
3565            TypeTag::Error => {
3566                if let Some(arr) = &ch.errors {
3567                    if arr.is_null(in_off) {
3568                        return LiteralValue::Empty;
3569                    }
3570                    let kind = unmap_error_code(arr.value(in_off));
3571                    LiteralValue::Error(ExcelError::new(kind))
3572                } else {
3573                    LiteralValue::Empty
3574                }
3575            }
3576            TypeTag::Pending => LiteralValue::Pending,
3577        }
3578    }
3579
3580    /// Ensure capacity to address at least `target_rows` rows by extending the row chunk map.
3581    ///
3582    /// This updates `chunk_starts`/`nrows` but does **not** eagerly densify all columns with
3583    /// new empty chunks. Missing chunks are treated as all-empty and can be materialized lazily.
3584    pub fn ensure_row_capacity(&mut self, target_rows: usize) {
3585        if target_rows as u32 <= self.nrows {
3586            return;
3587        }
3588
3589        let chunk_size = self.chunk_rows.max(1);
3590
3591        // `chunk_starts` must represent fixed-size chunk boundaries based on `chunk_rows`, not
3592        // incremental growth steps. In particular, repeated calls like ensure_row_capacity(1),
3593        // ensure_row_capacity(2), ... must NOT create a new chunk per row.
3594        if self.chunk_starts.is_empty() {
3595            self.chunk_starts.push(0);
3596        }
3597
3598        // Extend chunk starts only when `target_rows` crosses a chunk boundary.
3599        // Example: chunk_size=3, target_rows=6 => chunk_starts=[0,3]
3600        let mut next_start = self
3601            .chunk_starts
3602            .last()
3603            .copied()
3604            .unwrap_or(0)
3605            .saturating_add(chunk_size);
3606        while next_start < target_rows {
3607            self.chunk_starts.push(next_start);
3608            next_start = next_start.saturating_add(chunk_size);
3609        }
3610
3611        self.nrows = target_rows as u32;
3612
3613        // Any previously-materialized chunk may have been created when the sheet had fewer rows.
3614        // When `chunk_starts` extends, chunks that used to be "last" can become interior chunks
3615        // with a larger fixed boundary. Ensure materialized chunks are grown to their current
3616        // boundary-derived length so RangeView slicing stays in-bounds.
3617        let starts = self.chunk_starts.clone();
3618        let nrows = self.nrows as usize;
3619        let required_len_for = |ch_idx: usize| -> Option<usize> {
3620            let start = *starts.get(ch_idx)?;
3621            let end = starts.get(ch_idx + 1).copied().unwrap_or(nrows);
3622            Some(end.saturating_sub(start))
3623        };
3624
3625        for col in &mut self.columns {
3626            for (idx, ch) in col.chunks.iter_mut().enumerate() {
3627                if let Some(req) = required_len_for(idx) {
3628                    ch.grow_len_to(req);
3629                }
3630            }
3631            if !col.sparse_chunks.is_empty() {
3632                let keys: Vec<usize> = col.sparse_chunks.keys().copied().collect();
3633                for idx in keys {
3634                    if let (Some(req), Some(ch)) =
3635                        (required_len_for(idx), col.sparse_chunks.get_mut(&idx))
3636                    {
3637                        ch.grow_len_to(req);
3638                    }
3639                }
3640            }
3641        }
3642    }
3643
3644    /// Ensure a mutable chunk for a given column/chunk index.
3645    ///
3646    /// If the chunk is beyond the column's dense chunk vector, it is stored in `sparse_chunks`.
3647    pub fn ensure_column_chunk_mut(
3648        &mut self,
3649        col_idx: usize,
3650        ch_idx: usize,
3651    ) -> Option<&mut ColumnChunk> {
3652        let start = *self.chunk_starts.get(ch_idx)?;
3653        let end = self
3654            .chunk_starts
3655            .get(ch_idx + 1)
3656            .copied()
3657            .unwrap_or(self.nrows as usize);
3658        let len = end.saturating_sub(start);
3659
3660        let col = self.columns.get_mut(col_idx)?;
3661        if ch_idx < col.chunks.len() {
3662            return Some(&mut col.chunks[ch_idx]);
3663        }
3664        Some(
3665            col.sparse_chunks
3666                .entry(ch_idx)
3667                .or_insert_with(|| Self::make_empty_chunk(len)),
3668        )
3669    }
3670
3671    /// Return (chunk_idx, in_chunk_offset) for absolute 0-based row.
3672    pub fn chunk_of_row(&self, abs_row: usize) -> Option<(usize, usize)> {
3673        if abs_row >= self.nrows as usize {
3674            return None;
3675        }
3676        let ch_idx = match self.chunk_starts.binary_search(&abs_row) {
3677            Ok(i) => i,
3678            Err(0) => 0,
3679            Err(i) => i - 1,
3680        };
3681        let start = self.chunk_starts[ch_idx];
3682        Some((ch_idx, abs_row - start))
3683    }
3684
3685    fn recompute_chunk_starts(&mut self) {
3686        self.chunk_starts.clear();
3687        if let Some(col0) = self.columns.first() {
3688            let mut cur = 0usize;
3689            for ch in &col0.chunks {
3690                self.chunk_starts.push(cur);
3691                cur += ch.type_tag.len();
3692            }
3693        }
3694    }
3695
3696    fn make_empty_chunk(len: usize) -> ColumnChunk {
3697        ColumnChunk {
3698            numbers: None,
3699            booleans: None,
3700            text: None,
3701            errors: None,
3702            type_tag: Arc::new(UInt8Array::from(vec![TypeTag::Empty as u8; len])),
3703            formula_id: None,
3704            meta: ColumnChunkMeta {
3705                len,
3706                non_null_num: 0,
3707                non_null_bool: 0,
3708                non_null_text: 0,
3709                non_null_err: 0,
3710            },
3711            lazy_null_numbers: OnceCell::new(),
3712            lazy_null_booleans: OnceCell::new(),
3713            lazy_null_text: OnceCell::new(),
3714            lazy_null_errors: OnceCell::new(),
3715            lowered_text: OnceCell::new(),
3716            overlay: Overlay::new(),
3717            computed_overlay: Overlay::new(),
3718        }
3719    }
3720
3721    fn slice_chunk(ch: &ColumnChunk, off: usize, len: usize) -> ColumnChunk {
3722        // Slice type tags
3723        use arrow_array::Array;
3724        let type_tag: Arc<UInt8Array> = Arc::new(
3725            Array::slice(ch.type_tag.as_ref(), off, len)
3726                .as_any()
3727                .downcast_ref::<UInt8Array>()
3728                .unwrap()
3729                .clone(),
3730        );
3731        // Slice numbers if present and keep only if any non-null
3732        let numbers: Option<Arc<Float64Array>> = ch.numbers.as_ref().and_then(|a| {
3733            let sl = Array::slice(a.as_ref(), off, len);
3734            let fa = sl.as_any().downcast_ref::<Float64Array>().unwrap().clone();
3735            let nn = len.saturating_sub(fa.null_count());
3736            if nn == 0 { None } else { Some(Arc::new(fa)) }
3737        });
3738        let booleans: Option<Arc<BooleanArray>> = ch.booleans.as_ref().and_then(|a| {
3739            let sl = Array::slice(a.as_ref(), off, len);
3740            let ba = sl.as_any().downcast_ref::<BooleanArray>().unwrap().clone();
3741            let nn = len.saturating_sub(ba.null_count());
3742            if nn == 0 { None } else { Some(Arc::new(ba)) }
3743        });
3744        let text: Option<ArrayRef> = ch.text.as_ref().and_then(|a| {
3745            let sl = Array::slice(a.as_ref(), off, len);
3746            let sa = sl.as_any().downcast_ref::<StringArray>().unwrap().clone();
3747            let nn = len.saturating_sub(sa.null_count());
3748            if nn == 0 {
3749                None
3750            } else {
3751                Some(Arc::new(sa) as ArrayRef)
3752            }
3753        });
3754        let errors: Option<Arc<UInt8Array>> = ch.errors.as_ref().and_then(|a| {
3755            let sl = Array::slice(a.as_ref(), off, len);
3756            let ea = sl.as_any().downcast_ref::<UInt8Array>().unwrap().clone();
3757            let nn = len.saturating_sub(ea.null_count());
3758            if nn == 0 { None } else { Some(Arc::new(ea)) }
3759        });
3760        // Split overlays for this slice.
3761        let overlay = ch.overlay.slice(off, len);
3762        let computed_overlay = ch.computed_overlay.slice(off, len);
3763        let non_null_num = numbers.as_ref().map(|a| len - a.null_count()).unwrap_or(0);
3764        let non_null_bool = booleans.as_ref().map(|a| len - a.null_count()).unwrap_or(0);
3765        let non_null_text = text.as_ref().map(|a| len - a.null_count()).unwrap_or(0);
3766        let non_null_err = errors.as_ref().map(|a| len - a.null_count()).unwrap_or(0);
3767        ColumnChunk {
3768            numbers: numbers.clone(),
3769            booleans: booleans.clone(),
3770            text: text.clone(),
3771            errors: errors.clone(),
3772            type_tag,
3773            formula_id: None,
3774            meta: ColumnChunkMeta {
3775                len,
3776                non_null_num,
3777                non_null_bool,
3778                non_null_text,
3779                non_null_err,
3780            },
3781            lazy_null_numbers: OnceCell::new(),
3782            lazy_null_booleans: OnceCell::new(),
3783            lazy_null_text: OnceCell::new(),
3784            lazy_null_errors: OnceCell::new(),
3785            lowered_text: OnceCell::new(),
3786            overlay,
3787            computed_overlay,
3788        }
3789    }
3790
3791    /// Heuristic compaction: rebuilds a chunk's base arrays by applying its overlay when
3792    /// overlay density crosses thresholds. Returns true if a rebuild occurred.
3793    pub fn maybe_compact_chunk(
3794        &mut self,
3795        col_idx: usize,
3796        ch_idx: usize,
3797        abs_threshold: usize,
3798        frac_den: usize,
3799    ) -> usize {
3800        if col_idx >= self.columns.len() {
3801            return 0;
3802        }
3803
3804        let (len, tags, numbers, booleans, text, errors, non_num, non_bool, non_text, non_err) = {
3805            let Some(ch_ref) = self.columns[col_idx].chunk(ch_idx) else {
3806                return 0;
3807            };
3808            let len = ch_ref.type_tag.len();
3809            if len == 0 {
3810                return 0;
3811            }
3812
3813            let ov_len = ch_ref.overlay.len();
3814            let den = frac_den.max(1);
3815            let trig = ov_len > (len / den) || ov_len > abs_threshold;
3816            if !trig {
3817                return 0;
3818            }
3819
3820            // Rebuild: merge base lanes with overlays row-by-row.
3821            let mut tag_b = UInt8Builder::with_capacity(len);
3822            let mut nb = Float64Builder::with_capacity(len);
3823            let mut bb = BooleanBuilder::with_capacity(len);
3824            let mut sb = StringBuilder::with_capacity(len, len * 8);
3825            let mut eb = UInt8Builder::with_capacity(len);
3826            let mut non_num = 0usize;
3827            let mut non_bool = 0usize;
3828            let mut non_text = 0usize;
3829            let mut non_err = 0usize;
3830
3831            for i in 0..len {
3832                // If overlay present, use it. Otherwise, use base tag+lane.
3833                if let Some(ov) = ch_ref.overlay.get_scalar(i) {
3834                    let ov = ov.to_overlay_value();
3835                    append_overlay_value_to_lane_builders(
3836                        &ov,
3837                        &mut tag_b,
3838                        &mut nb,
3839                        &mut bb,
3840                        &mut sb,
3841                        &mut eb,
3842                        &mut non_num,
3843                        &mut non_bool,
3844                        &mut non_text,
3845                        &mut non_err,
3846                    );
3847                } else {
3848                    let tag = TypeTag::from_u8(ch_ref.type_tag.value(i));
3849                    match tag {
3850                        TypeTag::Empty => {
3851                            tag_b.append_value(TypeTag::Empty as u8);
3852                            nb.append_null();
3853                            bb.append_null();
3854                            sb.append_null();
3855                            eb.append_null();
3856                        }
3857                        TypeTag::Number | TypeTag::DateTime | TypeTag::Duration => {
3858                            tag_b.append_value(tag as u8);
3859                            if let Some(a) = &ch_ref.numbers {
3860                                let fa = a.as_any().downcast_ref::<Float64Array>().unwrap();
3861                                if fa.is_null(i) {
3862                                    nb.append_null();
3863                                } else {
3864                                    nb.append_value(fa.value(i));
3865                                    non_num += 1;
3866                                }
3867                            } else {
3868                                nb.append_null();
3869                            }
3870                            bb.append_null();
3871                            sb.append_null();
3872                            eb.append_null();
3873                        }
3874                        TypeTag::Boolean => {
3875                            tag_b.append_value(TypeTag::Boolean as u8);
3876                            nb.append_null();
3877                            if let Some(a) = &ch_ref.booleans {
3878                                let ba = a.as_any().downcast_ref::<BooleanArray>().unwrap();
3879                                if ba.is_null(i) {
3880                                    bb.append_null();
3881                                } else {
3882                                    bb.append_value(ba.value(i));
3883                                    non_bool += 1;
3884                                }
3885                            } else {
3886                                bb.append_null();
3887                            }
3888                            sb.append_null();
3889                            eb.append_null();
3890                        }
3891                        TypeTag::Text => {
3892                            tag_b.append_value(TypeTag::Text as u8);
3893                            nb.append_null();
3894                            bb.append_null();
3895                            if let Some(a) = &ch_ref.text {
3896                                let sa = a.as_any().downcast_ref::<StringArray>().unwrap();
3897                                if sa.is_null(i) {
3898                                    sb.append_null();
3899                                } else {
3900                                    sb.append_value(sa.value(i));
3901                                    non_text += 1;
3902                                }
3903                            } else {
3904                                sb.append_null();
3905                            }
3906                            eb.append_null();
3907                        }
3908                        TypeTag::Error => {
3909                            tag_b.append_value(TypeTag::Error as u8);
3910                            nb.append_null();
3911                            bb.append_null();
3912                            sb.append_null();
3913                            if let Some(a) = &ch_ref.errors {
3914                                let ea = a.as_any().downcast_ref::<UInt8Array>().unwrap();
3915                                if ea.is_null(i) {
3916                                    eb.append_null();
3917                                } else {
3918                                    eb.append_value(ea.value(i));
3919                                    non_err += 1;
3920                                }
3921                            } else {
3922                                eb.append_null();
3923                            }
3924                        }
3925                        TypeTag::Pending => {
3926                            tag_b.append_value(TypeTag::Pending as u8);
3927                            nb.append_null();
3928                            bb.append_null();
3929                            sb.append_null();
3930                            eb.append_null();
3931                        }
3932                    }
3933                }
3934            }
3935
3936            let tags = Arc::new(tag_b.finish());
3937            let numbers = {
3938                let a = nb.finish();
3939                if non_num == 0 {
3940                    None
3941                } else {
3942                    Some(Arc::new(a))
3943                }
3944            };
3945            let booleans = {
3946                let a = bb.finish();
3947                if non_bool == 0 {
3948                    None
3949                } else {
3950                    Some(Arc::new(a))
3951                }
3952            };
3953            let text = {
3954                let a = sb.finish();
3955                if non_text == 0 {
3956                    None
3957                } else {
3958                    Some(Arc::new(a) as ArrayRef)
3959                }
3960            };
3961            let errors = {
3962                let a = eb.finish();
3963                if non_err == 0 {
3964                    None
3965                } else {
3966                    Some(Arc::new(a))
3967                }
3968            };
3969
3970            (
3971                len, tags, numbers, booleans, text, errors, non_num, non_bool, non_text, non_err,
3972            )
3973        };
3974
3975        let Some(ch_mut) = self.columns[col_idx].chunk_mut(ch_idx) else {
3976            return 0;
3977        };
3978
3979        ch_mut.type_tag = tags;
3980        ch_mut.numbers = numbers;
3981        ch_mut.booleans = booleans;
3982        ch_mut.text = text;
3983        ch_mut.errors = errors;
3984        let freed = ch_mut.overlay.clear();
3985        ch_mut.lowered_text = OnceCell::new();
3986        ch_mut.meta.len = len;
3987        ch_mut.meta.non_null_num = non_num;
3988        ch_mut.meta.non_null_bool = non_bool;
3989        ch_mut.meta.non_null_text = non_text;
3990        ch_mut.meta.non_null_err = non_err;
3991        freed
3992    }
3993
3994    /// Compact a dense chunk's computed overlay into its base arrays, freeing overlay memory
3995    /// while preserving the data. Returns the number of bytes freed.
3996    ///
3997    /// This is the computed-overlay counterpart of `maybe_compact_chunk` (which compacts
3998    /// user-edit overlays). The read cascade is `overlay → computed_overlay → base`, so
3999    /// folding computed overlay entries into base arrays is transparent: the `overlay` layer
4000    /// (user edits) is left untouched and still takes precedence on reads.
4001    pub fn compact_computed_overlay_chunk(&mut self, col_idx: usize, ch_idx: usize) -> usize {
4002        if col_idx >= self.columns.len() {
4003            return 0;
4004        }
4005
4006        let (len, tags, numbers, booleans, text, errors, non_num, non_bool, non_text, non_err) = {
4007            let Some(ch_ref) = self.columns[col_idx].chunk(ch_idx) else {
4008                return 0;
4009            };
4010            let len = ch_ref.type_tag.len();
4011            if len == 0 || ch_ref.computed_overlay.is_empty() {
4012                return 0;
4013            }
4014
4015            let mut tag_b = UInt8Builder::with_capacity(len);
4016            let mut nb = Float64Builder::with_capacity(len);
4017            let mut bb = BooleanBuilder::with_capacity(len);
4018            let mut sb = StringBuilder::with_capacity(len, len * 8);
4019            let mut eb = UInt8Builder::with_capacity(len);
4020            let mut non_num = 0usize;
4021            let mut non_bool = 0usize;
4022            let mut non_text = 0usize;
4023            let mut non_err = 0usize;
4024
4025            for i in 0..len {
4026                if let Some(ov) = ch_ref.computed_overlay.get_scalar(i) {
4027                    let ov = ov.to_overlay_value();
4028                    append_overlay_value_to_lane_builders(
4029                        &ov,
4030                        &mut tag_b,
4031                        &mut nb,
4032                        &mut bb,
4033                        &mut sb,
4034                        &mut eb,
4035                        &mut non_num,
4036                        &mut non_bool,
4037                        &mut non_text,
4038                        &mut non_err,
4039                    );
4040                } else {
4041                    let tag = TypeTag::from_u8(ch_ref.type_tag.value(i));
4042                    match tag {
4043                        TypeTag::Empty => {
4044                            tag_b.append_value(TypeTag::Empty as u8);
4045                            nb.append_null();
4046                            bb.append_null();
4047                            sb.append_null();
4048                            eb.append_null();
4049                        }
4050                        TypeTag::Number | TypeTag::DateTime | TypeTag::Duration => {
4051                            tag_b.append_value(tag as u8);
4052                            if let Some(a) = &ch_ref.numbers {
4053                                let fa = a.as_any().downcast_ref::<Float64Array>().unwrap();
4054                                if fa.is_null(i) {
4055                                    nb.append_null();
4056                                } else {
4057                                    nb.append_value(fa.value(i));
4058                                    non_num += 1;
4059                                }
4060                            } else {
4061                                nb.append_null();
4062                            }
4063                            bb.append_null();
4064                            sb.append_null();
4065                            eb.append_null();
4066                        }
4067                        TypeTag::Boolean => {
4068                            tag_b.append_value(TypeTag::Boolean as u8);
4069                            nb.append_null();
4070                            if let Some(a) = &ch_ref.booleans {
4071                                let ba = a.as_any().downcast_ref::<BooleanArray>().unwrap();
4072                                if ba.is_null(i) {
4073                                    bb.append_null();
4074                                } else {
4075                                    bb.append_value(ba.value(i));
4076                                    non_bool += 1;
4077                                }
4078                            } else {
4079                                bb.append_null();
4080                            }
4081                            sb.append_null();
4082                            eb.append_null();
4083                        }
4084                        TypeTag::Text => {
4085                            tag_b.append_value(TypeTag::Text as u8);
4086                            nb.append_null();
4087                            bb.append_null();
4088                            if let Some(a) = &ch_ref.text {
4089                                let sa = a.as_any().downcast_ref::<StringArray>().unwrap();
4090                                if sa.is_null(i) {
4091                                    sb.append_null();
4092                                } else {
4093                                    sb.append_value(sa.value(i));
4094                                    non_text += 1;
4095                                }
4096                            } else {
4097                                sb.append_null();
4098                            }
4099                            eb.append_null();
4100                        }
4101                        TypeTag::Error => {
4102                            tag_b.append_value(TypeTag::Error as u8);
4103                            nb.append_null();
4104                            bb.append_null();
4105                            sb.append_null();
4106                            if let Some(a) = &ch_ref.errors {
4107                                let ea = a.as_any().downcast_ref::<UInt8Array>().unwrap();
4108                                if ea.is_null(i) {
4109                                    eb.append_null();
4110                                } else {
4111                                    eb.append_value(ea.value(i));
4112                                    non_err += 1;
4113                                }
4114                            } else {
4115                                eb.append_null();
4116                            }
4117                        }
4118                        TypeTag::Pending => {
4119                            tag_b.append_value(TypeTag::Pending as u8);
4120                            nb.append_null();
4121                            bb.append_null();
4122                            sb.append_null();
4123                            eb.append_null();
4124                        }
4125                    }
4126                }
4127            }
4128
4129            let tags = Arc::new(tag_b.finish());
4130            let numbers = {
4131                let a = nb.finish();
4132                if non_num == 0 {
4133                    None
4134                } else {
4135                    Some(Arc::new(a))
4136                }
4137            };
4138            let booleans = {
4139                let a = bb.finish();
4140                if non_bool == 0 {
4141                    None
4142                } else {
4143                    Some(Arc::new(a))
4144                }
4145            };
4146            let text = {
4147                let a = sb.finish();
4148                if non_text == 0 {
4149                    None
4150                } else {
4151                    Some(Arc::new(a) as ArrayRef)
4152                }
4153            };
4154            let errors = {
4155                let a = eb.finish();
4156                if non_err == 0 {
4157                    None
4158                } else {
4159                    Some(Arc::new(a))
4160                }
4161            };
4162
4163            (
4164                len, tags, numbers, booleans, text, errors, non_num, non_bool, non_text, non_err,
4165            )
4166        };
4167
4168        let Some(ch_mut) = self.columns[col_idx].chunk_mut(ch_idx) else {
4169            return 0;
4170        };
4171
4172        ch_mut.type_tag = tags;
4173        ch_mut.numbers = numbers;
4174        ch_mut.booleans = booleans;
4175        ch_mut.text = text;
4176        ch_mut.errors = errors;
4177        let freed = ch_mut.computed_overlay.clear();
4178        ch_mut.lowered_text = OnceCell::new();
4179        ch_mut.meta.len = len;
4180        ch_mut.meta.non_null_num = non_num;
4181        ch_mut.meta.non_null_bool = non_bool;
4182        ch_mut.meta.non_null_text = non_text;
4183        ch_mut.meta.non_null_err = non_err;
4184        freed
4185    }
4186
4187    /// Compact a sparse chunk's computed overlay into its base arrays.
4188    /// Equivalent to `compact_computed_overlay_chunk` but for sparse chunks.
4189    pub fn compact_computed_overlay_sparse_chunk(
4190        &mut self,
4191        col_idx: usize,
4192        ch_idx: usize,
4193    ) -> usize {
4194        // Sparse chunks are accessed via the same chunk/chunk_mut API,
4195        // so we delegate to the dense method which already handles both.
4196        self.compact_computed_overlay_chunk(col_idx, ch_idx)
4197    }
4198
4199    /// Insert `count` rows before absolute 0-based row `before`.
4200    pub fn insert_rows(&mut self, before: usize, count: usize) {
4201        if count == 0 {
4202            return;
4203        }
4204
4205        let total_rows = self.nrows as usize;
4206        if total_rows == 0 {
4207            self.nrows = count as u32;
4208            if self.nrows > 0 && self.chunk_starts.is_empty() {
4209                self.chunk_starts.push(0);
4210            }
4211            return;
4212        }
4213
4214        // Ensure a valid chunk map for non-empty sheets.
4215        if self.chunk_starts.is_empty() {
4216            self.chunk_starts.push(0);
4217        }
4218
4219        // "Dense" mode: every column has every chunk (legacy invariant).
4220        let dense_aligned = self
4221            .columns
4222            .iter()
4223            .all(|c| c.sparse_chunks.is_empty() && c.chunks.len() == self.chunk_starts.len());
4224
4225        let insert_at = before.min(total_rows);
4226        let (split_idx, split_off) = if insert_at == total_rows {
4227            // Append at end: split after last chunk.
4228            let last_idx = self.chunk_starts.len() - 1;
4229            let last_start = self.chunk_starts[last_idx];
4230            let last_len = total_rows.saturating_sub(last_start);
4231            (last_idx, last_len)
4232        } else {
4233            self.chunk_of_row(insert_at).unwrap_or((0, 0))
4234        };
4235
4236        if dense_aligned {
4237            // Rebuild chunks for each column (including inserted empty chunk) and recompute starts.
4238            for col in &mut self.columns {
4239                let mut new_chunks: Vec<ColumnChunk> = Vec::with_capacity(col.chunks.len() + 2);
4240                for i in 0..col.chunks.len() {
4241                    if i != split_idx {
4242                        new_chunks.push(col.chunks[i].clone());
4243                    } else {
4244                        let orig = &col.chunks[i];
4245                        let len = orig.type_tag.len();
4246                        if split_off > 0 {
4247                            new_chunks.push(Self::slice_chunk(orig, 0, split_off));
4248                        }
4249                        new_chunks.push(Self::make_empty_chunk(count));
4250                        if split_off < len {
4251                            new_chunks.push(Self::slice_chunk(orig, split_off, len - split_off));
4252                        }
4253                    }
4254                }
4255                col.chunks = new_chunks;
4256                col.sparse_chunks.clear();
4257            }
4258            self.nrows = (total_rows + count) as u32;
4259            self.recompute_chunk_starts();
4260            return;
4261        }
4262
4263        // Sparse-aware mode: `chunk_starts` is authoritative and missing chunks are treated as empty.
4264        #[derive(Clone, Copy)]
4265        enum PlanItem {
4266            Slice {
4267                old_idx: usize,
4268                off: usize,
4269                len: usize,
4270            },
4271            Empty {
4272                len: usize,
4273            },
4274        }
4275
4276        let mut plan: Vec<PlanItem> = Vec::with_capacity(self.chunk_starts.len() + 2);
4277        for old_idx in 0..self.chunk_starts.len() {
4278            let ch_start = self.chunk_starts[old_idx];
4279            let ch_end = self
4280                .chunk_starts
4281                .get(old_idx + 1)
4282                .copied()
4283                .unwrap_or(total_rows);
4284            let ch_len = ch_end.saturating_sub(ch_start);
4285            if ch_len == 0 {
4286                continue;
4287            }
4288
4289            if old_idx != split_idx {
4290                plan.push(PlanItem::Slice {
4291                    old_idx,
4292                    off: 0,
4293                    len: ch_len,
4294                });
4295                continue;
4296            }
4297
4298            let left_len = split_off.min(ch_len);
4299            let right_len = ch_len.saturating_sub(left_len);
4300            if left_len > 0 {
4301                plan.push(PlanItem::Slice {
4302                    old_idx,
4303                    off: 0,
4304                    len: left_len,
4305                });
4306            }
4307            plan.push(PlanItem::Empty { len: count });
4308            if right_len > 0 {
4309                plan.push(PlanItem::Slice {
4310                    old_idx,
4311                    off: left_len,
4312                    len: right_len,
4313                });
4314            }
4315        }
4316
4317        let mut new_starts: Vec<usize> = Vec::with_capacity(plan.len());
4318        let mut cur = 0usize;
4319        for item in &plan {
4320            let len = match *item {
4321                PlanItem::Slice { len, .. } => len,
4322                PlanItem::Empty { len } => len,
4323            };
4324            if len == 0 {
4325                continue;
4326            }
4327            new_starts.push(cur);
4328            cur = cur.saturating_add(len);
4329        }
4330
4331        debug_assert_eq!(cur, total_rows.saturating_add(count));
4332
4333        // Update sheet row layout first.
4334        self.nrows = (total_rows + count) as u32;
4335        self.chunk_starts = new_starts;
4336
4337        // Rebuild stored chunks per column using the plan.
4338        for col in &mut self.columns {
4339            let old_dense = std::mem::take(&mut col.chunks);
4340            let old_sparse = std::mem::take(&mut col.sparse_chunks);
4341            let get_old = |idx: usize| -> Option<&ColumnChunk> {
4342                if idx < old_dense.len() {
4343                    Some(&old_dense[idx])
4344                } else {
4345                    old_sparse.get(&idx)
4346                }
4347            };
4348
4349            let mut dense: Vec<ColumnChunk> = Vec::new();
4350            let mut sparse: FxHashMap<usize, ColumnChunk> = FxHashMap::default();
4351            let mut dense_prefix = true;
4352
4353            for (new_idx, item) in plan.iter().enumerate() {
4354                let produced: Option<ColumnChunk> = match *item {
4355                    PlanItem::Empty { .. } => None,
4356                    PlanItem::Slice { old_idx, off, len } => match get_old(old_idx) {
4357                        Some(orig) => {
4358                            if off == 0 && len == orig.type_tag.len() {
4359                                Some(orig.clone())
4360                            } else {
4361                                Some(Self::slice_chunk(orig, off, len))
4362                            }
4363                        }
4364                        None => None,
4365                    },
4366                };
4367
4368                if let Some(ch) = produced {
4369                    if dense_prefix && new_idx == dense.len() {
4370                        dense.push(ch);
4371                    } else {
4372                        sparse.insert(new_idx, ch);
4373                        dense_prefix = false;
4374                    }
4375                } else if dense_prefix && new_idx == dense.len() {
4376                    dense_prefix = false;
4377                }
4378            }
4379
4380            col.chunks = dense;
4381            col.sparse_chunks = sparse;
4382        }
4383    }
4384
4385    /// Delete `count` rows starting from absolute 0-based row `start`.
4386    pub fn delete_rows(&mut self, start: usize, count: usize) {
4387        if count == 0 || self.nrows == 0 {
4388            return;
4389        }
4390
4391        let total_rows = self.nrows as usize;
4392        if start >= total_rows {
4393            return;
4394        }
4395        let end = (start + count).min(total_rows);
4396        let del_len = end.saturating_sub(start);
4397        if del_len == 0 {
4398            return;
4399        }
4400
4401        // Ensure a valid chunk map for non-empty sheets.
4402        if total_rows > 0 && self.chunk_starts.is_empty() {
4403            self.chunk_starts.push(0);
4404        }
4405
4406        // "Dense" mode: every column has every chunk (legacy invariant).
4407        let dense_aligned = self
4408            .columns
4409            .iter()
4410            .all(|c| c.sparse_chunks.is_empty() && c.chunks.len() == self.chunk_starts.len());
4411
4412        if dense_aligned {
4413            // Dense rebuild by slicing out the deleted window.
4414            for col in &mut self.columns {
4415                let mut new_chunks: Vec<ColumnChunk> = Vec::new();
4416                let mut cur_start = 0usize;
4417                for ch in &col.chunks {
4418                    let len = ch.type_tag.len();
4419                    let ch_end = cur_start + len;
4420                    // No overlap
4421                    if ch_end <= start || cur_start >= end {
4422                        new_chunks.push(ch.clone());
4423                    } else {
4424                        // Overlap exists
4425                        let del_start = start.max(cur_start);
4426                        let del_end = end.min(ch_end);
4427                        let left_len = del_start.saturating_sub(cur_start);
4428                        let right_len = ch_end.saturating_sub(del_end);
4429                        if left_len > 0 {
4430                            new_chunks.push(Self::slice_chunk(ch, 0, left_len));
4431                        }
4432                        if right_len > 0 {
4433                            let off = len - right_len;
4434                            new_chunks.push(Self::slice_chunk(ch, off, right_len));
4435                        }
4436                    }
4437                    cur_start = ch_end;
4438                }
4439                col.chunks = new_chunks;
4440                col.sparse_chunks.clear();
4441            }
4442            self.nrows = (total_rows - del_len) as u32;
4443            self.recompute_chunk_starts();
4444            return;
4445        }
4446
4447        // Sparse-aware mode: `chunk_starts` is authoritative and missing chunks are treated as empty.
4448        #[derive(Clone, Copy)]
4449        enum PlanItem {
4450            Slice {
4451                old_idx: usize,
4452                off: usize,
4453                len: usize,
4454            },
4455        }
4456
4457        let mut plan: Vec<PlanItem> = Vec::with_capacity(self.chunk_starts.len());
4458        for old_idx in 0..self.chunk_starts.len() {
4459            let ch_start = self.chunk_starts[old_idx];
4460            let ch_end = self
4461                .chunk_starts
4462                .get(old_idx + 1)
4463                .copied()
4464                .unwrap_or(total_rows);
4465            let ch_len = ch_end.saturating_sub(ch_start);
4466            if ch_len == 0 {
4467                continue;
4468            }
4469
4470            // No overlap
4471            if ch_end <= start || ch_start >= end {
4472                plan.push(PlanItem::Slice {
4473                    old_idx,
4474                    off: 0,
4475                    len: ch_len,
4476                });
4477                continue;
4478            }
4479
4480            // Left remainder
4481            if start > ch_start {
4482                let left_end = start.min(ch_end);
4483                let left_len = left_end.saturating_sub(ch_start);
4484                if left_len > 0 {
4485                    plan.push(PlanItem::Slice {
4486                        old_idx,
4487                        off: 0,
4488                        len: left_len,
4489                    });
4490                }
4491            }
4492
4493            // Right remainder
4494            if end < ch_end {
4495                let right_off = end.saturating_sub(ch_start);
4496                let right_len = ch_end.saturating_sub(end);
4497                if right_len > 0 {
4498                    plan.push(PlanItem::Slice {
4499                        old_idx,
4500                        off: right_off,
4501                        len: right_len,
4502                    });
4503                }
4504            }
4505        }
4506
4507        let mut new_starts: Vec<usize> = Vec::with_capacity(plan.len());
4508        let mut cur = 0usize;
4509        for item in &plan {
4510            let len = match *item {
4511                PlanItem::Slice { len, .. } => len,
4512            };
4513            if len == 0 {
4514                continue;
4515            }
4516            new_starts.push(cur);
4517            cur = cur.saturating_add(len);
4518        }
4519
4520        debug_assert_eq!(cur, total_rows.saturating_sub(del_len));
4521
4522        // Update sheet row layout first.
4523        self.nrows = (total_rows - del_len) as u32;
4524        self.chunk_starts = new_starts;
4525
4526        // Rebuild stored chunks per column using the plan.
4527        for col in &mut self.columns {
4528            let old_dense = std::mem::take(&mut col.chunks);
4529            let old_sparse = std::mem::take(&mut col.sparse_chunks);
4530            let get_old = |idx: usize| -> Option<&ColumnChunk> {
4531                if idx < old_dense.len() {
4532                    Some(&old_dense[idx])
4533                } else {
4534                    old_sparse.get(&idx)
4535                }
4536            };
4537
4538            let mut dense: Vec<ColumnChunk> = Vec::new();
4539            let mut sparse: FxHashMap<usize, ColumnChunk> = FxHashMap::default();
4540            let mut dense_prefix = true;
4541
4542            for (new_idx, item) in plan.iter().enumerate() {
4543                let produced: Option<ColumnChunk> = match *item {
4544                    PlanItem::Slice { old_idx, off, len } => match get_old(old_idx) {
4545                        Some(orig) => {
4546                            if off == 0 && len == orig.type_tag.len() {
4547                                Some(orig.clone())
4548                            } else {
4549                                Some(Self::slice_chunk(orig, off, len))
4550                            }
4551                        }
4552                        None => None,
4553                    },
4554                };
4555
4556                if let Some(ch) = produced {
4557                    if dense_prefix && new_idx == dense.len() {
4558                        dense.push(ch);
4559                    } else {
4560                        sparse.insert(new_idx, ch);
4561                        dense_prefix = false;
4562                    }
4563                } else if dense_prefix && new_idx == dense.len() {
4564                    dense_prefix = false;
4565                }
4566            }
4567
4568            col.chunks = dense;
4569            col.sparse_chunks = sparse;
4570        }
4571    }
4572
4573    /// Insert `count` columns before absolute 0-based column `before` with empty chunks.
4574    pub fn insert_columns(&mut self, before: usize, count: usize) {
4575        if count == 0 {
4576            return;
4577        }
4578        // Determine chunk schema from first column if present
4579        let empty_col = |lens: &[usize]| -> ArrowColumn {
4580            let mut chunks = Vec::with_capacity(lens.len());
4581            for &l in lens {
4582                chunks.push(Self::make_empty_chunk(l));
4583            }
4584            ArrowColumn {
4585                chunks,
4586                sparse_chunks: FxHashMap::default(),
4587                index: 0,
4588            }
4589        };
4590        let dense_aligned = !self.columns.is_empty()
4591            && self
4592                .columns
4593                .iter()
4594                .all(|c| c.sparse_chunks.is_empty() && c.chunks.len() == self.chunk_starts.len());
4595
4596        let lens: Vec<usize> = if dense_aligned {
4597            self.columns[0]
4598                .chunks
4599                .iter()
4600                .map(|c| c.type_tag.len())
4601                .collect()
4602        } else if self.columns.is_empty() {
4603            // No columns: single chunk matching nrows if any
4604            if self.nrows > 0 {
4605                vec![self.nrows as usize]
4606            } else {
4607                Vec::new()
4608            }
4609        } else {
4610            // Sparse sheet: keep inserted columns cheap by materializing no chunks.
4611            Vec::new()
4612        };
4613        let mut cols_new: Vec<ArrowColumn> = Vec::with_capacity(self.columns.len() + count);
4614        let before_idx = before.min(self.columns.len());
4615        for (i, col) in self.columns.iter_mut().enumerate() {
4616            if i == before_idx {
4617                for _ in 0..count {
4618                    cols_new.push(empty_col(&lens));
4619                }
4620            }
4621            cols_new.push(col.clone());
4622        }
4623        if before_idx == self.columns.len() {
4624            for _ in 0..count {
4625                cols_new.push(empty_col(&lens));
4626            }
4627        }
4628        // Fix column indices
4629        for (idx, col) in cols_new.iter_mut().enumerate() {
4630            col.index = idx as u32;
4631        }
4632        self.columns = cols_new;
4633        // chunk_starts unchanged; lens were matched
4634    }
4635
4636    /// Delete `count` columns starting at absolute 0-based column `start`.
4637    pub fn delete_columns(&mut self, start: usize, count: usize) {
4638        if count == 0 || self.columns.is_empty() {
4639            return;
4640        }
4641        let end = (start + count).min(self.columns.len());
4642        if start >= end {
4643            return;
4644        }
4645        self.columns.drain(start..end);
4646        for (idx, col) in self.columns.iter_mut().enumerate() {
4647            col.index = idx as u32;
4648        }
4649    }
4650}
4651
4652#[derive(Debug, Clone, Copy)]
4653pub struct ColumnShape {
4654    pub index: u32,
4655    pub chunks: usize,
4656    pub rows: usize,
4657    pub has_num: bool,
4658    pub has_bool: bool,
4659    pub has_text: bool,
4660    pub has_err: bool,
4661}
4662
4663#[cfg(test)]
4664mod tests {
4665    use super::*;
4666
4667    #[test]
4668    fn known_error_storage_codes_are_stable() {
4669        let cases = [
4670            (ExcelErrorKind::Null, 1),
4671            (ExcelErrorKind::Ref, 2),
4672            (ExcelErrorKind::Name, 3),
4673            (ExcelErrorKind::Value, 4),
4674            (ExcelErrorKind::Div, 5),
4675            (ExcelErrorKind::Na, 6),
4676            (ExcelErrorKind::Num, 7),
4677            (ExcelErrorKind::Error, 8),
4678            (ExcelErrorKind::NImpl, 9),
4679            (ExcelErrorKind::Spill, 10),
4680            (ExcelErrorKind::Calc, 11),
4681            (ExcelErrorKind::Circ, 12),
4682            (ExcelErrorKind::Cancelled, 13),
4683        ];
4684        for (kind, code) in cases {
4685            assert_eq!(map_error_code(kind), code, "{kind:?}");
4686            assert_eq!(unmap_error_code(code), kind, "code {code}");
4687        }
4688        assert_eq!(unmap_error_code(u8::MAX), ExcelErrorKind::Error);
4689    }
4690    use arrow_array::Array;
4691    use arrow_schema::DataType;
4692    use chrono::{Datelike, Timelike};
4693
4694    #[test]
4695    fn sparse_constructor_defaults_to_excel_1900_and_decodes_excel_1904() {
4696        let date = chrono::NaiveDate::from_ymd_opt(1904, 1, 1).unwrap();
4697        let datetime = date.and_hms_opt(12, 0, 0).unwrap();
4698
4699        let mut default_sheet = ArrowSheet::new_sparse("Default", 1, 1, 16);
4700        assert_eq!(
4701            default_sheet.date_system,
4702            crate::engine::DateSystem::Excel1900
4703        );
4704        default_sheet.set_sparse_overlay_value(0, 0, OverlayValue::DateTime(1462.5));
4705        assert_eq!(
4706            default_sheet.get_cell_value(0, 0),
4707            LiteralValue::DateTime(datetime)
4708        );
4709
4710        let mut excel_1904 = ArrowSheet::new_sparse_with_date_system(
4711            "1904",
4712            1,
4713            1,
4714            16,
4715            crate::engine::DateSystem::Excel1904,
4716        );
4717        assert_eq!(excel_1904.date_system, crate::engine::DateSystem::Excel1904);
4718        excel_1904.set_sparse_overlay_value(0, 0, OverlayValue::DateTime(0.5));
4719        assert_eq!(
4720            excel_1904.get_cell_value(0, 0),
4721            LiteralValue::DateTime(datetime)
4722        );
4723    }
4724
4725    #[test]
4726    fn datetime_lanes_round_trip_the_sheet_date_system() {
4727        let date = chrono::NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
4728        let datetime = date.and_hms_opt(12, 30, 0).unwrap();
4729
4730        for system in [
4731            crate::engine::DateSystem::Excel1900,
4732            crate::engine::DateSystem::Excel1904,
4733        ] {
4734            let values = vec![LiteralValue::Date(date), LiteralValue::DateTime(datetime)];
4735            let mut ingest = IngestBuilder::new("Sheet1", 2, 16, system);
4736            ingest.append_row(&values).unwrap();
4737            let sheet = ingest.finish();
4738
4739            assert_eq!(sheet.date_system, system);
4740            assert_eq!(sheet.get_cell_value(0, 0), values[0]);
4741            assert_eq!(sheet.get_cell_value(0, 1), values[1]);
4742            let view = sheet.range_view(0, 0, 0, 1);
4743            assert_eq!(view.get_cell(0, 0), values[0]);
4744            assert_eq!(view.get_cell(0, 1), values[1]);
4745
4746            let mut sparse = ArrowSheet::new_sparse_with_date_system("Sparse", 1, 1, 16, system);
4747            sparse.set_sparse_overlay_value(
4748                0,
4749                0,
4750                OverlayValue::from_literal_value(&values[1], system),
4751            );
4752            assert_eq!(sparse.get_cell_value(0, 0), values[1]);
4753        }
4754    }
4755
4756    fn add_overlay_stats(into: &mut OverlayDebugStats, next: OverlayDebugStats) {
4757        into.points += next.points;
4758        into.sparse_fragments += next.sparse_fragments;
4759        into.dense_fragments += next.dense_fragments;
4760        into.run_fragments += next.run_fragments;
4761        into.covered_len += next.covered_len;
4762    }
4763
4764    fn column_overlay_stats(
4765        sheet: &ArrowSheet,
4766        col_idx: usize,
4767        computed: bool,
4768    ) -> OverlayDebugStats {
4769        let mut stats = OverlayDebugStats::default();
4770        let Some(column) = sheet.columns.get(col_idx) else {
4771            return stats;
4772        };
4773        for chunk in &column.chunks {
4774            add_overlay_stats(
4775                &mut stats,
4776                if computed {
4777                    chunk.computed_overlay.debug_stats()
4778                } else {
4779                    chunk.overlay.debug_stats()
4780                },
4781            );
4782        }
4783        for chunk in column.sparse_chunks.values() {
4784            add_overlay_stats(
4785                &mut stats,
4786                if computed {
4787                    chunk.computed_overlay.debug_stats()
4788                } else {
4789                    chunk.overlay.debug_stats()
4790                },
4791            );
4792        }
4793        stats
4794    }
4795
4796    fn assert_column_overlays_normalized(sheet: &ArrowSheet, col_idx: usize) {
4797        let column = &sheet.columns[col_idx];
4798        for chunk in &column.chunks {
4799            assert!(chunk.overlay.debug_is_normalized());
4800            assert!(chunk.computed_overlay.debug_is_normalized());
4801            assert_eq!(
4802                chunk.overlay.estimated_bytes(),
4803                chunk.overlay.debug_recomputed_estimated_bytes()
4804            );
4805            assert_eq!(
4806                chunk.computed_overlay.estimated_bytes(),
4807                chunk.computed_overlay.debug_recomputed_estimated_bytes()
4808            );
4809        }
4810        for chunk in column.sparse_chunks.values() {
4811            assert!(chunk.overlay.debug_is_normalized());
4812            assert!(chunk.computed_overlay.debug_is_normalized());
4813            assert_eq!(
4814                chunk.overlay.estimated_bytes(),
4815                chunk.overlay.debug_recomputed_estimated_bytes()
4816            );
4817            assert_eq!(
4818                chunk.computed_overlay.estimated_bytes(),
4819                chunk.computed_overlay.debug_recomputed_estimated_bytes()
4820            );
4821        }
4822    }
4823
4824    fn column_computed_overlay_estimated_bytes(sheet: &ArrowSheet, col_idx: usize) -> usize {
4825        let Some(column) = sheet.columns.get(col_idx) else {
4826            return 0;
4827        };
4828        column
4829            .chunks
4830            .iter()
4831            .map(|chunk| chunk.computed_overlay.estimated_bytes())
4832            .chain(
4833                column
4834                    .sparse_chunks
4835                    .values()
4836                    .map(|chunk| chunk.computed_overlay.estimated_bytes()),
4837            )
4838            .fold(0usize, usize::saturating_add)
4839    }
4840
4841    #[derive(Debug, Clone, Copy)]
4842    enum Phase4ProbeFixture {
4843        PointNumeric,
4844        DenseNumeric,
4845        RunNumeric,
4846        SparseNumeric,
4847        EmptyRun,
4848        MixedDense,
4849    }
4850
4851    impl Phase4ProbeFixture {
4852        fn name(self) -> &'static str {
4853            match self {
4854                Phase4ProbeFixture::PointNumeric => "point_numeric",
4855                Phase4ProbeFixture::DenseNumeric => "dense_numeric",
4856                Phase4ProbeFixture::RunNumeric => "run_numeric",
4857                Phase4ProbeFixture::SparseNumeric => "sparse_numeric",
4858                Phase4ProbeFixture::EmptyRun => "empty_run",
4859                Phase4ProbeFixture::MixedDense => "mixed_dense",
4860            }
4861        }
4862    }
4863
4864    #[derive(Debug, serde::Serialize)]
4865    struct Phase4ProbeOp {
4866        ms: f64,
4867        segments: usize,
4868        arrays: usize,
4869        rows_scanned: usize,
4870        checksum: f64,
4871        non_null: usize,
4872    }
4873
4874    #[derive(Debug, serde::Serialize)]
4875    struct Phase4ProbeRow {
4876        fixture: &'static str,
4877        rows: usize,
4878        points: usize,
4879        sparse_fragments: usize,
4880        dense_fragments: usize,
4881        run_fragments: usize,
4882        covered_len: usize,
4883        overlay_estimated_bytes: usize,
4884        numbers: Phase4ProbeOp,
4885        type_tags: Phase4ProbeOp,
4886        lowered_text: Phase4ProbeOp,
4887        get_cell_scan: Phase4ProbeOp,
4888        select_stats: OverlaySelectStats,
4889    }
4890
4891    fn build_phase4_probe_sheet(rows: usize, fixture: Phase4ProbeFixture) -> ArrowSheet {
4892        let mut builder =
4893            IngestBuilder::new("S", 1, rows.max(1), crate::engine::DateSystem::Excel1900);
4894        for row in 0..rows {
4895            builder
4896                .append_row(&[LiteralValue::Number((row + 1) as f64)])
4897                .unwrap();
4898        }
4899        let mut sheet = builder.finish();
4900        let chunk = sheet.columns[0].chunk_mut(0).unwrap();
4901        match fixture {
4902            Phase4ProbeFixture::PointNumeric => {
4903                for row in 0..rows {
4904                    chunk
4905                        .computed_overlay
4906                        .set_scalar(row, OverlayValue::Number((row + 1) as f64));
4907                }
4908            }
4909            Phase4ProbeFixture::DenseNumeric => {
4910                chunk.computed_overlay.apply_fragment(
4911                    OverlayFragment::dense_range(
4912                        0,
4913                        (0..rows)
4914                            .map(|row| OverlayValue::Number((row + 1) as f64))
4915                            .collect(),
4916                    )
4917                    .unwrap(),
4918                );
4919            }
4920            Phase4ProbeFixture::RunNumeric => {
4921                chunk.computed_overlay.apply_fragment(
4922                    OverlayFragment::run_range(0, vec![OverlayValue::Number(1.0); rows]).unwrap(),
4923                );
4924            }
4925            Phase4ProbeFixture::SparseNumeric => {
4926                chunk.computed_overlay.apply_fragment(
4927                    OverlayFragment::sparse_offsets(
4928                        (0..rows)
4929                            .step_by(10)
4930                            .map(|row| (row, OverlayValue::Number(10.0)))
4931                            .collect(),
4932                    )
4933                    .unwrap(),
4934                );
4935            }
4936            Phase4ProbeFixture::EmptyRun => {
4937                chunk.computed_overlay.apply_fragment(
4938                    OverlayFragment::run_range(0, vec![OverlayValue::Empty; rows]).unwrap(),
4939                );
4940            }
4941            Phase4ProbeFixture::MixedDense => {
4942                let pattern = [
4943                    OverlayValue::Number(1.0),
4944                    OverlayValue::Boolean(true),
4945                    OverlayValue::Text(Arc::from("Alpha")),
4946                    OverlayValue::Empty,
4947                    OverlayValue::Error(map_error_code(ExcelErrorKind::Div)),
4948                    OverlayValue::Pending,
4949                    OverlayValue::DateTime(45000.25),
4950                    OverlayValue::Duration(0.5),
4951                ];
4952                chunk.computed_overlay.apply_fragment(
4953                    OverlayFragment::dense_range(
4954                        0,
4955                        (0..rows)
4956                            .map(|row| pattern[row % pattern.len()].clone())
4957                            .collect(),
4958                    )
4959                    .unwrap(),
4960                );
4961            }
4962        }
4963        sheet
4964    }
4965
4966    fn measure_probe_numbers(sheet: &ArrowSheet, rows: usize) -> Phase4ProbeOp {
4967        let view = sheet.range_view(0, 0, rows.saturating_sub(1), 0);
4968        let start = std::time::Instant::now();
4969        let mut segments = 0usize;
4970        let mut arrays = 0usize;
4971        let mut rows_scanned = 0usize;
4972        let mut checksum = 0.0;
4973        let mut non_null = 0usize;
4974        for segment in view.numbers_slices() {
4975            let (_row_start, row_len, cols) = segment.unwrap();
4976            segments += 1;
4977            rows_scanned += row_len;
4978            for array in cols {
4979                arrays += 1;
4980                for idx in 0..array.len() {
4981                    if array.is_valid(idx) {
4982                        checksum += array.value(idx);
4983                        non_null += 1;
4984                    }
4985                }
4986            }
4987        }
4988        Phase4ProbeOp {
4989            ms: start.elapsed().as_secs_f64() * 1000.0,
4990            segments,
4991            arrays,
4992            rows_scanned,
4993            checksum,
4994            non_null,
4995        }
4996    }
4997
4998    fn measure_probe_type_tags(sheet: &ArrowSheet, rows: usize) -> Phase4ProbeOp {
4999        let view = sheet.range_view(0, 0, rows.saturating_sub(1), 0);
5000        let start = std::time::Instant::now();
5001        let mut segments = 0usize;
5002        let mut arrays = 0usize;
5003        let mut rows_scanned = 0usize;
5004        let mut checksum = 0.0;
5005        let mut non_null = 0usize;
5006        for segment in view.type_tags_slices() {
5007            let (_row_start, row_len, cols) = segment.unwrap();
5008            segments += 1;
5009            rows_scanned += row_len;
5010            for array in cols {
5011                arrays += 1;
5012                for idx in 0..array.len() {
5013                    if array.is_valid(idx) {
5014                        checksum += array.value(idx) as f64;
5015                        non_null += 1;
5016                    }
5017                }
5018            }
5019        }
5020        Phase4ProbeOp {
5021            ms: start.elapsed().as_secs_f64() * 1000.0,
5022            segments,
5023            arrays,
5024            rows_scanned,
5025            checksum,
5026            non_null,
5027        }
5028    }
5029
5030    fn measure_probe_lowered_text(sheet: &ArrowSheet, rows: usize) -> Phase4ProbeOp {
5031        let view = sheet.range_view(0, 0, rows.saturating_sub(1), 0);
5032        let start = std::time::Instant::now();
5033        let mut segments = 0usize;
5034        let mut arrays = 0usize;
5035        let mut rows_scanned = 0usize;
5036        let mut checksum = 0.0;
5037        let mut non_null = 0usize;
5038        for segment in view.lowered_text_slices() {
5039            let (_row_start, row_len, cols) = segment.unwrap();
5040            segments += 1;
5041            rows_scanned += row_len;
5042            for array in cols {
5043                arrays += 1;
5044                for idx in 0..array.len() {
5045                    if array.is_valid(idx) {
5046                        checksum += array.value(idx).len() as f64;
5047                        non_null += 1;
5048                    }
5049                }
5050            }
5051        }
5052        Phase4ProbeOp {
5053            ms: start.elapsed().as_secs_f64() * 1000.0,
5054            segments,
5055            arrays,
5056            rows_scanned,
5057            checksum,
5058            non_null,
5059        }
5060    }
5061
5062    fn literal_probe_weight(value: LiteralValue) -> f64 {
5063        match value {
5064            LiteralValue::Empty => 0.0,
5065            LiteralValue::Int(value) => value as f64,
5066            LiteralValue::Number(value) => value,
5067            LiteralValue::Boolean(value) => {
5068                if value {
5069                    1.0
5070                } else {
5071                    0.0
5072                }
5073            }
5074            LiteralValue::Text(value) => value.len() as f64,
5075            LiteralValue::Error(_) => -1.0,
5076            LiteralValue::Date(value) => value.num_days_from_ce() as f64,
5077            LiteralValue::DateTime(value) => value.and_utc().timestamp() as f64,
5078            LiteralValue::Time(value) => value.num_seconds_from_midnight() as f64,
5079            LiteralValue::Duration(value) => value.num_seconds() as f64,
5080            LiteralValue::Array(values) => values.len() as f64,
5081            LiteralValue::Pending => -2.0,
5082        }
5083    }
5084
5085    fn measure_probe_get_cell(sheet: &ArrowSheet, rows: usize) -> Phase4ProbeOp {
5086        let view = sheet.range_view(0, 0, rows.saturating_sub(1), 0);
5087        let start = std::time::Instant::now();
5088        let mut checksum = 0.0;
5089        for row in 0..rows {
5090            checksum += literal_probe_weight(view.get_cell(row, 0));
5091        }
5092        Phase4ProbeOp {
5093            ms: start.elapsed().as_secs_f64() * 1000.0,
5094            segments: 1,
5095            arrays: 0,
5096            rows_scanned: rows,
5097            checksum,
5098            non_null: rows,
5099        }
5100    }
5101
5102    fn run_phase4_probe_fixture(rows: usize, fixture: Phase4ProbeFixture) -> Phase4ProbeRow {
5103        let sheet = build_phase4_probe_sheet(rows, fixture);
5104        assert_column_overlays_normalized(&sheet, 0);
5105        let stats = column_overlay_stats(&sheet, 0, true);
5106        reset_overlay_select_stats();
5107        let numbers = measure_probe_numbers(&sheet, rows);
5108        let type_tags = measure_probe_type_tags(&sheet, rows);
5109        let lowered_text = measure_probe_lowered_text(&sheet, rows);
5110        let select_stats = snapshot_overlay_select_stats();
5111        let get_cell_scan = measure_probe_get_cell(&sheet, rows);
5112        Phase4ProbeRow {
5113            fixture: fixture.name(),
5114            rows,
5115            points: stats.points,
5116            sparse_fragments: stats.sparse_fragments,
5117            dense_fragments: stats.dense_fragments,
5118            run_fragments: stats.run_fragments,
5119            covered_len: stats.covered_len,
5120            overlay_estimated_bytes: column_computed_overlay_estimated_bytes(&sheet, 0),
5121            numbers,
5122            type_tags,
5123            lowered_text,
5124            get_cell_scan,
5125            select_stats,
5126        }
5127    }
5128
5129    #[test]
5130    #[ignore = "manual Phase 4 observability probe; run with --ignored --nocapture"]
5131    fn phase4_overlay_rangeview_observability_probe() {
5132        let rows = std::env::var("FORMUALIZER_OVERLAY_PROBE_ROWS")
5133            .ok()
5134            .and_then(|value| value.parse::<usize>().ok())
5135            .unwrap_or(100_000)
5136            .max(1);
5137        for fixture in [
5138            Phase4ProbeFixture::PointNumeric,
5139            Phase4ProbeFixture::DenseNumeric,
5140            Phase4ProbeFixture::RunNumeric,
5141            Phase4ProbeFixture::SparseNumeric,
5142            Phase4ProbeFixture::EmptyRun,
5143            Phase4ProbeFixture::MixedDense,
5144        ] {
5145            let row = run_phase4_probe_fixture(rows, fixture);
5146            println!("{}", serde_json::to_string(&row).unwrap());
5147        }
5148    }
5149
5150    #[test]
5151    fn ingest_mixed_rows_into_lanes_and_tags() {
5152        let mut b = IngestBuilder::new("Sheet1", 1, 1024, crate::engine::DateSystem::Excel1900);
5153        let data = vec![
5154            LiteralValue::Number(42.5),                   // Number
5155            LiteralValue::Empty,                          // Empty
5156            LiteralValue::Text(String::new()),            // Empty text (Text lane)
5157            LiteralValue::Boolean(true),                  // Boolean
5158            LiteralValue::Error(ExcelError::new_value()), // Error
5159        ];
5160        for v in &data {
5161            b.append_row(std::slice::from_ref(v)).unwrap();
5162        }
5163        let sheet = b.finish();
5164        assert_eq!(sheet.nrows, 5);
5165        assert_eq!(sheet.columns.len(), 1);
5166        assert_eq!(sheet.columns[0].chunks.len(), 1);
5167        let ch = &sheet.columns[0].chunks[0];
5168
5169        // Type tags
5170        let tags = ch.type_tag.values();
5171        assert_eq!(tags.len(), 5);
5172        assert_eq!(tags[0], TypeTag::Number as u8);
5173        assert_eq!(tags[1], TypeTag::Empty as u8);
5174        assert_eq!(tags[2], TypeTag::Text as u8);
5175        assert_eq!(tags[3], TypeTag::Boolean as u8);
5176        assert_eq!(tags[4], TypeTag::Error as u8);
5177
5178        // Numbers lane validity
5179        let nums = ch.numbers.as_ref().unwrap();
5180        assert_eq!(nums.len(), 5);
5181        assert_eq!(nums.null_count(), 4);
5182        assert!(nums.is_valid(0));
5183
5184        // Booleans lane validity
5185        let bools = ch.booleans.as_ref().unwrap();
5186        assert_eq!(bools.len(), 5);
5187        assert_eq!(bools.null_count(), 4);
5188        assert!(bools.is_valid(3));
5189
5190        // Text lane validity
5191        let txt = ch.text.as_ref().unwrap();
5192        assert_eq!(txt.len(), 5);
5193        assert_eq!(txt.null_count(), 4);
5194        assert!(txt.is_valid(2)); // ""
5195
5196        // Errors lane
5197        let errs = ch.errors.as_ref().unwrap();
5198        assert_eq!(errs.len(), 5);
5199        assert_eq!(errs.null_count(), 4);
5200        assert!(errs.is_valid(4));
5201    }
5202
5203    #[test]
5204    fn range_view_get_cell_and_padding() {
5205        let mut b = IngestBuilder::new("S", 2, 2, crate::engine::DateSystem::Excel1900);
5206        b.append_row(&[LiteralValue::Number(1.0), LiteralValue::Text("".into())])
5207            .unwrap();
5208        b.append_row(&[LiteralValue::Empty, LiteralValue::Text("x".into())])
5209            .unwrap();
5210        b.append_row(&[LiteralValue::Boolean(true), LiteralValue::Empty])
5211            .unwrap();
5212        let sheet = b.finish();
5213        let rv = sheet.range_view(0, 0, 2, 1);
5214        assert_eq!(rv.dims(), (3, 2));
5215        // Inside
5216        assert_eq!(rv.get_cell(0, 0), LiteralValue::Number(1.0));
5217        assert_eq!(rv.get_cell(0, 1), LiteralValue::Text(String::new())); // empty string
5218        assert_eq!(rv.get_cell(1, 0), LiteralValue::Empty); // truly Empty
5219        assert_eq!(rv.get_cell(2, 0), LiteralValue::Boolean(true));
5220        // OOB padding
5221        assert_eq!(rv.get_cell(3, 0), LiteralValue::Empty);
5222        assert_eq!(rv.get_cell(0, 2), LiteralValue::Empty);
5223
5224        // Numbers slices should produce one 2-row and one 1-row segment
5225        let nums: Vec<_> = rv.numbers_slices().map(|r| r.unwrap()).collect();
5226        assert_eq!(nums.len(), 2);
5227        assert_eq!(nums[0].0, 0);
5228        assert_eq!(nums[0].1, 2);
5229        assert_eq!(nums[1].0, 2);
5230        assert_eq!(nums[1].1, 1);
5231    }
5232
5233    #[test]
5234    fn overlay_precedence_user_over_computed() {
5235        let mut b = IngestBuilder::new("S", 1, 8, crate::engine::DateSystem::Excel1900);
5236        b.append_row(&[LiteralValue::Number(1.0)]).unwrap();
5237        b.append_row(&[LiteralValue::Empty]).unwrap();
5238        b.append_row(&[LiteralValue::Empty]).unwrap();
5239        let mut sheet = b.finish();
5240
5241        let (ch_i, off) = sheet.chunk_of_row(0).unwrap();
5242        sheet.columns[0].chunks[ch_i]
5243            .computed_overlay
5244            .set(off, OverlayValue::Number(2.0));
5245
5246        let rv0 = sheet.range_view(0, 0, 0, 0);
5247        assert_eq!(rv0.get_cell(0, 0), LiteralValue::Number(2.0));
5248        let nums0: Vec<_> = rv0.numbers_slices().map(|r| r.unwrap()).collect();
5249        assert_eq!(nums0.len(), 1);
5250        assert_eq!(nums0[0].2[0].value(0), 2.0);
5251
5252        sheet.columns[0].chunks[ch_i]
5253            .overlay
5254            .set(off, OverlayValue::Number(3.0));
5255
5256        let rv1 = sheet.range_view(0, 0, 0, 0);
5257        assert_eq!(rv1.get_cell(0, 0), LiteralValue::Number(3.0));
5258        let nums1: Vec<_> = rv1.numbers_slices().map(|r| r.unwrap()).collect();
5259        assert_eq!(nums1.len(), 1);
5260        assert_eq!(nums1[0].2[0].value(0), 3.0);
5261    }
5262
5263    #[test]
5264    fn overlay_slice_preserves_explicit_empty_and_offsets() {
5265        let mut overlay = Overlay::new();
5266        overlay.set(2, OverlayValue::Number(2.0));
5267        overlay.set(4, OverlayValue::Empty);
5268        overlay.set(6, OverlayValue::Text(Arc::from("outside")));
5269
5270        let sliced = overlay.slice(1, 4);
5271        assert!(sliced.get_scalar(0).is_none());
5272        assert_eq!(
5273            sliced.get_scalar(1).unwrap().to_literal(),
5274            LiteralValue::Number(2.0)
5275        );
5276        assert_eq!(
5277            sliced.get_scalar(3).unwrap().to_literal(),
5278            LiteralValue::Empty
5279        );
5280        assert!(sliced.get_scalar(5).is_none());
5281    }
5282
5283    #[test]
5284    fn overlay_cascade_user_empty_masks_computed_and_base() {
5285        let mut user = Overlay::new();
5286        let mut computed = Overlay::new();
5287        computed.set(1, OverlayValue::Number(42.0));
5288        user.set(1, OverlayValue::Empty);
5289
5290        let cascade = OverlayCascade::new(&user, &computed);
5291        assert_eq!(
5292            cascade.get_scalar(1).unwrap().to_literal(),
5293            LiteralValue::Empty
5294        );
5295        assert!(cascade.has_any_in_range(1..2));
5296    }
5297
5298    #[test]
5299    fn overlay_storage_pointmap_backward_compat_get_set_remove() {
5300        let mut overlay = Overlay::new();
5301        assert!(overlay.is_empty());
5302
5303        let delta = overlay.set_scalar(1, OverlayValue::Number(10.0));
5304        assert!(delta > 0);
5305        assert_eq!(overlay.len(), 1);
5306        assert_eq!(
5307            overlay.get_scalar(1).unwrap().to_literal(),
5308            LiteralValue::Number(10.0)
5309        );
5310
5311        let replace_delta = overlay.set_scalar(1, OverlayValue::Text(Arc::from("x")));
5312        assert_ne!(replace_delta, 0);
5313        assert_eq!(overlay.len(), 1);
5314        assert_eq!(
5315            overlay.get_scalar(1).unwrap().to_literal(),
5316            LiteralValue::Text("x".into())
5317        );
5318
5319        let remove_delta = overlay.remove_scalar(1);
5320        assert!(remove_delta < 0);
5321        assert!(overlay.is_empty());
5322        assert!(overlay.get_scalar(1).is_none());
5323    }
5324
5325    #[test]
5326    fn overlay_remove_range_splits_fragments_and_points() {
5327        let mut overlay = Overlay::new();
5328        overlay.set_scalar(2, OverlayValue::Number(20.0));
5329        overlay.apply_fragment(
5330            OverlayFragment::dense_range(
5331                0,
5332                (0..6)
5333                    .map(|i| OverlayValue::Number(i as f64))
5334                    .collect::<Vec<_>>(),
5335            )
5336            .unwrap(),
5337        );
5338        overlay.set_scalar(3, OverlayValue::Number(30.0));
5339        overlay.set_scalar(8, OverlayValue::Number(80.0));
5340
5341        let delta = overlay.remove_range(2..5);
5342
5343        assert!(delta < 0);
5344        assert_eq!(
5345            overlay.get_scalar(0).unwrap().to_literal(),
5346            LiteralValue::Number(0.0)
5347        );
5348        assert_eq!(
5349            overlay.get_scalar(1).unwrap().to_literal(),
5350            LiteralValue::Number(1.0)
5351        );
5352        assert!(overlay.get_scalar(2).is_none());
5353        assert!(overlay.get_scalar(3).is_none());
5354        assert!(overlay.get_scalar(4).is_none());
5355        assert_eq!(
5356            overlay.get_scalar(5).unwrap().to_literal(),
5357            LiteralValue::Number(5.0)
5358        );
5359        assert_eq!(
5360            overlay.get_scalar(8).unwrap().to_literal(),
5361            LiteralValue::Number(80.0)
5362        );
5363        assert!(overlay.debug_is_normalized());
5364        assert_eq!(
5365            overlay.estimated_bytes(),
5366            overlay.debug_recomputed_estimated_bytes()
5367        );
5368    }
5369
5370    #[test]
5371    fn overlay_storage_no_fragments_behavior_matches_old_map() {
5372        let mut overlay = Overlay::new();
5373        overlay.set_scalar(0, OverlayValue::Number(1.0));
5374        overlay.set_scalar(3, OverlayValue::Empty);
5375
5376        assert!(overlay.has_any_in_range(0..1));
5377        assert!(!overlay.has_any_in_range(1..3));
5378        assert!(overlay.has_any_in_range(3..4));
5379
5380        let sliced = overlay.slice(2, 3);
5381        assert!(sliced.get_scalar(0).is_none());
5382        assert_eq!(
5383            sliced.get_scalar(1).unwrap().to_literal(),
5384            LiteralValue::Empty
5385        );
5386    }
5387
5388    #[test]
5389    fn overlay_cascade_user_layer_masks_computed_fragment_regardless_of_sequence() {
5390        let mut user = Overlay::new();
5391        let mut computed = Overlay::new();
5392
5393        user.set_scalar(0, OverlayValue::Number(3.0));
5394        computed.apply_fragment(
5395            OverlayFragment::dense_range(0, vec![OverlayValue::Number(2.0)]).unwrap(),
5396        );
5397
5398        let cascade = OverlayCascade::new(&user, &computed);
5399        assert_eq!(
5400            cascade.get_scalar(0).unwrap().to_literal(),
5401            LiteralValue::Number(3.0)
5402        );
5403    }
5404
5405    #[test]
5406    fn overlay_same_layer_later_point_replaces_fragment_cell() {
5407        let mut overlay = Overlay::new();
5408        overlay.apply_fragment(
5409            OverlayFragment::dense_range(
5410                0,
5411                vec![
5412                    OverlayValue::Number(1.0),
5413                    OverlayValue::Number(2.0),
5414                    OverlayValue::Number(3.0),
5415                ],
5416            )
5417            .unwrap(),
5418        );
5419
5420        overlay.set_scalar(1, OverlayValue::Number(99.0));
5421
5422        assert_eq!(
5423            overlay.get_scalar(0).unwrap().to_literal(),
5424            LiteralValue::Number(1.0)
5425        );
5426        assert_eq!(
5427            overlay.get_scalar(1).unwrap().to_literal(),
5428            LiteralValue::Number(99.0)
5429        );
5430        assert_eq!(
5431            overlay.get_scalar(2).unwrap().to_literal(),
5432            LiteralValue::Number(3.0)
5433        );
5434    }
5435
5436    #[test]
5437    fn overlay_same_layer_later_fragment_replaces_point_range() {
5438        let mut overlay = Overlay::new();
5439        overlay.set_scalar(0, OverlayValue::Number(1.0));
5440        overlay.set_scalar(1, OverlayValue::Number(2.0));
5441        overlay.set_scalar(2, OverlayValue::Number(3.0));
5442
5443        overlay.apply_fragment(
5444            OverlayFragment::dense_range(
5445                0,
5446                vec![
5447                    OverlayValue::Number(10.0),
5448                    OverlayValue::Number(20.0),
5449                    OverlayValue::Number(30.0),
5450                ],
5451            )
5452            .unwrap(),
5453        );
5454
5455        let stats = overlay.debug_stats();
5456        assert_eq!(stats.points, 0);
5457        assert_eq!(stats.dense_fragments, 1);
5458        assert!(overlay.debug_is_normalized());
5459        assert_eq!(
5460            overlay.get_scalar(0).unwrap().to_literal(),
5461            LiteralValue::Number(10.0)
5462        );
5463        assert_eq!(
5464            overlay.get_scalar(1).unwrap().to_literal(),
5465            LiteralValue::Number(20.0)
5466        );
5467        assert_eq!(
5468            overlay.get_scalar(2).unwrap().to_literal(),
5469            LiteralValue::Number(30.0)
5470        );
5471    }
5472
5473    #[test]
5474    fn overlay_sparse_far_apart_replacement_does_not_rewrite_unrelated_dense_fragment() {
5475        let mut overlay = Overlay::new();
5476        overlay.apply_fragment(
5477            OverlayFragment::dense_range(100, vec![OverlayValue::Number(1.0); 10]).unwrap(),
5478        );
5479
5480        overlay.apply_fragment(
5481            OverlayFragment::sparse_offsets(vec![
5482                (0, OverlayValue::Empty),
5483                (1000, OverlayValue::Number(1000.0)),
5484            ])
5485            .unwrap(),
5486        );
5487
5488        let stats = overlay.debug_stats();
5489        assert_eq!(stats.dense_fragments, 1);
5490        assert_eq!(stats.sparse_fragments, 1);
5491        assert_eq!(stats.run_fragments, 0);
5492        assert!(overlay.debug_is_normalized());
5493        assert_eq!(
5494            overlay.get_scalar(105).unwrap().to_literal(),
5495            LiteralValue::Number(1.0)
5496        );
5497        assert_eq!(
5498            overlay.get_scalar(0).unwrap().to_literal(),
5499            LiteralValue::Empty
5500        );
5501        assert_eq!(
5502            overlay.get_scalar(1000).unwrap().to_literal(),
5503            LiteralValue::Number(1000.0)
5504        );
5505    }
5506
5507    #[test]
5508    fn overlay_sparse_offsets_are_sorted_unique_last_write_wins() {
5509        let mut overlay = Overlay::new();
5510        overlay.apply_fragment(
5511            OverlayFragment::sparse_offsets(vec![
5512                (3, OverlayValue::Number(3.0)),
5513                (1, OverlayValue::Number(1.0)),
5514                (3, OverlayValue::Number(33.0)),
5515            ])
5516            .unwrap(),
5517        );
5518
5519        let stats = overlay.debug_stats();
5520        assert_eq!(stats.sparse_fragments, 1);
5521        assert_eq!(overlay.len(), 2);
5522        assert_eq!(
5523            overlay.get_scalar(1).unwrap().to_literal(),
5524            LiteralValue::Number(1.0)
5525        );
5526        assert_eq!(
5527            overlay.get_scalar(3).unwrap().to_literal(),
5528            LiteralValue::Number(33.0)
5529        );
5530        assert!(overlay.debug_is_normalized());
5531    }
5532
5533    #[test]
5534    fn overlay_dense_point_replacement_splits_dense_not_sparse() {
5535        let mut overlay = Overlay::new();
5536        overlay.apply_fragment(
5537            OverlayFragment::dense_range(
5538                0,
5539                (0..6)
5540                    .map(|i| OverlayValue::Number(i as f64))
5541                    .collect::<Vec<_>>(),
5542            )
5543            .unwrap(),
5544        );
5545
5546        overlay.set_scalar(3, OverlayValue::Number(99.0));
5547
5548        let stats = overlay.debug_stats();
5549        assert_eq!(stats.points, 1);
5550        assert_eq!(stats.dense_fragments, 2);
5551        assert_eq!(stats.sparse_fragments, 0);
5552        assert!(overlay.debug_is_normalized());
5553        assert_eq!(
5554            overlay.get_scalar(2).unwrap().to_literal(),
5555            LiteralValue::Number(2.0)
5556        );
5557        assert_eq!(
5558            overlay.get_scalar(3).unwrap().to_literal(),
5559            LiteralValue::Number(99.0)
5560        );
5561        assert_eq!(
5562            overlay.get_scalar(4).unwrap().to_literal(),
5563            LiteralValue::Number(4.0)
5564        );
5565    }
5566
5567    #[test]
5568    fn overlay_dense_fragment_replacement_splits_left_and_right_dense() {
5569        let mut overlay = Overlay::new();
5570        overlay.apply_fragment(
5571            OverlayFragment::dense_range(
5572                0,
5573                (0..8)
5574                    .map(|i| OverlayValue::Number(i as f64))
5575                    .collect::<Vec<_>>(),
5576            )
5577            .unwrap(),
5578        );
5579
5580        overlay.apply_fragment(
5581            OverlayFragment::dense_range(
5582                3,
5583                vec![OverlayValue::Number(30.0), OverlayValue::Number(40.0)],
5584            )
5585            .unwrap(),
5586        );
5587
5588        let stats = overlay.debug_stats();
5589        assert_eq!(stats.points, 0);
5590        assert_eq!(stats.dense_fragments, 3);
5591        assert_eq!(stats.sparse_fragments, 0);
5592        assert!(overlay.debug_is_normalized());
5593        assert_eq!(
5594            overlay.get_scalar(2).unwrap().to_literal(),
5595            LiteralValue::Number(2.0)
5596        );
5597        assert_eq!(
5598            overlay.get_scalar(3).unwrap().to_literal(),
5599            LiteralValue::Number(30.0)
5600        );
5601        assert_eq!(
5602            overlay.get_scalar(4).unwrap().to_literal(),
5603            LiteralValue::Number(40.0)
5604        );
5605        assert_eq!(
5606            overlay.get_scalar(5).unwrap().to_literal(),
5607            LiteralValue::Number(5.0)
5608        );
5609    }
5610
5611    #[test]
5612    fn overlay_run_point_replacement_splits_run_not_sparse() {
5613        let mut overlay = Overlay::new();
5614        overlay.apply_fragment(
5615            OverlayFragment::run_range(0, vec![OverlayValue::Number(1.0); 10]).unwrap(),
5616        );
5617
5618        overlay.set_scalar(5, OverlayValue::Number(99.0));
5619
5620        let stats = overlay.debug_stats();
5621        assert_eq!(stats.points, 1);
5622        assert_eq!(stats.run_fragments, 2);
5623        assert_eq!(stats.sparse_fragments, 0);
5624        assert!(overlay.debug_is_normalized());
5625        assert_eq!(
5626            overlay.get_scalar(4).unwrap().to_literal(),
5627            LiteralValue::Number(1.0)
5628        );
5629        assert_eq!(
5630            overlay.get_scalar(5).unwrap().to_literal(),
5631            LiteralValue::Number(99.0)
5632        );
5633        assert_eq!(
5634            overlay.get_scalar(6).unwrap().to_literal(),
5635            LiteralValue::Number(1.0)
5636        );
5637    }
5638
5639    #[test]
5640    fn overlay_run_fragment_replacement_splits_left_and_right_run() {
5641        let mut overlay = Overlay::new();
5642        let values = [
5643            vec![OverlayValue::Number(1.0); 4],
5644            vec![OverlayValue::Number(2.0); 4],
5645            vec![OverlayValue::Number(3.0); 4],
5646        ]
5647        .concat();
5648        overlay.apply_fragment(OverlayFragment::run_range(0, values).unwrap());
5649
5650        overlay.apply_fragment(
5651            OverlayFragment::dense_range(
5652                5,
5653                vec![OverlayValue::Number(50.0), OverlayValue::Number(60.0)],
5654            )
5655            .unwrap(),
5656        );
5657
5658        let stats = overlay.debug_stats();
5659        assert_eq!(stats.run_fragments, 2);
5660        assert_eq!(stats.dense_fragments, 1);
5661        assert_eq!(stats.sparse_fragments, 0);
5662        assert!(overlay.debug_is_normalized());
5663        assert_eq!(
5664            overlay.get_scalar(4).unwrap().to_literal(),
5665            LiteralValue::Number(2.0)
5666        );
5667        assert_eq!(
5668            overlay.get_scalar(5).unwrap().to_literal(),
5669            LiteralValue::Number(50.0)
5670        );
5671        assert_eq!(
5672            overlay.get_scalar(6).unwrap().to_literal(),
5673            LiteralValue::Number(60.0)
5674        );
5675        assert_eq!(
5676            overlay.get_scalar(7).unwrap().to_literal(),
5677            LiteralValue::Number(2.0)
5678        );
5679    }
5680
5681    #[test]
5682    fn overlay_slice_preserves_dense_and_run_encodings() {
5683        let mut overlay = Overlay::new();
5684        overlay.apply_fragment(
5685            OverlayFragment::dense_range(
5686                10,
5687                (0..5)
5688                    .map(|i| OverlayValue::Number(i as f64))
5689                    .collect::<Vec<_>>(),
5690            )
5691            .unwrap(),
5692        );
5693        overlay.apply_fragment(
5694            OverlayFragment::run_range(
5695                20,
5696                [
5697                    vec![OverlayValue::Number(1.0); 3],
5698                    vec![OverlayValue::Number(2.0); 3],
5699                ]
5700                .concat(),
5701            )
5702            .unwrap(),
5703        );
5704
5705        let dense_slice = overlay.slice(12, 2);
5706        let dense_stats = dense_slice.debug_stats();
5707        assert_eq!(dense_stats.dense_fragments, 1);
5708        assert_eq!(dense_stats.sparse_fragments, 0);
5709        assert_eq!(
5710            dense_slice.get_scalar(0).unwrap().to_literal(),
5711            LiteralValue::Number(2.0)
5712        );
5713        assert_eq!(
5714            dense_slice.get_scalar(1).unwrap().to_literal(),
5715            LiteralValue::Number(3.0)
5716        );
5717        assert!(dense_slice.debug_is_normalized());
5718
5719        let run_slice = overlay.slice(22, 3);
5720        let run_stats = run_slice.debug_stats();
5721        assert_eq!(run_stats.run_fragments, 1);
5722        assert_eq!(run_stats.sparse_fragments, 0);
5723        assert_eq!(
5724            run_slice.get_scalar(0).unwrap().to_literal(),
5725            LiteralValue::Number(1.0)
5726        );
5727        assert_eq!(
5728            run_slice.get_scalar(1).unwrap().to_literal(),
5729            LiteralValue::Number(2.0)
5730        );
5731        assert_eq!(
5732            run_slice.get_scalar(2).unwrap().to_literal(),
5733            LiteralValue::Number(2.0)
5734        );
5735        assert!(run_slice.debug_is_normalized());
5736    }
5737
5738    #[test]
5739    fn overlay_computed_empty_run_masks_non_empty_base() {
5740        let mut b = IngestBuilder::new("S", 1, 8, crate::engine::DateSystem::Excel1900);
5741        b.append_row(&[LiteralValue::Number(1.0)]).unwrap();
5742        b.append_row(&[LiteralValue::Number(2.0)]).unwrap();
5743        b.append_row(&[LiteralValue::Number(3.0)]).unwrap();
5744        let mut sheet = b.finish();
5745
5746        let (ch_i, _) = sheet.chunk_of_row(0).unwrap();
5747        sheet.columns[0].chunks[ch_i]
5748            .computed_overlay
5749            .apply_fragment(
5750                OverlayFragment::run_range(
5751                    0,
5752                    vec![
5753                        OverlayValue::Empty,
5754                        OverlayValue::Empty,
5755                        OverlayValue::Empty,
5756                    ],
5757                )
5758                .unwrap(),
5759            );
5760
5761        assert_eq!(sheet.get_cell_value(0, 0), LiteralValue::Empty);
5762        assert_eq!(sheet.get_cell_value(1, 0), LiteralValue::Empty);
5763        assert_eq!(sheet.get_cell_value(2, 0), LiteralValue::Empty);
5764    }
5765
5766    #[test]
5767    fn overlay_fragments_reconstruct_scalars_from_typed_lanes() {
5768        let values = vec![
5769            OverlayValue::Empty,
5770            OverlayValue::Number(1.5),
5771            OverlayValue::DateTime(45000.25),
5772            OverlayValue::Duration(0.5),
5773            OverlayValue::Boolean(true),
5774            OverlayValue::Text(Arc::from("Hello")),
5775            OverlayValue::Error(map_error_code(ExcelErrorKind::Div)),
5776            OverlayValue::Pending,
5777        ];
5778
5779        let mut dense = Overlay::new();
5780        dense.apply_fragment(OverlayFragment::dense_range(0, values.clone()).unwrap());
5781        for (idx, expected) in values.iter().enumerate() {
5782            assert_eq!(
5783                dense.get_scalar(idx).unwrap().to_overlay_value(),
5784                expected.clone()
5785            );
5786        }
5787
5788        let mut sparse = Overlay::new();
5789        sparse.apply_fragment(
5790            OverlayFragment::sparse_offsets(
5791                values
5792                    .iter()
5793                    .cloned()
5794                    .enumerate()
5795                    .map(|(idx, value)| (idx * 2, value))
5796                    .collect(),
5797            )
5798            .unwrap(),
5799        );
5800        for (idx, expected) in values.iter().enumerate() {
5801            assert_eq!(
5802                sparse.get_scalar(idx * 2).unwrap().to_overlay_value(),
5803                expected.clone()
5804            );
5805        }
5806
5807        let mut run = Overlay::new();
5808        run.apply_fragment(
5809            OverlayFragment::run_range(
5810                0,
5811                vec![
5812                    OverlayValue::Number(7.0),
5813                    OverlayValue::Number(7.0),
5814                    OverlayValue::Text(Arc::from("run")),
5815                    OverlayValue::Text(Arc::from("run")),
5816                ],
5817            )
5818            .unwrap(),
5819        );
5820        assert_eq!(
5821            run.get_scalar(0).unwrap().to_overlay_value(),
5822            OverlayValue::Number(7.0)
5823        );
5824        assert_eq!(
5825            run.get_scalar(2).unwrap().to_overlay_value(),
5826            OverlayValue::Text(Arc::from("run"))
5827        );
5828    }
5829
5830    #[test]
5831    fn overlay_iter_returns_complete_logical_entries() {
5832        let mut overlay = Overlay::new();
5833        overlay.apply_fragment(
5834            OverlayFragment::dense_range(
5835                2,
5836                vec![OverlayValue::Number(2.0), OverlayValue::Number(3.0)],
5837            )
5838            .unwrap(),
5839        );
5840        overlay.set_scalar(5, OverlayValue::Text(Arc::from("point")));
5841
5842        let entries: Vec<_> = overlay.iter().collect();
5843        assert_eq!(
5844            entries,
5845            vec![
5846                (2, OverlayValue::Number(2.0)),
5847                (3, OverlayValue::Number(3.0)),
5848                (5, OverlayValue::Text(Arc::from("point"))),
5849            ]
5850        );
5851        assert_eq!(overlay.iter_points().count(), 1);
5852    }
5853
5854    #[test]
5855    fn overlay_fragment_estimates_follow_encoded_shapes() {
5856        let mut points = Overlay::new();
5857        for idx in 0..512 {
5858            points.set_scalar(idx, OverlayValue::Number(idx as f64));
5859        }
5860
5861        let mut dense = Overlay::new();
5862        dense.apply_fragment(
5863            OverlayFragment::dense_range(
5864                0,
5865                (0..512)
5866                    .map(|idx| OverlayValue::Number(idx as f64))
5867                    .collect::<Vec<_>>(),
5868            )
5869            .unwrap(),
5870        );
5871        assert_eq!(
5872            dense.estimated_bytes(),
5873            dense.debug_recomputed_estimated_bytes()
5874        );
5875        assert!(
5876            dense.estimated_bytes() < points.estimated_bytes(),
5877            "dense fragment should account like encoded lanes, not point-map entries"
5878        );
5879
5880        let mut short_run = Overlay::new();
5881        short_run.apply_fragment(
5882            OverlayFragment::run_range(0, vec![OverlayValue::Number(1.0); 8]).unwrap(),
5883        );
5884        let mut long_run = Overlay::new();
5885        long_run.apply_fragment(
5886            OverlayFragment::run_range(0, vec![OverlayValue::Number(1.0); 4096]).unwrap(),
5887        );
5888        assert_eq!(
5889            short_run.estimated_bytes(),
5890            short_run.debug_recomputed_estimated_bytes()
5891        );
5892        assert_eq!(
5893            long_run.estimated_bytes(),
5894            long_run.debug_recomputed_estimated_bytes()
5895        );
5896        assert_eq!(
5897            short_run.estimated_bytes(),
5898            long_run.estimated_bytes(),
5899            "single-run estimate should scale with run count, not covered rows"
5900        );
5901
5902        let sparse10 = OverlayFragment::sparse_offsets(
5903            (0..10)
5904                .map(|idx| (idx * 3, OverlayValue::Number(idx as f64)))
5905                .collect(),
5906        )
5907        .unwrap();
5908        let sparse20 = OverlayFragment::sparse_offsets(
5909            (0..20)
5910                .map(|idx| (idx * 3, OverlayValue::Number(idx as f64)))
5911                .collect(),
5912        )
5913        .unwrap();
5914        assert!(sparse20.estimated_bytes() > sparse10.estimated_bytes());
5915    }
5916
5917    #[test]
5918    fn overlay_estimated_bytes_stay_consistent_after_split_and_clear() {
5919        let mut overlay = Overlay::new();
5920        overlay.apply_fragment(
5921            OverlayFragment::dense_range(
5922                0,
5923                (0..16)
5924                    .map(|idx| OverlayValue::Number(idx as f64))
5925                    .collect::<Vec<_>>(),
5926            )
5927            .unwrap(),
5928        );
5929        assert_eq!(
5930            overlay.estimated_bytes(),
5931            overlay.debug_recomputed_estimated_bytes()
5932        );
5933
5934        overlay.set_scalar(8, OverlayValue::Text(Arc::from("split")));
5935        assert!(overlay.debug_is_normalized());
5936        assert_eq!(
5937            overlay.estimated_bytes(),
5938            overlay.debug_recomputed_estimated_bytes()
5939        );
5940
5941        overlay.apply_fragment(
5942            OverlayFragment::sparse_offsets(vec![
5943                (0, OverlayValue::Empty),
5944                (15, OverlayValue::Boolean(true)),
5945            ])
5946            .unwrap(),
5947        );
5948        assert!(overlay.debug_is_normalized());
5949        assert_eq!(
5950            overlay.estimated_bytes(),
5951            overlay.debug_recomputed_estimated_bytes()
5952        );
5953
5954        let freed = overlay.clear_all();
5955        assert!(freed > 0);
5956        assert_eq!(overlay.estimated_bytes(), 0);
5957        assert_eq!(overlay.debug_recomputed_estimated_bytes(), 0);
5958        assert!(overlay.is_empty());
5959    }
5960
5961    #[test]
5962    fn overlay_segment_numbers_masks_base_for_non_numeric_overlays() {
5963        let mut user = Overlay::new();
5964        user.set(1, OverlayValue::Text(Arc::from("x")));
5965        user.set(2, OverlayValue::Empty);
5966        user.set(3, OverlayValue::Error(map_error_code(ExcelErrorKind::Div)));
5967        user.set(4, OverlayValue::Pending);
5968        let computed = Overlay::new();
5969        let cascade = OverlayCascade::new(&user, &computed);
5970
5971        let base = Float64Array::from(vec![10.0, 20.0, 30.0, 40.0, 50.0]);
5972        let selected = cascade.select_numbers(0..5, &base);
5973        assert_eq!(selected.value(0), 10.0);
5974        assert!(selected.is_null(1));
5975        assert!(selected.is_null(2));
5976        assert!(selected.is_null(3));
5977        assert!(selected.is_null(4));
5978    }
5979
5980    #[test]
5981    fn overlay_segment_type_tags_preserve_temporal_tags() {
5982        let mut computed = Overlay::new();
5983        computed.set(0, OverlayValue::DateTime(45000.5));
5984        computed.set(1, OverlayValue::Duration(0.25));
5985        let user = Overlay::new();
5986        let cascade = OverlayCascade::new(&user, &computed);
5987
5988        let base = UInt8Array::from(vec![TypeTag::Empty as u8; 2]);
5989        let selected = cascade.select_type_tags(0..2, &base);
5990        assert_eq!(selected.value(0), TypeTag::DateTime as u8);
5991        assert_eq!(selected.value(1), TypeTag::Duration as u8);
5992    }
5993
5994    #[test]
5995    fn overlay_lowered_text_matches_existing_overlay_semantics() {
5996        let mut user = Overlay::new();
5997        user.set(0, OverlayValue::Text(Arc::from("HeLLo")));
5998        user.set(1, OverlayValue::Number(1.5));
5999        user.set(2, OverlayValue::Boolean(true));
6000        user.set(3, OverlayValue::Empty);
6001        let computed = Overlay::new();
6002        let cascade = OverlayCascade::new(&user, &computed);
6003
6004        let base = StringArray::from(vec![Some("A"), Some("B"), Some("C"), Some("D")]);
6005        let selected = cascade.select_lowered_text(0..4, &base);
6006        assert_eq!(selected.value(0), "hello");
6007        assert_eq!(selected.value(1), "1.5");
6008        assert_eq!(selected.value(2), "true");
6009        assert!(selected.is_null(3));
6010    }
6011
6012    fn numeric_sheet(rows: usize) -> ArrowSheet {
6013        let mut b = IngestBuilder::new("S", 1, rows.max(1), crate::engine::DateSystem::Excel1900);
6014        for row in 0..rows {
6015            b.append_row(&[LiteralValue::Number((row + 1) as f64)])
6016                .unwrap();
6017        }
6018        b.finish()
6019    }
6020
6021    fn numbers_for_range(sheet: &ArrowSheet, sr: usize, er: usize) -> Arc<Float64Array> {
6022        let view = sheet.range_view(sr, 0, er, 0);
6023        let segments: Vec<_> = view.numbers_slices().map(|res| res.unwrap()).collect();
6024        assert_eq!(segments.len(), 1);
6025        assert_eq!(segments[0].2.len(), 1);
6026        segments[0].2[0].clone()
6027    }
6028
6029    fn type_tags_for_range(sheet: &ArrowSheet, sr: usize, er: usize) -> Arc<UInt8Array> {
6030        let view = sheet.range_view(sr, 0, er, 0);
6031        let segments: Vec<_> = view.type_tags_slices().map(|res| res.unwrap()).collect();
6032        assert_eq!(segments.len(), 1);
6033        assert_eq!(segments[0].2.len(), 1);
6034        segments[0].2[0].clone()
6035    }
6036
6037    fn lowered_for_range(sheet: &ArrowSheet, sr: usize, er: usize) -> Arc<StringArray> {
6038        let view = sheet.range_view(sr, 0, er, 0);
6039        let segments: Vec<_> = view.lowered_text_slices().map(|res| res.unwrap()).collect();
6040        assert_eq!(segments.len(), 1);
6041        assert_eq!(segments[0].2.len(), 1);
6042        segments[0].2[0].clone()
6043    }
6044
6045    #[test]
6046    fn rangeview_dense_text_masks_base_numbers() {
6047        let mut sheet = numeric_sheet(4);
6048        sheet.columns[0].chunks[0].computed_overlay.apply_fragment(
6049            OverlayFragment::dense_range(
6050                0,
6051                vec![
6052                    OverlayValue::Text(Arc::from("x")),
6053                    OverlayValue::Text(Arc::from("y")),
6054                    OverlayValue::Text(Arc::from("z")),
6055                    OverlayValue::Text(Arc::from("w")),
6056                ],
6057            )
6058            .unwrap(),
6059        );
6060
6061        reset_overlay_select_stats();
6062        let numbers = numbers_for_range(&sheet, 0, 3);
6063        assert_eq!(numbers.null_count(), 4);
6064        let stats = snapshot_overlay_select_stats();
6065        assert_eq!(stats.direct_dense_slices, 1);
6066        assert_eq!(stats.zip_select_calls, 0);
6067    }
6068
6069    #[test]
6070    fn rangeview_empty_dense_masks_base_all_selectors() {
6071        let mut sheet = numeric_sheet(3);
6072        sheet.columns[0].chunks[0]
6073            .computed_overlay
6074            .apply_fragment(OverlayFragment::dense_range(0, vec![OverlayValue::Empty; 3]).unwrap());
6075
6076        reset_overlay_select_stats();
6077        let numbers = numbers_for_range(&sheet, 0, 2);
6078        let type_tags = type_tags_for_range(&sheet, 0, 2);
6079        let lowered = lowered_for_range(&sheet, 0, 2);
6080        assert_eq!(numbers.null_count(), 3);
6081        assert_eq!(lowered.null_count(), 3);
6082        assert_eq!(type_tags.values(), &[TypeTag::Empty as u8; 3]);
6083        let stats = snapshot_overlay_select_stats();
6084        assert_eq!(stats.direct_dense_slices, 3);
6085        assert_eq!(stats.zip_select_calls, 0);
6086    }
6087
6088    #[test]
6089    fn rangeview_pending_masks_base_type_tag_present_lanes_null() {
6090        let mut sheet = numeric_sheet(2);
6091        sheet.columns[0].chunks[0].computed_overlay.apply_fragment(
6092            OverlayFragment::dense_range(0, vec![OverlayValue::Pending; 2]).unwrap(),
6093        );
6094
6095        reset_overlay_select_stats();
6096        let numbers = numbers_for_range(&sheet, 0, 1);
6097        let type_tags = type_tags_for_range(&sheet, 0, 1);
6098        let lowered = lowered_for_range(&sheet, 0, 1);
6099        assert_eq!(numbers.null_count(), 2);
6100        assert_eq!(lowered.null_count(), 2);
6101        assert_eq!(type_tags.values(), &[TypeTag::Pending as u8; 2]);
6102        let stats = snapshot_overlay_select_stats();
6103        assert_eq!(stats.direct_dense_slices, 3);
6104        assert_eq!(stats.zip_select_calls, 0);
6105    }
6106
6107    #[test]
6108    fn rangeview_subrange_inside_dense_fragment_uses_direct_path() {
6109        let mut sheet = numeric_sheet(10);
6110        sheet.columns[0].chunks[0].computed_overlay.apply_fragment(
6111            OverlayFragment::dense_range(
6112                0,
6113                (0..10)
6114                    .map(|row| OverlayValue::Number((row + 10) as f64))
6115                    .collect(),
6116            )
6117            .unwrap(),
6118        );
6119
6120        reset_overlay_select_stats();
6121        let numbers = numbers_for_range(&sheet, 2, 6);
6122        assert_eq!(numbers.len(), 5);
6123        assert_eq!(numbers.value(0), 12.0);
6124        assert_eq!(numbers.value(4), 16.0);
6125        let stats = snapshot_overlay_select_stats();
6126        assert_eq!(stats.direct_dense_slices, 1);
6127        assert_eq!(stats.zip_select_calls, 0);
6128    }
6129
6130    #[test]
6131    fn rangeview_subrange_inside_run_fragment_uses_direct_path() {
6132        let mut sheet = numeric_sheet(10);
6133        sheet.columns[0].chunks[0].computed_overlay.apply_fragment(
6134            OverlayFragment::run_range(0, vec![OverlayValue::Number(7.0); 10]).unwrap(),
6135        );
6136
6137        reset_overlay_select_stats();
6138        let numbers = numbers_for_range(&sheet, 2, 6);
6139        assert_eq!(numbers.len(), 5);
6140        for idx in 0..numbers.len() {
6141            assert_eq!(numbers.value(idx), 7.0);
6142        }
6143        let stats = snapshot_overlay_select_stats();
6144        assert_eq!(stats.direct_run_materializations, 1);
6145        assert_eq!(stats.zip_select_calls, 0);
6146    }
6147
6148    #[test]
6149    fn rangeview_user_partial_wrong_type_masks_computed_numeric() {
6150        let mut sheet = numeric_sheet(5);
6151        let chunk = &mut sheet.columns[0].chunks[0];
6152        chunk.computed_overlay.apply_fragment(
6153            OverlayFragment::dense_range(
6154                0,
6155                (0..5)
6156                    .map(|row| OverlayValue::Number((row + 10) as f64))
6157                    .collect(),
6158            )
6159            .unwrap(),
6160        );
6161        chunk.overlay.apply_fragment(
6162            OverlayFragment::dense_range(2, vec![OverlayValue::Text(Arc::from("mask"))]).unwrap(),
6163        );
6164
6165        reset_overlay_select_stats();
6166        let numbers = numbers_for_range(&sheet, 0, 4);
6167        assert_eq!(numbers.value(0), 10.0);
6168        assert_eq!(numbers.value(1), 11.0);
6169        assert!(numbers.is_null(2));
6170        assert_eq!(numbers.value(3), 13.0);
6171        assert_eq!(numbers.value(4), 14.0);
6172        let stats = snapshot_overlay_select_stats();
6173        assert_eq!(stats.direct_dense_slices, 0);
6174        assert_eq!(stats.zip_select_calls, 1);
6175        assert_eq!(stats.partial_dense_intersections, 2);
6176    }
6177
6178    #[test]
6179    fn rangeview_computed_full_cover_user_no_overlap_uses_computed_direct() {
6180        let mut sheet = numeric_sheet(5);
6181        let chunk = &mut sheet.columns[0].chunks[0];
6182        chunk.computed_overlay.apply_fragment(
6183            OverlayFragment::dense_range(0, vec![OverlayValue::Number(3.0); 5]).unwrap(),
6184        );
6185        chunk
6186            .overlay
6187            .set_scalar(10, OverlayValue::Text(Arc::from("outside")));
6188
6189        reset_overlay_select_stats();
6190        let numbers = numbers_for_range(&sheet, 0, 4);
6191        assert_eq!(numbers.value(0), 3.0);
6192        assert_eq!(numbers.value(4), 3.0);
6193        let stats = snapshot_overlay_select_stats();
6194        assert_eq!(stats.direct_dense_slices, 1);
6195        assert_eq!(stats.zip_select_calls, 0);
6196    }
6197
6198    #[test]
6199    fn rangeview_user_full_cover_ignores_computed() {
6200        let mut sheet = numeric_sheet(4);
6201        let chunk = &mut sheet.columns[0].chunks[0];
6202        chunk.computed_overlay.apply_fragment(
6203            OverlayFragment::dense_range(0, vec![OverlayValue::Number(99.0); 4]).unwrap(),
6204        );
6205        chunk.overlay.apply_fragment(
6206            OverlayFragment::dense_range(0, vec![OverlayValue::Text(Arc::from("user")); 4])
6207                .unwrap(),
6208        );
6209
6210        reset_overlay_select_stats();
6211        let numbers = numbers_for_range(&sheet, 0, 3);
6212        assert_eq!(numbers.null_count(), 4);
6213        let stats = snapshot_overlay_select_stats();
6214        assert_eq!(stats.direct_dense_slices, 1);
6215        assert_eq!(stats.zip_select_calls, 0);
6216    }
6217
6218    #[test]
6219    fn rangeview_point_overlay_still_matches_legacy_scalar_path() {
6220        let mut sheet = numeric_sheet(3);
6221        sheet.columns[0].chunks[0]
6222            .computed_overlay
6223            .set_scalar(1, OverlayValue::Text(Arc::from("point")));
6224
6225        reset_overlay_select_stats();
6226        let numbers = numbers_for_range(&sheet, 0, 2);
6227        assert_eq!(numbers.value(0), 1.0);
6228        assert!(numbers.is_null(1));
6229        assert_eq!(numbers.value(2), 3.0);
6230        let stats = snapshot_overlay_select_stats();
6231        assert_eq!(stats.zip_select_calls, 1);
6232        assert_eq!(stats.point_entries_applied, 1);
6233        assert_eq!(stats.row_scalar_fallbacks, 0);
6234    }
6235
6236    #[test]
6237    fn rangeview_multi_fragment_full_union_does_not_use_direct_path() {
6238        let mut sheet = numeric_sheet(4);
6239        let chunk = &mut sheet.columns[0].chunks[0];
6240        chunk.computed_overlay.apply_fragment(
6241            OverlayFragment::dense_range(0, vec![OverlayValue::Number(10.0); 2]).unwrap(),
6242        );
6243        chunk.computed_overlay.apply_fragment(
6244            OverlayFragment::dense_range(2, vec![OverlayValue::Number(20.0); 2]).unwrap(),
6245        );
6246
6247        reset_overlay_select_stats();
6248        let numbers = numbers_for_range(&sheet, 0, 3);
6249        assert_eq!(numbers.value(0), 10.0);
6250        assert_eq!(numbers.value(1), 10.0);
6251        assert_eq!(numbers.value(2), 20.0);
6252        assert_eq!(numbers.value(3), 20.0);
6253        let stats = snapshot_overlay_select_stats();
6254        assert_eq!(stats.direct_dense_slices, 0);
6255        assert_eq!(stats.zip_select_calls, 1);
6256        assert_eq!(stats.partial_dense_intersections, 2);
6257    }
6258
6259    #[test]
6260    fn rangeview_lowered_text_fragment_semantics_match_scalar_semantics() {
6261        let mut sheet = numeric_sheet(8);
6262        sheet.columns[0].chunks[0].computed_overlay.apply_fragment(
6263            OverlayFragment::dense_range(
6264                0,
6265                vec![
6266                    OverlayValue::Text(Arc::from("HeLLo")),
6267                    OverlayValue::Number(1.5),
6268                    OverlayValue::DateTime(45000.25),
6269                    OverlayValue::Duration(0.5),
6270                    OverlayValue::Boolean(true),
6271                    OverlayValue::Empty,
6272                    OverlayValue::Error(map_error_code(ExcelErrorKind::Div)),
6273                    OverlayValue::Pending,
6274                ],
6275            )
6276            .unwrap(),
6277        );
6278
6279        reset_overlay_select_stats();
6280        let lowered = lowered_for_range(&sheet, 0, 7);
6281        assert_eq!(lowered.value(0), "hello");
6282        assert_eq!(lowered.value(1), "1.5");
6283        assert_eq!(lowered.value(2), "45000.25");
6284        assert_eq!(lowered.value(3), "0.5");
6285        assert_eq!(lowered.value(4), "true");
6286        assert!(lowered.is_null(5));
6287        assert!(lowered.is_null(6));
6288        assert!(lowered.is_null(7));
6289        let stats = snapshot_overlay_select_stats();
6290        assert_eq!(stats.direct_dense_slices, 1);
6291        assert_eq!(stats.zip_select_calls, 0);
6292    }
6293
6294    #[test]
6295    fn row_chunk_slices_shape() {
6296        // chunk_rows=2 leads to two slices for 3 rows
6297        let mut b = IngestBuilder::new("S", 2, 2, crate::engine::DateSystem::Excel1900);
6298        b.append_row(&[LiteralValue::Text("a".into()), LiteralValue::Number(1.0)])
6299            .unwrap();
6300        b.append_row(&[LiteralValue::Text("b".into()), LiteralValue::Number(2.0)])
6301            .unwrap();
6302        b.append_row(&[LiteralValue::Text("c".into()), LiteralValue::Number(3.0)])
6303            .unwrap();
6304        let sheet = b.finish();
6305        let rv = sheet.range_view(0, 0, 2, 1);
6306        let slices: Vec<_> = rv.iter_row_chunks().map(|r| r.unwrap()).collect();
6307        assert_eq!(slices.len(), 2);
6308        assert_eq!(slices[0].row_start, 0);
6309        assert_eq!(slices[0].row_len, 2);
6310        assert_eq!(slices[0].cols.len(), 2);
6311        assert_eq!(slices[1].row_start, 2);
6312        assert_eq!(slices[1].row_len, 1);
6313        assert_eq!(slices[1].cols.len(), 2);
6314    }
6315
6316    #[test]
6317    fn oob_columns_are_padded() {
6318        // Build with 2 columns; request 3 columns (ec beyond last col)
6319        let mut b = IngestBuilder::new("S", 2, 2, crate::engine::DateSystem::Excel1900);
6320        b.append_row(&[LiteralValue::Number(1.0), LiteralValue::Text("a".into())])
6321            .unwrap();
6322        b.append_row(&[LiteralValue::Number(2.0), LiteralValue::Text("b".into())])
6323            .unwrap();
6324        let sheet = b.finish();
6325        // Request cols [0..=2] → 3 columns with padding
6326        let rv = sheet.range_view(0, 0, 1, 2);
6327        assert_eq!(rv.dims(), (2, 3));
6328        let slices: Vec<_> = rv.iter_row_chunks().map(|r| r.unwrap()).collect();
6329        assert!(!slices.is_empty());
6330        for cs in &slices {
6331            assert_eq!(cs.cols.len(), 3);
6332        }
6333        // Also validate typed slices return 3 entries per segment
6334        for res in rv.numbers_slices() {
6335            let (_rs, _rl, cols) = res.unwrap();
6336            assert_eq!(cols.len(), 3);
6337        }
6338        for res in rv.booleans_slices() {
6339            let (_rs, _rl, cols) = res.unwrap();
6340            assert_eq!(cols.len(), 3);
6341        }
6342        for res in rv.text_slices() {
6343            let (_rs, _rl, cols) = res.unwrap();
6344            assert_eq!(cols.len(), 3);
6345        }
6346        for res in rv.errors_slices() {
6347            let (_rs, _rl, cols) = res.unwrap();
6348            assert_eq!(cols.len(), 3);
6349        }
6350        for res in rv.lowered_text_slices() {
6351            let (_rs, _rl, cols) = res.unwrap();
6352            assert_eq!(cols.len(), 3);
6353        }
6354    }
6355
6356    #[test]
6357    fn reversed_range_is_empty() {
6358        let mut b = IngestBuilder::new("S", 1, 4, crate::engine::DateSystem::Excel1900);
6359        b.append_row(&[LiteralValue::Number(1.0)]).unwrap();
6360        b.append_row(&[LiteralValue::Number(2.0)]).unwrap();
6361        let sheet = b.finish();
6362        let rv = sheet.range_view(3, 0, 1, 0); // er < sr
6363        assert_eq!(rv.dims(), (0, 0));
6364        assert!(rv.iter_row_chunks().next().is_none());
6365        assert_eq!(rv.get_cell(0, 0), LiteralValue::Empty);
6366    }
6367
6368    #[test]
6369    fn chunk_alignment_invariant() {
6370        let mut b = IngestBuilder::new("S", 3, 2, crate::engine::DateSystem::Excel1900);
6371        // 5 rows, 2-row chunks => 3 chunks (2,2,1)
6372        for r in 0..5 {
6373            b.append_row(&[
6374                LiteralValue::Number(r as f64),
6375                LiteralValue::Text(format!("{r}")),
6376                if r % 2 == 0 {
6377                    LiteralValue::Empty
6378                } else {
6379                    LiteralValue::Boolean(true)
6380                },
6381            ])
6382            .unwrap();
6383        }
6384        let sheet = b.finish();
6385        // chunk_starts should be [0,2,4]
6386        assert_eq!(sheet.chunk_starts, vec![0, 2, 4]);
6387        // All columns must share per-chunk lengths equal to [2,2,1]
6388        let lens0: Vec<usize> = sheet.columns[0]
6389            .chunks
6390            .iter()
6391            .map(|ch| ch.type_tag.len())
6392            .collect();
6393        for col in &sheet.columns[1..] {
6394            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6395            assert_eq!(lens, lens0);
6396        }
6397    }
6398
6399    #[test]
6400    fn chunking_splits_rows() {
6401        // Two columns, chunk size 2 → expect two chunks
6402        let mut b = IngestBuilder::new("S", 2, 2, crate::engine::DateSystem::Excel1900);
6403        let rows = vec![
6404            vec![LiteralValue::Number(1.0), LiteralValue::Text("a".into())],
6405            vec![LiteralValue::Empty, LiteralValue::Text("b".into())],
6406            vec![LiteralValue::Boolean(true), LiteralValue::Empty],
6407        ];
6408        for r in rows {
6409            b.append_row(&r).unwrap();
6410        }
6411        let sheet = b.finish();
6412        assert_eq!(sheet.columns[0].chunks.len(), 2);
6413        assert_eq!(sheet.columns[1].chunks.len(), 2);
6414        assert_eq!(sheet.columns[0].chunks[0].numbers_or_null().len(), 2);
6415        assert_eq!(sheet.columns[0].chunks[1].numbers_or_null().len(), 1);
6416    }
6417
6418    #[test]
6419    fn pending_is_not_error() {
6420        let mut b = IngestBuilder::new("S", 1, 8, crate::engine::DateSystem::Excel1900);
6421        b.append_row(&[LiteralValue::Pending]).unwrap();
6422        let sheet = b.finish();
6423        let ch = &sheet.columns[0].chunks[0];
6424        // tag is Pending
6425        assert_eq!(ch.type_tag.values()[0], super::TypeTag::Pending as u8);
6426        // errors lane is effectively null
6427        let errs = ch.errors_or_null();
6428        assert_eq!(errs.null_count(), 1);
6429    }
6430
6431    #[test]
6432    fn all_null_numeric_lane_uses_null_array() {
6433        // Only text values in first column → numbers lane should be all null with correct dtype
6434        let mut b = IngestBuilder::new("S", 1, 16, crate::engine::DateSystem::Excel1900);
6435        b.append_row(&[LiteralValue::Text("a".into())]).unwrap();
6436        b.append_row(&[LiteralValue::Text("".into())]).unwrap();
6437        b.append_row(&[LiteralValue::Text("b".into())]).unwrap();
6438        let sheet = b.finish();
6439        let ch = &sheet.columns[0].chunks[0];
6440        let nums = ch.numbers_or_null();
6441        assert_eq!(nums.len(), 3);
6442        assert_eq!(nums.null_count(), 3);
6443        assert_eq!(nums.data_type(), &DataType::Float64);
6444    }
6445
6446    #[test]
6447    fn row_insert_delete_across_chunk_boundaries_with_overlays() {
6448        // Build 1 column, chunk size 4, 10 rows -> chunks at [0..4],[4..8],[8..10]
6449        let mut b = IngestBuilder::new("S", 1, 4, crate::engine::DateSystem::Excel1900);
6450        for _ in 0..10 {
6451            b.append_row(&[LiteralValue::Empty]).unwrap();
6452        }
6453        let mut sheet = b.finish();
6454        // Add overlays at row 3 and row 4
6455        {
6456            let (c0, o0) = sheet.chunk_of_row(3).unwrap();
6457            sheet.columns[0].chunks[c0]
6458                .overlay
6459                .set(o0, OverlayValue::Number(30.0));
6460            let (c1, o1) = sheet.chunk_of_row(4).unwrap();
6461            sheet.columns[0].chunks[c1]
6462                .overlay
6463                .set(o1, OverlayValue::Number(40.0));
6464        }
6465        // Insert 2 rows before row 4 (at chunk boundary)
6466        sheet.insert_rows(4, 2);
6467        assert_eq!(sheet.nrows, 12);
6468        // Validate overlays moved correctly: 3 stays, 4 becomes Empty, 6 has 40
6469        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6470        assert_eq!(av.get_cell(3, 0), LiteralValue::Number(30.0));
6471        assert_eq!(av.get_cell(4, 0), LiteralValue::Empty);
6472        assert_eq!(av.get_cell(6, 0), LiteralValue::Number(40.0));
6473
6474        // Now delete 3 rows starting at 3: removes rows 3,4,5 → moves 40.0 from 6 → 3
6475        sheet.delete_rows(3, 3);
6476        assert_eq!(sheet.nrows, 9);
6477        let av2 = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6478        assert_eq!(av2.get_cell(3, 0), LiteralValue::Number(40.0));
6479        // All columns share chunk lengths; chunk_starts monotonic and cover nrows
6480        let lens0: Vec<usize> = sheet.columns[0]
6481            .chunks
6482            .iter()
6483            .map(|ch| ch.type_tag.len())
6484            .collect();
6485        for col in &sheet.columns {
6486            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6487            assert_eq!(lens, lens0);
6488        }
6489        // chunk_starts should be monotonic and final chunk end == nrows
6490        assert!(sheet.chunk_starts.windows(2).all(|w| w[0] < w[1]));
6491        let last_start = *sheet.chunk_starts.last().unwrap_or(&0);
6492        let last_len = sheet.columns[0]
6493            .chunks
6494            .last()
6495            .map(|c| c.type_tag.len())
6496            .unwrap_or(0);
6497        assert_eq!(last_start + last_len, sheet.nrows as usize);
6498    }
6499
6500    #[test]
6501    fn row_insert_delete_preserves_user_dense_fragments() {
6502        let mut b = IngestBuilder::new("S", 1, 4, crate::engine::DateSystem::Excel1900);
6503        for _ in 0..10 {
6504            b.append_row(&[LiteralValue::Empty]).unwrap();
6505        }
6506        let mut sheet = b.finish();
6507
6508        let (ch_idx, off) = sheet.chunk_of_row(1).unwrap();
6509        sheet.columns[0]
6510            .chunk_mut(ch_idx)
6511            .unwrap()
6512            .overlay
6513            .apply_fragment(
6514                OverlayFragment::dense_range(
6515                    off,
6516                    vec![
6517                        OverlayValue::Number(10.0),
6518                        OverlayValue::Number(20.0),
6519                        OverlayValue::Number(30.0),
6520                    ],
6521                )
6522                .unwrap(),
6523            );
6524
6525        let before = column_overlay_stats(&sheet, 0, false);
6526        assert_eq!(before.dense_fragments, 1);
6527        assert_eq!(before.sparse_fragments, 0);
6528        assert_column_overlays_normalized(&sheet, 0);
6529
6530        sheet.insert_rows(2, 2);
6531        assert_eq!(sheet.nrows, 12);
6532        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6533        assert_eq!(av.get_cell(1, 0), LiteralValue::Number(10.0));
6534        assert_eq!(av.get_cell(2, 0), LiteralValue::Empty);
6535        assert_eq!(av.get_cell(3, 0), LiteralValue::Empty);
6536        assert_eq!(av.get_cell(4, 0), LiteralValue::Number(20.0));
6537        assert_eq!(av.get_cell(5, 0), LiteralValue::Number(30.0));
6538        let after_insert = column_overlay_stats(&sheet, 0, false);
6539        assert_eq!(after_insert.sparse_fragments, 0);
6540        assert!(after_insert.dense_fragments >= 2);
6541        assert_column_overlays_normalized(&sheet, 0);
6542
6543        sheet.delete_rows(2, 2);
6544        assert_eq!(sheet.nrows, 10);
6545        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6546        assert_eq!(av.get_cell(1, 0), LiteralValue::Number(10.0));
6547        assert_eq!(av.get_cell(2, 0), LiteralValue::Number(20.0));
6548        assert_eq!(av.get_cell(3, 0), LiteralValue::Number(30.0));
6549        let after_delete = column_overlay_stats(&sheet, 0, false);
6550        assert_eq!(after_delete.sparse_fragments, 0);
6551        assert!(after_delete.dense_fragments >= 1);
6552        assert_column_overlays_normalized(&sheet, 0);
6553    }
6554
6555    #[test]
6556    fn row_insert_delete_preserves_computed_empty_run_fragments() {
6557        let mut b = IngestBuilder::new("S", 1, 4, crate::engine::DateSystem::Excel1900);
6558        for row in 0..8 {
6559            b.append_row(&[LiteralValue::Number((row + 1) as f64)])
6560                .unwrap();
6561        }
6562        let mut sheet = b.finish();
6563
6564        let (ch_idx, off) = sheet.chunk_of_row(1).unwrap();
6565        sheet.columns[0]
6566            .chunk_mut(ch_idx)
6567            .unwrap()
6568            .computed_overlay
6569            .apply_fragment(
6570                OverlayFragment::run_range(
6571                    off,
6572                    vec![
6573                        OverlayValue::Empty,
6574                        OverlayValue::Empty,
6575                        OverlayValue::Empty,
6576                    ],
6577                )
6578                .unwrap(),
6579            );
6580
6581        let before = column_overlay_stats(&sheet, 0, true);
6582        assert_eq!(before.run_fragments, 1);
6583        assert_eq!(before.sparse_fragments, 0);
6584        assert_column_overlays_normalized(&sheet, 0);
6585
6586        sheet.insert_rows(2, 1);
6587        assert_eq!(sheet.nrows, 9);
6588        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6589        assert_eq!(av.get_cell(1, 0), LiteralValue::Empty);
6590        assert_eq!(av.get_cell(2, 0), LiteralValue::Empty);
6591        assert_eq!(av.get_cell(3, 0), LiteralValue::Empty);
6592        assert_eq!(av.get_cell(4, 0), LiteralValue::Empty);
6593        assert_eq!(av.get_cell(5, 0), LiteralValue::Number(5.0));
6594        let after_insert = column_overlay_stats(&sheet, 0, true);
6595        assert_eq!(after_insert.sparse_fragments, 0);
6596        assert!(after_insert.run_fragments >= 2);
6597        assert_column_overlays_normalized(&sheet, 0);
6598
6599        sheet.delete_rows(2, 1);
6600        assert_eq!(sheet.nrows, 8);
6601        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6602        assert_eq!(av.get_cell(1, 0), LiteralValue::Empty);
6603        assert_eq!(av.get_cell(2, 0), LiteralValue::Empty);
6604        assert_eq!(av.get_cell(3, 0), LiteralValue::Empty);
6605        assert_eq!(av.get_cell(4, 0), LiteralValue::Number(5.0));
6606        let after_delete = column_overlay_stats(&sheet, 0, true);
6607        assert_eq!(after_delete.sparse_fragments, 0);
6608        assert!(after_delete.run_fragments >= 1);
6609        assert_column_overlays_normalized(&sheet, 0);
6610    }
6611
6612    #[test]
6613    fn column_insert_delete_retains_chunk_alignment() {
6614        let mut b = IngestBuilder::new("S", 3, 3, crate::engine::DateSystem::Excel1900);
6615        for _ in 0..5 {
6616            b.append_row(&[
6617                LiteralValue::Empty,
6618                LiteralValue::Empty,
6619                LiteralValue::Empty,
6620            ])
6621            .unwrap();
6622        }
6623        let mut sheet = b.finish();
6624        // Record reference chunk lengths of first column
6625        let ref_lens: Vec<usize> = sheet.columns[0]
6626            .chunks
6627            .iter()
6628            .map(|ch| ch.type_tag.len())
6629            .collect();
6630        // Insert 2 columns before index 1
6631        sheet.insert_columns(1, 2);
6632        assert_eq!(sheet.columns.len(), 5);
6633        for col in &sheet.columns {
6634            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6635            assert_eq!(lens, ref_lens);
6636        }
6637        let starts_before = sheet.chunk_starts.clone();
6638        // Delete 2 columns starting at index 2 → back to 3 columns
6639        sheet.delete_columns(2, 2);
6640        assert_eq!(sheet.columns.len(), 3);
6641        for col in &sheet.columns {
6642            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6643            assert_eq!(lens, ref_lens);
6644        }
6645        // chunk_starts unchanged by column operations
6646        assert_eq!(sheet.chunk_starts, starts_before);
6647    }
6648
6649    #[test]
6650    fn multiple_adjacent_row_ops_overlay_mixed_types() {
6651        use formualizer_common::ExcelErrorKind;
6652        // Two columns to ensure alignment preserved across columns
6653        let mut b = IngestBuilder::new("S", 2, 3, crate::engine::DateSystem::Excel1900);
6654        for _ in 0..9 {
6655            b.append_row(&[LiteralValue::Empty, LiteralValue::Empty])
6656                .unwrap();
6657        }
6658        let mut sheet = b.finish();
6659        // Overlays at rows (0-based): 2->Number, 3->Text, 5->Boolean, 6->Error, 8->Empty
6660        // Column 0 only
6661        let set_ov = |sh: &mut ArrowSheet, row: usize, ov: OverlayValue| {
6662            let (ch_i, off) = sh.chunk_of_row(row).unwrap();
6663            let _ = sh.columns[0].chunks[ch_i].overlay.set(off, ov);
6664        };
6665        set_ov(&mut sheet, 2, OverlayValue::Number(12.5));
6666        set_ov(&mut sheet, 3, OverlayValue::Text(Arc::from("hello")));
6667        set_ov(&mut sheet, 5, OverlayValue::Boolean(true));
6668        set_ov(
6669            &mut sheet,
6670            6,
6671            OverlayValue::Error(map_error_code(ExcelErrorKind::Div)),
6672        );
6673        set_ov(&mut sheet, 8, OverlayValue::Empty);
6674
6675        // Insert 1 row before index 3
6676        sheet.insert_rows(3, 1);
6677        // Expected new positions: 2->2 (unchanged), 3->4, 5->6, 6->7, 8->9
6678        let av1 = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6679        assert_eq!(av1.get_cell(2, 0), LiteralValue::Number(12.5));
6680        assert_eq!(av1.get_cell(4, 0), LiteralValue::Text("hello".into()));
6681        assert_eq!(av1.get_cell(6, 0), LiteralValue::Boolean(true));
6682        match av1.get_cell(7, 0) {
6683            LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Div),
6684            other => panic!("expected error at row 7, got {other:?}"),
6685        }
6686        assert_eq!(av1.get_cell(9, 0), LiteralValue::Empty);
6687
6688        // Insert 2 rows before index 4 (adjacent to previous region)
6689        sheet.insert_rows(4, 2);
6690        // Now positions: 2->2, 4->6, 6->8, 7->9, 9->11
6691        let av2 = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6692        assert_eq!(av2.get_cell(2, 0), LiteralValue::Number(12.5));
6693        assert_eq!(av2.get_cell(6, 0), LiteralValue::Text("hello".into()));
6694        assert_eq!(av2.get_cell(8, 0), LiteralValue::Boolean(true));
6695        match av2.get_cell(9, 0) {
6696            LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Div),
6697            other => panic!("expected error at row 9, got {other:?}"),
6698        }
6699        assert_eq!(av2.get_cell(11, 0), LiteralValue::Empty);
6700
6701        // Delete 2 rows starting at index 6 → removes the text at 6 and one empty row
6702        sheet.delete_rows(6, 2);
6703        let av3 = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 0);
6704        // Remaining expected: 2->Number 12.5, 6 (was 8)->true, 7 (was 9)->#DIV/0!, 9 (was 11)->Empty
6705        assert_eq!(av3.get_cell(2, 0), LiteralValue::Number(12.5));
6706        assert_eq!(av3.get_cell(6, 0), LiteralValue::Boolean(true));
6707        match av3.get_cell(7, 0) {
6708            LiteralValue::Error(e) => assert_eq!(e.kind, ExcelErrorKind::Div),
6709            other => panic!("expected error at row 8, got {other:?}"),
6710        }
6711        assert_eq!(av3.get_cell(9, 0), LiteralValue::Empty);
6712
6713        // Alignment checks
6714        let lens0: Vec<usize> = sheet.columns[0]
6715            .chunks
6716            .iter()
6717            .map(|ch| ch.type_tag.len())
6718            .collect();
6719        for col in &sheet.columns {
6720            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6721            assert_eq!(lens, lens0);
6722        }
6723        // chunk_starts monotonically increasing and cover nrows
6724        assert!(sheet.chunk_starts.windows(2).all(|w| w[0] < w[1]));
6725        let last_start = *sheet.chunk_starts.last().unwrap_or(&0);
6726        let last_len = sheet.columns[0]
6727            .chunks
6728            .last()
6729            .map(|c| c.type_tag.len())
6730            .unwrap_or(0);
6731        assert_eq!(last_start + last_len, sheet.nrows as usize);
6732    }
6733
6734    #[test]
6735    fn multiple_adjacent_column_ops_alignment() {
6736        // Start with 2 columns, chunk_rows=2, rows=5
6737        let mut b = IngestBuilder::new("S", 2, 2, crate::engine::DateSystem::Excel1900);
6738        for _ in 0..5 {
6739            b.append_row(&[LiteralValue::Empty, LiteralValue::Empty])
6740                .unwrap();
6741        }
6742        let mut sheet = b.finish();
6743        let ref_lens: Vec<usize> = sheet.columns[0]
6744            .chunks
6745            .iter()
6746            .map(|ch| ch.type_tag.len())
6747            .collect();
6748        // Insert 1 at start, then 2 at index 2 → columns = 5
6749        sheet.insert_columns(0, 1);
6750        sheet.insert_columns(2, 2);
6751        assert_eq!(sheet.columns.len(), 5);
6752        for col in &sheet.columns {
6753            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6754            assert_eq!(lens, ref_lens);
6755        }
6756        let starts_before = sheet.chunk_starts.clone();
6757        // Delete 1 at index 1, then 2 at the end if available
6758        sheet.delete_columns(1, 1);
6759        let remain = sheet.columns.len();
6760        if remain >= 3 {
6761            sheet.delete_columns(remain - 2, 2);
6762        }
6763        for col in &sheet.columns {
6764            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6765            assert_eq!(lens, ref_lens);
6766        }
6767        assert_eq!(sheet.chunk_starts, starts_before);
6768    }
6769
6770    #[test]
6771    fn overlays_on_multiple_columns_row_col_ops() {
6772        // 3 columns, chunk_rows=3, rows=6 → chunks [0..3), [3..6)
6773        let mut b = IngestBuilder::new("S", 3, 3, crate::engine::DateSystem::Excel1900);
6774        for _ in 0..6 {
6775            b.append_row(&[
6776                LiteralValue::Empty,
6777                LiteralValue::Empty,
6778                LiteralValue::Empty,
6779            ])
6780            .unwrap();
6781        }
6782        let mut sheet = b.finish();
6783        // Overlays at row2 and row3 across columns with different types
6784        let set_ov = |sh: &mut ArrowSheet, col: usize, row: usize, ov: OverlayValue| {
6785            let (ch_i, off) = sh.chunk_of_row(row).unwrap();
6786            let _ = sh.columns[col].chunks[ch_i].overlay.set(off, ov);
6787        };
6788        set_ov(&mut sheet, 0, 2, OverlayValue::Number(12.0));
6789        set_ov(&mut sheet, 1, 2, OverlayValue::Text(Arc::from("xx")));
6790        set_ov(&mut sheet, 2, 2, OverlayValue::Boolean(true));
6791        set_ov(&mut sheet, 0, 3, OverlayValue::Number(33.0));
6792        set_ov(&mut sheet, 1, 3, OverlayValue::Text(Arc::from("yy")));
6793        set_ov(&mut sheet, 2, 3, OverlayValue::Boolean(false));
6794
6795        // Insert a row at boundary (before row index 3)
6796        sheet.insert_rows(3, 1);
6797        // Now original row>=3 shift down by 1
6798        let av = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 2);
6799        // Row 2 values unchanged
6800        assert_eq!(av.get_cell(2, 0), LiteralValue::Number(12.0));
6801        assert_eq!(av.get_cell(2, 1), LiteralValue::Text("xx".into()));
6802        assert_eq!(av.get_cell(2, 2), LiteralValue::Boolean(true));
6803        // Row 3 became Empty (inserted)
6804        assert_eq!(av.get_cell(3, 0), LiteralValue::Empty);
6805        // Row 4 holds old row 3 overlays
6806        assert_eq!(av.get_cell(4, 0), LiteralValue::Number(33.0));
6807        assert_eq!(av.get_cell(4, 1), LiteralValue::Text("yy".into()));
6808        assert_eq!(av.get_cell(4, 2), LiteralValue::Boolean(false));
6809
6810        // Delete column 1 (middle), values shift left
6811        sheet.delete_columns(1, 1);
6812        let av2 = sheet.range_view(0, 0, (sheet.nrows - 1) as usize, 1);
6813        assert_eq!(av2.get_cell(2, 0), LiteralValue::Number(12.0));
6814        // Column 1 now was old column 2
6815        assert_eq!(av2.get_cell(2, 1), LiteralValue::Boolean(true));
6816        assert_eq!(av2.get_cell(4, 0), LiteralValue::Number(33.0));
6817        assert_eq!(av2.get_cell(4, 1), LiteralValue::Boolean(false));
6818
6819        // Alignment preserved
6820        let lens0: Vec<usize> = sheet.columns[0]
6821            .chunks
6822            .iter()
6823            .map(|ch| ch.type_tag.len())
6824            .collect();
6825        for col in &sheet.columns {
6826            let lens: Vec<usize> = col.chunks.iter().map(|ch| ch.type_tag.len()).collect();
6827            assert_eq!(lens, lens0);
6828        }
6829    }
6830
6831    #[test]
6832    fn effective_slices_overlay_precedence_numbers_text() {
6833        // 1 column, chunk_rows=3, rows=6. Base numbers in lane; overlays include text on row1 and number on row4.
6834        let mut b = IngestBuilder::new("S", 1, 3, crate::engine::DateSystem::Excel1900);
6835        for i in 0..6 {
6836            b.append_row(&[LiteralValue::Number((i + 1) as f64)])
6837                .unwrap();
6838        }
6839        let mut sheet = b.finish();
6840        // Overlays: row1 -> Text("X"), row4 -> Number(99)
6841        let (c1, o1) = sheet.chunk_of_row(1).unwrap();
6842        sheet.columns[0].chunks[c1]
6843            .overlay
6844            .set(o1, OverlayValue::Text(Arc::from("X")));
6845        let (c4, o4) = sheet.chunk_of_row(4).unwrap();
6846        sheet.columns[0].chunks[c4]
6847            .overlay
6848            .set(o4, OverlayValue::Number(99.0));
6849
6850        let av = sheet.range_view(0, 0, 5, 0);
6851        // Validate numbers_slices: row1 should be null (text overlay), row4 should be 99.0, others base
6852        let mut numeric: Vec<Option<f64>> = vec![None; 6];
6853        for res in av.numbers_slices() {
6854            let (row_start, row_len, cols) = res.unwrap();
6855            let a = &cols[0];
6856            for i in 0..row_len {
6857                let idx = row_start + i;
6858                numeric[idx] = if a.is_null(i) { None } else { Some(a.value(i)) };
6859            }
6860        }
6861        assert_eq!(numeric[0], Some(1.0));
6862        assert_eq!(numeric[1], None); // overshadowed by text overlay
6863        assert_eq!(numeric[2], Some(3.0));
6864        assert_eq!(numeric[3], Some(4.0));
6865        assert_eq!(numeric[4], Some(99.0));
6866        assert_eq!(numeric[5], Some(6.0));
6867
6868        // Validate text_slices: row1 has "X", others null
6869        let mut texts: Vec<Option<String>> = vec![None; 6];
6870        for res in av.text_slices() {
6871            let (row_start, row_len, cols) = res.unwrap();
6872            let a = cols[0].as_any().downcast_ref::<StringArray>().unwrap();
6873            for i in 0..row_len {
6874                let idx = row_start + i;
6875                texts[idx] = if a.is_null(i) {
6876                    None
6877                } else {
6878                    Some(a.value(i).to_string())
6879                };
6880            }
6881        }
6882        assert_eq!(texts[1].as_deref(), Some("X"));
6883        assert!(texts[0].is_none());
6884        assert!(texts[2].is_none());
6885        assert!(texts[3].is_none());
6886        assert!(texts[4].is_none());
6887        assert!(texts[5].is_none());
6888    }
6889
6890    #[test]
6891    fn effective_slices_overlay_precedence_booleans() {
6892        // Base booleans over 1 column; overlays include boolean and non-boolean types.
6893        let mut b = IngestBuilder::new("S", 1, 4, crate::engine::DateSystem::Excel1900);
6894        for i in 0..6 {
6895            let v = if i % 2 == 0 {
6896                LiteralValue::Boolean(true)
6897            } else {
6898                LiteralValue::Boolean(false)
6899            };
6900            b.append_row(&[v]).unwrap();
6901        }
6902        let mut sheet = b.finish();
6903        // Overlays: row1 -> Boolean(true), row2 -> Text("T")
6904        let (c1, o1) = sheet.chunk_of_row(1).unwrap();
6905        sheet.columns[0].chunks[c1]
6906            .overlay
6907            .set(o1, OverlayValue::Boolean(true));
6908        let (c2, o2) = sheet.chunk_of_row(2).unwrap();
6909        sheet.columns[0].chunks[c2]
6910            .overlay
6911            .set(o2, OverlayValue::Text(Arc::from("T")));
6912
6913        let av = sheet.range_view(0, 0, 5, 0);
6914        // Validate booleans_slices: row1 should be true (overlay), row2 should be null (text overlay), others base
6915        let mut bools: Vec<Option<bool>> = vec![None; 6];
6916        for res in av.booleans_slices() {
6917            let (row_start, row_len, cols) = res.unwrap();
6918            let a = &cols[0];
6919            for i in 0..row_len {
6920                let idx = row_start + i;
6921                bools[idx] = if a.is_null(i) { None } else { Some(a.value(i)) };
6922            }
6923        }
6924        assert_eq!(bools[0], Some(true));
6925        assert_eq!(bools[1], Some(true)); // overlay to true
6926        assert_eq!(bools[2], None); // overshadowed by text overlay
6927        // spot-check others remain base
6928        assert_eq!(bools[3], Some(false));
6929    }
6930
6931    #[test]
6932    fn effective_slices_overlay_precedence_errors() {
6933        // Base numbers; overlay an error at one row and ensure errors_slices reflect it.
6934        let mut b = IngestBuilder::new("S", 1, 3, crate::engine::DateSystem::Excel1900);
6935        for i in 0..6 {
6936            b.append_row(&[LiteralValue::Number((i + 1) as f64)])
6937                .unwrap();
6938        }
6939        let mut sheet = b.finish();
6940        // Overlay error at row 4
6941        let (c4, o4) = sheet.chunk_of_row(4).unwrap();
6942        sheet.columns[0].chunks[c4]
6943            .overlay
6944            .set(o4, OverlayValue::Error(map_error_code(ExcelErrorKind::Div)));
6945
6946        let av = sheet.range_view(0, 0, 5, 0);
6947        let mut errs: Vec<Option<u8>> = vec![None; 6];
6948        for res in av.errors_slices() {
6949            let (row_start, row_len, cols) = res.unwrap();
6950            let a = &cols[0];
6951            for i in 0..row_len {
6952                let idx = row_start + i;
6953                errs[idx] = if a.is_null(i) { None } else { Some(a.value(i)) };
6954            }
6955        }
6956        assert_eq!(errs[4], Some(map_error_code(ExcelErrorKind::Div)));
6957        assert!(errs[3].is_none());
6958    }
6959}