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