1use crate::arrow_store;
2use crate::arrow_store::IngestBuilder;
3use crate::engine::CancelToken;
4use crate::stripes::NumericChunk;
5use arrow_array::Array;
6use arrow_schema::DataType;
7use formualizer_common::{CoercionPolicy, DateSystem, ExcelError, LiteralValue};
8use std::sync::Arc;
9
10#[cfg(test)]
11pub(crate) mod range_work {
12 use std::cell::Cell;
13
14 #[derive(Clone, Copy, Debug, Default, serde::Serialize)]
15 pub(crate) struct Work {
16 pub iterators: usize,
17 pub search_probes: usize,
18 pub candidates: usize,
19 pub segments: usize,
20 pub segment_rows: usize,
21 pub generic_columns: usize,
22 pub null_arrays: usize,
23 pub null_slots: usize,
24 pub provider_requests: [usize; 4],
25 pub provider_builds: [usize; 4],
26 pub provider_slots: [usize; 4],
27 }
28
29 thread_local! {
30 static WORK: Cell<Option<Work>> = const { Cell::new(None) };
31 }
32
33 pub(crate) fn begin() {
34 WORK.with(|work| work.set(Some(Work::default())));
35 }
36
37 pub(crate) fn take() -> Work {
38 WORK.with(|work| work.replace(None).unwrap_or_default())
39 }
40
41 #[inline]
42 pub(crate) fn record(f: impl FnOnce(&mut Work)) {
43 WORK.with(|work| {
44 if let Some(mut value) = work.get() {
45 f(&mut value);
46 work.set(Some(value));
47 }
48 });
49 }
50}
51
52#[derive(Clone)]
53pub enum RangeBacking<'a> {
54 Borrowed(&'a arrow_store::ArrowSheet),
55 Owned(Arc<arrow_store::ArrowSheet>),
56}
57
58#[derive(Clone)]
61pub struct RangeView<'a> {
62 backing: RangeBacking<'a>,
63 sr: usize,
64 sc: usize,
65 er: usize,
66 ec: usize,
67 rows: usize,
68 cols: usize,
69 cancel_token: Option<CancelToken>,
70}
71
72impl<'a> core::fmt::Debug for RangeView<'a> {
73 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
74 f.debug_struct("RangeView")
75 .field("rows", &self.rows)
76 .field("cols", &self.cols)
77 .field("kind", &self.kind_probe())
78 .finish()
79 }
80}
81
82#[derive(Copy, Clone, Debug, Eq, PartialEq)]
83pub enum RangeKind {
84 Empty,
85 NumericOnly,
86 TextOnly,
87 Mixed,
88}
89
90pub struct ChunkCol {
91 pub numbers: Option<arrow_array::ArrayRef>,
92 pub booleans: Option<arrow_array::ArrayRef>,
93 pub text: Option<arrow_array::ArrayRef>,
94 pub errors: Option<arrow_array::ArrayRef>,
95 pub type_tag: arrow_array::ArrayRef,
96}
97
98pub struct ChunkSlice {
99 pub row_start: usize, pub row_len: usize,
101 pub cols: Vec<ChunkCol>,
102}
103
104struct RowSegment {
105 chunk_idx: usize,
106 chunk_offset: usize,
107 row_start: usize,
108 row_len: usize,
109}
110
111struct RowSegmentIterator<'a> {
112 view: &'a RangeView<'a>,
113 chunks: Option<core::ops::Range<usize>>,
114}
115
116impl Iterator for RowSegmentIterator<'_> {
117 type Item = Result<RowSegment, ExcelError>;
118
119 fn next(&mut self) -> Option<Self::Item> {
120 if self
121 .view
122 .cancel_token
123 .as_ref()
124 .is_some_and(CancelToken::is_cancelled)
125 {
126 return Some(Err(ExcelError::new(
127 formualizer_common::ExcelErrorKind::Cancelled,
128 )));
129 }
130 let sheet = self.view.sheet();
131 let starts = &sheet.chunk_starts;
132 let sheet_rows = sheet.nrows as usize;
133 let row_end = self.view.er.min(sheet_rows.saturating_sub(1));
134 let chunks = self.chunks.get_or_insert_with(|| {
135 if sheet_rows == 0 || starts.is_empty() || self.view.sr > row_end {
137 return 0..0;
138 }
139 let first = starts
140 .partition_point(|&start| {
141 #[cfg(test)]
142 range_work::record(|w| w.search_probes += 1);
143 start <= self.view.sr
144 })
145 .saturating_sub(1);
146 let end = starts.partition_point(|&start| {
147 #[cfg(test)]
148 range_work::record(|w| w.search_probes += 1);
149 start <= row_end
150 });
151 first..end
152 });
153 for ci in chunks.by_ref() {
154 #[cfg(test)]
155 range_work::record(|w| w.candidates += 1);
156 let start = starts[ci];
157 let end = starts.get(ci + 1).copied().unwrap_or(sheet_rows);
158 let len = end.saturating_sub(start);
159 if len == 0 {
160 continue;
161 }
162 let lo = start.max(self.view.sr);
163 let hi = (start + len - 1).min(row_end);
164 if lo > hi {
165 continue;
166 }
167 let row_len = hi - lo + 1;
168 #[cfg(test)]
169 range_work::record(|w| {
170 w.segments += 1;
171 w.segment_rows += row_len;
172 });
173 return Some(Ok(RowSegment {
174 chunk_idx: ci,
175 chunk_offset: lo - start,
176 row_start: lo - self.view.sr,
177 row_len,
178 }));
179 }
180 None
181 }
182}
183
184pub struct RowChunkIterator<'a> {
185 segments: RowSegmentIterator<'a>,
186}
187
188impl Iterator for RowChunkIterator<'_> {
189 type Item = Result<ChunkSlice, ExcelError>;
190
191 fn next(&mut self) -> Option<Self::Item> {
192 let segment = match self.segments.next()? {
193 Ok(segment) => segment,
194 Err(error) => return Some(Err(error)),
195 };
196 let view = self.segments.view;
197 let sheet = view.sheet();
198 let ci = segment.chunk_idx;
199 let rel_off = segment.chunk_offset;
200 let seg_len = segment.row_len;
201 let mut cols = Vec::with_capacity(view.cols);
202 for col_idx in view.sc..=view.ec {
203 #[cfg(test)]
204 range_work::record(|w| w.generic_columns += 1);
205 if col_idx >= sheet.columns.len() {
206 #[cfg(test)]
207 range_work::record(|w| {
208 w.null_arrays += 4;
209 w.null_slots += 4 * seg_len;
210 });
211 let numbers = Some(arrow_array::new_null_array(&DataType::Float64, seg_len));
212 let booleans = Some(arrow_array::new_null_array(&DataType::Boolean, seg_len));
213 let text = Some(arrow_array::new_null_array(&DataType::Utf8, seg_len));
214 let errors = Some(arrow_array::new_null_array(&DataType::UInt8, seg_len));
215 let type_tag: arrow_array::ArrayRef =
216 Arc::new(arrow_array::UInt8Array::from(vec![
217 arrow_store::TypeTag::Empty
218 as u8;
219 seg_len
220 ]));
221 cols.push(ChunkCol {
222 numbers,
223 booleans,
224 text,
225 errors,
226 type_tag,
227 });
228 } else {
229 let col = &sheet.columns[col_idx];
230 let Some(ch) = col.chunk(ci) else {
231 #[cfg(test)]
232 range_work::record(|w| {
233 w.null_arrays += 4;
234 w.null_slots += 4 * seg_len;
235 });
236 let numbers = Some(arrow_array::new_null_array(&DataType::Float64, seg_len));
237 let booleans = Some(arrow_array::new_null_array(&DataType::Boolean, seg_len));
238 let text = Some(arrow_array::new_null_array(&DataType::Utf8, seg_len));
239 let errors = Some(arrow_array::new_null_array(&DataType::UInt8, seg_len));
240 let type_tag: arrow_array::ArrayRef =
241 Arc::new(arrow_array::UInt8Array::from(vec![
242 arrow_store::TypeTag::Empty
243 as u8;
244 seg_len
245 ]));
246 cols.push(ChunkCol {
247 numbers,
248 booleans,
249 text,
250 errors,
251 type_tag,
252 });
253 continue;
254 };
255
256 let numbers_base: arrow_array::ArrayRef = ch.numbers_or_null();
257 let booleans_base: arrow_array::ArrayRef = ch.booleans_or_null();
258 let text_base: arrow_array::ArrayRef = ch.text_or_null();
259 let errors_base: arrow_array::ArrayRef = ch.errors_or_null();
260
261 let numbers = Some(numbers_base.slice(rel_off, seg_len));
262 let booleans = Some(booleans_base.slice(rel_off, seg_len));
263 let text = Some(text_base.slice(rel_off, seg_len));
264 let errors = Some(errors_base.slice(rel_off, seg_len));
265 let type_tag: arrow_array::ArrayRef = Arc::new(ch.type_tag.slice(rel_off, seg_len));
266 cols.push(ChunkCol {
267 numbers,
268 booleans,
269 text,
270 errors,
271 type_tag,
272 });
273 }
274 }
275 Some(Ok(ChunkSlice {
276 row_start: segment.row_start,
277 row_len: seg_len,
278 cols,
279 }))
280 }
281}
282
283impl<'a> RangeView<'a> {
284 pub(crate) fn new(
285 backing: RangeBacking<'a>,
286 sr: usize,
287 sc: usize,
288 er: usize,
289 ec: usize,
290 rows: usize,
291 cols: usize,
292 ) -> Self {
293 Self {
294 backing,
295 sr,
296 sc,
297 er,
298 ec,
299 rows,
300 cols,
301 cancel_token: None,
302 }
303 }
304
305 #[must_use]
311 pub fn with_cancel_token(mut self, token: Option<CancelToken>) -> Self {
312 self.cancel_token = token;
313 self
314 }
315
316 #[inline]
317 pub fn sheet(&self) -> &arrow_store::ArrowSheet {
318 match &self.backing {
319 RangeBacking::Borrowed(s) => s,
320 RangeBacking::Owned(s) => s,
321 }
322 }
323
324 pub fn from_owned_rows(
325 rows: Vec<Vec<LiteralValue>>,
326 date_system: DateSystem,
327 ) -> RangeView<'static> {
328 Self::try_from_owned_rows(rows, date_system, None)
329 .expect("uncancelled RangeView conversion")
330 }
331
332 pub(crate) fn try_from_owned_rows(
333 rows: Vec<Vec<LiteralValue>>,
334 date_system: DateSystem,
335 cancel_token: Option<CancelToken>,
336 ) -> Result<RangeView<'static>, ExcelError> {
337 let nrows = rows.len();
338 let ncols = rows.iter().map(|r| r.len()).max().unwrap_or(0);
339
340 let chunk_rows = 32 * 1024;
341 let mut ib = IngestBuilder::new("__tmp", ncols, chunk_rows, date_system);
342
343 for mut r in rows {
344 if cancel_token.as_ref().is_some_and(CancelToken::is_cancelled) {
345 return Err(ExcelError::new(
346 formualizer_common::ExcelErrorKind::Cancelled,
347 ));
348 }
349 r.resize(ncols, LiteralValue::Empty);
350 ib.append_row(&r).expect("append_row for RangeView");
351 }
352
353 let sheet = Arc::new(ib.finish());
354
355 if nrows == 0 || ncols == 0 {
356 return Ok(RangeView {
357 backing: RangeBacking::Owned(sheet),
358 sr: 1,
359 sc: 1,
360 er: 0,
361 ec: 0,
362 rows: 0,
363 cols: 0,
364 cancel_token,
365 });
366 }
367
368 Ok(RangeView {
369 backing: RangeBacking::Owned(sheet),
370 sr: 0,
371 sc: 0,
372 er: nrows - 1,
373 ec: ncols - 1,
374 rows: nrows,
375 cols: ncols,
376 cancel_token,
377 })
378 }
379
380 pub fn dims(&self) -> (usize, usize) {
381 (self.rows, self.cols)
382 }
383
384 pub fn expand_to(&self, rows: usize, cols: usize) -> RangeView<'a> {
385 let er = self.sr + rows.saturating_sub(1);
386 let ec = self.sc + cols.saturating_sub(1);
387 RangeView {
388 backing: match &self.backing {
389 RangeBacking::Borrowed(s) => RangeBacking::Borrowed(s),
390 RangeBacking::Owned(s) => RangeBacking::Owned(s.clone()),
391 },
392 sr: self.sr,
393 sc: self.sc,
394 er,
395 ec,
396 rows,
397 cols,
398 cancel_token: self.cancel_token.clone(),
399 }
400 }
401
402 pub fn sub_view(&self, rs: usize, cs: usize, rows: usize, cols: usize) -> RangeView<'a> {
403 let abs_sr = self.sr + rs;
404 let abs_sc = self.sc + cs;
405 let er = abs_sr + rows.saturating_sub(1);
406 let ec = abs_sc + cols.saturating_sub(1);
407 RangeView {
408 backing: match &self.backing {
409 RangeBacking::Borrowed(s) => RangeBacking::Borrowed(s),
410 RangeBacking::Owned(s) => RangeBacking::Owned(s.clone()),
411 },
412 sr: abs_sr,
413 sc: abs_sc,
414 er,
415 ec,
416 rows,
417 cols,
418 cancel_token: self.cancel_token.clone(),
419 }
420 }
421
422 #[inline]
423 pub fn is_empty(&self) -> bool {
424 self.rows == 0 || self.cols == 0
425 }
426
427 pub fn start_row(&self) -> usize {
429 self.sr
430 }
431 pub fn end_row(&self) -> usize {
433 self.er
434 }
435 pub fn start_col(&self) -> usize {
437 self.sc
438 }
439 pub fn end_col(&self) -> usize {
441 self.ec
442 }
443 pub(crate) fn is_sheet_backed(&self) -> bool {
447 matches!(self.backing, RangeBacking::Borrowed(_))
448 }
449
450 pub fn sheet_name(&self) -> &str {
452 &self.sheet().name
453 }
454
455 pub fn kind_probe(&self) -> RangeKind {
456 if self.is_empty() {
457 return RangeKind::Empty;
458 }
459
460 let mut has_num = false;
461 let mut has_text = false;
462
463 for r in 0..self.rows {
464 for c in 0..self.cols {
465 match self.get_cell(r, c) {
466 LiteralValue::Empty => {}
467 LiteralValue::Number(_) | LiteralValue::Int(_) => has_num = true,
468 LiteralValue::Text(_) => has_text = true,
469 _ => return RangeKind::Mixed,
470 }
471 if has_num && has_text {
472 return RangeKind::Mixed;
473 }
474 }
475 }
476
477 match (has_num, has_text) {
478 (false, false) => RangeKind::Empty,
479 (true, false) => RangeKind::NumericOnly,
480 (false, true) => RangeKind::TextOnly,
481 (true, true) => RangeKind::Mixed,
482 }
483 }
484
485 pub fn as_1x1(&self) -> Option<LiteralValue> {
486 if self.rows == 1 && self.cols == 1 {
487 Some(self.get_cell(0, 0))
488 } else {
489 None
490 }
491 }
492
493 pub fn get_cell(&self, row: usize, col: usize) -> LiteralValue {
496 if row >= self.rows || col >= self.cols {
497 return LiteralValue::Empty;
498 }
499 let abs_row = self.sr + row;
500 let abs_col = self.sc + col;
501 let sheet = self.sheet();
502 let sheet_rows = sheet.nrows as usize;
503 if abs_row >= sheet_rows {
504 return LiteralValue::Empty;
505 }
506 if abs_col >= sheet.columns.len() {
507 return LiteralValue::Empty;
508 }
509 let col_ref = &sheet.columns[abs_col];
510 let chunk_starts = &sheet.chunk_starts;
512 let ch_idx = match chunk_starts.binary_search(&abs_row) {
513 Ok(i) => i,
514 Err(0) => 0,
515 Err(i) => i - 1,
516 };
517 let Some(ch) = col_ref.chunk(ch_idx) else {
518 return LiteralValue::Empty;
519 };
520 let row_start = chunk_starts[ch_idx];
521 let in_off = abs_row - row_start;
522 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
524 if let Some(ov) = cascade.get_scalar(in_off) {
525 return ov.to_literal_for(sheet.date_system);
526 }
527 let tag_u8 = ch.type_tag.value(in_off);
529 match arrow_store::TypeTag::from_u8(tag_u8) {
530 arrow_store::TypeTag::Empty => LiteralValue::Empty,
531 arrow_store::TypeTag::Number => {
532 if let Some(arr) = &ch.numbers {
533 if arr.is_null(in_off) {
534 return LiteralValue::Empty;
535 }
536 LiteralValue::Number(arr.value(in_off))
537 } else {
538 LiteralValue::Empty
539 }
540 }
541 arrow_store::TypeTag::DateTime | arrow_store::TypeTag::Duration => {
542 if let Some(arr) = &ch.numbers {
543 if arr.is_null(in_off) {
544 LiteralValue::Empty
545 } else {
546 LiteralValue::Number(arr.value(in_off))
547 }
548 } else {
549 LiteralValue::Empty
550 }
551 }
552 arrow_store::TypeTag::Boolean => {
553 if let Some(arr) = &ch.booleans {
554 if arr.is_null(in_off) {
555 return LiteralValue::Empty;
556 }
557 LiteralValue::Boolean(arr.value(in_off))
558 } else {
559 LiteralValue::Empty
560 }
561 }
562 arrow_store::TypeTag::Text => {
563 if let Some(arr) = &ch.text {
564 if arr.is_null(in_off) {
565 return LiteralValue::Empty;
566 }
567 let sa = arr
568 .as_any()
569 .downcast_ref::<arrow_array::StringArray>()
570 .unwrap();
571 LiteralValue::Text(sa.value(in_off).to_string())
572 } else {
573 LiteralValue::Empty
574 }
575 }
576 arrow_store::TypeTag::Error => {
577 if let Some(arr) = &ch.errors {
578 if arr.is_null(in_off) {
579 return LiteralValue::Empty;
580 }
581 let kind = arrow_store::unmap_error_code(arr.value(in_off));
582 LiteralValue::Error(ExcelError::new(kind))
583 } else {
584 LiteralValue::Empty
585 }
586 }
587 arrow_store::TypeTag::Pending => LiteralValue::Pending,
588 }
589 }
590
591 pub fn iter_row_chunks(&self) -> RowChunkIterator<'_> {
593 RowChunkIterator {
594 segments: self.iter_row_segments(),
595 }
596 }
597
598 fn iter_row_segments(&self) -> RowSegmentIterator<'_> {
599 #[cfg(test)]
600 range_work::record(|w| w.iterators += 1);
601 RowSegmentIterator {
602 view: self,
603 chunks: None,
604 }
605 }
606
607 pub fn for_each_cell(
609 &self,
610 f: &mut dyn FnMut(&LiteralValue) -> Result<(), ExcelError>,
611 ) -> Result<(), ExcelError> {
612 for res in self.iter_row_chunks() {
613 let cs = res?;
614 for r in 0..cs.row_len {
615 for c in 0..self.cols {
616 let tmp = self.get_cell(cs.row_start + r, c);
617 f(&tmp)?;
618 }
619 }
620 }
621 Ok(())
622 }
623
624 pub fn for_each_row(
626 &self,
627 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
628 ) -> Result<(), ExcelError> {
629 let mut buf: Vec<LiteralValue> = Vec::with_capacity(self.cols);
630 for r in 0..self.rows {
631 buf.clear();
632 for c in 0..self.cols {
633 buf.push(self.get_cell(r, c));
634 }
635 f(&buf[..])?;
636 }
637 Ok(())
638 }
639
640 pub fn for_each_col(
642 &self,
643 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
644 ) -> Result<(), ExcelError> {
645 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(self.rows);
646 for c in 0..self.cols {
647 col_buf.clear();
648 for r in 0..self.rows {
649 col_buf.push(self.get_cell(r, c));
650 }
651 f(&col_buf[..])?;
652 }
653 Ok(())
654 }
655
656 pub fn get_cell_numeric(&self, row: usize, col: usize, policy: CoercionPolicy) -> Option<f64> {
659 if row >= self.rows || col >= self.cols {
660 return None;
661 }
662
663 let val = self.get_cell(row, col);
664 pack_numeric(&val, policy).ok().flatten()
665 }
666
667 pub fn numbers_chunked(
669 &self,
670 policy: CoercionPolicy,
671 min_chunk: usize,
672 f: &mut dyn FnMut(NumericChunk) -> Result<(), ExcelError>,
673 ) -> Result<(), ExcelError> {
674 if matches!(policy, CoercionPolicy::NumberStrict) {
676 for res in self.numbers_slices() {
677 let (_, _, cols) = res?;
678 for col in cols {
679 if col.null_count() < col.len() {
680 let data = col.values();
681 let validity = if col.null_count() > 0 {
687 None } else {
691 None
692 };
693
694 if col.null_count() == 0 {
695 f(NumericChunk { data, validity })?;
696 } else {
697 let mut buf = Vec::with_capacity(col.len());
699 for i in 0..col.len() {
700 if !col.is_null(i) {
701 buf.push(col.value(i));
702 }
703 }
704 if !buf.is_empty() {
705 f(NumericChunk {
706 data: &buf,
707 validity: None,
708 })?;
709 }
710 }
711 }
712 }
713 }
714 return Ok(());
715 }
716
717 let min_chunk = min_chunk.max(1);
718 let mut buf: Vec<f64> = Vec::with_capacity(min_chunk);
719 let mut flush = |buf: &mut Vec<f64>| -> Result<(), ExcelError> {
720 if buf.is_empty() {
721 return Ok(());
722 }
723 let ptr = buf.as_ptr();
725 let len = buf.len();
726 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
727 let chunk = NumericChunk {
728 data: slice,
729 validity: None,
730 };
731 f(chunk)?;
732 buf.clear();
733 Ok(())
734 };
735
736 self.for_each_cell(&mut |v| {
737 if let Some(n) = pack_numeric(v, policy)? {
738 buf.push(n);
739 if buf.len() >= min_chunk {
740 flush(&mut buf)?;
741 }
742 }
743 Ok(())
744 })?;
745 flush(&mut buf)?;
746
747 Ok(())
748 }
749
750 pub fn numbers_slices(
752 &self,
753 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::Float64Array>>), ExcelError>> + '_
754 {
755 self.iter_row_segments().map(move |res| {
756 let segment = res?;
757 let mut out_cols = Vec::with_capacity(self.cols);
758 let sheet = self.sheet();
759 for col_idx in self.sc..=self.ec {
760 let Some(ch) = sheet
761 .columns
762 .get(col_idx)
763 .and_then(|col| col.chunk(segment.chunk_idx))
764 else {
765 #[cfg(test)]
766 range_work::record(|w| {
767 w.null_arrays += 1;
768 w.null_slots += segment.row_len;
769 });
770 out_cols.push(Arc::new(arrow_array::Float64Array::new_null(
771 segment.row_len,
772 )));
773 continue;
774 };
775 let base = ch
776 .numbers_or_null()
777 .slice(segment.chunk_offset, segment.row_len);
778 let range = segment.chunk_offset..segment.chunk_offset + segment.row_len;
779 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
780 out_cols.push(if cascade.has_any_in_range(range.clone()) {
781 ch.merged_numbers(range.clone())
782 .unwrap_or_else(|| cascade.select_numbers(range, &base))
783 } else {
784 Arc::new(base)
785 });
786 }
787 Ok((segment.row_start, segment.row_len, out_cols))
788 })
789 }
790
791 pub fn booleans_slices(
793 &self,
794 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::BooleanArray>>), ExcelError>> + '_
795 {
796 self.iter_row_chunks().map(move |res| {
797 let cs = res?;
798 let mut out_cols: Vec<Arc<arrow_array::BooleanArray>> =
799 Vec::with_capacity(cs.cols.len());
800 let sheet = self.sheet();
801 let chunk_starts = &sheet.chunk_starts;
802
803 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
804 let base = cs.cols[local_c]
805 .booleans
806 .as_ref()
807 .expect("booleans lane exists")
808 .clone();
809 let base_ba = base
810 .as_any()
811 .downcast_ref::<arrow_array::BooleanArray>()
812 .unwrap()
813 .clone();
814 let base_arc = Arc::new(base_ba);
815
816 let abs_seg_start = self.sr + cs.row_start;
818 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
819 Ok(i) => i,
820 Err(0) => 0,
821 Err(i) => i - 1,
822 };
823 if col_idx >= sheet.columns.len() {
824 out_cols.push(base_arc);
825 continue;
826 }
827 let col = &sheet.columns[col_idx];
828 let Some(ch) = col.chunk(ch_idx) else {
829 out_cols.push(base_arc);
830 continue;
831 };
832 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
833 let seg_range = rel_off..(rel_off + cs.row_len);
834 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
835 if cascade.has_any_in_range(seg_range.clone()) {
836 let base_ba = base
837 .as_any()
838 .downcast_ref::<arrow_array::BooleanArray>()
839 .unwrap();
840 out_cols.push(cascade.select_booleans(seg_range, base_ba));
841 } else {
842 out_cols.push(base_arc);
843 }
844 }
845 Ok((cs.row_start, cs.row_len, out_cols))
846 })
847 }
848
849 pub fn text_slices(
851 &self,
852 ) -> impl Iterator<Item = Result<(usize, usize, Vec<arrow_array::ArrayRef>), ExcelError>> + '_
853 {
854 self.iter_row_chunks().map(move |res| {
855 let cs = res?;
856 let mut out_cols: Vec<arrow_array::ArrayRef> = Vec::with_capacity(cs.cols.len());
857 let sheet = self.sheet();
858 let chunk_starts = &sheet.chunk_starts;
859
860 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
861 let base = cs.cols[local_c]
862 .text
863 .as_ref()
864 .expect("text lane exists")
865 .clone();
866 let abs_seg_start = self.sr + cs.row_start;
867 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
868 Ok(i) => i,
869 Err(0) => 0,
870 Err(i) => i - 1,
871 };
872 if col_idx >= sheet.columns.len() {
873 out_cols.push(base.clone());
874 continue;
875 }
876 let col = &sheet.columns[col_idx];
877 let Some(ch) = col.chunk(ch_idx) else {
878 out_cols.push(base.clone());
879 continue;
880 };
881 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
882 let seg_range = rel_off..(rel_off + cs.row_len);
883 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
884 if cascade.has_any_in_range(seg_range.clone()) {
885 let base_sa = base
886 .as_any()
887 .downcast_ref::<arrow_array::StringArray>()
888 .unwrap();
889 out_cols.push(cascade.select_text(seg_range, base_sa));
890 } else {
891 out_cols.push(base.clone());
892 }
893 }
894 Ok((cs.row_start, cs.row_len, out_cols))
895 })
896 }
897
898 pub fn lowered_text_slices(
900 &self,
901 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::StringArray>>), ExcelError>> + '_
902 {
903 self.iter_row_chunks().map(move |res| {
904 let cs = res?;
905 let mut out_cols: Vec<Arc<arrow_array::StringArray>> =
906 Vec::with_capacity(cs.cols.len());
907 let sheet = self.sheet();
908 let chunk_starts = &sheet.chunk_starts;
909
910 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
911 let abs_seg_start = self.sr + cs.row_start;
913 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
914 Ok(i) => i,
915 Err(0) => 0,
916 Err(i) => i - 1,
917 };
918 if col_idx >= sheet.columns.len() {
919 out_cols.push(Arc::new(arrow_array::StringArray::new_null(cs.row_len)));
920 continue;
921 }
922 let col = &sheet.columns[col_idx];
923 let Some(ch) = col.chunk(ch_idx) else {
924 out_cols.push(Arc::new(arrow_array::StringArray::new_null(cs.row_len)));
925 continue;
926 };
927 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
928 let seg_range = rel_off..(rel_off + cs.row_len);
929
930 let base_lowered = ch.text_lower_or_null();
931 let base_seg = base_lowered.slice(rel_off, cs.row_len);
932 let base_sa = base_seg
933 .as_any()
934 .downcast_ref::<arrow_array::StringArray>()
935 .expect("lowered slice downcast");
936
937 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
938 if cascade.has_any_in_range(seg_range.clone()) {
939 out_cols.push(cascade.select_lowered_text(seg_range, base_sa));
940 } else {
941 out_cols.push(Arc::new(base_sa.clone()));
942 }
943 }
944 Ok((cs.row_start, cs.row_len, out_cols))
945 })
946 }
947
948 pub fn errors_slices(
950 &self,
951 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::UInt8Array>>), ExcelError>> + '_
952 {
953 self.iter_row_segments().map(move |res| {
954 let segment = res?;
955 let mut out_cols = Vec::with_capacity(self.cols);
956 let sheet = self.sheet();
957 for col_idx in self.sc..=self.ec {
958 let Some(ch) = sheet
959 .columns
960 .get(col_idx)
961 .and_then(|col| col.chunk(segment.chunk_idx))
962 else {
963 #[cfg(test)]
964 range_work::record(|w| {
965 w.null_arrays += 1;
966 w.null_slots += segment.row_len;
967 });
968 out_cols.push(Arc::new(arrow_array::UInt8Array::new_null(segment.row_len)));
969 continue;
970 };
971 let base = ch
972 .errors_or_null()
973 .slice(segment.chunk_offset, segment.row_len);
974 let range = segment.chunk_offset..segment.chunk_offset + segment.row_len;
975 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
976 out_cols.push(if cascade.has_any_in_range(range.clone()) {
977 ch.merged_errors(range.clone())
978 .unwrap_or_else(|| cascade.select_errors(range, &base))
979 } else {
980 Arc::new(base)
981 });
982 }
983 Ok((segment.row_start, segment.row_len, out_cols))
984 })
985 }
986
987 pub fn type_tags_slices(
989 &self,
990 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::UInt8Array>>), ExcelError>> + '_
991 {
992 self.iter_row_chunks().map(move |res| {
993 let cs = res?;
994 let mut out_cols: Vec<Arc<arrow_array::UInt8Array>> = Vec::with_capacity(cs.cols.len());
995 let sheet = self.sheet();
996 let chunk_starts = &sheet.chunk_starts;
997
998 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
999 let base = cs.cols[local_c].type_tag.clone();
1000 let base_ta = base
1001 .as_any()
1002 .downcast_ref::<arrow_array::UInt8Array>()
1003 .unwrap()
1004 .clone();
1005 let base_arc = Arc::new(base_ta);
1006
1007 let abs_seg_start = self.sr + cs.row_start;
1008 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
1009 Ok(i) => i,
1010 Err(0) => 0,
1011 Err(i) => i - 1,
1012 };
1013 if col_idx >= sheet.columns.len() {
1014 out_cols.push(base_arc);
1015 continue;
1016 }
1017 let col = &sheet.columns[col_idx];
1018 let Some(ch) = col.chunk(ch_idx) else {
1019 out_cols.push(base_arc);
1020 continue;
1021 };
1022 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
1023 let seg_range = rel_off..(rel_off + cs.row_len);
1024 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1025 if cascade.has_any_in_range(seg_range.clone()) {
1026 let base_ta = base
1027 .as_any()
1028 .downcast_ref::<arrow_array::UInt8Array>()
1029 .unwrap();
1030 out_cols.push(cascade.select_type_tags(seg_range, base_ta));
1031 } else {
1032 out_cols.push(base_arc);
1033 }
1034 }
1035 Ok((cs.row_start, cs.row_len, out_cols))
1036 })
1037 }
1038
1039 pub fn lowered_text_columns(&self) -> Vec<arrow_array::ArrayRef> {
1042 use crate::compute_prelude::concat_arrays;
1043
1044 let mut out: Vec<arrow_array::ArrayRef> = Vec::with_capacity(self.cols);
1045 if self.rows == 0 || self.cols == 0 {
1046 return out;
1047 }
1048 let sheet = self.sheet();
1049 let chunk_starts = &sheet.chunk_starts;
1050 let sheet_rows = sheet.nrows as usize;
1052 if sheet_rows == 0 || self.sr >= sheet_rows {
1053 for _ in 0..self.cols {
1054 out.push(arrow_array::new_null_array(&DataType::Utf8, 0));
1055 }
1056 return out;
1057 }
1058 let row_end = self.er.min(sheet_rows.saturating_sub(1));
1059 let physical_len = row_end.saturating_sub(self.sr) + 1;
1060 for col_idx in self.sc..=self.ec {
1061 let mut segs: Vec<arrow_array::ArrayRef> = Vec::new();
1062 if col_idx >= sheet.columns.len() {
1063 segs.push(arrow_array::new_null_array(&DataType::Utf8, physical_len));
1065 } else {
1066 let col_ref = &sheet.columns[col_idx];
1067 for (ci, &start) in chunk_starts.iter().enumerate() {
1068 let chunk_end = chunk_starts
1069 .get(ci + 1)
1070 .copied()
1071 .unwrap_or(sheet.nrows as usize);
1072 let len = chunk_end.saturating_sub(start);
1073 if len == 0 {
1074 continue;
1075 }
1076 let end = start + len - 1;
1077 let is = start.max(self.sr);
1078 let ie = end.min(row_end);
1079 if is > ie {
1080 continue;
1081 }
1082 let seg_len = ie - is + 1;
1083 let rel_off = is - start;
1084 if let Some(ch) = col_ref.chunk(ci) {
1085 let seg_range = rel_off..(rel_off + seg_len);
1087 let cascade =
1088 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1089 if cascade.has_any_in_range(seg_range.clone()) {
1090 let base_lowered = ch.text_lower_or_null();
1091 let base_seg = base_lowered.slice(rel_off, seg_len);
1092 let base_sa = base_seg
1093 .as_any()
1094 .downcast_ref::<arrow_array::StringArray>()
1095 .expect("lowered slice downcast");
1096 segs.push(cascade.select_lowered_text(seg_range, base_sa));
1097 } else {
1098 let lowered = ch.text_lower_or_null();
1100 segs.push(lowered.slice(rel_off, seg_len));
1101 }
1102 } else {
1103 segs.push(arrow_array::new_null_array(&DataType::Utf8, seg_len));
1104 }
1105 }
1106 }
1107 if segs.is_empty() {
1109 segs.push(arrow_array::new_null_array(&DataType::Utf8, physical_len));
1110 }
1111 let anys: Vec<&dyn arrow_array::Array> = segs
1113 .iter()
1114 .map(|a| a.as_ref() as &dyn arrow_array::Array)
1115 .collect();
1116 let conc = concat_arrays(&anys).expect("concat lowered segments");
1117 out.push(conc);
1118 }
1119 out
1120 }
1121
1122 pub fn slice_numbers(
1124 &self,
1125 rel_start: usize,
1126 len: usize,
1127 ) -> Vec<Option<Arc<arrow_array::Float64Array>>> {
1128 let abs_start = self.sr + rel_start;
1129 let abs_end = abs_start + len;
1130 let sheet = self.sheet();
1131 let chunk_starts = &sheet.chunk_starts;
1132
1133 let mut out_cols = Vec::with_capacity(self.cols);
1134 for col_idx in self.sc..=self.ec {
1135 if col_idx >= sheet.columns.len() {
1136 out_cols.push(None);
1137 continue;
1138 }
1139 let col = &sheet.columns[col_idx];
1140
1141 let start_ch_idx = match chunk_starts.binary_search(&abs_start) {
1142 Ok(i) => i,
1143 Err(0) => 0,
1144 Err(i) => i - 1,
1145 };
1146
1147 let mut segments: Vec<Arc<arrow_array::Float64Array>> = Vec::new();
1148 let mut null_only = true;
1149
1150 let mut curr = abs_start;
1151 let mut remaining = len;
1152 let mut ch_idx = start_ch_idx;
1153
1154 while remaining > 0 && ch_idx < chunk_starts.len() {
1155 let ch_start = chunk_starts[ch_idx];
1156 let ch_end = chunk_starts
1157 .get(ch_idx + 1)
1158 .copied()
1159 .unwrap_or(sheet.nrows as usize);
1160 let ch_len = ch_end.saturating_sub(ch_start);
1161 if ch_len == 0 {
1162 ch_idx += 1;
1163 continue;
1164 }
1165
1166 let overlap_start = curr.max(ch_start);
1167 let overlap_end = ch_end.min(abs_end);
1168
1169 if overlap_start < overlap_end {
1170 let seg_len = overlap_end - overlap_start;
1171 let rel_off_in_chunk = overlap_start - ch_start;
1172
1173 if let Some(ch) = col.chunk(ch_idx) {
1174 let base_nums_arc = ch.numbers_or_null();
1175 let base_nums = base_nums_arc.as_ref();
1176
1177 let seg_range = rel_off_in_chunk..(rel_off_in_chunk + seg_len);
1178 let cascade =
1179 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1180
1181 let final_arr = if cascade.has_any_in_range(seg_range.clone()) {
1182 let base_slice = base_nums.slice(rel_off_in_chunk, seg_len);
1183 let base_fa = base_slice
1184 .as_any()
1185 .downcast_ref::<arrow_array::Float64Array>()
1186 .unwrap();
1187 cascade.select_numbers(seg_range, base_fa).as_ref().clone()
1188 } else {
1189 let sl = base_nums.slice(rel_off_in_chunk, seg_len);
1190 sl.as_any()
1191 .downcast_ref::<arrow_array::Float64Array>()
1192 .unwrap()
1193 .clone()
1194 };
1195
1196 if final_arr.null_count() < final_arr.len() {
1197 null_only = false;
1198 }
1199 segments.push(Arc::new(final_arr));
1200 } else {
1201 segments.push(Arc::new(arrow_array::Float64Array::new_null(seg_len)));
1202 }
1203 curr += seg_len;
1204 remaining -= seg_len;
1205 }
1206 ch_idx += 1;
1207 }
1208
1209 if remaining > 0 {
1210 segments.push(Arc::new(arrow_array::Float64Array::new_null(remaining)));
1211 }
1212
1213 if segments.len() == 1 {
1214 if null_only && segments[0].null_count() == segments[0].len() {
1215 out_cols.push(None);
1216 } else {
1217 out_cols.push(Some(segments.pop().unwrap()));
1218 }
1219 } else {
1220 let refs: Vec<&dyn Array> =
1221 segments.iter().map(|a| a.as_ref() as &dyn Array).collect();
1222 let c = crate::compute_prelude::concat_arrays(&refs).expect("concat slice");
1223 let fa = c
1224 .as_any()
1225 .downcast_ref::<arrow_array::Float64Array>()
1226 .unwrap()
1227 .clone();
1228 out_cols.push(Some(Arc::new(fa)));
1229 }
1230 }
1231 out_cols
1232 }
1233
1234 pub fn slice_lowered_text(
1236 &self,
1237 rel_start: usize,
1238 len: usize,
1239 ) -> Vec<Option<Arc<arrow_array::StringArray>>> {
1240 let abs_start = self.sr + rel_start;
1241 let abs_end = abs_start + len;
1242 let sheet = self.sheet();
1243 let chunk_starts = &sheet.chunk_starts;
1244
1245 let mut out_cols = Vec::with_capacity(self.cols);
1246 for col_idx in self.sc..=self.ec {
1247 if col_idx >= sheet.columns.len() {
1248 out_cols.push(None);
1249 continue;
1250 }
1251 let col = &sheet.columns[col_idx];
1252 let start_ch_idx = match chunk_starts.binary_search(&abs_start) {
1253 Ok(i) => i,
1254 Err(0) => 0,
1255 Err(i) => i - 1,
1256 };
1257
1258 let mut segments: Vec<Arc<arrow_array::StringArray>> = Vec::new();
1259 let mut null_only = true;
1260
1261 let mut curr = abs_start;
1262 let mut remaining = len;
1263 let mut ch_idx = start_ch_idx;
1264
1265 while remaining > 0 && ch_idx < chunk_starts.len() {
1266 let ch_start = chunk_starts[ch_idx];
1267 let ch_end = chunk_starts
1268 .get(ch_idx + 1)
1269 .copied()
1270 .unwrap_or(sheet.nrows as usize);
1271 let ch_len = ch_end.saturating_sub(ch_start);
1272 if ch_len == 0 {
1273 ch_idx += 1;
1274 continue;
1275 }
1276
1277 let overlap_start = curr.max(ch_start);
1278 let overlap_end = ch_end.min(abs_end);
1279
1280 if overlap_start < overlap_end {
1281 let seg_len = overlap_end - overlap_start;
1282 let rel_off_in_chunk = overlap_start - ch_start;
1283
1284 if let Some(ch) = col.chunk(ch_idx) {
1285 let base_lowered = ch.text_lower_or_null();
1286 let seg_range = rel_off_in_chunk..(rel_off_in_chunk + seg_len);
1287 let cascade =
1288 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1289
1290 let final_arr = if cascade.has_any_in_range(seg_range.clone()) {
1291 let base_slice = base_lowered.slice(rel_off_in_chunk, seg_len);
1292 let base_sa = base_slice
1293 .as_any()
1294 .downcast_ref::<arrow_array::StringArray>()
1295 .unwrap();
1296 cascade
1297 .select_lowered_text(seg_range, base_sa)
1298 .as_ref()
1299 .clone()
1300 } else {
1301 let sl = base_lowered.slice(rel_off_in_chunk, seg_len);
1302 sl.as_any()
1303 .downcast_ref::<arrow_array::StringArray>()
1304 .unwrap()
1305 .clone()
1306 };
1307
1308 if final_arr.null_count() < final_arr.len() {
1309 null_only = false;
1310 }
1311 segments.push(Arc::new(final_arr));
1312 } else {
1313 segments.push(Arc::new(arrow_array::StringArray::new_null(seg_len)));
1314 }
1315 curr += seg_len;
1316 remaining -= seg_len;
1317 }
1318 ch_idx += 1;
1319 }
1320
1321 if remaining > 0 {
1322 segments.push(Arc::new(arrow_array::StringArray::new_null(remaining)));
1323 }
1324
1325 if segments.len() == 1 {
1326 if null_only && segments[0].null_count() == segments[0].len() {
1327 out_cols.push(None);
1328 } else {
1329 out_cols.push(Some(segments.pop().unwrap()));
1330 }
1331 } else {
1332 let refs: Vec<&dyn Array> =
1333 segments.iter().map(|a| a.as_ref() as &dyn Array).collect();
1334 let c = crate::compute_prelude::concat_arrays(&refs).expect("concat text");
1335 let sa = c
1336 .as_any()
1337 .downcast_ref::<arrow_array::StringArray>()
1338 .unwrap()
1339 .clone();
1340 out_cols.push(Some(Arc::new(sa)));
1341 }
1342 }
1343 out_cols
1344 }
1345}
1346
1347#[inline]
1348fn pack_numeric(v: &LiteralValue, policy: CoercionPolicy) -> Result<Option<f64>, ExcelError> {
1349 match policy {
1350 CoercionPolicy::NumberLenientText => match v {
1351 LiteralValue::Error(e) => Err(e.clone()),
1352 LiteralValue::Empty => Ok(None),
1353 other => Ok(crate::coercion::to_number_lenient(other).ok()),
1354 },
1355 CoercionPolicy::NumberStrict => match v {
1356 LiteralValue::Error(e) => Err(e.clone()),
1357 LiteralValue::Empty => Ok(None),
1358 other => Ok(crate::coercion::to_number_strict(other).ok()),
1359 },
1360 _ => match v {
1361 LiteralValue::Error(e) => Err(e.clone()),
1362 _ => Ok(None),
1363 },
1364 }
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369 use super::*;
1370
1371 #[test]
1372 fn owned_rows_numeric_chunking() {
1373 let data: Vec<Vec<LiteralValue>> = vec![
1374 vec![
1375 LiteralValue::Number(1.0),
1376 LiteralValue::Text("x".into()),
1377 LiteralValue::Number(3.0),
1378 ],
1379 vec![
1380 LiteralValue::Boolean(true),
1381 LiteralValue::Empty,
1382 LiteralValue::Number(2.5),
1383 ],
1384 ];
1385 let view = RangeView::from_owned_rows(data, DateSystem::Excel1900);
1386 let mut sum = 0.0f64;
1387 view.numbers_chunked(CoercionPolicy::NumberLenientText, 2, &mut |chunk| {
1388 for &n in chunk.data {
1389 sum += n;
1390 }
1391 Ok(())
1392 })
1393 .unwrap();
1394 assert!((sum - 7.5).abs() < 1e-9);
1395 }
1396
1397 #[test]
1398 fn as_1x1_works() {
1399 let view = RangeView::from_owned_rows(
1400 vec![vec![LiteralValue::Number(7.0)]],
1401 DateSystem::Excel1900,
1402 );
1403 assert_eq!(view.as_1x1(), Some(LiteralValue::Number(7.0)));
1404 }
1405
1406 #[test]
1407 fn pre_cancelled_token_stops_owned_row_construction() {
1408 let token = CancelToken::new();
1409 token.cancel();
1410
1411 let error = RangeView::try_from_owned_rows(
1412 vec![vec![LiteralValue::Number(1.0)]],
1413 DateSystem::Excel1900,
1414 Some(token),
1415 )
1416 .unwrap_err();
1417
1418 assert_eq!(error.kind, formualizer_common::ExcelErrorKind::Cancelled);
1419 }
1420
1421 #[test]
1422 fn pre_cancelled_token_stops_row_chunk_iteration() {
1423 let token = CancelToken::new();
1424 token.cancel();
1425 let view = RangeView::from_owned_rows(
1426 vec![vec![LiteralValue::Number(1.0)]],
1427 DateSystem::Excel1900,
1428 )
1429 .with_cancel_token(Some(token));
1430
1431 let Some(Err(error)) = view.iter_row_chunks().next() else {
1432 panic!("pre-cancelled chunk iteration should return cancellation");
1433 };
1434
1435 assert_eq!(error.kind, formualizer_common::ExcelErrorKind::Cancelled);
1436 }
1437}
1438#[cfg(test)]
1439mod bounded_projection_tests {
1440 use super::*;
1441 use crate::arrow_store::{ArrowSheet, OverlayFragment, OverlayValue};
1442 use arrow_array::{Float64Array, UInt8Array};
1443 use formualizer_common::ExcelErrorKind;
1444
1445 fn segments(view: &RangeView<'_>) -> Vec<(usize, usize, usize, usize)> {
1447 let sheet = view.sheet();
1448 let mut out = Vec::new();
1449 let row_end = view.er.min((sheet.nrows as usize).saturating_sub(1));
1450 for (ci, &start) in sheet.chunk_starts.iter().enumerate() {
1451 let end = sheet
1452 .chunk_starts
1453 .get(ci + 1)
1454 .copied()
1455 .unwrap_or(sheet.nrows as usize);
1456 let len = end.saturating_sub(start);
1457 if len == 0 {
1458 continue;
1459 }
1460 let lo = start.max(view.sr);
1461 let hi = (start + len - 1).min(row_end);
1462 if lo <= hi {
1463 out.push((ci, lo - start, lo - view.sr, hi - lo + 1));
1464 }
1465 }
1466 out
1467 }
1468
1469 fn assert_projection(view: &RangeView<'_>) {
1470 let expected = segments(view);
1471 let generic: Vec<_> = view.iter_row_chunks().map(Result::unwrap).collect();
1472 assert_eq!(
1473 generic
1474 .iter()
1475 .map(|s| (s.row_start, s.row_len))
1476 .collect::<Vec<_>>(),
1477 expected.iter().map(|s| (s.2, s.3)).collect::<Vec<_>>()
1478 );
1479 let numbers: Vec<_> = view.numbers_slices().map(Result::unwrap).collect();
1480 let errors: Vec<_> = view.errors_slices().map(Result::unwrap).collect();
1481 assert_eq!(numbers.len(), expected.len());
1482 assert_eq!(errors.len(), expected.len());
1483 for (seg, &(ci, offset, row_start, len)) in expected.iter().enumerate() {
1484 assert_eq!((numbers[seg].0, numbers[seg].1), (row_start, len));
1485 assert_eq!((errors[seg].0, errors[seg].1), (row_start, len));
1486 let columns = (view.sc..=view.ec).count();
1487 assert_eq!(generic[seg].cols.len(), columns);
1488 assert_eq!(numbers[seg].2.len(), columns);
1489 assert_eq!(errors[seg].2.len(), columns);
1490 for (c, absolute) in (view.sc..=view.ec).enumerate() {
1491 let (base_n, base_e) =
1492 match view.sheet().columns.get(absolute).and_then(|c| c.chunk(ci)) {
1493 Some(ch) => {
1494 let n = ch
1496 .numbers
1497 .as_ref()
1498 .map(|a| a.slice(offset, len))
1499 .unwrap_or_else(|| Float64Array::new_null(len));
1500 let e = ch
1501 .errors
1502 .as_ref()
1503 .map(|a| a.slice(offset, len))
1504 .unwrap_or_else(|| UInt8Array::new_null(len));
1505 let cascade =
1506 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1507 (
1508 cascade.select_numbers(offset..offset + len, &n),
1509 cascade.select_errors(offset..offset + len, &e),
1510 )
1511 }
1512 None => (
1513 Arc::new(Float64Array::new_null(len)),
1514 Arc::new(UInt8Array::new_null(len)),
1515 ),
1516 };
1517 let actual_n = &numbers[seg].2[c];
1518 let actual_e = &errors[seg].2[c];
1519 assert_eq!(actual_n.len(), len);
1520 assert_eq!(actual_e.len(), len);
1521 for i in 0..len {
1522 assert_eq!(actual_n.is_null(i), base_n.is_null(i));
1523 assert_eq!(actual_e.is_null(i), base_e.is_null(i));
1524 if !base_n.is_null(i) {
1525 assert_eq!(actual_n.value(i).to_bits(), base_n.value(i).to_bits());
1526 }
1527 if !base_e.is_null(i) {
1528 assert_eq!(actual_e.value(i), base_e.value(i));
1529 }
1530 }
1531 }
1532 }
1533 }
1534
1535 fn fixture(chunk_rows: usize) -> ArrowSheet {
1536 let mut ingest = IngestBuilder::new("S", 3, chunk_rows, DateSystem::Excel1900);
1537 for row in 0..11 {
1538 ingest
1539 .append_row(&[
1540 LiteralValue::Number(row as f64),
1541 if row % 3 == 0 {
1542 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))
1543 } else {
1544 LiteralValue::Empty
1545 },
1546 LiteralValue::Text(format!("r{row}")),
1547 ])
1548 .unwrap();
1549 }
1550 let mut sheet = ingest.finish();
1551 sheet.ensure_row_capacity(29);
1552 for (row, value) in [
1553 (1, OverlayValue::Empty),
1554 (3, OverlayValue::Text(Arc::from("mask"))),
1555 (
1556 7,
1557 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Na)),
1558 ),
1559 (22, OverlayValue::Number(17.0)),
1560 (28, OverlayValue::Pending),
1561 ] {
1562 let (ci, off) = sheet.chunk_of_row(row).unwrap();
1563 let chunk = sheet.ensure_column_chunk_mut(0, ci).unwrap();
1564 chunk.computed_overlay.set(off, OverlayValue::Number(99.0));
1565 chunk.overlay.set(off, value);
1566 }
1567 for (row, user) in [(0, OverlayValue::Empty), (8, OverlayValue::Number(17.0))] {
1568 let (ci, off) = sheet.chunk_of_row(row).unwrap();
1569 let ch = sheet.ensure_column_chunk_mut(1, ci).unwrap();
1570 ch.computed_overlay.set(
1571 off,
1572 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Na)),
1573 );
1574 ch.overlay.set(off, user);
1575 }
1576 sheet
1577 }
1578
1579 #[test]
1580 fn range_projection_matches_independent_oracle_for_all_small_bounds() {
1581 for chunk_rows in [1, 3, 8, 32] {
1582 let sheet = fixture(chunk_rows);
1583 for sr in 0..=31 {
1584 for er in 0..=31 {
1585 for (sc, ec) in [(0, 0), (1, 2), (0, 4), (4, 5), (2, 1)] {
1586 assert_projection(&sheet.range_view(sr, sc, er, ec));
1587 }
1588 }
1589 }
1590 let base = sheet.range_view(2, 1, 10, 2);
1591 for rows in [0, 1, 9, 40] {
1592 for cols in [0, 1, 4] {
1593 assert_projection(&base.sub_view(1, 1, rows, cols));
1594 assert_projection(&base.expand_to(rows, cols));
1595 }
1596 }
1597 }
1598 let empty = IngestBuilder::new("S", 1, 4, DateSystem::Excel1900).finish();
1599 assert_projection(&empty.range_view(0, 0, 7, 3));
1600 }
1601
1602 #[test]
1603 fn range_projection_preserves_dense_run_and_partial_overlay_paths() {
1604 for run in [false, true] {
1605 let mut sheet = fixture(32);
1606 let chunk = &mut sheet.columns[0].chunks[0];
1607 chunk.overlay.clear();
1608 chunk.computed_overlay.clear();
1609 let values = vec![OverlayValue::Number(7.0); 29];
1610 let fragment = if run {
1611 OverlayFragment::run_range(0, values)
1612 } else {
1613 OverlayFragment::dense_range(0, values)
1614 }
1615 .unwrap();
1616 chunk.computed_overlay.apply_fragment(fragment);
1617 let view = sheet.range_view(2, 0, 9, 0);
1618 arrow_store::reset_overlay_select_stats();
1619 let numeric = view.numbers_slices().next().unwrap().unwrap();
1620 assert_eq!(numeric.2[0].value(0), 7.0);
1621 let stats = arrow_store::snapshot_overlay_select_stats();
1622 assert_eq!(stats.zip_select_calls, 0);
1623 assert_eq!(stats.direct_dense_slices, usize::from(!run));
1624 assert_eq!(stats.direct_run_materializations, usize::from(run));
1625 assert_projection(&view);
1626 let chunk = &mut sheet.columns[0].chunks[0];
1627 for (off, value) in [
1628 (3, OverlayValue::Empty),
1629 (4, OverlayValue::Pending),
1630 (5, OverlayValue::DateTime(45000.25)),
1631 (6, OverlayValue::Duration(0.5)),
1632 (7, OverlayValue::Boolean(true)),
1633 (
1634 8,
1635 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Ref)),
1636 ),
1637 (9, OverlayValue::Text(Arc::from("not numeric"))),
1638 ] {
1639 chunk.overlay.set(off, value);
1640 }
1641 assert_projection(&sheet.range_view(2, 0, 10, 3));
1642 }
1643 }
1644
1645 #[test]
1646 fn range_discovery_work_is_bounded_and_projection_is_lane_local() {
1647 let mut ingest = IngestBuilder::new("S", 1, 256, DateSystem::Excel1900);
1648 for _ in 0..256 {
1649 ingest.append_row(&[LiteralValue::Number(1.0)]).unwrap();
1650 }
1651 let mut sheet = ingest.finish();
1652 sheet.ensure_row_capacity(256 * 4096);
1653 for start in [0, 256 * 2048 + 1, 256 * 4096 - 8, 256 * 4096 + 8] {
1654 let view = sheet.range_view(start, 0, start + 7, 2);
1655 range_work::begin();
1656 let generic: Vec<_> = view.iter_row_chunks().collect();
1657 let work = range_work::take();
1658 assert_eq!(work.candidates, generic.len());
1659 assert_eq!(work.segments, generic.len());
1660 assert!(work.search_probes <= 32);
1661 range_work::begin();
1662 let _: Vec<_> = view.numbers_slices().collect();
1663 let numeric = range_work::take();
1664 assert_eq!(numeric.generic_columns, 0);
1665 assert_eq!(numeric.provider_requests[1..], [0, 0, 0]);
1666 assert!(numeric.null_arrays <= 3);
1667 range_work::begin();
1668 let _: Vec<_> = view.errors_slices().collect();
1669 let errors = range_work::take();
1670 assert_eq!(errors.generic_columns, 0);
1671 assert_eq!(errors.provider_requests[0], 0);
1672 assert_eq!(errors.provider_requests[1], 0);
1673 assert_eq!(errors.provider_requests[3], 0);
1674 }
1675 let view = sheet.range_view(0, 0, sheet.nrows as usize - 1, 0);
1676 range_work::begin();
1677 assert!(view.iter_row_chunks().next().unwrap().is_ok());
1678 let work = range_work::take();
1679 assert_eq!(work.candidates, 1);
1680 assert_eq!(work.segments, 1);
1681 }
1682
1683 #[test]
1684 fn range_cold_numeric_does_not_initialize_unused_null_providers() {
1685 let mut ingest = IngestBuilder::new("S", 1, 32768, DateSystem::Excel1900);
1686 for _ in 0..32768 {
1687 ingest.append_row(&[LiteralValue::Number(1.0)]).unwrap();
1688 }
1689 let sheet = ingest.finish();
1690 let view = sheet.range_view(1, 0, 8, 0);
1691 range_work::begin();
1692 view.numbers_slices().next().unwrap().unwrap();
1693 let numeric = range_work::take();
1694 assert_eq!(numeric.provider_requests, [1, 0, 0, 0]);
1695 assert_eq!(numeric.provider_builds, [0; 4]);
1696 range_work::begin();
1697 view.errors_slices().next().unwrap().unwrap();
1698 let errors = range_work::take();
1699 assert_eq!(errors.provider_requests, [0, 0, 1, 0]);
1700 assert_eq!(errors.provider_builds, [0, 0, 1, 0]);
1701 assert_eq!(errors.provider_slots, [0, 0, 32768, 0]);
1702 }
1703
1704 #[test]
1705 fn range_cancellation_keeps_empty_exhausted_and_between_segment_errors() {
1706 let sheet = fixture(3);
1707 for bounds in [(0, 0, 28, 0), (30, 0, 35, 0), (3, 0, 1, 0), (0, 2, 5, 1)] {
1708 let token = CancelToken::new();
1709 let view = sheet
1710 .range_view(bounds.0, bounds.1, bounds.2, bounds.3)
1711 .with_cancel_token(Some(token.clone()));
1712 let mut generic = view.iter_row_chunks();
1713 let mut numeric = view.numbers_slices();
1714 let mut errors = view.errors_slices();
1715 token.cancel();
1716 for _ in 0..2 {
1717 assert_eq!(
1718 generic.next().unwrap().err().unwrap().kind,
1719 ExcelErrorKind::Cancelled
1720 );
1721 assert_eq!(
1722 numeric.next().unwrap().err().unwrap().kind,
1723 ExcelErrorKind::Cancelled
1724 );
1725 assert_eq!(
1726 errors.next().unwrap().err().unwrap().kind,
1727 ExcelErrorKind::Cancelled
1728 );
1729 }
1730 }
1731 let token = CancelToken::new();
1732 let view = sheet
1733 .range_view(0, 0, 28, 0)
1734 .with_cancel_token(Some(token.clone()));
1735 let mut numeric = view.numbers_slices();
1736 assert!(numeric.next().unwrap().is_ok());
1737 token.cancel();
1738 assert_eq!(
1739 numeric.next().unwrap().err().unwrap().kind,
1740 ExcelErrorKind::Cancelled
1741 );
1742 let token = CancelToken::new();
1743 let view = sheet
1744 .range_view(30, 0, 35, 0)
1745 .with_cancel_token(Some(token.clone()));
1746 let mut numeric = view.numbers_slices();
1747 assert!(numeric.next().is_none());
1748 token.cancel();
1749 assert_eq!(
1750 numeric.next().unwrap().err().unwrap().kind,
1751 ExcelErrorKind::Cancelled
1752 );
1753 }
1754
1755 #[test]
1756 fn range_projection_keeps_numeric_bits_and_engine_error_order() {
1757 let values = [
1758 0.0,
1759 -0.0,
1760 f64::INFINITY,
1761 f64::NEG_INFINITY,
1762 f64::from_bits(0x7ff8000000000001),
1763 ];
1764 let view = RangeView::from_owned_rows(
1765 values
1766 .iter()
1767 .map(|n| vec![LiteralValue::Number(*n)])
1768 .collect(),
1769 DateSystem::Excel1900,
1770 );
1771 assert_projection(&view);
1772 let slice = view.numbers_slices().next().unwrap().unwrap().2.remove(0);
1773 for (i, n) in values.iter().enumerate() {
1774 assert_eq!(slice.value(i).to_bits(), n.to_bits());
1775 }
1776 for chunk_rows in [1, 2] {
1777 let mut engine = crate::engine::Engine::new(
1778 crate::test_workbook::TestWorkbook::new(),
1779 crate::engine::EvalConfig {
1780 arrow_storage_enabled: true,
1781 delta_overlay_enabled: true,
1782 write_formula_overlay_enabled: true,
1783 enable_parallel: false,
1784 ..Default::default()
1785 },
1786 );
1787 let mut ingest = engine.begin_bulk_ingest_arrow();
1788 ingest.add_sheet("Source", 2, chunk_rows);
1789 ingest
1790 .append_row(
1791 "Source",
1792 &[
1793 LiteralValue::Number(1.0),
1794 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)),
1795 ],
1796 )
1797 .unwrap();
1798 ingest
1799 .append_row(
1800 "Source",
1801 &[
1802 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div)),
1803 LiteralValue::Number(2.0),
1804 ],
1805 )
1806 .unwrap();
1807 ingest.finish().unwrap();
1808 engine
1809 .set_cell_formula(
1810 "Result",
1811 1,
1812 1,
1813 formualizer_parse::parser::parse("=SUM(Source!A1:B2)").unwrap(),
1814 )
1815 .unwrap();
1816 engine.evaluate_all().unwrap();
1817 let expected = if chunk_rows == 1 {
1818 ExcelErrorKind::Na
1819 } else {
1820 ExcelErrorKind::Div
1821 };
1822 assert!(
1823 matches!(engine.get_cell_value("Result", 1, 1), Some(LiteralValue::Error(e)) if e.kind == expected)
1824 );
1825 }
1826 }
1827
1828 #[test]
1829 fn range_arrays_outlive_owned_view_and_new_views_follow_structural_edits() {
1830 let numbers = {
1831 let view = RangeView::from_owned_rows(
1832 vec![vec![LiteralValue::Number(-0.0)]],
1833 DateSystem::Excel1900,
1834 );
1835 view.numbers_slices().next().unwrap().unwrap().2.remove(0)
1836 };
1837 assert_eq!(numbers.value(0).to_bits(), (-0.0f64).to_bits());
1838 let errors = {
1839 let view = RangeView::from_owned_rows(
1840 vec![vec![LiteralValue::Error(ExcelError::new(
1841 ExcelErrorKind::Div,
1842 ))]],
1843 DateSystem::Excel1900,
1844 );
1845 view.errors_slices().next().unwrap().unwrap().2.remove(0)
1846 };
1847 assert_eq!(
1848 errors.value(0),
1849 arrow_store::map_error_code(ExcelErrorKind::Div)
1850 );
1851 let mut sheet = fixture(3);
1852 let chunks_before: usize = sheet.columns.iter().map(|c| c.total_chunk_count()).sum();
1853 assert_projection(&sheet.range_view(0, 0, 40, 4));
1854 assert_eq!(
1855 sheet
1856 .columns
1857 .iter()
1858 .map(|c| c.total_chunk_count())
1859 .sum::<usize>(),
1860 chunks_before
1861 );
1862 sheet.insert_rows(4, 2);
1863 assert_projection(&sheet.range_view(1, 0, 35, 4));
1864 sheet.delete_rows(2, 1);
1865 assert_projection(&sheet.range_view(0, 0, 35, 4));
1866 sheet.ensure_row_capacity(50);
1867 assert_projection(&sheet.range_view(0, 0, 55, 4));
1868 sheet.insert_columns(1, 1);
1869 assert_projection(&sheet.range_view(0, 0, 55, 4));
1870 sheet.delete_columns(0, 1);
1871 assert_projection(&sheet.range_view(0, 0, 55, 4));
1872 }
1873}