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