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