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 fn sheet_name(&self) -> &str {
445 &self.sheet().name
446 }
447
448 pub fn kind_probe(&self) -> RangeKind {
449 if self.is_empty() {
450 return RangeKind::Empty;
451 }
452
453 let mut has_num = false;
454 let mut has_text = false;
455
456 for r in 0..self.rows {
457 for c in 0..self.cols {
458 match self.get_cell(r, c) {
459 LiteralValue::Empty => {}
460 LiteralValue::Number(_) | LiteralValue::Int(_) => has_num = true,
461 LiteralValue::Text(_) => has_text = true,
462 _ => return RangeKind::Mixed,
463 }
464 if has_num && has_text {
465 return RangeKind::Mixed;
466 }
467 }
468 }
469
470 match (has_num, has_text) {
471 (false, false) => RangeKind::Empty,
472 (true, false) => RangeKind::NumericOnly,
473 (false, true) => RangeKind::TextOnly,
474 (true, true) => RangeKind::Mixed,
475 }
476 }
477
478 pub fn as_1x1(&self) -> Option<LiteralValue> {
479 if self.rows == 1 && self.cols == 1 {
480 Some(self.get_cell(0, 0))
481 } else {
482 None
483 }
484 }
485
486 pub fn get_cell(&self, row: usize, col: usize) -> LiteralValue {
489 if row >= self.rows || col >= self.cols {
490 return LiteralValue::Empty;
491 }
492 let abs_row = self.sr + row;
493 let abs_col = self.sc + col;
494 let sheet = self.sheet();
495 let sheet_rows = sheet.nrows as usize;
496 if abs_row >= sheet_rows {
497 return LiteralValue::Empty;
498 }
499 if abs_col >= sheet.columns.len() {
500 return LiteralValue::Empty;
501 }
502 let col_ref = &sheet.columns[abs_col];
503 let chunk_starts = &sheet.chunk_starts;
505 let ch_idx = match chunk_starts.binary_search(&abs_row) {
506 Ok(i) => i,
507 Err(0) => 0,
508 Err(i) => i - 1,
509 };
510 let Some(ch) = col_ref.chunk(ch_idx) else {
511 return LiteralValue::Empty;
512 };
513 let row_start = chunk_starts[ch_idx];
514 let in_off = abs_row - row_start;
515 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
517 if let Some(ov) = cascade.get_scalar(in_off) {
518 return ov.to_literal_for(sheet.date_system);
519 }
520 let tag_u8 = ch.type_tag.value(in_off);
522 match arrow_store::TypeTag::from_u8(tag_u8) {
523 arrow_store::TypeTag::Empty => LiteralValue::Empty,
524 arrow_store::TypeTag::Number => {
525 if let Some(arr) = &ch.numbers {
526 if arr.is_null(in_off) {
527 return LiteralValue::Empty;
528 }
529 LiteralValue::Number(arr.value(in_off))
530 } else {
531 LiteralValue::Empty
532 }
533 }
534 arrow_store::TypeTag::DateTime | arrow_store::TypeTag::Duration => {
535 if let Some(arr) = &ch.numbers {
536 if arr.is_null(in_off) {
537 LiteralValue::Empty
538 } else {
539 LiteralValue::Number(arr.value(in_off))
540 }
541 } else {
542 LiteralValue::Empty
543 }
544 }
545 arrow_store::TypeTag::Boolean => {
546 if let Some(arr) = &ch.booleans {
547 if arr.is_null(in_off) {
548 return LiteralValue::Empty;
549 }
550 LiteralValue::Boolean(arr.value(in_off))
551 } else {
552 LiteralValue::Empty
553 }
554 }
555 arrow_store::TypeTag::Text => {
556 if let Some(arr) = &ch.text {
557 if arr.is_null(in_off) {
558 return LiteralValue::Empty;
559 }
560 let sa = arr
561 .as_any()
562 .downcast_ref::<arrow_array::StringArray>()
563 .unwrap();
564 LiteralValue::Text(sa.value(in_off).to_string())
565 } else {
566 LiteralValue::Empty
567 }
568 }
569 arrow_store::TypeTag::Error => {
570 if let Some(arr) = &ch.errors {
571 if arr.is_null(in_off) {
572 return LiteralValue::Empty;
573 }
574 let kind = arrow_store::unmap_error_code(arr.value(in_off));
575 LiteralValue::Error(ExcelError::new(kind))
576 } else {
577 LiteralValue::Empty
578 }
579 }
580 arrow_store::TypeTag::Pending => LiteralValue::Pending,
581 }
582 }
583
584 pub fn iter_row_chunks(&self) -> RowChunkIterator<'_> {
586 RowChunkIterator {
587 segments: self.iter_row_segments(),
588 }
589 }
590
591 fn iter_row_segments(&self) -> RowSegmentIterator<'_> {
592 #[cfg(test)]
593 range_work::record(|w| w.iterators += 1);
594 RowSegmentIterator {
595 view: self,
596 chunks: None,
597 }
598 }
599
600 pub fn for_each_cell(
602 &self,
603 f: &mut dyn FnMut(&LiteralValue) -> Result<(), ExcelError>,
604 ) -> Result<(), ExcelError> {
605 for res in self.iter_row_chunks() {
606 let cs = res?;
607 for r in 0..cs.row_len {
608 for c in 0..self.cols {
609 let tmp = self.get_cell(cs.row_start + r, c);
610 f(&tmp)?;
611 }
612 }
613 }
614 Ok(())
615 }
616
617 pub fn for_each_row(
619 &self,
620 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
621 ) -> Result<(), ExcelError> {
622 let mut buf: Vec<LiteralValue> = Vec::with_capacity(self.cols);
623 for r in 0..self.rows {
624 buf.clear();
625 for c in 0..self.cols {
626 buf.push(self.get_cell(r, c));
627 }
628 f(&buf[..])?;
629 }
630 Ok(())
631 }
632
633 pub fn for_each_col(
635 &self,
636 f: &mut dyn FnMut(&[LiteralValue]) -> Result<(), ExcelError>,
637 ) -> Result<(), ExcelError> {
638 let mut col_buf: Vec<LiteralValue> = Vec::with_capacity(self.rows);
639 for c in 0..self.cols {
640 col_buf.clear();
641 for r in 0..self.rows {
642 col_buf.push(self.get_cell(r, c));
643 }
644 f(&col_buf[..])?;
645 }
646 Ok(())
647 }
648
649 pub fn get_cell_numeric(&self, row: usize, col: usize, policy: CoercionPolicy) -> Option<f64> {
652 if row >= self.rows || col >= self.cols {
653 return None;
654 }
655
656 let val = self.get_cell(row, col);
657 pack_numeric(&val, policy).ok().flatten()
658 }
659
660 pub fn numbers_chunked(
662 &self,
663 policy: CoercionPolicy,
664 min_chunk: usize,
665 f: &mut dyn FnMut(NumericChunk) -> Result<(), ExcelError>,
666 ) -> Result<(), ExcelError> {
667 if matches!(policy, CoercionPolicy::NumberStrict) {
669 for res in self.numbers_slices() {
670 let (_, _, cols) = res?;
671 for col in cols {
672 if col.null_count() < col.len() {
673 let data = col.values();
674 let validity = if col.null_count() > 0 {
680 None } else {
684 None
685 };
686
687 if col.null_count() == 0 {
688 f(NumericChunk { data, validity })?;
689 } else {
690 let mut buf = Vec::with_capacity(col.len());
692 for i in 0..col.len() {
693 if !col.is_null(i) {
694 buf.push(col.value(i));
695 }
696 }
697 if !buf.is_empty() {
698 f(NumericChunk {
699 data: &buf,
700 validity: None,
701 })?;
702 }
703 }
704 }
705 }
706 }
707 return Ok(());
708 }
709
710 let min_chunk = min_chunk.max(1);
711 let mut buf: Vec<f64> = Vec::with_capacity(min_chunk);
712 let mut flush = |buf: &mut Vec<f64>| -> Result<(), ExcelError> {
713 if buf.is_empty() {
714 return Ok(());
715 }
716 let ptr = buf.as_ptr();
718 let len = buf.len();
719 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
720 let chunk = NumericChunk {
721 data: slice,
722 validity: None,
723 };
724 f(chunk)?;
725 buf.clear();
726 Ok(())
727 };
728
729 self.for_each_cell(&mut |v| {
730 if let Some(n) = pack_numeric(v, policy)? {
731 buf.push(n);
732 if buf.len() >= min_chunk {
733 flush(&mut buf)?;
734 }
735 }
736 Ok(())
737 })?;
738 flush(&mut buf)?;
739
740 Ok(())
741 }
742
743 pub fn numbers_slices(
745 &self,
746 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::Float64Array>>), ExcelError>> + '_
747 {
748 self.iter_row_segments().map(move |res| {
749 let segment = res?;
750 let mut out_cols = Vec::with_capacity(self.cols);
751 let sheet = self.sheet();
752 for col_idx in self.sc..=self.ec {
753 let Some(ch) = sheet
754 .columns
755 .get(col_idx)
756 .and_then(|col| col.chunk(segment.chunk_idx))
757 else {
758 #[cfg(test)]
759 range_work::record(|w| {
760 w.null_arrays += 1;
761 w.null_slots += segment.row_len;
762 });
763 out_cols.push(Arc::new(arrow_array::Float64Array::new_null(
764 segment.row_len,
765 )));
766 continue;
767 };
768 let base = ch
769 .numbers_or_null()
770 .slice(segment.chunk_offset, segment.row_len);
771 let range = segment.chunk_offset..segment.chunk_offset + segment.row_len;
772 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
773 out_cols.push(if cascade.has_any_in_range(range.clone()) {
774 cascade.select_numbers(range, &base)
775 } else {
776 Arc::new(base)
777 });
778 }
779 Ok((segment.row_start, segment.row_len, out_cols))
780 })
781 }
782
783 pub fn booleans_slices(
785 &self,
786 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::BooleanArray>>), ExcelError>> + '_
787 {
788 self.iter_row_chunks().map(move |res| {
789 let cs = res?;
790 let mut out_cols: Vec<Arc<arrow_array::BooleanArray>> =
791 Vec::with_capacity(cs.cols.len());
792 let sheet = self.sheet();
793 let chunk_starts = &sheet.chunk_starts;
794
795 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
796 let base = cs.cols[local_c]
797 .booleans
798 .as_ref()
799 .expect("booleans lane exists")
800 .clone();
801 let base_ba = base
802 .as_any()
803 .downcast_ref::<arrow_array::BooleanArray>()
804 .unwrap()
805 .clone();
806 let base_arc = Arc::new(base_ba);
807
808 let abs_seg_start = self.sr + cs.row_start;
810 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
811 Ok(i) => i,
812 Err(0) => 0,
813 Err(i) => i - 1,
814 };
815 if col_idx >= sheet.columns.len() {
816 out_cols.push(base_arc);
817 continue;
818 }
819 let col = &sheet.columns[col_idx];
820 let Some(ch) = col.chunk(ch_idx) else {
821 out_cols.push(base_arc);
822 continue;
823 };
824 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
825 let seg_range = rel_off..(rel_off + cs.row_len);
826 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
827 if cascade.has_any_in_range(seg_range.clone()) {
828 let base_ba = base
829 .as_any()
830 .downcast_ref::<arrow_array::BooleanArray>()
831 .unwrap();
832 out_cols.push(cascade.select_booleans(seg_range, base_ba));
833 } else {
834 out_cols.push(base_arc);
835 }
836 }
837 Ok((cs.row_start, cs.row_len, out_cols))
838 })
839 }
840
841 pub fn text_slices(
843 &self,
844 ) -> impl Iterator<Item = Result<(usize, usize, Vec<arrow_array::ArrayRef>), ExcelError>> + '_
845 {
846 self.iter_row_chunks().map(move |res| {
847 let cs = res?;
848 let mut out_cols: Vec<arrow_array::ArrayRef> = Vec::with_capacity(cs.cols.len());
849 let sheet = self.sheet();
850 let chunk_starts = &sheet.chunk_starts;
851
852 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
853 let base = cs.cols[local_c]
854 .text
855 .as_ref()
856 .expect("text lane exists")
857 .clone();
858 let abs_seg_start = self.sr + cs.row_start;
859 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
860 Ok(i) => i,
861 Err(0) => 0,
862 Err(i) => i - 1,
863 };
864 if col_idx >= sheet.columns.len() {
865 out_cols.push(base.clone());
866 continue;
867 }
868 let col = &sheet.columns[col_idx];
869 let Some(ch) = col.chunk(ch_idx) else {
870 out_cols.push(base.clone());
871 continue;
872 };
873 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
874 let seg_range = rel_off..(rel_off + cs.row_len);
875 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
876 if cascade.has_any_in_range(seg_range.clone()) {
877 let base_sa = base
878 .as_any()
879 .downcast_ref::<arrow_array::StringArray>()
880 .unwrap();
881 out_cols.push(cascade.select_text(seg_range, base_sa));
882 } else {
883 out_cols.push(base.clone());
884 }
885 }
886 Ok((cs.row_start, cs.row_len, out_cols))
887 })
888 }
889
890 pub fn lowered_text_slices(
892 &self,
893 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::StringArray>>), ExcelError>> + '_
894 {
895 self.iter_row_chunks().map(move |res| {
896 let cs = res?;
897 let mut out_cols: Vec<Arc<arrow_array::StringArray>> =
898 Vec::with_capacity(cs.cols.len());
899 let sheet = self.sheet();
900 let chunk_starts = &sheet.chunk_starts;
901
902 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
903 let abs_seg_start = self.sr + cs.row_start;
905 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
906 Ok(i) => i,
907 Err(0) => 0,
908 Err(i) => i - 1,
909 };
910 if col_idx >= sheet.columns.len() {
911 out_cols.push(Arc::new(arrow_array::StringArray::new_null(cs.row_len)));
912 continue;
913 }
914 let col = &sheet.columns[col_idx];
915 let Some(ch) = col.chunk(ch_idx) else {
916 out_cols.push(Arc::new(arrow_array::StringArray::new_null(cs.row_len)));
917 continue;
918 };
919 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
920 let seg_range = rel_off..(rel_off + cs.row_len);
921
922 let base_lowered = ch.text_lower_or_null();
923 let base_seg = base_lowered.slice(rel_off, cs.row_len);
924 let base_sa = base_seg
925 .as_any()
926 .downcast_ref::<arrow_array::StringArray>()
927 .expect("lowered slice downcast");
928
929 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
930 if cascade.has_any_in_range(seg_range.clone()) {
931 out_cols.push(cascade.select_lowered_text(seg_range, base_sa));
932 } else {
933 out_cols.push(Arc::new(base_sa.clone()));
934 }
935 }
936 Ok((cs.row_start, cs.row_len, out_cols))
937 })
938 }
939
940 pub fn errors_slices(
942 &self,
943 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::UInt8Array>>), ExcelError>> + '_
944 {
945 self.iter_row_segments().map(move |res| {
946 let segment = res?;
947 let mut out_cols = Vec::with_capacity(self.cols);
948 let sheet = self.sheet();
949 for col_idx in self.sc..=self.ec {
950 let Some(ch) = sheet
951 .columns
952 .get(col_idx)
953 .and_then(|col| col.chunk(segment.chunk_idx))
954 else {
955 #[cfg(test)]
956 range_work::record(|w| {
957 w.null_arrays += 1;
958 w.null_slots += segment.row_len;
959 });
960 out_cols.push(Arc::new(arrow_array::UInt8Array::new_null(segment.row_len)));
961 continue;
962 };
963 let base = ch
964 .errors_or_null()
965 .slice(segment.chunk_offset, segment.row_len);
966 let range = segment.chunk_offset..segment.chunk_offset + segment.row_len;
967 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
968 out_cols.push(if cascade.has_any_in_range(range.clone()) {
969 cascade.select_errors(range, &base)
970 } else {
971 Arc::new(base)
972 });
973 }
974 Ok((segment.row_start, segment.row_len, out_cols))
975 })
976 }
977
978 pub fn type_tags_slices(
980 &self,
981 ) -> impl Iterator<Item = Result<(usize, usize, Vec<Arc<arrow_array::UInt8Array>>), ExcelError>> + '_
982 {
983 self.iter_row_chunks().map(move |res| {
984 let cs = res?;
985 let mut out_cols: Vec<Arc<arrow_array::UInt8Array>> = Vec::with_capacity(cs.cols.len());
986 let sheet = self.sheet();
987 let chunk_starts = &sheet.chunk_starts;
988
989 for (local_c, col_idx) in (self.sc..=self.ec).enumerate() {
990 let base = cs.cols[local_c].type_tag.clone();
991 let base_ta = base
992 .as_any()
993 .downcast_ref::<arrow_array::UInt8Array>()
994 .unwrap()
995 .clone();
996 let base_arc = Arc::new(base_ta);
997
998 let abs_seg_start = self.sr + cs.row_start;
999 let ch_idx = match chunk_starts.binary_search(&abs_seg_start) {
1000 Ok(i) => i,
1001 Err(0) => 0,
1002 Err(i) => i - 1,
1003 };
1004 if col_idx >= sheet.columns.len() {
1005 out_cols.push(base_arc);
1006 continue;
1007 }
1008 let col = &sheet.columns[col_idx];
1009 let Some(ch) = col.chunk(ch_idx) else {
1010 out_cols.push(base_arc);
1011 continue;
1012 };
1013 let rel_off = (self.sr + cs.row_start) - chunk_starts[ch_idx];
1014 let seg_range = rel_off..(rel_off + cs.row_len);
1015 let cascade = arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1016 if cascade.has_any_in_range(seg_range.clone()) {
1017 let base_ta = base
1018 .as_any()
1019 .downcast_ref::<arrow_array::UInt8Array>()
1020 .unwrap();
1021 out_cols.push(cascade.select_type_tags(seg_range, base_ta));
1022 } else {
1023 out_cols.push(base_arc);
1024 }
1025 }
1026 Ok((cs.row_start, cs.row_len, out_cols))
1027 })
1028 }
1029
1030 pub fn lowered_text_columns(&self) -> Vec<arrow_array::ArrayRef> {
1033 use crate::compute_prelude::concat_arrays;
1034
1035 let mut out: Vec<arrow_array::ArrayRef> = Vec::with_capacity(self.cols);
1036 if self.rows == 0 || self.cols == 0 {
1037 return out;
1038 }
1039 let sheet = self.sheet();
1040 let chunk_starts = &sheet.chunk_starts;
1041 let sheet_rows = sheet.nrows as usize;
1043 if sheet_rows == 0 || self.sr >= sheet_rows {
1044 for _ in 0..self.cols {
1045 out.push(arrow_array::new_null_array(&DataType::Utf8, 0));
1046 }
1047 return out;
1048 }
1049 let row_end = self.er.min(sheet_rows.saturating_sub(1));
1050 let physical_len = row_end.saturating_sub(self.sr) + 1;
1051 for col_idx in self.sc..=self.ec {
1052 let mut segs: Vec<arrow_array::ArrayRef> = Vec::new();
1053 if col_idx >= sheet.columns.len() {
1054 segs.push(arrow_array::new_null_array(&DataType::Utf8, physical_len));
1056 } else {
1057 let col_ref = &sheet.columns[col_idx];
1058 for (ci, &start) in chunk_starts.iter().enumerate() {
1059 let chunk_end = chunk_starts
1060 .get(ci + 1)
1061 .copied()
1062 .unwrap_or(sheet.nrows as usize);
1063 let len = chunk_end.saturating_sub(start);
1064 if len == 0 {
1065 continue;
1066 }
1067 let end = start + len - 1;
1068 let is = start.max(self.sr);
1069 let ie = end.min(row_end);
1070 if is > ie {
1071 continue;
1072 }
1073 let seg_len = ie - is + 1;
1074 let rel_off = is - start;
1075 if let Some(ch) = col_ref.chunk(ci) {
1076 let seg_range = rel_off..(rel_off + seg_len);
1078 let cascade =
1079 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1080 if cascade.has_any_in_range(seg_range.clone()) {
1081 let base_lowered = ch.text_lower_or_null();
1082 let base_seg = base_lowered.slice(rel_off, seg_len);
1083 let base_sa = base_seg
1084 .as_any()
1085 .downcast_ref::<arrow_array::StringArray>()
1086 .expect("lowered slice downcast");
1087 segs.push(cascade.select_lowered_text(seg_range, base_sa));
1088 } else {
1089 let lowered = ch.text_lower_or_null();
1091 segs.push(lowered.slice(rel_off, seg_len));
1092 }
1093 } else {
1094 segs.push(arrow_array::new_null_array(&DataType::Utf8, seg_len));
1095 }
1096 }
1097 }
1098 if segs.is_empty() {
1100 segs.push(arrow_array::new_null_array(&DataType::Utf8, physical_len));
1101 }
1102 let anys: Vec<&dyn arrow_array::Array> = segs
1104 .iter()
1105 .map(|a| a.as_ref() as &dyn arrow_array::Array)
1106 .collect();
1107 let conc = concat_arrays(&anys).expect("concat lowered segments");
1108 out.push(conc);
1109 }
1110 out
1111 }
1112
1113 pub fn slice_numbers(
1115 &self,
1116 rel_start: usize,
1117 len: usize,
1118 ) -> Vec<Option<Arc<arrow_array::Float64Array>>> {
1119 let abs_start = self.sr + rel_start;
1120 let abs_end = abs_start + len;
1121 let sheet = self.sheet();
1122 let chunk_starts = &sheet.chunk_starts;
1123
1124 let mut out_cols = Vec::with_capacity(self.cols);
1125 for col_idx in self.sc..=self.ec {
1126 if col_idx >= sheet.columns.len() {
1127 out_cols.push(None);
1128 continue;
1129 }
1130 let col = &sheet.columns[col_idx];
1131
1132 let start_ch_idx = match chunk_starts.binary_search(&abs_start) {
1133 Ok(i) => i,
1134 Err(0) => 0,
1135 Err(i) => i - 1,
1136 };
1137
1138 let mut segments: Vec<Arc<arrow_array::Float64Array>> = Vec::new();
1139 let mut null_only = true;
1140
1141 let mut curr = abs_start;
1142 let mut remaining = len;
1143 let mut ch_idx = start_ch_idx;
1144
1145 while remaining > 0 && ch_idx < chunk_starts.len() {
1146 let ch_start = chunk_starts[ch_idx];
1147 let ch_end = chunk_starts
1148 .get(ch_idx + 1)
1149 .copied()
1150 .unwrap_or(sheet.nrows as usize);
1151 let ch_len = ch_end.saturating_sub(ch_start);
1152 if ch_len == 0 {
1153 ch_idx += 1;
1154 continue;
1155 }
1156
1157 let overlap_start = curr.max(ch_start);
1158 let overlap_end = ch_end.min(abs_end);
1159
1160 if overlap_start < overlap_end {
1161 let seg_len = overlap_end - overlap_start;
1162 let rel_off_in_chunk = overlap_start - ch_start;
1163
1164 if let Some(ch) = col.chunk(ch_idx) {
1165 let base_nums_arc = ch.numbers_or_null();
1166 let base_nums = base_nums_arc.as_ref();
1167
1168 let seg_range = rel_off_in_chunk..(rel_off_in_chunk + seg_len);
1169 let cascade =
1170 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1171
1172 let final_arr = if cascade.has_any_in_range(seg_range.clone()) {
1173 let base_slice = base_nums.slice(rel_off_in_chunk, seg_len);
1174 let base_fa = base_slice
1175 .as_any()
1176 .downcast_ref::<arrow_array::Float64Array>()
1177 .unwrap();
1178 cascade.select_numbers(seg_range, base_fa).as_ref().clone()
1179 } else {
1180 let sl = base_nums.slice(rel_off_in_chunk, seg_len);
1181 sl.as_any()
1182 .downcast_ref::<arrow_array::Float64Array>()
1183 .unwrap()
1184 .clone()
1185 };
1186
1187 if final_arr.null_count() < final_arr.len() {
1188 null_only = false;
1189 }
1190 segments.push(Arc::new(final_arr));
1191 } else {
1192 segments.push(Arc::new(arrow_array::Float64Array::new_null(seg_len)));
1193 }
1194 curr += seg_len;
1195 remaining -= seg_len;
1196 }
1197 ch_idx += 1;
1198 }
1199
1200 if remaining > 0 {
1201 segments.push(Arc::new(arrow_array::Float64Array::new_null(remaining)));
1202 }
1203
1204 if segments.len() == 1 {
1205 if null_only && segments[0].null_count() == segments[0].len() {
1206 out_cols.push(None);
1207 } else {
1208 out_cols.push(Some(segments.pop().unwrap()));
1209 }
1210 } else {
1211 let refs: Vec<&dyn Array> =
1212 segments.iter().map(|a| a.as_ref() as &dyn Array).collect();
1213 let c = crate::compute_prelude::concat_arrays(&refs).expect("concat slice");
1214 let fa = c
1215 .as_any()
1216 .downcast_ref::<arrow_array::Float64Array>()
1217 .unwrap()
1218 .clone();
1219 out_cols.push(Some(Arc::new(fa)));
1220 }
1221 }
1222 out_cols
1223 }
1224
1225 pub fn slice_lowered_text(
1227 &self,
1228 rel_start: usize,
1229 len: usize,
1230 ) -> Vec<Option<Arc<arrow_array::StringArray>>> {
1231 let abs_start = self.sr + rel_start;
1232 let abs_end = abs_start + len;
1233 let sheet = self.sheet();
1234 let chunk_starts = &sheet.chunk_starts;
1235
1236 let mut out_cols = Vec::with_capacity(self.cols);
1237 for col_idx in self.sc..=self.ec {
1238 if col_idx >= sheet.columns.len() {
1239 out_cols.push(None);
1240 continue;
1241 }
1242 let col = &sheet.columns[col_idx];
1243 let start_ch_idx = match chunk_starts.binary_search(&abs_start) {
1244 Ok(i) => i,
1245 Err(0) => 0,
1246 Err(i) => i - 1,
1247 };
1248
1249 let mut segments: Vec<Arc<arrow_array::StringArray>> = Vec::new();
1250 let mut null_only = true;
1251
1252 let mut curr = abs_start;
1253 let mut remaining = len;
1254 let mut ch_idx = start_ch_idx;
1255
1256 while remaining > 0 && ch_idx < chunk_starts.len() {
1257 let ch_start = chunk_starts[ch_idx];
1258 let ch_end = chunk_starts
1259 .get(ch_idx + 1)
1260 .copied()
1261 .unwrap_or(sheet.nrows as usize);
1262 let ch_len = ch_end.saturating_sub(ch_start);
1263 if ch_len == 0 {
1264 ch_idx += 1;
1265 continue;
1266 }
1267
1268 let overlap_start = curr.max(ch_start);
1269 let overlap_end = ch_end.min(abs_end);
1270
1271 if overlap_start < overlap_end {
1272 let seg_len = overlap_end - overlap_start;
1273 let rel_off_in_chunk = overlap_start - ch_start;
1274
1275 if let Some(ch) = col.chunk(ch_idx) {
1276 let base_lowered = ch.text_lower_or_null();
1277 let seg_range = rel_off_in_chunk..(rel_off_in_chunk + seg_len);
1278 let cascade =
1279 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1280
1281 let final_arr = if cascade.has_any_in_range(seg_range.clone()) {
1282 let base_slice = base_lowered.slice(rel_off_in_chunk, seg_len);
1283 let base_sa = base_slice
1284 .as_any()
1285 .downcast_ref::<arrow_array::StringArray>()
1286 .unwrap();
1287 cascade
1288 .select_lowered_text(seg_range, base_sa)
1289 .as_ref()
1290 .clone()
1291 } else {
1292 let sl = base_lowered.slice(rel_off_in_chunk, seg_len);
1293 sl.as_any()
1294 .downcast_ref::<arrow_array::StringArray>()
1295 .unwrap()
1296 .clone()
1297 };
1298
1299 if final_arr.null_count() < final_arr.len() {
1300 null_only = false;
1301 }
1302 segments.push(Arc::new(final_arr));
1303 } else {
1304 segments.push(Arc::new(arrow_array::StringArray::new_null(seg_len)));
1305 }
1306 curr += seg_len;
1307 remaining -= seg_len;
1308 }
1309 ch_idx += 1;
1310 }
1311
1312 if remaining > 0 {
1313 segments.push(Arc::new(arrow_array::StringArray::new_null(remaining)));
1314 }
1315
1316 if segments.len() == 1 {
1317 if null_only && segments[0].null_count() == segments[0].len() {
1318 out_cols.push(None);
1319 } else {
1320 out_cols.push(Some(segments.pop().unwrap()));
1321 }
1322 } else {
1323 let refs: Vec<&dyn Array> =
1324 segments.iter().map(|a| a.as_ref() as &dyn Array).collect();
1325 let c = crate::compute_prelude::concat_arrays(&refs).expect("concat text");
1326 let sa = c
1327 .as_any()
1328 .downcast_ref::<arrow_array::StringArray>()
1329 .unwrap()
1330 .clone();
1331 out_cols.push(Some(Arc::new(sa)));
1332 }
1333 }
1334 out_cols
1335 }
1336}
1337
1338#[inline]
1339fn pack_numeric(v: &LiteralValue, policy: CoercionPolicy) -> Result<Option<f64>, ExcelError> {
1340 match policy {
1341 CoercionPolicy::NumberLenientText => match v {
1342 LiteralValue::Error(e) => Err(e.clone()),
1343 LiteralValue::Empty => Ok(None),
1344 other => Ok(crate::coercion::to_number_lenient(other).ok()),
1345 },
1346 CoercionPolicy::NumberStrict => match v {
1347 LiteralValue::Error(e) => Err(e.clone()),
1348 LiteralValue::Empty => Ok(None),
1349 other => Ok(crate::coercion::to_number_strict(other).ok()),
1350 },
1351 _ => match v {
1352 LiteralValue::Error(e) => Err(e.clone()),
1353 _ => Ok(None),
1354 },
1355 }
1356}
1357
1358#[cfg(test)]
1359mod tests {
1360 use super::*;
1361
1362 #[test]
1363 fn owned_rows_numeric_chunking() {
1364 let data: Vec<Vec<LiteralValue>> = vec![
1365 vec![
1366 LiteralValue::Number(1.0),
1367 LiteralValue::Text("x".into()),
1368 LiteralValue::Number(3.0),
1369 ],
1370 vec![
1371 LiteralValue::Boolean(true),
1372 LiteralValue::Empty,
1373 LiteralValue::Number(2.5),
1374 ],
1375 ];
1376 let view = RangeView::from_owned_rows(data, DateSystem::Excel1900);
1377 let mut sum = 0.0f64;
1378 view.numbers_chunked(CoercionPolicy::NumberLenientText, 2, &mut |chunk| {
1379 for &n in chunk.data {
1380 sum += n;
1381 }
1382 Ok(())
1383 })
1384 .unwrap();
1385 assert!((sum - 7.5).abs() < 1e-9);
1386 }
1387
1388 #[test]
1389 fn as_1x1_works() {
1390 let view = RangeView::from_owned_rows(
1391 vec![vec![LiteralValue::Number(7.0)]],
1392 DateSystem::Excel1900,
1393 );
1394 assert_eq!(view.as_1x1(), Some(LiteralValue::Number(7.0)));
1395 }
1396
1397 #[test]
1398 fn pre_cancelled_token_stops_owned_row_construction() {
1399 let token = CancelToken::new();
1400 token.cancel();
1401
1402 let error = RangeView::try_from_owned_rows(
1403 vec![vec![LiteralValue::Number(1.0)]],
1404 DateSystem::Excel1900,
1405 Some(token),
1406 )
1407 .unwrap_err();
1408
1409 assert_eq!(error.kind, formualizer_common::ExcelErrorKind::Cancelled);
1410 }
1411
1412 #[test]
1413 fn pre_cancelled_token_stops_row_chunk_iteration() {
1414 let token = CancelToken::new();
1415 token.cancel();
1416 let view = RangeView::from_owned_rows(
1417 vec![vec![LiteralValue::Number(1.0)]],
1418 DateSystem::Excel1900,
1419 )
1420 .with_cancel_token(Some(token));
1421
1422 let Some(Err(error)) = view.iter_row_chunks().next() else {
1423 panic!("pre-cancelled chunk iteration should return cancellation");
1424 };
1425
1426 assert_eq!(error.kind, formualizer_common::ExcelErrorKind::Cancelled);
1427 }
1428}
1429#[cfg(test)]
1430mod bounded_projection_tests {
1431 use super::*;
1432 use crate::arrow_store::{ArrowSheet, OverlayFragment, OverlayValue};
1433 use arrow_array::{Float64Array, UInt8Array};
1434 use formualizer_common::ExcelErrorKind;
1435
1436 fn segments(view: &RangeView<'_>) -> Vec<(usize, usize, usize, usize)> {
1438 let sheet = view.sheet();
1439 let mut out = Vec::new();
1440 let row_end = view.er.min((sheet.nrows as usize).saturating_sub(1));
1441 for (ci, &start) in sheet.chunk_starts.iter().enumerate() {
1442 let end = sheet
1443 .chunk_starts
1444 .get(ci + 1)
1445 .copied()
1446 .unwrap_or(sheet.nrows as usize);
1447 let len = end.saturating_sub(start);
1448 if len == 0 {
1449 continue;
1450 }
1451 let lo = start.max(view.sr);
1452 let hi = (start + len - 1).min(row_end);
1453 if lo <= hi {
1454 out.push((ci, lo - start, lo - view.sr, hi - lo + 1));
1455 }
1456 }
1457 out
1458 }
1459
1460 fn assert_projection(view: &RangeView<'_>) {
1461 let expected = segments(view);
1462 let generic: Vec<_> = view.iter_row_chunks().map(Result::unwrap).collect();
1463 assert_eq!(
1464 generic
1465 .iter()
1466 .map(|s| (s.row_start, s.row_len))
1467 .collect::<Vec<_>>(),
1468 expected.iter().map(|s| (s.2, s.3)).collect::<Vec<_>>()
1469 );
1470 let numbers: Vec<_> = view.numbers_slices().map(Result::unwrap).collect();
1471 let errors: Vec<_> = view.errors_slices().map(Result::unwrap).collect();
1472 assert_eq!(numbers.len(), expected.len());
1473 assert_eq!(errors.len(), expected.len());
1474 for (seg, &(ci, offset, row_start, len)) in expected.iter().enumerate() {
1475 assert_eq!((numbers[seg].0, numbers[seg].1), (row_start, len));
1476 assert_eq!((errors[seg].0, errors[seg].1), (row_start, len));
1477 let columns = (view.sc..=view.ec).count();
1478 assert_eq!(generic[seg].cols.len(), columns);
1479 assert_eq!(numbers[seg].2.len(), columns);
1480 assert_eq!(errors[seg].2.len(), columns);
1481 for (c, absolute) in (view.sc..=view.ec).enumerate() {
1482 let (base_n, base_e) =
1483 match view.sheet().columns.get(absolute).and_then(|c| c.chunk(ci)) {
1484 Some(ch) => {
1485 let n = ch
1487 .numbers
1488 .as_ref()
1489 .map(|a| a.slice(offset, len))
1490 .unwrap_or_else(|| Float64Array::new_null(len));
1491 let e = ch
1492 .errors
1493 .as_ref()
1494 .map(|a| a.slice(offset, len))
1495 .unwrap_or_else(|| UInt8Array::new_null(len));
1496 let cascade =
1497 arrow_store::OverlayCascade::new(&ch.overlay, &ch.computed_overlay);
1498 (
1499 cascade.select_numbers(offset..offset + len, &n),
1500 cascade.select_errors(offset..offset + len, &e),
1501 )
1502 }
1503 None => (
1504 Arc::new(Float64Array::new_null(len)),
1505 Arc::new(UInt8Array::new_null(len)),
1506 ),
1507 };
1508 let actual_n = &numbers[seg].2[c];
1509 let actual_e = &errors[seg].2[c];
1510 assert_eq!(actual_n.len(), len);
1511 assert_eq!(actual_e.len(), len);
1512 for i in 0..len {
1513 assert_eq!(actual_n.is_null(i), base_n.is_null(i));
1514 assert_eq!(actual_e.is_null(i), base_e.is_null(i));
1515 if !base_n.is_null(i) {
1516 assert_eq!(actual_n.value(i).to_bits(), base_n.value(i).to_bits());
1517 }
1518 if !base_e.is_null(i) {
1519 assert_eq!(actual_e.value(i), base_e.value(i));
1520 }
1521 }
1522 }
1523 }
1524 }
1525
1526 fn fixture(chunk_rows: usize) -> ArrowSheet {
1527 let mut ingest = IngestBuilder::new("S", 3, chunk_rows, DateSystem::Excel1900);
1528 for row in 0..11 {
1529 ingest
1530 .append_row(&[
1531 LiteralValue::Number(row as f64),
1532 if row % 3 == 0 {
1533 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div))
1534 } else {
1535 LiteralValue::Empty
1536 },
1537 LiteralValue::Text(format!("r{row}")),
1538 ])
1539 .unwrap();
1540 }
1541 let mut sheet = ingest.finish();
1542 sheet.ensure_row_capacity(29);
1543 for (row, value) in [
1544 (1, OverlayValue::Empty),
1545 (3, OverlayValue::Text(Arc::from("mask"))),
1546 (
1547 7,
1548 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Na)),
1549 ),
1550 (22, OverlayValue::Number(17.0)),
1551 (28, OverlayValue::Pending),
1552 ] {
1553 let (ci, off) = sheet.chunk_of_row(row).unwrap();
1554 let chunk = sheet.ensure_column_chunk_mut(0, ci).unwrap();
1555 chunk.computed_overlay.set(off, OverlayValue::Number(99.0));
1556 chunk.overlay.set(off, value);
1557 }
1558 for (row, user) in [(0, OverlayValue::Empty), (8, OverlayValue::Number(17.0))] {
1559 let (ci, off) = sheet.chunk_of_row(row).unwrap();
1560 let ch = sheet.ensure_column_chunk_mut(1, ci).unwrap();
1561 ch.computed_overlay.set(
1562 off,
1563 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Na)),
1564 );
1565 ch.overlay.set(off, user);
1566 }
1567 sheet
1568 }
1569
1570 #[test]
1571 fn range_projection_matches_independent_oracle_for_all_small_bounds() {
1572 for chunk_rows in [1, 3, 8, 32] {
1573 let sheet = fixture(chunk_rows);
1574 for sr in 0..=31 {
1575 for er in 0..=31 {
1576 for (sc, ec) in [(0, 0), (1, 2), (0, 4), (4, 5), (2, 1)] {
1577 assert_projection(&sheet.range_view(sr, sc, er, ec));
1578 }
1579 }
1580 }
1581 let base = sheet.range_view(2, 1, 10, 2);
1582 for rows in [0, 1, 9, 40] {
1583 for cols in [0, 1, 4] {
1584 assert_projection(&base.sub_view(1, 1, rows, cols));
1585 assert_projection(&base.expand_to(rows, cols));
1586 }
1587 }
1588 }
1589 let empty = IngestBuilder::new("S", 1, 4, DateSystem::Excel1900).finish();
1590 assert_projection(&empty.range_view(0, 0, 7, 3));
1591 }
1592
1593 #[test]
1594 fn range_projection_preserves_dense_run_and_partial_overlay_paths() {
1595 for run in [false, true] {
1596 let mut sheet = fixture(32);
1597 let chunk = &mut sheet.columns[0].chunks[0];
1598 chunk.overlay.clear();
1599 chunk.computed_overlay.clear();
1600 let values = vec![OverlayValue::Number(7.0); 29];
1601 let fragment = if run {
1602 OverlayFragment::run_range(0, values)
1603 } else {
1604 OverlayFragment::dense_range(0, values)
1605 }
1606 .unwrap();
1607 chunk.computed_overlay.apply_fragment(fragment);
1608 let view = sheet.range_view(2, 0, 9, 0);
1609 arrow_store::reset_overlay_select_stats();
1610 let numeric = view.numbers_slices().next().unwrap().unwrap();
1611 assert_eq!(numeric.2[0].value(0), 7.0);
1612 let stats = arrow_store::snapshot_overlay_select_stats();
1613 assert_eq!(stats.zip_select_calls, 0);
1614 assert_eq!(stats.direct_dense_slices, usize::from(!run));
1615 assert_eq!(stats.direct_run_materializations, usize::from(run));
1616 assert_projection(&view);
1617 let chunk = &mut sheet.columns[0].chunks[0];
1618 for (off, value) in [
1619 (3, OverlayValue::Empty),
1620 (4, OverlayValue::Pending),
1621 (5, OverlayValue::DateTime(45000.25)),
1622 (6, OverlayValue::Duration(0.5)),
1623 (7, OverlayValue::Boolean(true)),
1624 (
1625 8,
1626 OverlayValue::Error(arrow_store::map_error_code(ExcelErrorKind::Ref)),
1627 ),
1628 (9, OverlayValue::Text(Arc::from("not numeric"))),
1629 ] {
1630 chunk.overlay.set(off, value);
1631 }
1632 assert_projection(&sheet.range_view(2, 0, 10, 3));
1633 }
1634 }
1635
1636 #[test]
1637 fn range_discovery_work_is_bounded_and_projection_is_lane_local() {
1638 let mut ingest = IngestBuilder::new("S", 1, 256, DateSystem::Excel1900);
1639 for _ in 0..256 {
1640 ingest.append_row(&[LiteralValue::Number(1.0)]).unwrap();
1641 }
1642 let mut sheet = ingest.finish();
1643 sheet.ensure_row_capacity(256 * 4096);
1644 for start in [0, 256 * 2048 + 1, 256 * 4096 - 8, 256 * 4096 + 8] {
1645 let view = sheet.range_view(start, 0, start + 7, 2);
1646 range_work::begin();
1647 let generic: Vec<_> = view.iter_row_chunks().collect();
1648 let work = range_work::take();
1649 assert_eq!(work.candidates, generic.len());
1650 assert_eq!(work.segments, generic.len());
1651 assert!(work.search_probes <= 32);
1652 range_work::begin();
1653 let _: Vec<_> = view.numbers_slices().collect();
1654 let numeric = range_work::take();
1655 assert_eq!(numeric.generic_columns, 0);
1656 assert_eq!(numeric.provider_requests[1..], [0, 0, 0]);
1657 assert!(numeric.null_arrays <= 3);
1658 range_work::begin();
1659 let _: Vec<_> = view.errors_slices().collect();
1660 let errors = range_work::take();
1661 assert_eq!(errors.generic_columns, 0);
1662 assert_eq!(errors.provider_requests[0], 0);
1663 assert_eq!(errors.provider_requests[1], 0);
1664 assert_eq!(errors.provider_requests[3], 0);
1665 }
1666 let view = sheet.range_view(0, 0, sheet.nrows as usize - 1, 0);
1667 range_work::begin();
1668 assert!(view.iter_row_chunks().next().unwrap().is_ok());
1669 let work = range_work::take();
1670 assert_eq!(work.candidates, 1);
1671 assert_eq!(work.segments, 1);
1672 }
1673
1674 #[test]
1675 fn range_cold_numeric_does_not_initialize_unused_null_providers() {
1676 let mut ingest = IngestBuilder::new("S", 1, 32768, DateSystem::Excel1900);
1677 for _ in 0..32768 {
1678 ingest.append_row(&[LiteralValue::Number(1.0)]).unwrap();
1679 }
1680 let sheet = ingest.finish();
1681 let view = sheet.range_view(1, 0, 8, 0);
1682 range_work::begin();
1683 view.numbers_slices().next().unwrap().unwrap();
1684 let numeric = range_work::take();
1685 assert_eq!(numeric.provider_requests, [1, 0, 0, 0]);
1686 assert_eq!(numeric.provider_builds, [0; 4]);
1687 range_work::begin();
1688 view.errors_slices().next().unwrap().unwrap();
1689 let errors = range_work::take();
1690 assert_eq!(errors.provider_requests, [0, 0, 1, 0]);
1691 assert_eq!(errors.provider_builds, [0, 0, 1, 0]);
1692 assert_eq!(errors.provider_slots, [0, 0, 32768, 0]);
1693 }
1694
1695 #[test]
1696 fn range_cancellation_keeps_empty_exhausted_and_between_segment_errors() {
1697 let sheet = fixture(3);
1698 for bounds in [(0, 0, 28, 0), (30, 0, 35, 0), (3, 0, 1, 0), (0, 2, 5, 1)] {
1699 let token = CancelToken::new();
1700 let view = sheet
1701 .range_view(bounds.0, bounds.1, bounds.2, bounds.3)
1702 .with_cancel_token(Some(token.clone()));
1703 let mut generic = view.iter_row_chunks();
1704 let mut numeric = view.numbers_slices();
1705 let mut errors = view.errors_slices();
1706 token.cancel();
1707 for _ in 0..2 {
1708 assert_eq!(
1709 generic.next().unwrap().err().unwrap().kind,
1710 ExcelErrorKind::Cancelled
1711 );
1712 assert_eq!(
1713 numeric.next().unwrap().err().unwrap().kind,
1714 ExcelErrorKind::Cancelled
1715 );
1716 assert_eq!(
1717 errors.next().unwrap().err().unwrap().kind,
1718 ExcelErrorKind::Cancelled
1719 );
1720 }
1721 }
1722 let token = CancelToken::new();
1723 let view = sheet
1724 .range_view(0, 0, 28, 0)
1725 .with_cancel_token(Some(token.clone()));
1726 let mut numeric = view.numbers_slices();
1727 assert!(numeric.next().unwrap().is_ok());
1728 token.cancel();
1729 assert_eq!(
1730 numeric.next().unwrap().err().unwrap().kind,
1731 ExcelErrorKind::Cancelled
1732 );
1733 let token = CancelToken::new();
1734 let view = sheet
1735 .range_view(30, 0, 35, 0)
1736 .with_cancel_token(Some(token.clone()));
1737 let mut numeric = view.numbers_slices();
1738 assert!(numeric.next().is_none());
1739 token.cancel();
1740 assert_eq!(
1741 numeric.next().unwrap().err().unwrap().kind,
1742 ExcelErrorKind::Cancelled
1743 );
1744 }
1745
1746 #[test]
1747 fn range_projection_keeps_numeric_bits_and_engine_error_order() {
1748 let values = [
1749 0.0,
1750 -0.0,
1751 f64::INFINITY,
1752 f64::NEG_INFINITY,
1753 f64::from_bits(0x7ff8000000000001),
1754 ];
1755 let view = RangeView::from_owned_rows(
1756 values
1757 .iter()
1758 .map(|n| vec![LiteralValue::Number(*n)])
1759 .collect(),
1760 DateSystem::Excel1900,
1761 );
1762 assert_projection(&view);
1763 let slice = view.numbers_slices().next().unwrap().unwrap().2.remove(0);
1764 for (i, n) in values.iter().enumerate() {
1765 assert_eq!(slice.value(i).to_bits(), n.to_bits());
1766 }
1767 for chunk_rows in [1, 2] {
1768 let mut engine = crate::engine::Engine::new(
1769 crate::test_workbook::TestWorkbook::new(),
1770 crate::engine::EvalConfig {
1771 arrow_storage_enabled: true,
1772 delta_overlay_enabled: true,
1773 write_formula_overlay_enabled: true,
1774 enable_parallel: false,
1775 ..Default::default()
1776 },
1777 );
1778 let mut ingest = engine.begin_bulk_ingest_arrow();
1779 ingest.add_sheet("Source", 2, chunk_rows);
1780 ingest
1781 .append_row(
1782 "Source",
1783 &[
1784 LiteralValue::Number(1.0),
1785 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)),
1786 ],
1787 )
1788 .unwrap();
1789 ingest
1790 .append_row(
1791 "Source",
1792 &[
1793 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Div)),
1794 LiteralValue::Number(2.0),
1795 ],
1796 )
1797 .unwrap();
1798 ingest.finish().unwrap();
1799 engine
1800 .set_cell_formula(
1801 "Result",
1802 1,
1803 1,
1804 formualizer_parse::parser::parse("=SUM(Source!A1:B2)").unwrap(),
1805 )
1806 .unwrap();
1807 engine.evaluate_all().unwrap();
1808 let expected = if chunk_rows == 1 {
1809 ExcelErrorKind::Na
1810 } else {
1811 ExcelErrorKind::Div
1812 };
1813 assert!(
1814 matches!(engine.get_cell_value("Result", 1, 1), Some(LiteralValue::Error(e)) if e.kind == expected)
1815 );
1816 }
1817 }
1818
1819 #[test]
1820 fn range_arrays_outlive_owned_view_and_new_views_follow_structural_edits() {
1821 let numbers = {
1822 let view = RangeView::from_owned_rows(
1823 vec![vec![LiteralValue::Number(-0.0)]],
1824 DateSystem::Excel1900,
1825 );
1826 view.numbers_slices().next().unwrap().unwrap().2.remove(0)
1827 };
1828 assert_eq!(numbers.value(0).to_bits(), (-0.0f64).to_bits());
1829 let errors = {
1830 let view = RangeView::from_owned_rows(
1831 vec![vec![LiteralValue::Error(ExcelError::new(
1832 ExcelErrorKind::Div,
1833 ))]],
1834 DateSystem::Excel1900,
1835 );
1836 view.errors_slices().next().unwrap().unwrap().2.remove(0)
1837 };
1838 assert_eq!(
1839 errors.value(0),
1840 arrow_store::map_error_code(ExcelErrorKind::Div)
1841 );
1842 let mut sheet = fixture(3);
1843 let chunks_before: usize = sheet.columns.iter().map(|c| c.total_chunk_count()).sum();
1844 assert_projection(&sheet.range_view(0, 0, 40, 4));
1845 assert_eq!(
1846 sheet
1847 .columns
1848 .iter()
1849 .map(|c| c.total_chunk_count())
1850 .sum::<usize>(),
1851 chunks_before
1852 );
1853 sheet.insert_rows(4, 2);
1854 assert_projection(&sheet.range_view(1, 0, 35, 4));
1855 sheet.delete_rows(2, 1);
1856 assert_projection(&sheet.range_view(0, 0, 35, 4));
1857 sheet.ensure_row_capacity(50);
1858 assert_projection(&sheet.range_view(0, 0, 55, 4));
1859 sheet.insert_columns(1, 1);
1860 assert_projection(&sheet.range_view(0, 0, 55, 4));
1861 sheet.delete_columns(0, 1);
1862 assert_projection(&sheet.range_view(0, 0, 55, 4));
1863 }
1864}