1use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::ops::Range;
7use std::sync::Arc;
8use std::vec;
9
10use arrow_array::{Array, cast::AsArray, make_array};
11use arrow_schema::{DataType, Field as ArrowField};
12use bytes::Bytes;
13use futures::future::BoxFuture;
14use futures::stream::FuturesOrdered;
15use futures::{FutureExt, TryStreamExt};
16use itertools::Itertools;
17use lance_arrow::{DataTypeExt, r#struct::StructArrayExt};
18use lance_core::Result;
19use lance_core::cache::LanceCache;
20use lance_core::datatypes::Field;
21use lance_core::error::{Error, LanceOptionExt};
22use log::{debug, trace};
23
24use crate::EncodingsIo;
25use crate::compression::DecompressionStrategy;
26use crate::decoder::{
27 ColumnInfo, DecodePageTask, DecodedArray, FilterExpression, IO_REQUEST_BATCH_BYTES,
28 LoadedPageShard, MessageType, PageEncoding, PageInfo, RequestBatch, ScheduledScanLine,
29 SchedulerContext, StructuralDecodeArrayTask, StructuralFieldDecoder, StructuralFieldScheduler,
30 StructuralPageDecoder, StructuralSchedulingJob, UnloadedPageShard,
31};
32use crate::encodings::logical::primitive::blob::{BlobDescriptionPageScheduler, BlobPageScheduler};
33use crate::format::metadata::PageLayout;
34use crate::format::{EncodingUtils, metadata};
35use crate::repdef::{CompositeRepDefUnraveler, DefinitionInterpretation};
36
37use super::{
38 CachedPage, CachedPageData, ComplexAllNullScheduler, FullZipScheduler, LevelCodec,
39 MiniBlockScheduler, NoCachedPageData, PageDataCacheKey, PageInitialization,
40 SimpleAllNullScheduler, StructuralPageScheduler,
41};
42use super::{constant, planar, sparse};
43
44#[derive(Debug)]
45struct StructuralPrimitiveFieldSchedulingJob<'a> {
46 scheduler: &'a StructuralPrimitiveFieldScheduler,
47 mappings: std::vec::IntoIter<PageRangeMapping>,
48}
49
50impl<'a> StructuralPrimitiveFieldSchedulingJob<'a> {
51 pub fn try_new(
52 scheduler: &'a StructuralPrimitiveFieldScheduler,
53 ranges: Vec<Range<u64>>,
54 ) -> Result<Self> {
55 let mappings = map_ranges_to_pages(&scheduler.page_row_ends, &ranges)?.into_iter();
56 Ok(Self {
57 scheduler,
58 mappings,
59 })
60 }
61}
62
63impl StructuralSchedulingJob for StructuralPrimitiveFieldSchedulingJob<'_> {
64 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>> {
65 let Some(first_mapping) = self.mappings.next() else {
66 return Ok(Vec::new());
67 };
68 let batch = Arc::new(RequestBatch::new(context.io().clone()));
72 let batch_io: Arc<dyn EncodingsIo> = batch.clone();
73 let cur_path = context.current_path();
74 let mut scan_lines = Vec::new();
75 let mut mapping = Some(first_mapping);
76 while let Some(cur_mapping) = mapping.take() {
77 let cur_page = self.scheduler.built_page(cur_mapping.page_idx)?;
78
79 trace!(
80 "Scheduling {} rows across {} ranges from page with {} rows (column_index={}, page_index={})",
81 cur_mapping
82 .ranges_in_page
83 .iter()
84 .map(|r| r.end - r.start)
85 .sum::<u64>(),
86 cur_mapping.ranges_in_page.len(),
87 cur_page.row_range.end - cur_page.row_range.start,
88 self.scheduler.column_index,
89 cur_page.page_index,
90 );
91
92 let page_decoders = cur_page
93 .scheduler
94 .schedule_ranges(&cur_mapping.ranges_in_page, &batch_io)?;
95
96 scan_lines.extend(page_decoders.into_iter().map(|page_load_task| {
97 let cur_path = cur_path.clone();
98 let page_decoder = page_load_task.decoder_fut;
99 let unloaded_page = async move {
100 let page_decoder = page_decoder.await?;
101 Ok(LoadedPageShard {
102 decoder: page_decoder,
103 path: cur_path,
104 })
105 }
106 .boxed();
107 ScheduledScanLine {
108 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(unloaded_page))],
109 rows_scheduled: page_load_task.num_rows,
110 }
111 }));
112
113 if batch.pending_bytes() < IO_REQUEST_BATCH_BYTES {
114 mapping = self.mappings.next();
115 }
116 }
117 batch.flush();
118 Ok(scan_lines)
119 }
120}
121
122#[derive(Debug)]
123struct PageInfoAndScheduler {
124 page_index: usize,
125 row_range: Range<u64>,
126 scheduler: Box<dyn StructuralPageScheduler>,
127}
128
129#[derive(Debug)]
136struct PageSchedulerSource {
137 column_info: Arc<ColumnInfo>,
138 decompressors: Arc<dyn DecompressionStrategy>,
139 cache_repetition_index: bool,
140 target_field: Field,
141}
142
143#[derive(Debug)]
148pub struct StructuralPrimitiveFieldScheduler {
149 page_row_ends: Vec<u64>,
152 page_schedulers: Vec<PageInfoAndScheduler>,
156 page_source: Option<PageSchedulerSource>,
159 column_index: u32,
160 target_data_type_tag: Arc<str>,
161}
162
163impl StructuralPrimitiveFieldScheduler {
164 #[deprecated(
169 since = "13.0.0",
170 note = "builds every page scheduler eagerly; use try_new_lazy"
171 )]
172 pub fn try_new(
173 column_info: &ColumnInfo,
174 decompressors: &dyn DecompressionStrategy,
175 cache_repetition_index: bool,
176 target_field: &Field,
177 ) -> Result<Self> {
178 let page_row_ends = Self::page_row_ends(column_info)?;
179 let mut page_schedulers = Vec::with_capacity(page_row_ends.len());
180 let mut row_start = 0_u64;
181 for (page_index, (page_info, &row_end)) in column_info
182 .page_infos
183 .iter()
184 .zip(&page_row_ends)
185 .enumerate()
186 {
187 page_schedulers.push(Self::page_info_to_scheduler(
188 page_info,
189 page_index,
190 row_start..row_end,
191 decompressors,
192 cache_repetition_index,
193 target_field,
194 )?);
195 row_start = row_end;
196 }
197 Ok(Self {
198 page_row_ends,
199 page_schedulers,
200 page_source: None,
201 column_index: column_info.index,
202 target_data_type_tag: format!("{:?}", target_field.data_type()).into(),
203 })
204 }
205
206 pub fn try_new_lazy(
213 column_info: Arc<ColumnInfo>,
214 decompressors: Arc<dyn DecompressionStrategy>,
215 cache_repetition_index: bool,
216 target_field: &Field,
217 ) -> Result<Self> {
218 Ok(Self {
219 page_row_ends: Self::page_row_ends(&column_info)?,
220 page_schedulers: Vec::new(),
221 column_index: column_info.index,
222 target_data_type_tag: format!("{:?}", target_field.data_type()).into(),
223 page_source: Some(PageSchedulerSource {
224 column_info,
225 decompressors,
226 cache_repetition_index,
227 target_field: target_field.clone(),
228 }),
229 })
230 }
231
232 fn page_row_ends(column_info: &ColumnInfo) -> Result<Vec<u64>> {
234 let mut page_row_ends = Vec::with_capacity(column_info.page_infos.len());
235 let mut row_start = 0_u64;
236 for (page_index, page_info) in column_info.page_infos.iter().enumerate() {
237 let row_end = row_start.checked_add(page_info.num_rows).ok_or_else(|| {
238 Error::invalid_input_source(
239 format!(
240 "Page row range overflowed for column {} at page {page_index}: start={row_start}, rows={}",
241 column_info.index, page_info.num_rows
242 )
243 .into(),
244 )
245 })?;
246 page_row_ends.push(row_end);
247 row_start = row_end;
248 }
249 Ok(page_row_ends)
250 }
251
252 #[cfg(test)]
254 fn from_page_schedulers(
255 page_schedulers: Vec<PageInfoAndScheduler>,
256 column_index: u32,
257 target_data_type_tag: Arc<str>,
258 ) -> Self {
259 Self {
260 page_row_ends: page_schedulers
261 .iter()
262 .map(|page| page.row_range.end)
263 .collect(),
264 page_schedulers,
265 page_source: None,
266 column_index,
267 target_data_type_tag,
268 }
269 }
270
271 fn page_row_range(&self, page_index: usize) -> Range<u64> {
272 let start = match page_index.checked_sub(1) {
273 Some(previous) => self.page_row_ends[previous],
274 None => 0,
275 };
276 start..self.page_row_ends[page_index]
277 }
278
279 fn built_page_position(&self, page_index: usize) -> Option<usize> {
281 self.page_schedulers
282 .binary_search_by_key(&page_index, |page| page.page_index)
283 .ok()
284 }
285
286 fn built_page(&self, page_index: usize) -> Result<&PageInfoAndScheduler> {
287 let position = self.built_page_position(page_index).ok_or_else(|| {
288 Error::internal(format!(
289 "Page {page_index} of column {} was scheduled but not initialized; the scheduled ranges must be covered by the ranges passed to initialize",
290 self.column_index
291 ))
292 })?;
293 Ok(&self.page_schedulers[position])
294 }
295
296 fn build_pages(&mut self, page_indices: &[usize]) -> Result<Vec<usize>> {
299 if let Some(source) = &self.page_source {
300 let mut missing = Vec::new();
301 for &page_index in page_indices {
302 if self.built_page_position(page_index).is_some() {
303 continue;
304 }
305 let page_info = source
306 .column_info
307 .page_infos
308 .get(page_index)
309 .ok_or_else(|| {
310 Error::internal(format!(
311 "Page {page_index} is out of bounds for column {} with {} pages",
312 self.column_index,
313 source.column_info.page_infos.len()
314 ))
315 })?;
316 missing.push(Self::page_info_to_scheduler(
317 page_info,
318 page_index,
319 self.page_row_range(page_index),
320 source.decompressors.as_ref(),
321 source.cache_repetition_index,
322 &source.target_field,
323 )?);
324 }
325 if !missing.is_empty() {
326 self.page_schedulers = std::mem::take(&mut self.page_schedulers)
327 .into_iter()
328 .merge_by(missing, |built, new| built.page_index < new.page_index)
329 .collect();
330 }
331 if self.page_schedulers.len() == self.page_row_ends.len() {
332 self.page_source = None;
333 }
334 }
335 page_indices
336 .iter()
337 .map(|&page_index| {
338 self.built_page_position(page_index).ok_or_else(|| {
339 Error::internal(format!(
340 "Page {page_index} of column {} is missing after construction",
341 self.column_index
342 ))
343 })
344 })
345 .collect()
346 }
347
348 fn page_layout_to_scheduler(
349 page_info: &PageInfo,
350 page_layout: &PageLayout,
351 decompressors: &dyn DecompressionStrategy,
352 cache_repetition_index: bool,
353 target_field: &Field,
354 ) -> Result<Box<dyn StructuralPageScheduler>> {
355 use metadata::page_layout::Layout;
356 Ok(match page_layout.layout.as_ref().expect_ok()? {
357 Layout::MiniBlockLayout(mini_block) => Box::new(MiniBlockScheduler::try_new(
358 &page_info.buffer_offsets_and_sizes,
359 page_info.priority,
360 mini_block.num_items,
361 mini_block,
362 decompressors,
363 )?),
364 Layout::FullZipLayout(full_zip) => {
365 let mut scheduler = FullZipScheduler::try_new(
366 &page_info.buffer_offsets_and_sizes,
367 page_info.priority,
368 page_info.num_rows,
369 full_zip,
370 decompressors,
371 )?;
372 scheduler.enable_cache = cache_repetition_index;
373 Box::new(scheduler)
374 }
375 Layout::ConstantLayout(constant_layout) => {
376 let def_meaning = constant_layout
377 .layers
378 .iter()
379 .map(|l| EncodingUtils::repdef_layer_to_def_interp(*l))
380 .collect::<Vec<_>>();
381 let has_scalar_value = constant_layout.inline_value.is_some()
382 || page_info.buffer_offsets_and_sizes.len() == 1
383 || page_info.buffer_offsets_and_sizes.len() == 3;
384 if has_scalar_value {
385 Box::new(constant::ConstantPageScheduler::try_new(
386 page_info.buffer_offsets_and_sizes.clone(),
387 constant_layout.inline_value.clone(),
388 target_field.data_type(),
389 def_meaning.into(),
390 )?) as Box<dyn StructuralPageScheduler>
391 } else if def_meaning.len() == 1
392 && def_meaning[0] == DefinitionInterpretation::NullableItem
393 {
394 Box::new(SimpleAllNullScheduler::default()) as Box<dyn StructuralPageScheduler>
395 } else {
396 let rep_codec = LevelCodec::try_new(
399 constant_layout.rep_compression.as_ref(),
400 decompressors,
401 )?;
402 let def_codec = LevelCodec::try_new(
403 constant_layout.def_compression.as_ref(),
404 decompressors,
405 )?;
406
407 Box::new(ComplexAllNullScheduler::new(
408 page_info.buffer_offsets_and_sizes.clone(),
409 def_meaning.into(),
410 rep_codec,
411 def_codec,
412 constant_layout.num_rep_values,
413 constant_layout.num_def_values,
414 )) as Box<dyn StructuralPageScheduler>
415 }
416 }
417 Layout::BlobLayout(blob) => {
418 let inner_scheduler = Self::page_layout_to_scheduler(
419 page_info,
420 blob.inner_layout.as_ref().expect_ok()?.as_ref(),
421 decompressors,
422 cache_repetition_index,
423 target_field,
424 )?;
425 let def_meaning = blob
426 .layers
427 .iter()
428 .map(|l| EncodingUtils::repdef_layer_to_def_interp(*l))
429 .collect::<Vec<_>>();
430 if matches!(target_field.data_type(), DataType::Struct(_)) {
431 Box::new(BlobDescriptionPageScheduler::new(
433 inner_scheduler,
434 def_meaning.into(),
435 ))
436 } else {
437 Box::new(BlobPageScheduler::new(
439 inner_scheduler,
440 page_info.priority,
441 page_info.num_rows,
442 def_meaning.into(),
443 ))
444 }
445 }
446 })
447 }
448
449 fn page_info_to_scheduler(
450 page_info: &PageInfo,
451 page_index: usize,
452 row_range: Range<u64>,
453 decompressors: &dyn DecompressionStrategy,
454 cache_repetition_index: bool,
455 target_field: &Field,
456 ) -> Result<PageInfoAndScheduler> {
457 if let PageEncoding::Planar(layout) = &page_info.encoding {
458 let scheduler = if layout.sparse().is_some() {
459 Box::new(sparse::planar::PlanarSparseScheduler::try_new(
460 page_info,
461 layout,
462 target_field,
463 decompressors,
464 )?) as Box<dyn StructuralPageScheduler>
465 } else {
466 planar::validate_page(page_info, layout, target_field, decompressors)?;
467 Self::page_layout_to_scheduler(
468 page_info,
469 &layout.dense_execution_layout()?,
470 decompressors,
471 cache_repetition_index,
472 target_field,
473 )?
474 };
475 return Ok(PageInfoAndScheduler {
476 page_index,
477 row_range,
478 scheduler,
479 });
480 }
481 let page_layout = page_info.encoding.as_structural();
482 let scheduler = Self::page_layout_to_scheduler(
483 page_info,
484 page_layout,
485 decompressors,
486 cache_repetition_index,
487 target_field,
488 )?;
489 Ok(PageInfoAndScheduler {
490 page_index,
491 row_range,
492 scheduler,
493 })
494 }
495}
496
497#[derive(Debug)]
501struct PageRangeMapping {
502 page_idx: usize,
503 ranges_in_page: Vec<Range<u64>>,
504}
505
506fn for_each_page_range(
514 page_row_ends: &[u64],
515 ranges: &[Range<u64>],
516 mut visit: impl FnMut(usize, Range<u64>),
517) -> Result<()> {
518 for (range_idx, range) in ranges.iter().enumerate() {
519 if range.start > range.end {
520 return Err(Error::invalid_input_source(
521 format!(
522 "Requested row range {range_idx} is reversed: {}..{}",
523 range.start, range.end
524 )
525 .into(),
526 ));
527 }
528 if range_idx > 0 {
529 let previous = &ranges[range_idx - 1];
530 if previous.start >= range.start || previous.end > range.start {
531 return Err(Error::invalid_input_source(
532 format!(
533 "Requested row ranges overlap or are not strictly ordered at index {range_idx}: {previous:?}, then {range:?}"
534 )
535 .into(),
536 ));
537 }
538 }
539 }
540
541 for range in ranges {
542 let mut page_idx = page_row_ends.partition_point(|page_end| *page_end <= range.start);
543 let mut page_start = match page_idx.checked_sub(1) {
544 Some(previous) => page_row_ends[previous],
545 None => 0,
546 };
547 while let Some(&page_end) = page_row_ends.get(page_idx) {
548 if page_start >= range.end {
549 break;
550 }
551 let start_in_page = range.start.max(page_start) - page_start;
552 let end_in_page = range.end.min(page_end) - page_start;
553 if start_in_page < end_in_page {
554 visit(page_idx, start_in_page..end_in_page);
555 }
556 page_idx += 1;
557 page_start = page_end;
558 }
559 }
560 Ok(())
561}
562
563fn map_ranges_to_pages(
569 page_row_ends: &[u64],
570 ranges: &[Range<u64>],
571) -> Result<Vec<PageRangeMapping>> {
572 let mut result: Vec<PageRangeMapping> =
573 Vec::with_capacity(ranges.len().min(page_row_ends.len()));
574 for_each_page_range(page_row_ends, ranges, |page_idx, range_in_page| {
575 if let Some(mapping) = result.last_mut()
576 && mapping.page_idx == page_idx
577 {
578 mapping.ranges_in_page.push(range_in_page);
579 } else {
580 result.push(PageRangeMapping {
581 page_idx,
582 ranges_in_page: vec![range_in_page],
583 });
584 }
585 })?;
586 Ok(result)
587}
588
589fn pages_overlapping_ranges(page_row_ends: &[u64], ranges: &[Range<u64>]) -> Result<Vec<usize>> {
590 let mut result = Vec::with_capacity(ranges.len().min(page_row_ends.len()));
591 for_each_page_range(page_row_ends, ranges, |page_idx, _| {
592 if result.last().copied() != Some(page_idx) {
593 result.push(page_idx);
594 }
595 })?;
596 Ok(result)
597}
598
599#[derive(Debug)]
600struct PageCacheMiss {
601 page_idx: usize,
604 cache_key: PageDataCacheKey,
605 initialization: PageInitialization,
606}
607
608async fn read_page_initialization_buffers(
609 io: &Arc<dyn EncodingsIo>,
610 all_ranges: Vec<Range<u64>>,
611) -> Result<Vec<Bytes>> {
612 let mut sorted_ranges = all_ranges.into_iter().enumerate().collect::<Vec<_>>();
617 sorted_ranges.sort_unstable_by_key(|(_, range)| (range.start, range.end));
618 let sorted_requests = sorted_ranges
619 .iter()
620 .map(|(_, range)| range.clone())
621 .collect::<Vec<_>>();
622 let expected_buffer_count = sorted_requests.len();
623 let sorted_buffers = if sorted_requests.is_empty() {
624 Vec::new()
625 } else {
626 io.submit_request(sorted_requests, 0).await?
627 };
628 if sorted_buffers.len() != expected_buffer_count {
629 return Err(Error::invalid_input_source(
630 format!(
631 "Page initialization I/O returned {} buffers for {} ranges",
632 sorted_buffers.len(),
633 expected_buffer_count
634 )
635 .into(),
636 ));
637 }
638 let mut buffers = vec![None; expected_buffer_count];
639 for ((original_idx, _), buffer) in sorted_ranges.into_iter().zip(sorted_buffers) {
640 buffers[original_idx] = Some(buffer);
641 }
642 buffers
643 .into_iter()
644 .map(|buffer| {
645 buffer.ok_or_else(|| Error::internal("Page initialization buffer was not assigned"))
646 })
647 .collect()
648}
649
650async fn cache_initialized_page(
651 cache: Arc<LanceCache>,
652 cache_key: PageDataCacheKey,
653 data: Arc<dyn CachedPageData>,
654) {
655 if data
656 .clone()
657 .as_arc_any()
658 .downcast::<NoCachedPageData>()
659 .is_err()
660 {
661 cache
662 .insert_with_key(&cache_key, Arc::new(CachedPage { data }))
663 .await;
664 }
665}
666
667impl StructuralPrimitiveFieldScheduler {
668 async fn find_cache_misses(
672 &mut self,
673 positions: impl ExactSizeIterator<Item = usize>,
674 cache: &Arc<LanceCache>,
675 ) -> Result<Vec<PageCacheMiss>> {
676 let mut misses = Vec::with_capacity(positions.len());
678 for page_idx in positions {
679 self.page_schedulers[page_idx]
680 .scheduler
681 .set_cache(cache.clone());
682 let page = &self.page_schedulers[page_idx];
683 if !page.scheduler.needs_initialization() {
684 continue;
685 }
686 let page_index = u32::try_from(page.page_index).map_err(|_| {
687 Error::invalid_input_source(
688 format!("Page index does not fit u32: {}", page.page_index).into(),
689 )
690 })?;
691 let cache_key = PageDataCacheKey {
692 column_index: self.column_index,
693 page_index,
694 view: page.scheduler.cache_view(),
695 target_data_type_tag: self.target_data_type_tag.clone(),
696 };
697 if let Some(cached_page) = cache.get_with_key(&cache_key).await {
698 match self.page_schedulers[page_idx]
699 .scheduler
700 .try_load(&cached_page.data)
701 {
702 Ok(()) => continue,
703 Err(error) => {
704 debug!("Replacing invalid cached page data for {cache_key:?}: {error}");
705 }
706 }
707 }
708 let initialization = self.page_schedulers[page_idx].scheduler.init_layout()?;
709 misses.push(PageCacheMiss {
710 page_idx,
711 cache_key,
712 initialization,
713 });
714 }
715 Ok(misses)
716 }
717
718 fn initialization_ranges(misses: &[PageCacheMiss]) -> Vec<Range<u64>> {
719 let num_ranges = misses
720 .iter()
721 .map(|miss| miss.initialization.num_required_ranges())
722 .sum();
723 let mut all_ranges = Vec::with_capacity(num_ranges);
724 for miss in misses {
725 all_ranges.extend(miss.initialization.required_ranges().cloned());
726 }
727 all_ranges
728 }
729
730 async fn initialize_cache_misses(
731 &mut self,
732 misses: Vec<PageCacheMiss>,
733 buffers: Vec<Bytes>,
734 cache: Arc<LanceCache>,
735 io: &Arc<dyn EncodingsIo>,
736 ) -> Result<()> {
737 let mut buffers = buffers.into_iter();
738
739 let mut tail = self.page_schedulers.as_mut_slice();
745 let mut next_page = 0;
746 let mut page_inits = FuturesOrdered::new();
747 for miss in misses {
748 let PageCacheMiss {
749 page_idx,
750 cache_key,
751 initialization,
752 } = miss;
753 let relative_idx = page_idx
754 .checked_sub(next_page)
755 .ok_or_else(|| Error::internal("Page initialization indices are not ordered"))?;
756 if relative_idx >= tail.len() {
757 return Err(Error::internal(format!(
758 "Page initialization index {page_idx} is out of bounds"
759 )));
760 }
761 let (_, rest) = tail.split_at_mut(relative_idx);
762 let (page, rest) = rest.split_first_mut().ok_or_else(|| {
763 Error::internal(format!(
764 "Page initialization index {page_idx} is out of bounds"
765 ))
766 })?;
767 tail = rest;
768 next_page = page_idx + 1;
769
770 let count = initialization.num_required_ranges();
771 let page_buffers = buffers.by_ref().take(count).collect::<Vec<_>>();
772 if page_buffers.len() != count {
773 return Err(Error::invalid_input_source(
774 format!(
775 "Page {page_idx} received {} initialization buffers, expected {count}",
776 page_buffers.len()
777 )
778 .into(),
779 ));
780 }
781 let page_buffers = initialization.with_buffers(page_buffers)?;
782 let cache = cache.clone();
783 let scheduler = &mut page.scheduler;
784 page_inits.push_back(async move {
785 let cached = scheduler.init_from_buffers(page_buffers, io).await?;
786 scheduler.try_load(&cached)?;
787 cache_initialized_page(cache, cache_key, cached).await;
788 Ok::<(), Error>(())
789 });
790 }
791 if buffers.next().is_some() {
792 return Err(Error::invalid_input_source(
793 "Page initialization returned unassigned buffers".into(),
794 ));
795 }
796
797 page_inits.try_collect::<Vec<()>>().await?;
798 Ok(())
799 }
800
801 async fn initialize_pages(
802 &mut self,
803 requested_ranges: Option<&[Range<u64>]>,
804 context: &SchedulerContext,
805 ) -> Result<()> {
806 let cache = context.cache().clone();
807 let page_indices = match requested_ranges {
808 None => (0..self.page_row_ends.len()).collect::<Vec<_>>(),
809 Some(ranges) => pages_overlapping_ranges(&self.page_row_ends, ranges)?,
810 };
811 let positions = self.build_pages(&page_indices)?;
812 let misses = self
813 .find_cache_misses(positions.into_iter(), &cache)
814 .await?;
815
816 if misses.is_empty() {
817 return Ok(());
818 }
819
820 let all_ranges = Self::initialization_ranges(&misses);
823 let buffers = read_page_initialization_buffers(context.io(), all_ranges).await?;
824 self.initialize_cache_misses(misses, buffers, cache, context.io())
825 .await
826 }
827}
828
829impl StructuralFieldScheduler for StructuralPrimitiveFieldScheduler {
830 fn initialize<'a>(
831 &'a mut self,
832 requested_ranges: Option<&'a [Range<u64>]>,
833 _filter: &'a FilterExpression,
834 context: &'a SchedulerContext,
835 ) -> BoxFuture<'a, Result<()>> {
836 self.initialize_pages(requested_ranges, context).boxed()
837 }
838
839 fn schedule_ranges<'a>(
840 &'a self,
841 ranges: &[Range<u64>],
842 _filter: &FilterExpression,
843 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>> {
844 let ranges = ranges.to_vec();
845 Ok(Box::new(StructuralPrimitiveFieldSchedulingJob::try_new(
846 self, ranges,
847 )?))
848 }
849}
850
851#[derive(Debug)]
854pub struct StructuralCompositeDecodeArrayTask {
855 tasks: Vec<Box<dyn DecodePageTask>>,
856 should_validate: bool,
857 data_type: DataType,
858}
859
860impl StructuralCompositeDecodeArrayTask {
861 fn restore_validity(
862 array: Arc<dyn Array>,
863 unraveler: &mut CompositeRepDefUnraveler,
864 ) -> Result<Arc<dyn Array>> {
865 let validity = unraveler.unravel_validity(array.len())?;
866 let Some(validity) = validity else {
867 return Ok(array);
868 };
869 if array.data_type() == &DataType::Null {
870 return Ok(array);
872 }
873 if validity.len() != array.len() {
874 return Err(Error::invalid_input_source(
875 format!(
876 "Structural validity has {} entries for an array with {} values",
877 validity.len(),
878 array.len()
879 )
880 .into(),
881 ));
882 }
883 let array = make_array(unsafe {
886 array
887 .to_data()
888 .into_builder()
889 .nulls(Some(validity))
890 .build_unchecked()
891 });
892 if let Some(struct_array) = array.as_struct_opt() {
893 Ok(Arc::new(struct_array.pushdown_nulls()?))
897 } else {
898 Ok(array)
899 }
900 }
901}
902
903impl StructuralDecodeArrayTask for StructuralCompositeDecodeArrayTask {
904 fn decode(self: Box<Self>) -> Result<DecodedArray> {
905 let mut arrays = Vec::with_capacity(self.tasks.len());
906 let mut unravelers = Vec::with_capacity(self.tasks.len());
907 let mut data_size = 0u64;
908 for task in self.tasks {
909 let decoded = task.decode()?;
910 data_size += decoded.data.data_size();
911 unravelers.push(decoded.repdef);
912
913 let array = make_array(
914 decoded
915 .data
916 .into_arrow(self.data_type.clone(), self.should_validate)?,
917 );
918
919 arrays.push(array);
920 }
921 let array_refs = arrays.iter().map(|arr| arr.as_ref()).collect::<Vec<_>>();
922 let array = arrow_select::concat::concat(&array_refs)?;
923 let mut repdef = CompositeRepDefUnraveler::new(unravelers);
924
925 let array = Self::restore_validity(array, &mut repdef)?;
926
927 Ok(DecodedArray {
928 array,
929 repdef,
930 data_size,
931 })
932 }
933}
934
935fn fsl_child_bitmap_bytes(data_type: &arrow_schema::DataType, rows: u64) -> u64 {
941 match data_type {
942 arrow_schema::DataType::FixedSizeList(child_field, dimension) => {
943 let child_rows = rows * *dimension as u64;
944 let own = if child_field.is_nullable() {
945 child_rows.div_ceil(8)
946 } else {
947 0
948 };
949 own + fsl_child_bitmap_bytes(child_field.data_type(), child_rows)
950 }
951 _ => 0,
952 }
953}
954
955#[derive(Debug)]
956pub struct StructuralPrimitiveFieldDecoder {
957 field: Arc<ArrowField>,
958 page_decoders: VecDeque<Box<dyn StructuralPageDecoder>>,
959 should_validate: bool,
960 rows_drained_in_current: u64,
961}
962
963impl StructuralPrimitiveFieldDecoder {
964 pub fn new(field: &Arc<ArrowField>, should_validate: bool) -> Self {
965 Self {
966 field: field.clone(),
967 page_decoders: VecDeque::new(),
968 should_validate,
969 rows_drained_in_current: 0,
970 }
971 }
972
973 fn decoded_bytes_for_rows(&self, rows: u64) -> lance_core::Result<u64> {
974 let mut remaining = rows;
975 let mut total = 0u64;
976 for (page_num, page) in self.page_decoders.iter().enumerate() {
977 if remaining == 0 {
978 break;
979 }
980 let available = if page_num == 0 {
981 page.num_rows().saturating_sub(self.rows_drained_in_current)
982 } else {
983 page.num_rows()
984 };
985 let take = available.min(remaining);
986 total += page.decoded_bytes(take)?;
987 remaining -= take;
988 }
989 if remaining > 0 {
990 return Err(lance_core::Error::not_supported(format!(
991 "plan_decoded_bytes: queued pages cover only {} of {} requested rows",
992 rows - remaining,
993 rows,
994 )));
995 }
996 Ok(total)
997 }
998}
999
1000impl StructuralFieldDecoder for StructuralPrimitiveFieldDecoder {
1001 fn accept_page(&mut self, child: LoadedPageShard) -> Result<()> {
1002 assert!(child.path.is_empty());
1003 self.page_decoders.push_back(child.decoder);
1004 Ok(())
1005 }
1006
1007 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>> {
1008 let mut remaining = num_rows;
1009 let mut tasks = Vec::new();
1010 while remaining > 0 {
1011 let queued_pages = self.page_decoders.len();
1012 let Some(cur_page) = self.page_decoders.front_mut() else {
1013 return Err(Error::internal(format!(
1014 "Primitive decoder missing page decoder while draining field '{}' (data_type={:?}, requested_rows={}, remaining_rows={}, rows_drained_in_current={}, queued_pages={})",
1015 self.field.name(),
1016 self.field.data_type(),
1017 num_rows,
1018 remaining,
1019 self.rows_drained_in_current,
1020 queued_pages
1021 )));
1022 };
1023 let num_in_page = cur_page.num_rows() - self.rows_drained_in_current;
1024 let to_take = num_in_page.min(remaining);
1025
1026 let task = cur_page.drain(to_take)?;
1027 tasks.push(task);
1028
1029 if to_take == num_in_page {
1030 self.page_decoders.pop_front();
1031 self.rows_drained_in_current = 0;
1032 } else {
1033 self.rows_drained_in_current += to_take;
1034 }
1035
1036 remaining -= to_take;
1037 }
1038 Ok(Box::new(StructuralCompositeDecodeArrayTask {
1039 tasks,
1040 should_validate: self.should_validate,
1041 data_type: self.field.data_type().clone(),
1042 }))
1043 }
1044
1045 fn data_type(&self) -> &DataType {
1046 self.field.data_type()
1047 }
1048
1049 fn plan_decoded_bytes(&self, rows_remaining: u64) -> lance_core::Result<[u64; 8]> {
1050 use crate::decoder::CANDIDATE_BATCH_SIZES;
1051 let data_type = self.field.data_type();
1052 let is_nullable = self.field.is_nullable();
1053
1054 if let Some(byte_width) = data_type.byte_width_opt() {
1056 let mut out = [0u64; 8];
1057 for (i, &c) in CANDIDATE_BATCH_SIZES.iter().enumerate() {
1058 let rows = (c as u64).min(rows_remaining);
1059 out[i] = rows * byte_width as u64;
1060 if is_nullable {
1061 out[i] += rows.div_ceil(8);
1062 }
1063 out[i] += fsl_child_bitmap_bytes(data_type, rows);
1064 }
1065 return Ok(out);
1066 }
1067
1068 if matches!(data_type, DataType::Boolean) {
1070 let mut out = [0u64; 8];
1071 for (i, &c) in CANDIDATE_BATCH_SIZES.iter().enumerate() {
1072 let rows = (c as u64).min(rows_remaining);
1073 out[i] = rows.div_ceil(8);
1074 if is_nullable {
1075 out[i] += rows.div_ceil(8);
1076 }
1077 }
1078 return Ok(out);
1079 }
1080
1081 if matches!(data_type, DataType::Null) {
1083 return Ok([0u64; 8]);
1084 }
1085
1086 let mut out = [0u64; 8];
1088 for (i, &c) in CANDIDATE_BATCH_SIZES.iter().enumerate() {
1089 let rows = (c as u64).min(rows_remaining);
1090 out[i] = self.decoded_bytes_for_rows(rows)?;
1091 if is_nullable {
1092 out[i] += rows.div_ceil(8);
1093 }
1094 }
1095 Ok(out)
1096 }
1097}
1098
1099#[cfg(test)]
1100#[allow(clippy::single_range_in_vec_init)]
1101mod tests {
1102 use std::ops::Range;
1103 use std::sync::Arc;
1104
1105 use arrow_array::{Array, ArrayRef, Int32Array, StructArray, cast::AsArray};
1106 use arrow_schema::{DataType, Field as ArrowField};
1107 use bytes::Bytes;
1108
1109 use crate::EncodingsIo;
1110 use crate::decoder::{
1111 FilterExpression, RequestBatch, SchedulerContext, StructuralFieldDecoder,
1112 StructuralFieldScheduler,
1113 };
1114 use crate::encodings::logical::primitive::{
1115 CachedPage, CachedPageData, PageCacheView, PageDataCacheKey, SimpleAllNullScheduler,
1116 StructuralPageScheduler,
1117 };
1118 use crate::format::metadata;
1119 use crate::repdef::{CompositeRepDefUnraveler, DefinitionInterpretation, RepDefUnraveler};
1120 use crate::testing::SimulatedScheduler;
1121
1122 use super::{
1123 PageInfoAndScheduler, StructuralCompositeDecodeArrayTask, StructuralPrimitiveFieldDecoder,
1124 StructuralPrimitiveFieldScheduler, map_ranges_to_pages, pages_overlapping_ranges,
1125 };
1126
1127 #[derive(Debug)]
1128 struct CacheMarker(u8);
1129
1130 #[test]
1131 fn test_packed_struct_restores_child_validity() {
1132 let array = StructArray::new(
1133 vec![Arc::new(ArrowField::new("value", DataType::Int32, false))].into(),
1134 vec![Arc::new(Int32Array::from(vec![5, 0, 7])) as ArrayRef],
1135 None,
1136 );
1137 let mut repdef = CompositeRepDefUnraveler::new(vec![RepDefUnraveler::new(
1138 None,
1139 Some(vec![0, 1, 0]),
1140 vec![DefinitionInterpretation::NullableItem].into(),
1141 3,
1142 )]);
1143 let result =
1144 StructuralCompositeDecodeArrayTask::restore_validity(Arc::new(array), &mut repdef)
1145 .unwrap();
1146 let result = result.as_struct();
1147 assert_eq!(result.null_count(), 1);
1148 assert_eq!(result.column(0).nulls(), result.nulls());
1149 assert!(result.column(0).is_null(1));
1150 result.to_data().validate_full().unwrap();
1151 }
1152
1153 impl lance_core::cache::DeepSizeOf for CacheMarker {
1154 fn deep_size_of_children(&self, _context: &mut lance_core::cache::Context) -> usize {
1155 0
1156 }
1157 }
1158
1159 impl CachedPageData for CacheMarker {
1160 fn as_arc_any(self: Arc<Self>) -> Arc<dyn std::any::Any + Send + Sync + 'static> {
1161 self
1162 }
1163 }
1164
1165 #[test]
1166 fn test_primitive_decoder_empty_page_queue_returns_error() {
1167 let field = Arc::new(ArrowField::new("vector", DataType::Float32, true));
1168 let mut decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
1169
1170 let err = decoder.drain(1).unwrap_err();
1171 assert!(
1172 matches!(&err, lance_core::Error::Internal { .. }),
1173 "expected internal error, got: {err:?}"
1174 );
1175 let message = err.to_string();
1176 for expected in [
1177 "Primitive decoder missing page decoder",
1178 "field 'vector'",
1179 "data_type=Float32",
1180 "requested_rows=1",
1181 "remaining_rows=1",
1182 "rows_drained_in_current=0",
1183 "queued_pages=0",
1184 ] {
1185 assert!(
1186 message.contains(expected),
1187 "expected error to contain {expected:?}, got: {message}"
1188 );
1189 }
1190 }
1191
1192 #[test]
1193 fn test_plan_decoded_bytes_i32() {
1194 use crate::decoder::CANDIDATE_BATCH_SIZES;
1195
1196 let field = Arc::new(ArrowField::new("x", DataType::Int32, false));
1197 let decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
1198
1199 let rows_remaining = 100u64;
1200 let bytes = decoder.plan_decoded_bytes(rows_remaining).unwrap();
1201
1202 for (i, &candidate) in CANDIDATE_BATCH_SIZES.iter().enumerate() {
1203 let rows = (candidate as u64).min(rows_remaining);
1204 assert_eq!(bytes[i], rows * 4, "candidate batch size {candidate}");
1205 }
1206 }
1207
1208 #[test]
1209 fn test_plan_decoded_bytes_i32_with_nulls() {
1210 use arrow_array::Int32Array;
1211
1212 const N: i32 = 1024;
1218 let array = Int32Array::from_iter((0..N).map(|i| if i % 2 == 0 { Some(i) } else { None }));
1219 let expected = array.get_buffer_memory_size() as u64;
1220
1221 let field = Arc::new(ArrowField::new("x", DataType::Int32, true));
1222 let decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
1223
1224 let bytes = decoder.plan_decoded_bytes(N as u64).unwrap();
1225 assert_eq!(
1227 bytes[5], expected,
1228 "plan_decoded_bytes({N}) = {} but get_buffer_memory_size = {expected}",
1229 bytes[5],
1230 );
1231 }
1232
1233 #[test]
1234 fn test_plan_decoded_bytes_nullable_fsl_with_nullable_items() {
1235 use arrow_array::{FixedSizeListArray, Int32Array};
1244 use arrow_buffer::NullBuffer;
1245
1246 const N: usize = 1024;
1247 const DIM: i32 = 4;
1248
1249 let child_values = Int32Array::from_iter(
1250 (0..(N as i32 * DIM)).map(|i| if i % 2 == 0 { Some(i) } else { None }),
1251 );
1252 let item_field = Arc::new(ArrowField::new("item", DataType::Int32, true));
1253 let fsl_nulls = NullBuffer::from((0..N).map(|i| i % 3 != 0).collect::<Vec<_>>());
1254 let fsl_array = FixedSizeListArray::new(
1255 item_field.clone(),
1256 DIM,
1257 Arc::new(child_values),
1258 Some(fsl_nulls),
1259 );
1260 let expected = fsl_array.get_buffer_memory_size() as u64;
1261
1262 let fsl_type = DataType::FixedSizeList(item_field, DIM);
1263 let field = Arc::new(ArrowField::new("v", fsl_type, true));
1264 let decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
1265
1266 let bytes = decoder.plan_decoded_bytes(N as u64).unwrap();
1267 assert_eq!(
1269 bytes[5], expected,
1270 "plan_decoded_bytes({N}) = {} but get_buffer_memory_size = {expected}",
1271 bytes[5],
1272 );
1273 }
1274
1275 #[test]
1276 fn test_plan_decoded_bytes_fixed_size_list_of_f32() {
1277 use crate::decoder::CANDIDATE_BATCH_SIZES;
1278
1279 const DIMENSION: i32 = 8;
1280 let item_field = Arc::new(ArrowField::new("item", DataType::Float32, false));
1282 let fsl_type = DataType::FixedSizeList(item_field, DIMENSION);
1283 let field = Arc::new(ArrowField::new("vector", fsl_type, true));
1284 let decoder = StructuralPrimitiveFieldDecoder::new(&field, false);
1285
1286 let rows_remaining = 100u64;
1287 let bytes = decoder.plan_decoded_bytes(rows_remaining).unwrap();
1288
1289 let bytes_per_row = DIMENSION as u64 * 4;
1292 for (i, &candidate) in CANDIDATE_BATCH_SIZES.iter().enumerate() {
1293 let rows = (candidate as u64).min(rows_remaining);
1294 assert_eq!(
1295 bytes[i],
1296 rows * bytes_per_row + rows.div_ceil(8),
1297 "candidate batch size {candidate}"
1298 );
1299 }
1300 }
1301
1302 #[tokio::test]
1306 async fn test_initialize_coalesces_missed_page_metadata() {
1307 use std::ops::Range;
1308
1309 use futures::FutureExt;
1310 use futures::future::BoxFuture;
1311
1312 use super::{
1313 CachedPage, CachedPageData, Error, PageDataCacheKey, PageInfoAndScheduler,
1314 PageInitialization, StructuralPrimitiveFieldScheduler,
1315 };
1316 use crate::decoder::{FilterExpression, SchedulerContext, StructuralFieldScheduler};
1317 use crate::encodings::logical::primitive::{
1318 PageCacheView, PageInitializationBuffers, PageLoadTask,
1319 };
1320
1321 #[derive(Debug)]
1325 struct FakePageScheduler {
1326 meta_range: Range<u64>,
1327 }
1328 impl StructuralPageScheduler for FakePageScheduler {
1329 fn init_layout(&self) -> crate::Result<PageInitialization> {
1330 Ok(PageInitialization::new([Some(self.meta_range.clone())]))
1331 }
1332 fn init_from_buffers<'a>(
1333 &'a mut self,
1334 buffers: PageInitializationBuffers,
1335 _io: &Arc<dyn EncodingsIo>,
1336 ) -> BoxFuture<'a, crate::Result<Arc<dyn CachedPageData>>> {
1337 let [buffer] = buffers.try_into_array("fake").unwrap();
1338 let buffer = buffer.unwrap();
1339 assert_eq!(
1340 &buffer[..8],
1341 &self.meta_range.start.to_le_bytes(),
1342 "each page gets the buffer for its own range"
1343 );
1344 std::future::ready(Ok(Arc::new(CacheMarker(42)) as Arc<dyn CachedPageData>)).boxed()
1345 }
1346 fn try_load(&mut self, data: &Arc<dyn CachedPageData>) -> crate::Result<()> {
1347 data.clone()
1348 .as_arc_any()
1349 .downcast::<CacheMarker>()
1350 .map(|_| ())
1351 .map_err(|_| {
1352 Error::invalid_input_source(
1353 "Fake page cache data has an unexpected type".into(),
1354 )
1355 })
1356 }
1357 fn schedule_ranges(
1358 &self,
1359 _ranges: &[Range<u64>],
1360 _io: &Arc<dyn EncodingsIo>,
1361 ) -> crate::Result<Vec<PageLoadTask>> {
1362 unreachable!("initialize never schedules data reads");
1363 }
1364 }
1365
1366 const NUM_PAGES: u64 = 64;
1367 const META_LEN: u64 = 64;
1368 const STRIDE: u64 = 1024 * 1024;
1369 let page_schedulers = (0..NUM_PAGES)
1370 .map(|i| PageInfoAndScheduler {
1371 page_index: i as usize,
1372 row_range: (i * 100)..((i + 1) * 100),
1373 scheduler: Box::new(FakePageScheduler {
1374 meta_range: (i * STRIDE)..(i * STRIDE + META_LEN),
1375 }),
1376 })
1377 .collect();
1378 let mut scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
1379 page_schedulers,
1380 0,
1381 Arc::from("test"),
1382 );
1383
1384 let io = Arc::new(RecordingScheduler::default());
1385 let cache = Arc::new(lance_core::cache::LanceCache::no_cache());
1387 let context = SchedulerContext::new(io.clone(), cache.clone());
1388
1389 let filter = FilterExpression::no_filter();
1392 scheduler.initialize(None, &filter, &context).await.unwrap();
1393
1394 {
1395 let requests = io.requests.lock().unwrap();
1396 assert_eq!(
1397 requests.len(),
1398 1,
1399 "an N-page miss must issue exactly one request, not one per page"
1400 );
1401 assert_eq!(
1402 requests[0].len() as u64,
1403 NUM_PAGES,
1404 "the single request must carry every missed page's metadata range"
1405 );
1406 }
1407
1408 io.requests.lock().unwrap().clear();
1409 let cache_misses_before = cache.stats().await.misses;
1410 let requested_ranges = Arc::<[Range<u64>]>::from([150..151, 6_350..6_351]);
1411 scheduler
1412 .initialize(Some(&requested_ranges), &filter, &context)
1413 .await
1414 .unwrap();
1415 assert_eq!(
1416 cache.stats().await.misses - cache_misses_before,
1417 2,
1418 "range-scoped initialization must only look up touched pages"
1419 );
1420
1421 {
1422 let requests = io.requests.lock().unwrap();
1423 assert_eq!(requests.len(), 1);
1424 assert_eq!(
1425 requests[0],
1426 vec![
1427 STRIDE..(STRIDE + META_LEN),
1428 63 * STRIDE..(63 * STRIDE + META_LEN),
1429 ],
1430 "range-scoped initialization must omit untouched pages"
1431 );
1432 }
1433
1434 io.requests.lock().unwrap().clear();
1435 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(4 * 1024));
1436 let cache_key = PageDataCacheKey {
1437 column_index: 9,
1438 page_index: 0,
1439 view: PageCacheView::Native,
1440 target_data_type_tag: Arc::from("test"),
1441 };
1442 cache
1443 .insert_with_key(
1444 &cache_key,
1445 Arc::new(CachedPage {
1446 data: Arc::new(super::NoCachedPageData),
1447 }),
1448 )
1449 .await;
1450 let mut recovering_scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
1451 vec![PageInfoAndScheduler {
1452 page_index: 0,
1453 row_range: 0..100,
1454 scheduler: Box::new(FakePageScheduler {
1455 meta_range: 0..META_LEN,
1456 }),
1457 }],
1458 9,
1459 Arc::from("test"),
1460 );
1461 let recovering_context = SchedulerContext::new(io.clone(), cache.clone());
1462 recovering_scheduler
1463 .initialize(None, &filter, &recovering_context)
1464 .await
1465 .unwrap();
1466 assert_eq!(io.requests.lock().unwrap().as_slice(), &[vec![0..META_LEN]]);
1467
1468 io.requests.lock().unwrap().clear();
1469 let mut warm_scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
1470 vec![PageInfoAndScheduler {
1471 page_index: 0,
1472 row_range: 0..100,
1473 scheduler: Box::new(FakePageScheduler {
1474 meta_range: 0..META_LEN,
1475 }),
1476 }],
1477 9,
1478 Arc::from("test"),
1479 );
1480 warm_scheduler
1481 .initialize(None, &filter, &recovering_context)
1482 .await
1483 .unwrap();
1484 assert!(
1485 io.requests.lock().unwrap().is_empty(),
1486 "the rebuilt cache entry must be reusable"
1487 );
1488
1489 let mut out_of_order_scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
1490 vec![
1491 PageInfoAndScheduler {
1492 page_index: 0,
1493 row_range: 0..100,
1494 scheduler: Box::new(FakePageScheduler {
1495 meta_range: STRIDE..(STRIDE + META_LEN),
1496 }),
1497 },
1498 PageInfoAndScheduler {
1499 page_index: 1,
1500 row_range: 100..200,
1501 scheduler: Box::new(FakePageScheduler {
1502 meta_range: 0..META_LEN,
1503 }),
1504 },
1505 ],
1506 0,
1507 Arc::from("test"),
1508 );
1509 out_of_order_scheduler
1510 .initialize(None, &filter, &context)
1511 .await
1512 .unwrap();
1513 assert_eq!(
1514 io.requests.lock().unwrap().as_slice(),
1515 &[vec![0..META_LEN, STRIDE..(STRIDE + META_LEN)]],
1516 "metadata reads must be sorted without changing page buffer assignment"
1517 );
1518 }
1519
1520 #[tokio::test]
1521 async fn page_cache_key_distinguishes_page_view_and_target_type() {
1522 let cache = lance_core::cache::LanceCache::with_capacity(4 * 1024);
1523 let cases = [
1524 (
1525 PageDataCacheKey {
1526 column_index: 7,
1527 page_index: 0,
1528 view: PageCacheView::Native,
1529 target_data_type_tag: Arc::from("Boolean"),
1530 },
1531 10,
1532 ),
1533 (
1534 PageDataCacheKey {
1535 column_index: 7,
1536 page_index: 1,
1537 view: PageCacheView::Native,
1538 target_data_type_tag: Arc::from("Boolean"),
1539 },
1540 11,
1541 ),
1542 (
1543 PageDataCacheKey {
1544 column_index: 7,
1545 page_index: 0,
1546 view: PageCacheView::Native,
1547 target_data_type_tag: Arc::from("UInt8"),
1548 },
1549 12,
1550 ),
1551 (
1552 PageDataCacheKey {
1553 column_index: 7,
1554 page_index: 0,
1555 view: PageCacheView::BlobDescriptor,
1556 target_data_type_tag: Arc::from("Struct"),
1557 },
1558 20,
1559 ),
1560 (
1561 PageDataCacheKey {
1562 column_index: 7,
1563 page_index: 0,
1564 view: PageCacheView::BlobPayload,
1565 target_data_type_tag: Arc::from("LargeBinary"),
1566 },
1567 21,
1568 ),
1569 ];
1570
1571 for (key, marker) in &cases {
1572 cache
1573 .insert_with_key(
1574 key,
1575 Arc::new(CachedPage {
1576 data: Arc::new(CacheMarker(*marker)),
1577 }),
1578 )
1579 .await;
1580 }
1581
1582 assert_eq!(cache.size().await, cases.len());
1583 for (key, expected) in &cases {
1584 let cached = cache.get_with_key(key).await.unwrap();
1585 let marker = cached
1586 .data
1587 .clone()
1588 .as_arc_any()
1589 .downcast::<CacheMarker>()
1590 .unwrap();
1591 assert_eq!(marker.0, *expected);
1592 }
1593 }
1594
1595 #[tokio::test]
1596 async fn no_op_pages_do_not_create_cache_entries() {
1597 let mut scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
1598 vec![PageInfoAndScheduler {
1599 page_index: 0,
1600 row_range: 0..10,
1601 scheduler: Box::new(SimpleAllNullScheduler::default()),
1602 }],
1603 3,
1604 Arc::from("test"),
1605 );
1606 let io: Arc<dyn crate::EncodingsIo> =
1607 Arc::new(SimulatedScheduler::new(bytes::Bytes::new()));
1608 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(4 * 1024));
1609 let context = SchedulerContext::new(io, cache.clone());
1610
1611 scheduler
1612 .initialize(None, &FilterExpression::no_filter(), &context)
1613 .await
1614 .unwrap();
1615
1616 assert_eq!(cache.size().await, 0);
1617 }
1618
1619 #[tokio::test]
1620 async fn initialize_builds_only_requested_page_schedulers() {
1621 use crate::compression::DefaultDecompressionStrategy;
1622 use crate::decoder::{
1623 ColumnInfo, FilterExpression, PageEncoding, PageInfo, SchedulerContext,
1624 StructuralFieldScheduler,
1625 };
1626 use crate::format::pb;
1627 use lance_core::datatypes::Field;
1628
1629 const NUM_PAGES: u64 = 64;
1630 const ROWS_PER_PAGE: u64 = 100;
1631 let page_infos = (0..NUM_PAGES)
1634 .map(|page_index| PageInfo {
1635 num_rows: ROWS_PER_PAGE,
1636 priority: page_index * ROWS_PER_PAGE,
1637 encoding: PageEncoding::Structural(metadata::PageLayout {
1638 layout: Some(metadata::page_layout::Layout::ConstantLayout(
1639 metadata::ConstantLayout {
1640 layers: vec![metadata::RepDefLayer::RepdefNullableItem as i32],
1641 ..Default::default()
1642 },
1643 )),
1644 }),
1645 buffer_offsets_and_sizes: Arc::new([]),
1646 })
1647 .collect::<Vec<_>>();
1648 let column_info = Arc::new(ColumnInfo::new(
1649 3,
1650 Arc::from(page_infos),
1651 Vec::new(),
1652 pb::ColumnEncoding::default(),
1653 ));
1654 let field = Field::try_from(&ArrowField::new("x", DataType::Int32, true)).unwrap();
1655 #[allow(deprecated)]
1656 let eager = StructuralPrimitiveFieldScheduler::try_new(
1657 &column_info,
1658 &DefaultDecompressionStrategy {},
1659 false,
1660 &field,
1661 )
1662 .unwrap();
1663 assert_eq!(eager.page_schedulers.len(), NUM_PAGES as usize);
1664 assert!(eager.page_source.is_none());
1665
1666 let mut scheduler = StructuralPrimitiveFieldScheduler::try_new_lazy(
1667 column_info,
1668 Arc::new(DefaultDecompressionStrategy {}),
1669 false,
1670 &field,
1671 )
1672 .unwrap();
1673 assert!(
1674 scheduler.page_schedulers.is_empty(),
1675 "construction must not build page schedulers"
1676 );
1677 assert_eq!(scheduler.page_row_ends.len(), NUM_PAGES as usize);
1678
1679 let io: Arc<dyn EncodingsIo> = Arc::new(SimulatedScheduler::new(bytes::Bytes::new()));
1680 let cache = Arc::new(lance_core::cache::LanceCache::no_cache());
1681 let mut context = SchedulerContext::new(io, cache);
1682 let filter = FilterExpression::no_filter();
1683
1684 let requested_ranges = [150..151, 6_350..6_351];
1685 scheduler
1686 .initialize(Some(&requested_ranges), &filter, &context)
1687 .await
1688 .unwrap();
1689 let built_pages = |scheduler: &StructuralPrimitiveFieldScheduler| {
1690 scheduler
1691 .page_schedulers
1692 .iter()
1693 .map(|page| page.page_index)
1694 .collect::<Vec<_>>()
1695 };
1696 assert_eq!(built_pages(&scheduler), [1, 63]);
1697 assert_eq!(scheduler.page_schedulers[1].row_range, 6_300..6_400);
1698 assert!(scheduler.page_source.is_some());
1699
1700 {
1701 let mut job = scheduler
1702 .schedule_ranges(&requested_ranges, &filter)
1703 .unwrap();
1704 assert_eq!(job.schedule_next(&mut context).unwrap().len(), 2);
1706 assert!(job.schedule_next(&mut context).unwrap().is_empty());
1707
1708 let mut job = scheduler.schedule_ranges(&[0..1], &filter).unwrap();
1711 let error = job.schedule_next(&mut context).unwrap_err();
1712 assert!(matches!(error, lance_core::Error::Internal { .. }));
1713 assert!(error.to_string().contains("not initialized"));
1714 }
1715
1716 scheduler
1718 .initialize(Some(&[0..250]), &filter, &context)
1719 .await
1720 .unwrap();
1721 assert_eq!(built_pages(&scheduler), [0, 1, 2, 63]);
1722
1723 scheduler.initialize(None, &filter, &context).await.unwrap();
1725 assert_eq!(scheduler.page_schedulers.len(), NUM_PAGES as usize);
1726 assert!(scheduler.page_source.is_none());
1727 let mut job = scheduler.schedule_ranges(&[0..1], &filter).unwrap();
1728 assert_eq!(job.schedule_next(&mut context).unwrap().len(), 1);
1729 }
1730
1731 #[derive(Debug, Default)]
1735 struct RecordingScheduler {
1736 requests: std::sync::Mutex<Vec<Vec<Range<u64>>>>,
1737 priorities: std::sync::Mutex<Vec<u64>>,
1738 }
1739
1740 impl crate::EncodingsIo for RecordingScheduler {
1741 fn submit_request(
1742 &self,
1743 ranges: Vec<Range<u64>>,
1744 priority: u64,
1745 ) -> futures::future::BoxFuture<'static, crate::Result<Vec<Bytes>>> {
1746 use futures::FutureExt;
1747
1748 let buffers = ranges
1749 .iter()
1750 .map(|range| {
1751 let mut buffer = vec![0u8; (range.end - range.start) as usize];
1752 let marker = range.start.to_le_bytes();
1753 let marker_len = marker.len().min(buffer.len());
1754 buffer[..marker_len].copy_from_slice(&marker[..marker_len]);
1755 Bytes::from(buffer)
1756 })
1757 .collect();
1758 self.requests.lock().unwrap().push(ranges);
1759 self.priorities.lock().unwrap().push(priority);
1760 std::future::ready(Ok(buffers)).boxed()
1761 }
1762 }
1763
1764 #[tokio::test]
1765 async fn page_read_batch_submits_pending_reads_as_one_request() {
1766 let io = Arc::new(RecordingScheduler::default());
1767 let batch = RequestBatch::new(io.clone());
1768 let first = batch.submit_request(vec![100..108, 300..308], 7);
1769 let second = batch.submit_request(vec![200..208], 3);
1770 assert_eq!(batch.pending_bytes(), 24);
1771 assert!(
1772 io.requests.lock().unwrap().is_empty(),
1773 "nothing is read before the flush"
1774 );
1775
1776 batch.flush();
1777 {
1778 let requests = io.requests.lock().unwrap();
1779 assert_eq!(requests.len(), 1, "the batch is one request");
1780 assert_eq!(
1781 requests[0],
1782 vec![100..108, 200..208, 300..308],
1783 "the batch is sorted by file offset"
1784 );
1785 }
1786 assert_eq!(
1787 io.priorities.lock().unwrap()[0],
1788 3,
1789 "the batch takes the lowest priority"
1790 );
1791 let first = first.await.unwrap();
1792 assert_eq!(first.len(), 2);
1793 assert_eq!(first[0].as_ref(), &100_u64.to_le_bytes());
1794 assert_eq!(first[1].as_ref(), &300_u64.to_le_bytes());
1795 let second = second.await.unwrap();
1796 assert_eq!(second.len(), 1);
1797 assert_eq!(second[0].as_ref(), &200_u64.to_le_bytes());
1798
1799 let late = batch.submit_request(vec![400..408], 9).await.unwrap();
1801 assert_eq!(late[0].as_ref(), &400_u64.to_le_bytes());
1802 assert_eq!(io.requests.lock().unwrap().len(), 2);
1803
1804 let io = Arc::new(RecordingScheduler::default());
1807 let batch = RequestBatch::new(io.clone());
1808 let budget = crate::decoder::IO_REQUEST_BATCH_BYTES;
1809 let first = batch.submit_request(vec![0..budget], 1);
1810 assert!(io.requests.lock().unwrap().is_empty());
1811 let second = batch.submit_request(vec![budget..(budget + 8)], 2);
1812 assert_eq!(
1813 io.requests.lock().unwrap().as_slice(),
1814 &[vec![0..budget]],
1815 "the full batch is submitted before the read that would overflow it"
1816 );
1817 batch.flush();
1818 assert_eq!(io.requests.lock().unwrap().len(), 2);
1819 assert_eq!(first.await.unwrap()[0].len(), budget as usize);
1820 assert_eq!(second.await.unwrap()[0].as_ref(), &budget.to_le_bytes());
1821 }
1822
1823 #[tokio::test]
1824 async fn request_batch_flush_keeps_collecting_and_awaiting_flushes() {
1825 let io = Arc::new(RecordingScheduler::default());
1826 let batch = RequestBatch::new(io.clone());
1827 let first = batch.submit_request(vec![100..108], 1);
1828 let second = batch.submit_request(vec![300..308], 2);
1829 batch.flush();
1830 assert_eq!(
1831 io.requests.lock().unwrap().as_slice(),
1832 &[vec![100..108, 300..308]],
1833 "a flush submits the collected reads as one request"
1834 );
1835 let third = batch.submit_request(vec![500..508], 3);
1837 let fourth = batch.submit_request(vec![700..708], 4);
1838 assert_eq!(batch.pending_bytes(), 16);
1839 assert_eq!(io.requests.lock().unwrap().len(), 1);
1840 assert_eq!(third.await.unwrap()[0].as_ref(), &500_u64.to_le_bytes());
1843 assert_eq!(
1844 io.requests.lock().unwrap().as_slice(),
1845 &[vec![100..108, 300..308], vec![500..508, 700..708]]
1846 );
1847 assert_eq!(fourth.await.unwrap()[0].as_ref(), &700_u64.to_le_bytes());
1848 assert_eq!(first.await.unwrap()[0].as_ref(), &100_u64.to_le_bytes());
1849 assert_eq!(second.await.unwrap()[0].as_ref(), &300_u64.to_le_bytes());
1850 assert_eq!(io.requests.lock().unwrap().len(), 2);
1851
1852 let (fifth, sixth) = futures::join!(
1856 async { batch.submit_request(vec![900..908], 5).await.unwrap() },
1857 async { batch.submit_request(vec![1100..1108], 6).await.unwrap() },
1858 );
1859 assert_eq!(fifth[0].as_ref(), &900_u64.to_le_bytes());
1860 assert_eq!(sixth[0].as_ref(), &1100_u64.to_le_bytes());
1861 assert_eq!(
1862 io.requests.lock().unwrap().last().unwrap(),
1863 &vec![900..908, 1100..1108],
1864 "reads submitted by sibling futures share one request"
1865 );
1866 assert_eq!(io.requests.lock().unwrap().len(), 3);
1867 }
1868
1869 #[tokio::test]
1870 async fn schedule_next_batches_page_reads_up_to_the_budget() {
1871 use super::{
1872 CachedPageData, IO_REQUEST_BATCH_BYTES, PageInfoAndScheduler, PageInitialization,
1873 StructuralPageScheduler, StructuralPrimitiveFieldScheduler,
1874 };
1875 use crate::decoder::{FilterExpression, SchedulerContext, StructuralFieldScheduler};
1876 use crate::encodings::logical::primitive::{PageInitializationBuffers, PageLoadTask};
1877
1878 #[derive(Debug)]
1882 struct FakeDataPage {
1883 data_ranges: Vec<Range<u64>>,
1884 }
1885
1886 impl StructuralPageScheduler for FakeDataPage {
1887 fn needs_initialization(&self) -> bool {
1888 false
1889 }
1890
1891 fn init_layout(&self) -> crate::Result<PageInitialization> {
1892 unreachable!("fake data pages need no initialization")
1893 }
1894
1895 fn init_from_buffers<'a>(
1896 &'a mut self,
1897 _buffers: PageInitializationBuffers,
1898 _io: &Arc<dyn EncodingsIo>,
1899 ) -> futures::future::BoxFuture<'a, crate::Result<Arc<dyn CachedPageData>>>
1900 {
1901 unreachable!("fake data pages need no initialization")
1902 }
1903
1904 fn try_load(&mut self, _data: &Arc<dyn CachedPageData>) -> crate::Result<()> {
1905 Ok(())
1906 }
1907
1908 fn schedule_ranges(
1909 &self,
1910 ranges: &[Range<u64>],
1911 io: &Arc<dyn EncodingsIo>,
1912 ) -> crate::Result<Vec<PageLoadTask>> {
1913 use futures::FutureExt;
1914
1915 let num_rows = ranges.iter().map(|r| r.end - r.start).sum();
1916 Ok(self
1917 .data_ranges
1918 .iter()
1919 .enumerate()
1920 .map(|(shard, range)| {
1921 drop(io.submit_request(vec![range.clone()], range.start));
1922 PageLoadTask {
1923 decoder_fut: std::future::ready(Err(lance_core::Error::internal(
1924 "fake page",
1925 )))
1926 .boxed(),
1927 num_rows: if shard == 0 { num_rows } else { 0 },
1929 }
1930 })
1931 .collect())
1932 }
1933 }
1934
1935 fn scheduler_with_pages(
1936 page_bytes: u64,
1937 num_pages: u64,
1938 ) -> StructuralPrimitiveFieldScheduler {
1939 let stride = page_bytes * 2;
1942 let pages = (0..num_pages)
1943 .map(|page| PageInfoAndScheduler {
1944 page_index: page as usize,
1945 row_range: (page * 10)..((page + 1) * 10),
1946 scheduler: Box::new(FakeDataPage {
1947 data_ranges: vec![(page * stride)..(page * stride + page_bytes)],
1948 }),
1949 })
1950 .collect();
1951 StructuralPrimitiveFieldScheduler::from_page_schedulers(pages, 0, Arc::from("test"))
1952 }
1953
1954 let filter = FilterExpression::no_filter();
1955 let cache = Arc::new(lance_core::cache::LanceCache::no_cache());
1956
1957 let io = Arc::new(RecordingScheduler::default());
1959 let scheduler = scheduler_with_pages(64, 6);
1960 let mut context = SchedulerContext::new(io.clone(), cache.clone());
1961 let mut job = scheduler.schedule_ranges(&[5..45], &filter).unwrap();
1962 let scan_lines = job.schedule_next(&mut context).unwrap();
1963 context.flush_io();
1964 assert_eq!(scan_lines.len(), 5, "one scan line per touched page");
1965 assert_eq!(
1966 scan_lines
1967 .iter()
1968 .map(|line| line.rows_scheduled)
1969 .sum::<u64>(),
1970 40
1971 );
1972 assert!(job.schedule_next(&mut context).unwrap().is_empty());
1973 {
1974 let requests = io.requests.lock().unwrap();
1975 assert_eq!(requests.len(), 1, "five pages cost one request");
1976 assert_eq!(
1977 requests[0],
1978 vec![0..64, 128..192, 256..320, 384..448, 512..576],
1979 "the untouched last page is not read"
1980 );
1981 }
1982 assert_eq!(
1983 io.priorities.lock().unwrap()[0],
1984 0,
1985 "the batch carries the first page's priority"
1986 );
1987
1988 let io = Arc::new(RecordingScheduler::default());
1990 let scheduler = scheduler_with_pages(IO_REQUEST_BATCH_BYTES, 2);
1991 let mut context = SchedulerContext::new(io.clone(), cache);
1992 let mut job = scheduler.schedule_ranges(&[0..20], &filter).unwrap();
1993 let first = job.schedule_next(&mut context).unwrap();
1994 context.flush_io();
1995 assert_eq!(first.len(), 1);
1996 assert_eq!(io.requests.lock().unwrap().len(), 1);
1997 let second = job.schedule_next(&mut context).unwrap();
1998 context.flush_io();
1999 assert_eq!(second.len(), 1);
2000 assert_eq!(io.requests.lock().unwrap().len(), 2);
2001 assert!(job.schedule_next(&mut context).unwrap().is_empty());
2002
2003 let io = Arc::new(RecordingScheduler::default());
2006 let shard = IO_REQUEST_BATCH_BYTES;
2007 let scheduler = StructuralPrimitiveFieldScheduler::from_page_schedulers(
2008 vec![PageInfoAndScheduler {
2009 page_index: 0,
2010 row_range: 0..10,
2011 scheduler: Box::new(FakeDataPage {
2012 data_ranges: vec![0..shard, (2 * shard)..(3 * shard)],
2013 }),
2014 }],
2015 0,
2016 Arc::from("test"),
2017 );
2018 let mut context = SchedulerContext::new(
2019 io.clone(),
2020 Arc::new(lance_core::cache::LanceCache::no_cache()),
2021 );
2022 let mut job = scheduler.schedule_ranges(&[0..10], &filter).unwrap();
2023 let scan_lines = job.schedule_next(&mut context).unwrap();
2024 context.flush_io();
2025 assert_eq!(scan_lines.len(), 2, "one scan line per shard");
2026 assert_eq!(
2027 io.requests.lock().unwrap().as_slice(),
2028 &[vec![0..shard], vec![(2 * shard)..(3 * shard)]],
2029 "each shard stays its own bounded request"
2030 );
2031 assert!(job.schedule_next(&mut context).unwrap().is_empty());
2032 }
2033
2034 #[test]
2035 fn page_range_mapping_validates_and_splits_ranges() {
2036 let pages = [10_u64, 20, 30];
2038
2039 let mappings = map_ranges_to_pages(&pages, &[8..22]).unwrap();
2040 assert_eq!(mappings.len(), 3);
2041 assert_eq!(mappings[0].page_idx, 0);
2042 assert_eq!(mappings[0].ranges_in_page, [8..10]);
2043 assert_eq!(mappings[1].page_idx, 1);
2044 assert_eq!(mappings[1].ranges_in_page, [0..10]);
2045 assert_eq!(mappings[2].page_idx, 2);
2046 assert_eq!(mappings[2].ranges_in_page, [0..2]);
2047
2048 let sparse_mappings = map_ranges_to_pages(&pages, &[1..2, 3..4, 28..29]).unwrap();
2049 assert_eq!(sparse_mappings.len(), 2);
2050 assert_eq!(sparse_mappings[0].page_idx, 0);
2051 assert_eq!(sparse_mappings[0].ranges_in_page, [1..2, 3..4]);
2052 assert_eq!(sparse_mappings[1].page_idx, 2);
2053 assert_eq!(sparse_mappings[1].ranges_in_page, [8..9]);
2054 assert_eq!(
2055 pages_overlapping_ranges(&pages, &[1..2, 3..4, 28..29]).unwrap(),
2056 [0, 2]
2057 );
2058
2059 for (ranges, expected_message) in [
2060 (vec![Range { start: 2, end: 1 }], "is reversed"),
2061 (vec![15..16, 5..6], "not strictly ordered"),
2062 (vec![5..15, 10..20], "overlap"),
2063 (vec![5..5, 5..6], "not strictly ordered"),
2064 ] {
2065 let error = map_ranges_to_pages(&pages, &ranges).unwrap_err();
2066 assert!(matches!(error, lance_core::Error::InvalidInput { .. }));
2067 assert!(error.to_string().contains(expected_message));
2068 }
2069 }
2070}