1use std::collections::VecDeque;
216use std::sync::atomic::{AtomicU64, Ordering};
217use std::sync::{LazyLock, Once, OnceLock};
218use std::{ops::Range, sync::Arc};
219
220use arrow_array::cast::AsArray;
221use arrow_array::{ArrayRef, RecordBatch, RecordBatchIterator, RecordBatchReader};
222use arrow_schema::{ArrowError, DataType, Field as ArrowField, Fields, Schema as ArrowSchema};
223use bytes::Bytes;
224use futures::future::{BoxFuture, MaybeDone, maybe_done};
225use futures::stream::{self, BoxStream};
226use futures::{FutureExt, StreamExt};
227use lance_arrow::DataTypeExt;
228use lance_core::cache::{Context, DeepSizeOf, LanceCache};
229use lance_core::datatypes::{
230 BLOB_DESC_LANCE_FIELD, Field, Schema, validate_fixed_size_list_dimensions,
231};
232use lance_core::utils::futures::{FinallyStreamExt, StreamOnDropExt};
233use lance_core::utils::parse::parse_env_as_bool;
234use log::{debug, trace, warn};
235use prost::Message;
236use tokio::sync::mpsc::error::SendError;
237use tokio::sync::mpsc::{self, unbounded_channel};
238
239use lance_core::error::LanceOptionExt;
240use lance_core::{ArrowResult, Error, Result};
241use tracing::instrument;
242
243use crate::compression::{DecompressionStrategy, DefaultDecompressionStrategy};
244use crate::data::DataBlock;
245use crate::encoder::EncodedBatch;
246use crate::encodings::logical::fixed_size_list::StructuralFixedSizeListScheduler;
247use crate::encodings::logical::list::StructuralListScheduler;
248use crate::encodings::logical::map::StructuralMapScheduler;
249use crate::encodings::logical::primitive::StructuralPrimitiveFieldScheduler;
250use crate::encodings::logical::r#struct::{StructuralStructDecoder, StructuralStructScheduler};
251use crate::format::pb::{self, column_encoding};
252use crate::format::pb21;
253use crate::previous::decoder::LogicalPageDecoder;
254use crate::previous::encodings::logical::list::OffsetPageInfo;
255use crate::previous::encodings::logical::r#struct::{SimpleStructDecoder, SimpleStructScheduler};
256use crate::previous::encodings::logical::{
257 binary::BinaryFieldScheduler, blob::BlobFieldScheduler, list::ListFieldScheduler,
258 primitive::PrimitiveFieldScheduler,
259};
260use crate::repdef::{CompositeRepDefUnraveler, RepDefUnraveler};
261use crate::version::LanceFileVersion;
262use crate::{BufferScheduler, EncodingsIo};
263
264const BATCH_SIZE_BYTES_WARNING: u64 = 10 * 1024 * 1024;
266const ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE: &str =
267 "LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE";
268const ENV_LANCE_READ_CACHE_REPETITION_INDEX: &str = "LANCE_READ_CACHE_REPETITION_INDEX";
269const ENV_LANCE_INLINE_SCHEDULING_THRESHOLD: &str = "LANCE_INLINE_SCHEDULING_THRESHOLD";
270
271const DEFAULT_INLINE_SCHEDULING_THRESHOLD: u64 = 16 * 1024;
274
275fn default_cache_repetition_index() -> bool {
276 static DEFAULT_CACHE_REPETITION_INDEX: OnceLock<bool> = OnceLock::new();
277 *DEFAULT_CACHE_REPETITION_INDEX
278 .get_or_init(|| parse_env_as_bool(ENV_LANCE_READ_CACHE_REPETITION_INDEX, true))
279}
280
281fn inline_scheduling_threshold() -> u64 {
282 static THRESHOLD: OnceLock<u64> = OnceLock::new();
283 *THRESHOLD.get_or_init(|| {
284 std::env::var(ENV_LANCE_INLINE_SCHEDULING_THRESHOLD)
285 .ok()
286 .and_then(|v| v.trim().parse::<u64>().ok())
287 .unwrap_or(DEFAULT_INLINE_SCHEDULING_THRESHOLD)
288 })
289}
290
291#[derive(Debug)]
298pub enum PageEncoding {
299 Legacy(pb::ArrayEncoding),
300 Structural(pb21::PageLayout),
301}
302
303impl DeepSizeOf for PageEncoding {
304 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
305 match self {
306 Self::Legacy(encoding) => encoding.encoded_len() * 4,
307 Self::Structural(encoding) => encoding.encoded_len() * 4,
308 }
309 }
310}
311
312impl PageEncoding {
313 pub fn as_legacy(&self) -> &pb::ArrayEncoding {
314 match self {
315 Self::Legacy(enc) => enc,
316 Self::Structural(_) => panic!("Expected a legacy encoding"),
317 }
318 }
319
320 pub fn as_structural(&self) -> &pb21::PageLayout {
321 match self {
322 Self::Structural(enc) => enc,
323 Self::Legacy(_) => panic!("Expected a structural encoding"),
324 }
325 }
326
327 pub fn is_structural(&self) -> bool {
328 matches!(self, Self::Structural(_))
329 }
330}
331
332#[derive(Debug)]
336pub struct PageInfo {
337 pub num_rows: u64,
339 pub priority: u64,
343 pub encoding: PageEncoding,
345 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
347}
348
349impl DeepSizeOf for PageInfo {
350 fn deep_size_of_children(&self, context: &mut Context) -> usize {
351 self.encoding.deep_size_of_children(context)
352 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
353 }
354}
355
356#[derive(Debug, Clone)]
360pub struct ColumnInfo {
361 pub index: u32,
363 pub page_infos: Arc<[PageInfo]>,
365 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
367 pub encoding: pb::ColumnEncoding,
368}
369
370impl DeepSizeOf for ColumnInfo {
371 fn deep_size_of_children(&self, context: &mut Context) -> usize {
372 self.page_infos.deep_size_of_children(context)
373 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
374 + self.encoding.encoded_len() * 4
375 }
376}
377
378impl ColumnInfo {
379 pub fn new(
381 index: u32,
382 page_infos: Arc<[PageInfo]>,
383 buffer_offsets_and_sizes: Vec<(u64, u64)>,
384 encoding: pb::ColumnEncoding,
385 ) -> Self {
386 Self {
387 index,
388 page_infos,
389 buffer_offsets_and_sizes: buffer_offsets_and_sizes.into_boxed_slice().into(),
390 encoding,
391 }
392 }
393
394 pub fn is_structural(&self) -> bool {
395 self.page_infos
396 .first()
398 .map(|page| page.encoding.is_structural())
399 .unwrap_or(false)
400 }
401}
402
403enum RootScheduler {
404 Structural(Box<dyn StructuralFieldScheduler>),
405 Legacy(Arc<dyn crate::previous::decoder::FieldScheduler>),
406}
407
408impl RootScheduler {
409 fn as_legacy(&self) -> &Arc<dyn crate::previous::decoder::FieldScheduler> {
410 match self {
411 Self::Structural(_) => panic!("Expected a legacy scheduler"),
412 Self::Legacy(s) => s,
413 }
414 }
415
416 fn as_structural(&self) -> &dyn StructuralFieldScheduler {
417 match self {
418 Self::Structural(s) => s.as_ref(),
419 Self::Legacy(_) => panic!("Expected a structural scheduler"),
420 }
421 }
422}
423
424pub struct DecodeBatchScheduler {
446 root_scheduler: RootScheduler,
447 pub root_fields: Fields,
448 cache: Arc<LanceCache>,
449}
450
451pub struct ColumnInfoIter<'a> {
452 column_infos: Vec<Arc<ColumnInfo>>,
453 column_indices: &'a [u32],
454 column_info_pos: usize,
455 column_indices_pos: usize,
456}
457
458impl<'a> ColumnInfoIter<'a> {
459 pub fn new(column_infos: Vec<Arc<ColumnInfo>>, column_indices: &'a [u32]) -> Self {
460 let initial_pos = column_indices.first().copied().unwrap_or(0) as usize;
461 Self {
462 column_infos,
463 column_indices,
464 column_info_pos: initial_pos,
465 column_indices_pos: 0,
466 }
467 }
468
469 pub fn peek(&self) -> &Arc<ColumnInfo> {
470 &self.column_infos[self.column_info_pos]
471 }
472
473 pub fn peek_transform(&mut self, transform: impl FnOnce(Arc<ColumnInfo>) -> Arc<ColumnInfo>) {
474 let column_info = self.column_infos[self.column_info_pos].clone();
475 let transformed = transform(column_info);
476 self.column_infos[self.column_info_pos] = transformed;
477 }
478
479 pub fn expect_next(&mut self) -> Result<&Arc<ColumnInfo>> {
480 self.next().ok_or_else(|| {
481 Error::invalid_input(
482 "there were more fields in the schema than provided column indices / infos",
483 )
484 })
485 }
486
487 fn next(&mut self) -> Option<&Arc<ColumnInfo>> {
488 if self.column_info_pos < self.column_infos.len() {
489 let info = &self.column_infos[self.column_info_pos];
490 self.column_info_pos += 1;
491 Some(info)
492 } else {
493 None
494 }
495 }
496
497 pub(crate) fn next_top_level(&mut self) {
498 self.column_indices_pos += 1;
499 if self.column_indices_pos < self.column_indices.len() {
500 self.column_info_pos = self.column_indices[self.column_indices_pos] as usize;
501 } else {
502 self.column_info_pos = self.column_infos.len();
503 }
504 }
505}
506
507#[derive(Clone, Copy, Debug)]
509pub struct FileBuffers<'a> {
510 pub positions_and_sizes: &'a [(u64, u64)],
511}
512
513#[derive(Clone, Copy, Debug)]
515pub struct ColumnBuffers<'a, 'b> {
516 pub file_buffers: FileBuffers<'a>,
517 pub positions_and_sizes: &'b [(u64, u64)],
518}
519
520#[derive(Clone, Copy, Debug)]
522pub struct PageBuffers<'a, 'b, 'c> {
523 pub column_buffers: ColumnBuffers<'a, 'b>,
524 pub positions_and_sizes: &'c [(u64, u64)],
525}
526
527#[derive(Debug)]
529pub struct CoreFieldDecoderStrategy {
530 pub validate_data: bool,
531 pub decompressor_strategy: Arc<dyn DecompressionStrategy>,
532 pub cache_repetition_index: bool,
533}
534
535impl Default for CoreFieldDecoderStrategy {
536 fn default() -> Self {
537 Self {
538 validate_data: false,
539 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
540 cache_repetition_index: false,
541 }
542 }
543}
544
545impl CoreFieldDecoderStrategy {
546 pub fn with_cache_repetition_index(mut self, cache_repetition_index: bool) -> Self {
548 self.cache_repetition_index = cache_repetition_index;
549 self
550 }
551
552 pub fn from_decoder_config(config: &DecoderConfig) -> Self {
554 Self {
555 validate_data: config.validate_on_decode,
556 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
557 cache_repetition_index: config.cache_repetition_index,
558 }
559 }
560
561 fn ensure_values_encoded(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
564 let column_encoding = column_info
565 .encoding
566 .column_encoding
567 .as_ref()
568 .ok_or_else(|| {
569 Error::invalid_input(format!(
570 "the column at index {} was missing a ColumnEncoding",
571 column_info.index
572 ))
573 })?;
574 if matches!(
575 column_encoding,
576 pb::column_encoding::ColumnEncoding::Values(_)
577 ) {
578 Ok(())
579 } else {
580 Err(Error::invalid_input(format!(
581 "the column at index {} mapping to the input field {} has column encoding {:?} and no decoder is registered to handle it",
582 column_info.index, field_name, column_encoding
583 )))
584 }
585 }
586
587 fn is_structural_primitive(data_type: &DataType) -> bool {
588 if data_type.is_primitive() {
589 true
590 } else {
591 match data_type {
592 DataType::Dictionary(_, value_type) => Self::is_structural_primitive(value_type),
594 DataType::Boolean
595 | DataType::Null
596 | DataType::FixedSizeBinary(_)
597 | DataType::Binary
598 | DataType::LargeBinary
599 | DataType::Utf8
600 | DataType::LargeUtf8 => true,
601 DataType::FixedSizeList(inner, _) => {
602 Self::is_structural_primitive(inner.data_type())
603 }
604 _ => false,
605 }
606 }
607 }
608
609 fn is_primitive_legacy(data_type: &DataType) -> bool {
610 if data_type.is_primitive() {
611 true
612 } else {
613 match data_type {
614 DataType::Boolean | DataType::Null | DataType::FixedSizeBinary(_) => true,
616 DataType::FixedSizeList(inner, _) => Self::is_primitive_legacy(inner.data_type()),
617 _ => false,
618 }
619 }
620 }
621
622 fn create_primitive_scheduler(
623 &self,
624 field: &Field,
625 column: &ColumnInfo,
626 buffers: FileBuffers,
627 ) -> Result<Box<dyn crate::previous::decoder::FieldScheduler>> {
628 Self::ensure_values_encoded(column, &field.name)?;
629 let column_buffers = ColumnBuffers {
631 file_buffers: buffers,
632 positions_and_sizes: &column.buffer_offsets_and_sizes,
633 };
634 Ok(Box::new(PrimitiveFieldScheduler::new(
635 column.index,
636 field.data_type(),
637 column.page_infos.clone(),
638 column_buffers,
639 self.validate_data,
640 )))
641 }
642
643 fn check_simple_struct(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
645 Self::ensure_values_encoded(column_info, field_name)?;
646 if column_info.page_infos.len() != 1 {
647 return Err(Error::invalid_input_source(format!("Due to schema we expected a struct column but we received a column with {} pages and right now we only support struct columns with 1 page", column_info.page_infos.len()).into()));
648 }
649 let encoding = &column_info.page_infos[0].encoding;
650 match encoding.as_legacy().array_encoding.as_ref().unwrap() {
651 pb::array_encoding::ArrayEncoding::Struct(_) => Ok(()),
652 _ => Err(Error::invalid_input_source(format!("Expected a struct encoding because we have a struct field in the schema but got the encoding {:?}", encoding).into())),
653 }
654 }
655
656 fn check_packed_struct(column_info: &ColumnInfo) -> bool {
657 let encoding = &column_info.page_infos[0].encoding;
658 matches!(
659 encoding.as_legacy().array_encoding.as_ref().unwrap(),
660 pb::array_encoding::ArrayEncoding::PackedStruct(_)
661 )
662 }
663
664 fn create_list_scheduler(
665 &self,
666 list_field: &Field,
667 column_infos: &mut ColumnInfoIter,
668 buffers: FileBuffers,
669 offsets_column: &ColumnInfo,
670 ) -> Result<Box<dyn crate::previous::decoder::FieldScheduler>> {
671 Self::ensure_values_encoded(offsets_column, &list_field.name)?;
672 let offsets_column_buffers = ColumnBuffers {
673 file_buffers: buffers,
674 positions_and_sizes: &offsets_column.buffer_offsets_and_sizes,
675 };
676 let items_scheduler =
677 self.create_legacy_field_scheduler(&list_field.children[0], column_infos, buffers)?;
678
679 let (inner_infos, null_offset_adjustments): (Vec<_>, Vec<_>) = offsets_column
680 .page_infos
681 .iter()
682 .filter(|offsets_page| offsets_page.num_rows > 0)
683 .map(|offsets_page| {
684 if let Some(pb::array_encoding::ArrayEncoding::List(list_encoding)) =
685 &offsets_page.encoding.as_legacy().array_encoding
686 {
687 let inner = PageInfo {
688 buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
689 encoding: PageEncoding::Legacy(
690 list_encoding.offsets.as_ref().unwrap().as_ref().clone(),
691 ),
692 num_rows: offsets_page.num_rows,
693 priority: 0,
694 };
695 (
696 inner,
697 OffsetPageInfo {
698 offsets_in_page: offsets_page.num_rows,
699 null_offset_adjustment: list_encoding.null_offset_adjustment,
700 num_items_referenced_by_page: list_encoding.num_items,
701 },
702 )
703 } else {
704 panic!("Expected a list column");
706 }
707 })
708 .unzip();
709 let inner = Arc::new(PrimitiveFieldScheduler::new(
710 offsets_column.index,
711 DataType::UInt64,
712 Arc::from(inner_infos.into_boxed_slice()),
713 offsets_column_buffers,
714 self.validate_data,
715 )) as Arc<dyn crate::previous::decoder::FieldScheduler>;
716 let items_field = match list_field.data_type() {
717 DataType::List(inner) => inner,
718 DataType::LargeList(inner) => inner,
719 _ => unreachable!(),
720 };
721 let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
722 DataType::Int32
723 } else {
724 DataType::Int64
725 };
726 Ok(Box::new(ListFieldScheduler::new(
727 inner,
728 items_scheduler.into(),
729 items_field,
730 offset_type,
731 null_offset_adjustments,
732 )))
733 }
734
735 fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
736 if let column_encoding::ColumnEncoding::Blob(blob) =
737 column_info.encoding.column_encoding.as_ref().unwrap()
738 {
739 let mut column_info = column_info.clone();
740 column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
741 Some(column_info)
742 } else {
743 None
744 }
745 }
746
747 fn create_structural_field_scheduler(
748 &self,
749 field: &Field,
750 column_infos: &mut ColumnInfoIter,
751 ) -> Result<Box<dyn StructuralFieldScheduler>> {
752 let data_type = field.data_type();
753 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
754 if Self::is_structural_primitive(&data_type) {
755 let column_info = column_infos.expect_next()?;
756 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
757 column_info.as_ref(),
758 self.decompressor_strategy.as_ref(),
759 self.cache_repetition_index,
760 field,
761 )?);
762
763 column_infos.next_top_level();
765
766 return Ok(scheduler);
767 }
768 match &data_type {
769 DataType::Struct(fields) => {
770 if field.is_packed_struct() {
771 let column_info = column_infos.expect_next()?;
773 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
774 column_info.as_ref(),
775 self.decompressor_strategy.as_ref(),
776 self.cache_repetition_index,
777 field,
778 )?);
779
780 column_infos.next_top_level();
782
783 return Ok(scheduler);
784 }
785 if field.is_blob() {
787 let column_info = column_infos.peek();
788 if column_info.page_infos.iter().any(|page| {
789 matches!(
790 page.encoding,
791 PageEncoding::Structural(pb21::PageLayout {
792 layout: Some(pb21::page_layout::Layout::BlobLayout(_))
793 })
794 )
795 }) {
796 let column_info = column_infos.expect_next()?;
797 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
798 column_info.as_ref(),
799 self.decompressor_strategy.as_ref(),
800 self.cache_repetition_index,
801 field,
802 )?);
803 column_infos.next_top_level();
804 return Ok(scheduler);
805 }
806 }
807
808 let mut child_schedulers = Vec::with_capacity(field.children.len());
809 for field in field.children.iter() {
810 let field_scheduler =
811 self.create_structural_field_scheduler(field, column_infos)?;
812 child_schedulers.push(field_scheduler);
813 }
814
815 let fields = fields.clone();
816 Ok(
817 Box::new(StructuralStructScheduler::new(child_schedulers, fields))
818 as Box<dyn StructuralFieldScheduler>,
819 )
820 }
821 DataType::List(_) | DataType::LargeList(_) => {
822 let child = field.children.first().expect_ok()?;
823 let child_scheduler =
824 self.create_structural_field_scheduler(child, column_infos)?;
825 Ok(Box::new(StructuralListScheduler::new(child_scheduler))
826 as Box<dyn StructuralFieldScheduler>)
827 }
828 DataType::FixedSizeList(inner, dimension)
829 if matches!(inner.data_type(), DataType::Struct(_)) =>
830 {
831 let child = field.children.first().expect_ok()?;
832 let child_scheduler =
833 self.create_structural_field_scheduler(child, column_infos)?;
834 Ok(Box::new(StructuralFixedSizeListScheduler::new(
835 child_scheduler,
836 *dimension,
837 )) as Box<dyn StructuralFieldScheduler>)
838 }
839 DataType::Map(_, keys_sorted) => {
840 if *keys_sorted {
844 return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
845 }
846 let entries_child = field.children.first().expect_ok()?;
847 let child_scheduler =
848 self.create_structural_field_scheduler(entries_child, column_infos)?;
849 Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
850 as Box<dyn StructuralFieldScheduler>)
851 }
852 _ => todo!("create_structural_field_scheduler for {}", data_type),
853 }
854 }
855
856 fn create_legacy_field_scheduler(
857 &self,
858 field: &Field,
859 column_infos: &mut ColumnInfoIter,
860 buffers: FileBuffers,
861 ) -> Result<Box<dyn crate::previous::decoder::FieldScheduler>> {
862 let data_type = field.data_type();
863 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
864 if Self::is_primitive_legacy(&data_type) {
865 let column_info = column_infos.expect_next()?;
866 let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
867 return Ok(scheduler);
868 } else if data_type.is_binary_like() {
869 let column_info = column_infos.expect_next()?.clone();
870 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
872 let desc_scheduler =
873 self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
874 let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
875 return Ok(blob_scheduler);
876 }
877 if let Some(page_info) = column_info.page_infos.first() {
878 if matches!(
879 page_info.encoding.as_legacy(),
880 pb::ArrayEncoding {
881 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
882 }
883 ) {
884 let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
885 DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
886 } else {
887 DataType::LargeList(Arc::new(ArrowField::new(
888 "item",
889 DataType::UInt8,
890 false,
891 )))
892 };
893 let list_field = Field::try_from(ArrowField::new(
894 field.name.clone(),
895 list_type,
896 field.nullable,
897 ))
898 .unwrap();
899 let list_scheduler = self.create_list_scheduler(
900 &list_field,
901 column_infos,
902 buffers,
903 &column_info,
904 )?;
905 let binary_scheduler = Box::new(BinaryFieldScheduler::new(
906 list_scheduler.into(),
907 field.data_type(),
908 ));
909 return Ok(binary_scheduler);
910 } else {
911 let scheduler =
912 self.create_primitive_scheduler(field, &column_info, buffers)?;
913 return Ok(scheduler);
914 }
915 } else {
916 return self.create_primitive_scheduler(field, &column_info, buffers);
917 }
918 }
919 match &data_type {
920 DataType::FixedSizeList(inner, _dimension) => {
921 if Self::is_primitive_legacy(inner.data_type()) {
924 let primitive_col = column_infos.expect_next()?;
925 let scheduler =
926 self.create_primitive_scheduler(field, primitive_col, buffers)?;
927 Ok(scheduler)
928 } else {
929 todo!()
930 }
931 }
932 DataType::Dictionary(_key_type, value_type) => {
933 if Self::is_primitive_legacy(value_type) || value_type.is_binary_like() {
934 let primitive_col = column_infos.expect_next()?;
935 let scheduler =
936 self.create_primitive_scheduler(field, primitive_col, buffers)?;
937 Ok(scheduler)
938 } else {
939 Err(Error::not_supported_source(
940 format!(
941 "No way to decode into a dictionary field of type {}",
942 value_type
943 )
944 .into(),
945 ))
946 }
947 }
948 DataType::List(_) | DataType::LargeList(_) => {
949 let offsets_column = column_infos.expect_next()?.clone();
950 column_infos.next_top_level();
951 self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
952 }
953 DataType::Struct(fields) => {
954 let column_info = column_infos.expect_next()?;
955
956 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
958 return self.create_primitive_scheduler(field, &blob_col, buffers);
960 }
961
962 if Self::check_packed_struct(column_info) {
963 self.create_primitive_scheduler(field, column_info, buffers)
965 } else {
966 Self::check_simple_struct(column_info, &field.name).unwrap();
968 let num_rows = column_info
969 .page_infos
970 .iter()
971 .map(|page| page.num_rows)
972 .sum();
973 let mut child_schedulers = Vec::with_capacity(field.children.len());
974 for field in &field.children {
975 column_infos.next_top_level();
976 let field_scheduler =
977 self.create_legacy_field_scheduler(field, column_infos, buffers)?;
978 child_schedulers.push(Arc::from(field_scheduler));
979 }
980
981 let fields = fields.clone();
982 Ok(Box::new(SimpleStructScheduler::new(
983 child_schedulers,
984 fields,
985 num_rows,
986 )))
987 }
988 }
989 _ => todo!(),
991 }
992 }
993}
994
995fn root_column(num_rows: u64) -> ColumnInfo {
997 let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
998 let final_page_num_rows = num_rows % (u32::MAX as u64);
999 let root_pages = (0..num_root_pages)
1000 .map(|i| PageInfo {
1001 num_rows: if i == num_root_pages - 1 {
1002 final_page_num_rows
1003 } else {
1004 u64::MAX
1005 },
1006 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1007 array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1008 pb::SimpleStruct {},
1009 )),
1010 }),
1011 priority: 0, buffer_offsets_and_sizes: Arc::new([]),
1013 })
1014 .collect::<Vec<_>>();
1015 ColumnInfo {
1016 buffer_offsets_and_sizes: Arc::new([]),
1017 encoding: pb::ColumnEncoding {
1018 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1019 },
1020 index: u32::MAX,
1021 page_infos: Arc::from(root_pages),
1022 }
1023}
1024
1025pub enum RootDecoder {
1026 Structural(StructuralStructDecoder),
1027 Legacy(SimpleStructDecoder),
1028}
1029
1030impl RootDecoder {
1031 pub fn into_structural(self) -> StructuralStructDecoder {
1032 match self {
1033 Self::Structural(decoder) => decoder,
1034 Self::Legacy(_) => panic!("Expected a structural decoder"),
1035 }
1036 }
1037
1038 pub fn into_legacy(self) -> SimpleStructDecoder {
1039 match self {
1040 Self::Legacy(decoder) => decoder,
1041 Self::Structural(_) => panic!("Expected a legacy decoder"),
1042 }
1043 }
1044}
1045
1046impl DecodeBatchScheduler {
1047 #[allow(clippy::too_many_arguments)]
1050 pub async fn try_new<'a>(
1051 schema: &'a Schema,
1052 column_indices: &[u32],
1053 column_infos: &[Arc<ColumnInfo>],
1054 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1055 num_rows: u64,
1056 _decoder_plugins: Arc<DecoderPlugins>,
1057 io: Arc<dyn EncodingsIo>,
1058 cache: Arc<LanceCache>,
1059 filter: &FilterExpression,
1060 decoder_config: &DecoderConfig,
1061 ) -> Result<Self> {
1062 assert!(num_rows > 0);
1063 let buffers = FileBuffers {
1064 positions_and_sizes: file_buffer_positions_and_sizes,
1065 };
1066 let arrow_schema = ArrowSchema::from(schema);
1067 let root_fields = arrow_schema.fields().clone();
1068 let root_type = DataType::Struct(root_fields.clone());
1069 let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1070 root_field.children.clone_from(&schema.fields);
1074 root_field
1075 .metadata
1076 .insert("__lance_decoder_root".to_string(), "true".to_string());
1077
1078 if column_infos.is_empty() || column_infos[0].is_structural() {
1079 let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1080
1081 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1082 let mut root_scheduler =
1083 strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1084
1085 let context = SchedulerContext::new(io, cache.clone());
1086 root_scheduler.initialize(filter, &context).await?;
1087
1088 Ok(Self {
1089 root_scheduler: RootScheduler::Structural(root_scheduler),
1090 root_fields,
1091 cache,
1092 })
1093 } else {
1094 let mut columns = Vec::with_capacity(column_infos.len() + 1);
1097 columns.push(Arc::new(root_column(num_rows)));
1098 columns.extend(column_infos.iter().cloned());
1099
1100 let adjusted_column_indices = [0_u32]
1101 .into_iter()
1102 .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1103 .collect::<Vec<_>>();
1104 let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1105 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1106 let root_scheduler =
1107 strategy.create_legacy_field_scheduler(&root_field, &mut column_iter, buffers)?;
1108
1109 let context = SchedulerContext::new(io, cache.clone());
1110 root_scheduler.initialize(filter, &context).await?;
1111
1112 Ok(Self {
1113 root_scheduler: RootScheduler::Legacy(root_scheduler.into()),
1114 root_fields,
1115 cache,
1116 })
1117 }
1118 }
1119
1120 #[deprecated(since = "0.29.1", note = "This is for legacy 2.0 paths")]
1121 pub fn from_scheduler(
1122 root_scheduler: Arc<dyn crate::previous::decoder::FieldScheduler>,
1123 root_fields: Fields,
1124 cache: Arc<LanceCache>,
1125 ) -> Self {
1126 Self {
1127 root_scheduler: RootScheduler::Legacy(root_scheduler),
1128 root_fields,
1129 cache,
1130 }
1131 }
1132
1133 fn do_schedule_ranges_structural(
1134 &mut self,
1135 ranges: &[Range<u64>],
1136 filter: &FilterExpression,
1137 io: Arc<dyn EncodingsIo>,
1138 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1139 ) {
1140 let root_scheduler = self.root_scheduler.as_structural();
1141 let mut context = SchedulerContext::new(io, self.cache.clone());
1142 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1143 if let Err(schedule_ranges_err) = maybe_root_job {
1144 schedule_action(Err(schedule_ranges_err));
1145 return;
1146 }
1147 let mut root_job = maybe_root_job.unwrap();
1148 let mut num_rows_scheduled = 0;
1149 loop {
1150 let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1151 if let Err(err) = maybe_next_scan_lines {
1152 schedule_action(Err(err));
1153 return;
1154 }
1155 let next_scan_lines = maybe_next_scan_lines.unwrap();
1156 if next_scan_lines.is_empty() {
1157 return;
1158 }
1159 for next_scan_line in next_scan_lines {
1160 trace!(
1161 "Scheduled scan line of {} rows and {} decoders",
1162 next_scan_line.rows_scheduled,
1163 next_scan_line.decoders.len()
1164 );
1165 num_rows_scheduled += next_scan_line.rows_scheduled;
1166 if !schedule_action(Ok(DecoderMessage {
1167 scheduled_so_far: num_rows_scheduled,
1168 decoders: next_scan_line.decoders,
1169 })) {
1170 return;
1172 }
1173 }
1174 }
1175 }
1176
1177 fn do_schedule_ranges_legacy(
1178 &mut self,
1179 ranges: &[Range<u64>],
1180 filter: &FilterExpression,
1181 io: Arc<dyn EncodingsIo>,
1182 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1183 priority: Option<Box<dyn PriorityRange>>,
1187 ) {
1188 let root_scheduler = self.root_scheduler.as_legacy();
1189 let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1190 trace!(
1191 "Scheduling {} ranges across {}..{} ({} rows){}",
1192 ranges.len(),
1193 ranges.first().unwrap().start,
1194 ranges.last().unwrap().end,
1195 rows_requested,
1196 priority
1197 .as_ref()
1198 .map(|p| format!(" (priority={:?})", p))
1199 .unwrap_or_default()
1200 );
1201
1202 let mut context = SchedulerContext::new(io, self.cache.clone());
1203 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1204 if let Err(schedule_ranges_err) = maybe_root_job {
1205 schedule_action(Err(schedule_ranges_err));
1206 return;
1207 }
1208 let mut root_job = maybe_root_job.unwrap();
1209 let mut num_rows_scheduled = 0;
1210 let mut rows_to_schedule = root_job.num_rows();
1211 let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1212 trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1213 while rows_to_schedule > 0 {
1214 let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1215 if let Err(schedule_next_err) = maybe_next_scan_line {
1216 schedule_action(Err(schedule_next_err));
1217 return;
1218 }
1219 let next_scan_line = maybe_next_scan_line.unwrap();
1220 priority.advance(next_scan_line.rows_scheduled);
1221 num_rows_scheduled += next_scan_line.rows_scheduled;
1222 rows_to_schedule -= next_scan_line.rows_scheduled;
1223 trace!(
1224 "Scheduled scan line of {} rows and {} decoders",
1225 next_scan_line.rows_scheduled,
1226 next_scan_line.decoders.len()
1227 );
1228 if !schedule_action(Ok(DecoderMessage {
1229 scheduled_so_far: num_rows_scheduled,
1230 decoders: next_scan_line.decoders,
1231 })) {
1232 return;
1234 }
1235
1236 trace!("Finished scheduling {} ranges", ranges.len());
1237 }
1238 }
1239
1240 fn do_schedule_ranges(
1241 &mut self,
1242 ranges: &[Range<u64>],
1243 filter: &FilterExpression,
1244 io: Arc<dyn EncodingsIo>,
1245 schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1246 priority: Option<Box<dyn PriorityRange>>,
1250 ) {
1251 match &self.root_scheduler {
1252 RootScheduler::Legacy(_) => {
1253 self.do_schedule_ranges_legacy(ranges, filter, io, schedule_action, priority)
1254 }
1255 RootScheduler::Structural(_) => {
1256 self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1257 }
1258 }
1259 }
1260
1261 pub fn schedule_ranges_to_vec(
1264 &mut self,
1265 ranges: &[Range<u64>],
1266 filter: &FilterExpression,
1267 io: Arc<dyn EncodingsIo>,
1268 priority: Option<Box<dyn PriorityRange>>,
1269 ) -> Result<Vec<DecoderMessage>> {
1270 let mut decode_messages = Vec::new();
1271 self.do_schedule_ranges(
1272 ranges,
1273 filter,
1274 io,
1275 |msg| {
1276 decode_messages.push(msg);
1277 true
1278 },
1279 priority,
1280 );
1281 decode_messages.into_iter().collect::<Result<Vec<_>>>()
1282 }
1283
1284 #[instrument(level = "debug", skip_all)]
1294 pub fn schedule_ranges(
1295 &mut self,
1296 ranges: &[Range<u64>],
1297 filter: &FilterExpression,
1298 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1299 scheduler: Arc<dyn EncodingsIo>,
1300 ) {
1301 self.do_schedule_ranges(
1302 ranges,
1303 filter,
1304 scheduler,
1305 |msg| {
1306 match sink.send(msg) {
1307 Ok(_) => true,
1308 Err(SendError { .. }) => {
1309 debug!(
1312 "schedule_ranges aborting early since decoder appears to have been dropped"
1313 );
1314 false
1315 }
1316 }
1317 },
1318 None,
1319 )
1320 }
1321
1322 #[instrument(level = "debug", skip_all)]
1330 pub fn schedule_range(
1331 &mut self,
1332 range: Range<u64>,
1333 filter: &FilterExpression,
1334 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1335 scheduler: Arc<dyn EncodingsIo>,
1336 ) {
1337 self.schedule_ranges(&[range], filter, sink, scheduler)
1338 }
1339
1340 pub fn schedule_take(
1348 &mut self,
1349 indices: &[u64],
1350 filter: &FilterExpression,
1351 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1352 scheduler: Arc<dyn EncodingsIo>,
1353 ) {
1354 debug_assert!(indices.windows(2).all(|w| w[0] < w[1]));
1355 if indices.is_empty() {
1356 return;
1357 }
1358 trace!("Scheduling take of {} rows", indices.len());
1359 let ranges = Self::indices_to_ranges(indices);
1360 self.schedule_ranges(&ranges, filter, sink, scheduler)
1361 }
1362
1363 fn indices_to_ranges(indices: &[u64]) -> Vec<Range<u64>> {
1365 let mut ranges = Vec::new();
1366 let mut start = indices[0];
1367
1368 for window in indices.windows(2) {
1369 if window[1] != window[0] + 1 {
1370 ranges.push(start..window[0] + 1);
1371 start = window[1];
1372 }
1373 }
1374
1375 ranges.push(start..*indices.last().unwrap() + 1);
1376 ranges
1377 }
1378}
1379
1380pub struct ReadBatchTask {
1381 pub task: BoxFuture<'static, Result<RecordBatch>>,
1382 pub num_rows: u32,
1383}
1384
1385pub struct BatchDecodeStream {
1387 context: DecoderContext,
1388 root_decoder: SimpleStructDecoder,
1389 rows_remaining: u64,
1390 rows_per_batch: u32,
1391 rows_scheduled: u64,
1392 rows_drained: u64,
1393 scheduler_exhausted: bool,
1394 emitted_batch_size_warning: Arc<Once>,
1395}
1396
1397impl BatchDecodeStream {
1398 pub fn new(
1408 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1409 rows_per_batch: u32,
1410 num_rows: u64,
1411 root_decoder: SimpleStructDecoder,
1412 ) -> Self {
1413 Self {
1414 context: DecoderContext::new(scheduled),
1415 root_decoder,
1416 rows_remaining: num_rows,
1417 rows_per_batch,
1418 rows_scheduled: 0,
1419 rows_drained: 0,
1420 scheduler_exhausted: false,
1421 emitted_batch_size_warning: Arc::new(Once::new()),
1422 }
1423 }
1424
1425 fn accept_decoder(&mut self, decoder: crate::previous::decoder::DecoderReady) -> Result<()> {
1426 if decoder.path.is_empty() {
1427 Ok(())
1429 } else {
1430 self.root_decoder.accept_child(decoder)
1431 }
1432 }
1433
1434 #[instrument(level = "debug", skip_all)]
1435 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1436 if self.scheduler_exhausted {
1437 return Ok(self.rows_scheduled);
1438 }
1439 while self.rows_scheduled < scheduled_need {
1440 let next_message = self.context.source.recv().await;
1441 match next_message {
1442 Some(scan_line) => {
1443 let scan_line = scan_line?;
1444 self.rows_scheduled = scan_line.scheduled_so_far;
1445 for message in scan_line.decoders {
1446 self.accept_decoder(message.into_legacy())?;
1447 }
1448 }
1449 None => {
1450 self.scheduler_exhausted = true;
1454 return Ok(self.rows_scheduled);
1455 }
1456 }
1457 }
1458 Ok(scheduled_need)
1459 }
1460
1461 #[instrument(level = "debug", skip_all)]
1462 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1463 trace!(
1464 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1465 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1466 );
1467 if self.rows_remaining == 0 {
1468 return Ok(None);
1469 }
1470
1471 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1472 self.rows_remaining -= to_take;
1473
1474 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1475 trace!(
1476 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1477 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1478 );
1479 if scheduled_need > 0 {
1480 let desired_scheduled = scheduled_need + self.rows_scheduled;
1481 trace!(
1482 "Draining from scheduler (desire at least {} scheduled rows)",
1483 desired_scheduled
1484 );
1485 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1486 if actually_scheduled < desired_scheduled {
1487 let under_scheduled = desired_scheduled - actually_scheduled;
1488 to_take -= under_scheduled;
1489 }
1490 }
1491
1492 if to_take == 0 {
1493 return Ok(None);
1494 }
1495
1496 let loaded_need = self.rows_drained + to_take - 1;
1498 trace!(
1499 "Waiting for I/O (desire at least {} fully loaded rows)",
1500 loaded_need
1501 );
1502 self.root_decoder.wait_for_loaded(loaded_need).await?;
1503
1504 let next_task = self.root_decoder.drain(to_take)?;
1505 self.rows_drained += to_take;
1506 Ok(Some(next_task))
1507 }
1508
1509 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1510 let stream = futures::stream::unfold(self, |mut slf| async move {
1511 let next_task = match slf.next_batch_task().await {
1512 Ok(Some(next_task)) => next_task,
1513 Ok(None) => return None,
1514 Err(err) => {
1515 slf.rows_remaining = 0;
1516 return Some((
1517 ReadBatchTask {
1518 task: async move { Err(err) }.boxed(),
1519 num_rows: 0,
1520 },
1521 slf,
1522 ));
1523 }
1524 };
1525 let num_rows = next_task.num_rows;
1526 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1527 let task = async move {
1528 let (batch, _data_size) =
1532 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1533 .await
1534 .map_err(|err| Error::wrapped(err.into()))??;
1535 Ok(batch)
1536 };
1537 debug_assert!(num_rows <= u32::MAX as u64);
1539 Some((
1540 ReadBatchTask {
1541 task: task.boxed(),
1542 num_rows: num_rows as u32,
1543 },
1544 slf,
1545 ))
1546 });
1547 stream.boxed()
1548 }
1549}
1550
1551enum RootDecoderMessage {
1554 LoadedPage(LoadedPageShard),
1555 LegacyPage(crate::previous::decoder::DecoderReady),
1556}
1557trait RootDecoderType {
1558 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1559 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1560 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1561}
1562impl RootDecoderType for StructuralStructDecoder {
1563 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1564 let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1565 unreachable!()
1566 };
1567 self.accept_page(loaded_page)
1568 }
1569 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1570 self.drain_batch_task(num_rows)
1571 }
1572 fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1573 Ok(())
1575 }
1576}
1577impl RootDecoderType for SimpleStructDecoder {
1578 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1579 let RootDecoderMessage::LegacyPage(legacy_page) = message else {
1580 unreachable!()
1581 };
1582 self.accept_child(legacy_page)
1583 }
1584 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1585 self.drain(num_rows)
1586 }
1587 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1588 runtime.block_on(self.wait_for_loaded(loaded_need))
1589 }
1590}
1591
1592struct BatchDecodeIterator<T: RootDecoderType> {
1594 messages: VecDeque<Result<DecoderMessage>>,
1595 root_decoder: T,
1596 rows_remaining: u64,
1597 rows_per_batch: u32,
1598 rows_scheduled: u64,
1599 rows_drained: u64,
1600 emitted_batch_size_warning: Arc<Once>,
1601 wait_for_io_runtime: tokio::runtime::Runtime,
1605 schema: Arc<ArrowSchema>,
1606}
1607
1608impl<T: RootDecoderType> BatchDecodeIterator<T> {
1609 pub fn new(
1611 messages: VecDeque<Result<DecoderMessage>>,
1612 rows_per_batch: u32,
1613 num_rows: u64,
1614 root_decoder: T,
1615 schema: Arc<ArrowSchema>,
1616 ) -> Self {
1617 Self {
1618 messages,
1619 root_decoder,
1620 rows_remaining: num_rows,
1621 rows_per_batch,
1622 rows_scheduled: 0,
1623 rows_drained: 0,
1624 wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1625 .build()
1626 .unwrap(),
1627 emitted_batch_size_warning: Arc::new(Once::new()),
1628 schema,
1629 }
1630 }
1631
1632 fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1637 match maybe_done(unloaded_page.0) {
1638 MaybeDone::Done(loaded_page) => loaded_page,
1640 MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1642 MaybeDone::Gone => unreachable!(),
1643 }
1644 }
1645
1646 #[instrument(level = "debug", skip_all)]
1651 fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1652 while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1653 let message = self.messages.pop_front().unwrap()?;
1654 self.rows_scheduled = message.scheduled_so_far;
1655 for decoder_message in message.decoders {
1656 match decoder_message {
1657 MessageType::UnloadedPage(unloaded_page) => {
1658 let loaded_page = self.wait_for_page(unloaded_page)?;
1659 self.root_decoder
1660 .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1661 }
1662 MessageType::DecoderReady(decoder_ready) => {
1663 if !decoder_ready.path.is_empty() {
1665 self.root_decoder
1666 .accept_message(RootDecoderMessage::LegacyPage(decoder_ready))?;
1667 }
1668 }
1669 }
1670 }
1671 }
1672
1673 let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1674
1675 self.root_decoder
1676 .wait(loaded_need, &self.wait_for_io_runtime)?;
1677 Ok(self.rows_scheduled)
1678 }
1679
1680 #[instrument(level = "debug", skip_all)]
1681 fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1682 trace!(
1683 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1684 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1685 );
1686 if self.rows_remaining == 0 {
1687 return Ok(None);
1688 }
1689
1690 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1691 self.rows_remaining -= to_take;
1692
1693 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1694 trace!(
1695 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1696 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1697 );
1698 if scheduled_need > 0 {
1699 let desired_scheduled = scheduled_need + self.rows_scheduled;
1700 trace!(
1701 "Draining from scheduler (desire at least {} scheduled rows)",
1702 desired_scheduled
1703 );
1704 let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1705 if actually_scheduled < desired_scheduled {
1706 let under_scheduled = desired_scheduled - actually_scheduled;
1707 to_take -= under_scheduled;
1708 }
1709 }
1710
1711 if to_take == 0 {
1712 return Ok(None);
1713 }
1714
1715 let next_task = self.root_decoder.drain_batch(to_take)?;
1716
1717 self.rows_drained += to_take;
1718
1719 let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1720
1721 Ok(Some(batch))
1722 }
1723}
1724
1725impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1726 type Item = ArrowResult<RecordBatch>;
1727
1728 fn next(&mut self) -> Option<Self::Item> {
1729 self.next_batch_task()
1730 .transpose()
1731 .map(|r| r.map_err(ArrowError::from))
1732 }
1733}
1734
1735impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1736 fn schema(&self) -> Arc<ArrowSchema> {
1737 self.schema.clone()
1738 }
1739}
1740
1741fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1752 if let Some(w) = data_type.byte_width_opt() {
1753 return w as f64;
1754 }
1755 match data_type {
1756 DataType::Boolean => 1.0 / 8.0,
1757 DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1758 DataType::Struct(fields) => fields
1759 .iter()
1760 .map(|f| estimate_bytes_per_row(f.data_type()))
1761 .sum(),
1762 DataType::List(child) | DataType::LargeList(child) => {
1763 5.0 * estimate_bytes_per_row(child.data_type())
1764 }
1765 DataType::FixedSizeList(child, dim) => {
1766 *dim as f64 * estimate_bytes_per_row(child.data_type())
1767 }
1768 DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
1769 DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
1770 _ => 64.0,
1771 }
1772}
1773
1774pub struct StructuralBatchDecodeStream {
1776 context: DecoderContext,
1777 root_decoder: StructuralStructDecoder,
1778 rows_remaining: u64,
1779 rows_per_batch: u32,
1780 rows_scheduled: u64,
1781 rows_drained: u64,
1782 scheduler_exhausted: bool,
1783 emitted_batch_size_warning: Arc<Once>,
1784 spawn_batch_decode_tasks: bool,
1792 batch_size_bytes: Option<u64>,
1794 schema_bytes_per_row: f64,
1797 bytes_per_row_feedback: Arc<AtomicU64>,
1800}
1801
1802impl StructuralBatchDecodeStream {
1803 pub fn new(
1813 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1814 rows_per_batch: u32,
1815 num_rows: u64,
1816 root_decoder: StructuralStructDecoder,
1817 spawn_batch_decode_tasks: bool,
1818 batch_size_bytes: Option<u64>,
1819 ) -> Self {
1820 let schema_bytes_per_row = if batch_size_bytes.is_some() {
1821 estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
1822 } else {
1823 0.0
1824 };
1825 Self {
1826 context: DecoderContext::new(scheduled),
1827 root_decoder,
1828 rows_remaining: num_rows,
1829 rows_per_batch,
1830 rows_scheduled: 0,
1831 rows_drained: 0,
1832 scheduler_exhausted: false,
1833 emitted_batch_size_warning: Arc::new(Once::new()),
1834 spawn_batch_decode_tasks,
1835 batch_size_bytes,
1836 schema_bytes_per_row,
1837 bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
1838 }
1839 }
1840
1841 #[instrument(level = "debug", skip_all)]
1842 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1843 if self.scheduler_exhausted {
1844 return Ok(self.rows_scheduled);
1845 }
1846 while self.rows_scheduled < scheduled_need {
1847 let next_message = self.context.source.recv().await;
1848 match next_message {
1849 Some(scan_line) => {
1850 let scan_line = scan_line?;
1851 self.rows_scheduled = scan_line.scheduled_so_far;
1852 for message in scan_line.decoders {
1853 let unloaded_page = message.into_structural();
1854 let loaded_page = unloaded_page.0.await?;
1855 self.root_decoder.accept_page(loaded_page)?;
1856 }
1857 }
1858 None => {
1859 self.scheduler_exhausted = true;
1863 return Ok(self.rows_scheduled);
1864 }
1865 }
1866 }
1867 Ok(scheduled_need)
1868 }
1869
1870 #[instrument(level = "debug", skip_all)]
1871 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1872 trace!(
1873 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1874 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1875 );
1876 if self.rows_remaining == 0 {
1877 return Ok(None);
1878 }
1879
1880 let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
1881 let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
1882 let bpr = if feedback > 0 {
1883 feedback as f64
1884 } else {
1885 self.schema_bytes_per_row
1886 };
1887 let rows = (batch_size_bytes as f64 / bpr) as u64;
1888 self.rows_remaining.min(rows.max(1))
1889 } else {
1890 self.rows_remaining.min(self.rows_per_batch as u64)
1891 };
1892 self.rows_remaining -= to_take;
1893
1894 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1895 trace!(
1896 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1897 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1898 );
1899 if scheduled_need > 0 {
1900 let desired_scheduled = scheduled_need + self.rows_scheduled;
1901 trace!(
1902 "Draining from scheduler (desire at least {} scheduled rows)",
1903 desired_scheduled
1904 );
1905 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1906 if actually_scheduled < desired_scheduled {
1907 let under_scheduled = desired_scheduled - actually_scheduled;
1908 to_take -= under_scheduled;
1909 }
1910 }
1911
1912 if to_take == 0 {
1913 return Ok(None);
1914 }
1915
1916 let next_task = self.root_decoder.drain_batch_task(to_take)?;
1917 self.rows_drained += to_take;
1918 Ok(Some(next_task))
1919 }
1920
1921 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1922 let stream = futures::stream::unfold(self, |mut slf| async move {
1923 let next_task = match slf.next_batch_task().await {
1924 Ok(Some(next_task)) => next_task,
1925 Ok(None) => return None,
1926 Err(err) => {
1927 slf.rows_remaining = 0;
1928 return Some((
1929 ReadBatchTask {
1930 task: async move { Err(err) }.boxed(),
1931 num_rows: 0,
1932 },
1933 slf,
1934 ));
1935 }
1936 };
1937 let num_rows = next_task.num_rows;
1938 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1939 let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
1940 let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
1943 let task = async move {
1944 let (batch, data_size) = if spawn_batch_decode_tasks {
1945 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1946 .await
1947 .map_err(|err| Error::wrapped(err.into()))??
1948 } else {
1949 next_task.into_batch(emitted_batch_size_warning)?
1950 };
1951 let num_rows = batch.num_rows() as u64;
1952 if let Some(bpr) = data_size.checked_div(num_rows) {
1953 let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
1954 let next = if prev == 0 || bpr >= prev {
1955 bpr
1958 } else {
1959 (prev + bpr) / 2
1963 };
1964 bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
1965 }
1966 Ok(batch)
1967 };
1968 debug_assert!(num_rows <= u32::MAX as u64);
1970 Some((
1971 ReadBatchTask {
1972 task: task.boxed(),
1973 num_rows: num_rows as u32,
1974 },
1975 slf,
1976 ))
1977 });
1978 stream.boxed()
1979 }
1980}
1981
1982#[derive(Debug)]
1983pub enum RequestedRows {
1984 Ranges(Vec<Range<u64>>),
1985 Indices(Vec<u64>),
1986}
1987
1988impl RequestedRows {
1989 pub fn num_rows(&self) -> u64 {
1990 match self {
1991 Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
1992 Self::Indices(indices) => indices.len() as u64,
1993 }
1994 }
1995
1996 pub fn trim_empty_ranges(mut self) -> Self {
1997 if let Self::Ranges(ranges) = &mut self {
1998 ranges.retain(|r| !r.is_empty());
1999 }
2000 self
2001 }
2002}
2003
2004#[derive(Debug, Clone)]
2006pub struct DecoderConfig {
2007 pub cache_repetition_index: bool,
2014 pub validate_on_decode: bool,
2016 pub inline_scheduling: Option<bool>,
2034}
2035
2036impl Default for DecoderConfig {
2037 fn default() -> Self {
2038 Self {
2039 cache_repetition_index: default_cache_repetition_index(),
2040 validate_on_decode: false,
2041 inline_scheduling: None,
2042 }
2043 }
2044}
2045
2046#[derive(Debug, Clone)]
2047pub struct SchedulerDecoderConfig {
2048 pub decoder_plugins: Arc<DecoderPlugins>,
2049 pub batch_size: u32,
2050 pub io: Arc<dyn EncodingsIo>,
2051 pub cache: Arc<LanceCache>,
2052 pub decoder_config: DecoderConfig,
2054 pub batch_size_bytes: Option<u64>,
2059}
2060
2061fn check_scheduler_on_drop(
2062 stream: BoxStream<'static, ReadBatchTask>,
2063 scheduler_handle: tokio::task::JoinHandle<()>,
2064) -> BoxStream<'static, ReadBatchTask> {
2065 let abort_handle = scheduler_handle.abort_handle();
2069 let mut scheduler_handle = Some(scheduler_handle);
2070 let check_scheduler = stream::unfold((), move |_| {
2071 let handle = scheduler_handle.take();
2072 async move {
2073 if let Some(handle) = handle {
2074 handle.await.unwrap();
2075 }
2076 None
2077 }
2078 });
2079 stream
2080 .chain(check_scheduler)
2081 .on_drop(move || {
2082 abort_handle.abort();
2090 })
2091 .boxed()
2092}
2093
2094#[allow(clippy::too_many_arguments)]
2095pub fn create_decode_stream(
2096 schema: &Schema,
2097 num_rows: u64,
2098 batch_size: u32,
2099 is_structural: bool,
2100 should_validate: bool,
2101 spawn_structural_batch_decode_tasks: bool,
2102 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2103 batch_size_bytes: Option<u64>,
2104) -> Result<BoxStream<'static, ReadBatchTask>> {
2105 if is_structural {
2106 let arrow_schema = ArrowSchema::from(schema);
2107 let structural_decoder = StructuralStructDecoder::new(
2108 arrow_schema.fields,
2109 should_validate,
2110 true,
2111 )?;
2112 Ok(StructuralBatchDecodeStream::new(
2113 rx,
2114 batch_size,
2115 num_rows,
2116 structural_decoder,
2117 spawn_structural_batch_decode_tasks,
2118 batch_size_bytes,
2119 )
2120 .into_stream())
2121 } else {
2122 if batch_size_bytes.is_some() {
2123 warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2124 }
2125 let arrow_schema = ArrowSchema::from(schema);
2126 let root_fields = arrow_schema.fields;
2127
2128 let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2129 Ok(BatchDecodeStream::new(rx, batch_size, num_rows, simple_struct_decoder).into_stream())
2130 }
2131}
2132
2133pub fn create_decode_iterator(
2137 schema: &Schema,
2138 num_rows: u64,
2139 batch_size: u32,
2140 should_validate: bool,
2141 is_structural: bool,
2142 messages: VecDeque<Result<DecoderMessage>>,
2143) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2144 let arrow_schema = Arc::new(ArrowSchema::from(schema));
2145 let root_fields = arrow_schema.fields.clone();
2146 if is_structural {
2147 let simple_struct_decoder =
2148 StructuralStructDecoder::new(root_fields, should_validate, true)?;
2149 Ok(Box::new(BatchDecodeIterator::new(
2150 messages,
2151 batch_size,
2152 num_rows,
2153 simple_struct_decoder,
2154 arrow_schema,
2155 )))
2156 } else {
2157 let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2158 Ok(Box::new(BatchDecodeIterator::new(
2159 messages,
2160 batch_size,
2161 num_rows,
2162 root_decoder,
2163 arrow_schema,
2164 )))
2165 }
2166}
2167
2168async fn create_scheduler_decoder(
2169 column_infos: Vec<Arc<ColumnInfo>>,
2170 requested_rows: RequestedRows,
2171 filter: FilterExpression,
2172 column_indices: Vec<u32>,
2173 target_schema: Arc<Schema>,
2174 config: SchedulerDecoderConfig,
2175) -> Result<BoxStream<'static, ReadBatchTask>> {
2176 let num_rows = requested_rows.num_rows();
2177
2178 let is_structural = column_infos[0].is_structural();
2179 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2180 let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2181 Some("always") => true,
2182 Some("never") => false,
2183 _ => matches!(requested_rows, RequestedRows::Ranges(_)),
2184 };
2185
2186 let (tx, rx) = mpsc::unbounded_channel();
2187
2188 let decode_stream = create_decode_stream(
2189 &target_schema,
2190 num_rows,
2191 config.batch_size,
2192 is_structural,
2193 config.decoder_config.validate_on_decode,
2194 spawn_structural_batch_decode_tasks,
2195 rx,
2196 config.batch_size_bytes,
2197 )?;
2198
2199 let mut decode_scheduler = DecodeBatchScheduler::try_new(
2204 target_schema.as_ref(),
2205 &column_indices,
2206 &column_infos,
2207 &vec![],
2208 num_rows,
2209 config.decoder_plugins,
2210 config.io.clone(),
2211 config.cache,
2212 &filter,
2213 &config.decoder_config,
2214 )
2215 .await?;
2216
2217 let inline_scheduling = config
2223 .decoder_config
2224 .inline_scheduling
2225 .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2226
2227 if inline_scheduling {
2228 match requested_rows {
2229 RequestedRows::Ranges(ranges) => {
2230 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2231 }
2232 RequestedRows::Indices(indices) => {
2233 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2234 }
2235 }
2236 Ok(decode_stream)
2237 } else {
2238 let scheduling = async move {
2242 match requested_rows {
2243 RequestedRows::Ranges(ranges) => {
2244 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2245 }
2246 RequestedRows::Indices(indices) => {
2247 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2248 }
2249 }
2250 };
2251 let scheduler_handle = tokio::task::spawn(scheduling);
2252 Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2253 }
2254}
2255
2256pub async fn schedule_and_decode(
2271 column_infos: Vec<Arc<ColumnInfo>>,
2272 requested_rows: RequestedRows,
2273 filter: FilterExpression,
2274 column_indices: Vec<u32>,
2275 target_schema: Arc<Schema>,
2276 config: SchedulerDecoderConfig,
2277) -> Result<BoxStream<'static, ReadBatchTask>> {
2278 if requested_rows.num_rows() == 0 {
2279 return Ok(stream::empty().boxed());
2280 }
2281
2282 let requested_rows = requested_rows.trim_empty_ranges();
2285
2286 let io = config.io.clone();
2287
2288 let stream = create_scheduler_decoder(
2289 column_infos,
2290 requested_rows,
2291 filter,
2292 column_indices,
2293 target_schema,
2294 config,
2295 )
2296 .await?;
2297
2298 Ok(stream.finally(move || drop(io)).boxed())
2301}
2302
2303pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2304 tokio::runtime::Builder::new_current_thread()
2305 .build()
2306 .unwrap()
2307});
2308
2309pub fn schedule_and_decode_blocking(
2324 column_infos: Vec<Arc<ColumnInfo>>,
2325 requested_rows: RequestedRows,
2326 filter: FilterExpression,
2327 column_indices: Vec<u32>,
2328 target_schema: Arc<Schema>,
2329 config: SchedulerDecoderConfig,
2330) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2331 if requested_rows.num_rows() == 0 {
2332 let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2333 return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2334 }
2335
2336 let num_rows = requested_rows.num_rows();
2337 let is_structural = column_infos[0].is_structural();
2338
2339 let (tx, mut rx) = mpsc::unbounded_channel();
2340
2341 let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new(
2344 target_schema.as_ref(),
2345 &column_indices,
2346 &column_infos,
2347 &vec![],
2348 num_rows,
2349 config.decoder_plugins,
2350 config.io.clone(),
2351 config.cache,
2352 &filter,
2353 &config.decoder_config,
2354 ))?;
2355
2356 match requested_rows {
2358 RequestedRows::Ranges(ranges) => {
2359 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2360 }
2361 RequestedRows::Indices(indices) => {
2362 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2363 }
2364 }
2365
2366 let mut messages = Vec::new();
2368 while rx
2369 .recv_many(&mut messages, usize::MAX)
2370 .now_or_never()
2371 .unwrap()
2372 != 0
2373 {}
2374
2375 let decode_iterator = create_decode_iterator(
2377 &target_schema,
2378 num_rows,
2379 config.batch_size,
2380 config.decoder_config.validate_on_decode,
2381 is_structural,
2382 messages.into(),
2383 )?;
2384
2385 Ok(decode_iterator)
2386}
2387
2388pub trait PrimitivePageDecoder: Send + Sync {
2400 fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2432}
2433
2434pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2443 fn schedule_ranges(
2455 &self,
2456 ranges: &[Range<u64>],
2457 scheduler: &Arc<dyn EncodingsIo>,
2458 top_level_row: u64,
2459 ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2460}
2461
2462pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2464 fn advance(&mut self, num_rows: u64);
2465 fn current_priority(&self) -> u64;
2466 fn box_clone(&self) -> Box<dyn PriorityRange>;
2467}
2468
2469#[derive(Debug)]
2472pub struct SimplePriorityRange {
2473 priority: u64,
2474}
2475
2476impl SimplePriorityRange {
2477 fn new(priority: u64) -> Self {
2478 Self { priority }
2479 }
2480}
2481
2482impl PriorityRange for SimplePriorityRange {
2483 fn advance(&mut self, num_rows: u64) {
2484 self.priority += num_rows;
2485 }
2486
2487 fn current_priority(&self) -> u64 {
2488 self.priority
2489 }
2490
2491 fn box_clone(&self) -> Box<dyn PriorityRange> {
2492 Box::new(Self {
2493 priority: self.priority,
2494 })
2495 }
2496}
2497
2498pub struct ListPriorityRange {
2511 base: Box<dyn PriorityRange>,
2512 offsets: Arc<[u64]>,
2513 cur_index_into_offsets: usize,
2514 cur_position: u64,
2515}
2516
2517impl ListPriorityRange {
2518 pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2519 Self {
2520 base,
2521 offsets,
2522 cur_index_into_offsets: 0,
2523 cur_position: 0,
2524 }
2525 }
2526}
2527
2528impl std::fmt::Debug for ListPriorityRange {
2529 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2530 f.debug_struct("ListPriorityRange")
2531 .field("base", &self.base)
2532 .field("offsets.len()", &self.offsets.len())
2533 .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2534 .field("cur_position", &self.cur_position)
2535 .finish()
2536 }
2537}
2538
2539impl PriorityRange for ListPriorityRange {
2540 fn advance(&mut self, num_rows: u64) {
2541 self.cur_position += num_rows;
2544 let mut idx_into_offsets = self.cur_index_into_offsets;
2545 while idx_into_offsets + 1 < self.offsets.len()
2546 && self.offsets[idx_into_offsets + 1] <= self.cur_position
2547 {
2548 idx_into_offsets += 1;
2549 }
2550 let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2551 self.cur_index_into_offsets = idx_into_offsets;
2552 self.base.advance(base_rows_advanced as u64);
2553 }
2554
2555 fn current_priority(&self) -> u64 {
2556 self.base.current_priority()
2557 }
2558
2559 fn box_clone(&self) -> Box<dyn PriorityRange> {
2560 Box::new(Self {
2561 base: self.base.box_clone(),
2562 offsets: self.offsets.clone(),
2563 cur_index_into_offsets: self.cur_index_into_offsets,
2564 cur_position: self.cur_position,
2565 })
2566 }
2567}
2568
2569pub struct SchedulerContext {
2571 recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
2572 io: Arc<dyn EncodingsIo>,
2573 cache: Arc<LanceCache>,
2574 name: String,
2575 path: Vec<u32>,
2576 path_names: Vec<String>,
2577}
2578
2579pub struct ScopedSchedulerContext<'a> {
2580 pub context: &'a mut SchedulerContext,
2581}
2582
2583impl<'a> ScopedSchedulerContext<'a> {
2584 pub fn pop(self) -> &'a mut SchedulerContext {
2585 self.context.pop();
2586 self.context
2587 }
2588}
2589
2590impl SchedulerContext {
2591 pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
2592 Self {
2593 io,
2594 cache,
2595 recv: None,
2596 name: "".to_string(),
2597 path: Vec::new(),
2598 path_names: Vec::new(),
2599 }
2600 }
2601
2602 pub fn io(&self) -> &Arc<dyn EncodingsIo> {
2603 &self.io
2604 }
2605
2606 pub fn cache(&self) -> &Arc<LanceCache> {
2607 &self.cache
2608 }
2609
2610 pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
2611 self.path.push(index);
2612 self.path_names.push(name.to_string());
2613 ScopedSchedulerContext { context: self }
2614 }
2615
2616 pub fn pop(&mut self) {
2617 self.path.pop();
2618 self.path_names.pop();
2619 }
2620
2621 pub fn path_name(&self) -> String {
2622 let path = self.path_names.join("/");
2623 if self.recv.is_some() {
2624 format!("TEMP({}){}", self.name, path)
2625 } else {
2626 format!("ROOT{}", path)
2627 }
2628 }
2629
2630 pub fn current_path(&self) -> VecDeque<u32> {
2631 VecDeque::from_iter(self.path.iter().copied())
2632 }
2633
2634 #[deprecated(since = "0.29.1", note = "This is for legacy 2.0 paths")]
2635 pub fn locate_decoder(
2636 &mut self,
2637 decoder: Box<dyn crate::previous::decoder::LogicalPageDecoder>,
2638 ) -> crate::previous::decoder::DecoderReady {
2639 trace!(
2640 "Scheduling decoder of type {:?} for {:?}",
2641 decoder.data_type(),
2642 self.path,
2643 );
2644 crate::previous::decoder::DecoderReady {
2645 decoder,
2646 path: self.current_path(),
2647 }
2648 }
2649}
2650
2651pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
2652
2653impl std::fmt::Debug for UnloadedPageShard {
2654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2655 f.debug_struct("UnloadedPage").finish()
2656 }
2657}
2658
2659#[derive(Debug)]
2660pub struct ScheduledScanLine {
2661 pub rows_scheduled: u64,
2662 pub decoders: Vec<MessageType>,
2663}
2664
2665pub trait StructuralSchedulingJob: std::fmt::Debug {
2666 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
2673}
2674
2675pub struct FilterExpression(pub Bytes);
2683
2684impl FilterExpression {
2685 pub fn no_filter() -> Self {
2690 Self(Bytes::new())
2691 }
2692
2693 pub fn is_noop(&self) -> bool {
2695 self.0.is_empty()
2696 }
2697}
2698
2699pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
2700 fn initialize<'a>(
2701 &'a mut self,
2702 filter: &'a FilterExpression,
2703 context: &'a SchedulerContext,
2704 ) -> BoxFuture<'a, Result<()>>;
2705 fn schedule_ranges<'a>(
2706 &'a self,
2707 ranges: &[Range<u64>],
2708 filter: &FilterExpression,
2709 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
2710}
2711
2712pub trait DecodeArrayTask: Send {
2714 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
2716}
2717
2718impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
2719 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
2720 StructuralDecodeArrayTask::decode(*self)
2721 .map(|decoded_array| (decoded_array.array, decoded_array.data_size))
2722 }
2723}
2724
2725pub struct NextDecodeTask {
2730 pub task: Box<dyn DecodeArrayTask>,
2732 pub num_rows: u64,
2734}
2735
2736impl NextDecodeTask {
2737 #[instrument(name = "task_to_batch", level = "debug", skip_all)]
2742 fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
2743 let (struct_arr, data_size) = self
2744 .task
2745 .decode()
2746 .map_err(|e| Error::internal(format!("Error decoding batch: {}", e)))?;
2747 let batch = RecordBatch::from(struct_arr.as_struct());
2748 if data_size > BATCH_SIZE_BYTES_WARNING {
2749 emitted_batch_size_warning.call_once(|| {
2750 let size_mb = data_size / 1024 / 1024;
2751 debug!("Lance read in a single batch that contained more than {}MiB of data. You may want to consider reducing the batch size.", size_mb);
2752 });
2753 }
2754 Ok((batch, data_size))
2755 }
2756}
2757
2758#[derive(Debug)]
2762pub enum MessageType {
2763 DecoderReady(crate::previous::decoder::DecoderReady),
2768 UnloadedPage(UnloadedPageShard),
2772}
2773
2774impl MessageType {
2775 pub fn into_legacy(self) -> crate::previous::decoder::DecoderReady {
2776 match self {
2777 Self::DecoderReady(decoder) => decoder,
2778 Self::UnloadedPage(_) => {
2779 panic!("Expected DecoderReady but got UnloadedPage")
2780 }
2781 }
2782 }
2783
2784 pub fn into_structural(self) -> UnloadedPageShard {
2785 match self {
2786 Self::UnloadedPage(unloaded) => unloaded,
2787 Self::DecoderReady(_) => {
2788 panic!("Expected UnloadedPage but got DecoderReady")
2789 }
2790 }
2791 }
2792}
2793
2794pub struct DecoderMessage {
2795 pub scheduled_so_far: u64,
2796 pub decoders: Vec<MessageType>,
2797}
2798
2799pub struct DecoderContext {
2800 source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2801}
2802
2803impl DecoderContext {
2804 pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
2805 Self { source }
2806 }
2807}
2808
2809pub struct DecodedPage {
2810 pub data: DataBlock,
2811 pub repdef: RepDefUnraveler,
2812}
2813
2814pub trait DecodePageTask: Send + std::fmt::Debug {
2815 fn decode(self: Box<Self>) -> Result<DecodedPage>;
2817}
2818
2819pub trait StructuralPageDecoder: std::fmt::Debug + Send {
2820 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
2821 fn num_rows(&self) -> u64;
2822}
2823
2824#[derive(Debug)]
2825pub struct LoadedPageShard {
2826 pub decoder: Box<dyn StructuralPageDecoder>,
2828 pub path: VecDeque<u32>,
2847}
2848
2849pub struct DecodedArray {
2850 pub array: ArrayRef,
2851 pub repdef: CompositeRepDefUnraveler,
2852 pub data_size: u64,
2854}
2855
2856pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
2857 fn decode(self: Box<Self>) -> Result<DecodedArray>;
2858}
2859
2860pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
2861 fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
2866 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
2868 fn data_type(&self) -> &DataType;
2870}
2871
2872#[derive(Debug, Default)]
2873pub struct DecoderPlugins {}
2874
2875pub async fn decode_batch(
2877 batch: &EncodedBatch,
2878 filter: &FilterExpression,
2879 decoder_plugins: Arc<DecoderPlugins>,
2880 should_validate: bool,
2881 version: LanceFileVersion,
2882 cache: Option<Arc<LanceCache>>,
2883) -> Result<RecordBatch> {
2884 let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
2889 let cache = if let Some(cache) = cache {
2890 cache
2891 } else {
2892 Arc::new(lance_core::cache::LanceCache::with_capacity(
2893 128 * 1024 * 1024,
2894 ))
2895 };
2896 let mut decode_scheduler = DecodeBatchScheduler::try_new(
2897 batch.schema.as_ref(),
2898 &batch.top_level_columns,
2899 &batch.page_table,
2900 &vec![],
2901 batch.num_rows,
2902 decoder_plugins,
2903 io_scheduler.clone(),
2904 cache,
2905 filter,
2906 &DecoderConfig::default(),
2907 )
2908 .await?;
2909 let (tx, rx) = unbounded_channel();
2910 decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
2911 let is_structural = version >= LanceFileVersion::V2_1;
2912 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2913 let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
2914 let mut decode_stream = create_decode_stream(
2915 &batch.schema,
2916 batch.num_rows,
2917 batch.num_rows as u32,
2918 is_structural,
2919 should_validate,
2920 spawn_structural_batch_decode_tasks,
2921 rx,
2922 None,
2923 )?;
2924 decode_stream.next().await.unwrap().task.await
2925}
2926
2927#[cfg(test)]
2928mod tests {
2930 use super::*;
2931 use crate::previous::decoder::{DecoderReady, LogicalPageDecoder};
2932 use std::collections::VecDeque;
2933
2934 #[derive(Debug)]
2935 struct FailingPageDecoder {
2936 page_data_type: DataType,
2937 total_rows: u64,
2938 load_error_message: &'static str,
2939 }
2940
2941 impl FailingPageDecoder {
2942 fn new(
2943 page_data_type: DataType,
2944 total_rows: u64,
2945 load_error_message: &'static str,
2946 ) -> Self {
2947 Self {
2948 page_data_type,
2949 total_rows,
2950 load_error_message,
2951 }
2952 }
2953 }
2954
2955 impl LogicalPageDecoder for FailingPageDecoder {
2956 fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
2957 let load_error_message = self.load_error_message;
2958 async move { Err(Error::io(load_error_message)) }.boxed()
2959 }
2960
2961 fn rows_loaded(&self) -> u64 {
2962 0
2963 }
2964
2965 fn num_rows(&self) -> u64 {
2966 self.total_rows
2967 }
2968
2969 fn rows_drained(&self) -> u64 {
2970 0
2971 }
2972
2973 fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
2974 Err(Error::internal(format!(
2975 "failing page decoder should not be drained after load error \
2976 (requested_rows={})",
2977 requested_rows
2978 )))
2979 }
2980
2981 fn data_type(&self) -> &DataType {
2982 &self.page_data_type
2983 }
2984 }
2985
2986 #[test]
2987 fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
2988 use arrow_schema::Field as ArrowField;
2995
2996 let zero_dim = DataType::FixedSizeList(
2997 Arc::new(ArrowField::new("item", DataType::Float32, true)),
2998 0,
2999 );
3000 let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3001 let strategy = CoreFieldDecoderStrategy::default();
3002
3003 let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3004 let err = strategy
3005 .create_structural_field_scheduler(&field, &mut structural_columns)
3006 .unwrap_err();
3007 assert!(
3008 err.to_string()
3009 .contains("dimension must be a positive integer"),
3010 "unexpected error: {}",
3011 err
3012 );
3013
3014 let mut legacy_columns = ColumnInfoIter::new(vec![], &[]);
3015 let err = strategy
3016 .create_legacy_field_scheduler(
3017 &field,
3018 &mut legacy_columns,
3019 FileBuffers {
3020 positions_and_sizes: &[],
3021 },
3022 )
3023 .unwrap_err();
3024 assert!(
3025 err.to_string()
3026 .contains("dimension must be a positive integer"),
3027 "unexpected error: {}",
3028 err
3029 );
3030 }
3031
3032 #[tokio::test]
3033 async fn test_legacy_stream_stops_on_load_error() {
3034 use arrow_schema::Field as ArrowField;
3035
3036 let rows_per_batch = 1;
3037 let total_rows = 2;
3038 let scheduled_rows = 1;
3039 let page_rows = 1;
3040 let batch_readahead = 2;
3041 let load_error_message = "simulated page load failure";
3042 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3043 let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3044 let (tx, rx) = unbounded_channel();
3045
3046 tx.send(Ok(DecoderMessage {
3047 scheduled_so_far: scheduled_rows,
3048 decoders: vec![MessageType::DecoderReady(DecoderReady {
3049 decoder: Box::new(FailingPageDecoder::new(
3050 DataType::Float32,
3051 page_rows,
3052 load_error_message,
3053 )),
3054 path: VecDeque::from([0]),
3055 })],
3056 }))
3057 .unwrap();
3058 drop(tx);
3059
3060 let stream =
3061 BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3062 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3063
3064 let err = batches
3065 .next()
3066 .await
3067 .expect("stream should emit the legacy page-load error")
3068 .unwrap_err();
3069 assert!(
3070 err.to_string().contains(load_error_message),
3071 "unexpected error: {}",
3072 err
3073 );
3074 assert!(
3075 batches.next().await.is_none(),
3076 "stream should stop after the legacy page-load error"
3077 );
3078 }
3079
3080 #[tokio::test]
3081 async fn test_structural_stream_stops_on_load_error() {
3082 let rows_per_batch = 1;
3083 let total_rows = 2;
3084 let scheduled_rows = 1;
3085 let batch_readahead = 2;
3086 let load_error_message = "simulated page load failure";
3087 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3088 let root_decoder = StructuralStructDecoder::new(fields, false, true).unwrap();
3089 let (tx, rx) = unbounded_channel();
3090 let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3091
3092 tx.send(Ok(DecoderMessage {
3093 scheduled_so_far: scheduled_rows,
3094 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3095 }))
3096 .unwrap();
3097 drop(tx);
3098
3099 let stream = StructuralBatchDecodeStream::new(
3100 rx,
3101 rows_per_batch,
3102 total_rows,
3103 root_decoder,
3104 true,
3105 None,
3106 )
3107 .into_stream();
3108 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3109
3110 let err = batches
3111 .next()
3112 .await
3113 .expect("stream should emit the page-load error")
3114 .unwrap_err();
3115 assert!(
3116 err.to_string().contains(load_error_message),
3117 "unexpected error: {}",
3118 err
3119 );
3120 assert!(
3121 batches.next().await.is_none(),
3122 "stream should stop after the page-load error"
3123 );
3124 }
3125
3126 #[test]
3127 fn test_coalesce_indices_to_ranges_with_single_index() {
3128 let indices = vec![1];
3129 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3130 assert_eq!(ranges, vec![1..2]);
3131 }
3132
3133 #[test]
3134 fn test_coalesce_indices_to_ranges() {
3135 let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3136 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3137 assert_eq!(ranges, vec![1..10]);
3138 }
3139
3140 #[test]
3141 fn test_coalesce_indices_to_ranges_with_gaps() {
3142 let indices = vec![1, 2, 3, 5, 6, 7, 9];
3143 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3144 assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3145 }
3146
3147 #[test]
3148 fn test_estimate_bytes_per_row() {
3149 assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3150 assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3151 assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3152 assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3153 assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3154 assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3155 let struct_type = DataType::Struct(Fields::from(vec![
3157 ArrowField::new("a", DataType::Int32, false),
3158 ArrowField::new("b", DataType::Int32, false),
3159 ArrowField::new("c", DataType::Int32, false),
3160 ArrowField::new("d", DataType::Int32, false),
3161 ]));
3162 assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3163 }
3164
3165 async fn decode_batches_with_byte_limit(
3168 batch: &RecordBatch,
3169 batch_size: u32,
3170 batch_size_bytes: Option<u64>,
3171 ) -> Vec<RecordBatch> {
3172 use crate::encoder::{EncodingOptions, default_encoding_strategy, encode_batch};
3173 use crate::version::LanceFileVersion;
3174
3175 let version = LanceFileVersion::V2_1;
3176 let options = EncodingOptions {
3177 version,
3178 ..Default::default()
3179 };
3180 let strategy = default_encoding_strategy(version);
3181 let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3182 let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3183 .await
3184 .unwrap();
3185
3186 let io_scheduler =
3187 Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
3188 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
3189 128 * 1024 * 1024,
3190 ));
3191 let decoder_plugins = Arc::new(DecoderPlugins::default());
3192
3193 let mut decode_scheduler = DecodeBatchScheduler::try_new(
3194 encoded.schema.as_ref(),
3195 &encoded.top_level_columns,
3196 &encoded.page_table,
3197 &vec![],
3198 encoded.num_rows,
3199 decoder_plugins,
3200 io_scheduler.clone(),
3201 cache,
3202 &FilterExpression::no_filter(),
3203 &DecoderConfig::default(),
3204 )
3205 .await
3206 .unwrap();
3207
3208 let (tx, rx) = unbounded_channel();
3209 decode_scheduler.schedule_range(
3210 0..encoded.num_rows,
3211 &FilterExpression::no_filter(),
3212 tx,
3213 io_scheduler,
3214 );
3215
3216 let mut decode_stream = create_decode_stream(
3217 &encoded.schema,
3218 encoded.num_rows,
3219 batch_size,
3220 true,
3221 true,
3222 true,
3223 rx,
3224 batch_size_bytes,
3225 )
3226 .unwrap();
3227
3228 let mut batches = Vec::new();
3229 while let Some(task) = decode_stream.next().await {
3230 batches.push(task.task.await.unwrap());
3231 }
3232 batches
3233 }
3234
3235 #[tokio::test]
3236 async fn test_byte_sized_batches_fixed_width() {
3237 use arrow_array::Int32Array;
3238
3239 let num_rows: i32 = 1000;
3241 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
3242 .map(|col| {
3243 Arc::new(Int32Array::from_iter_values(
3244 (0..num_rows).map(move |row| row * 10 + col),
3245 )) as _
3246 })
3247 .collect();
3248
3249 let schema = Arc::new(ArrowSchema::new(vec![
3250 ArrowField::new("a", DataType::Int32, false),
3251 ArrowField::new("b", DataType::Int32, false),
3252 ArrowField::new("c", DataType::Int32, false),
3253 ArrowField::new("d", DataType::Int32, false),
3254 ]));
3255 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3256
3257 let batches =
3259 decode_batches_with_byte_limit(&input_batch, 1024, Some(1600)).await;
3260
3261 assert_eq!(batches.len(), 10);
3263 for (i, batch) in batches.iter().enumerate() {
3264 assert_eq!(
3265 batch.num_rows(),
3266 100,
3267 "batch {i} should have 100 rows, got {}",
3268 batch.num_rows()
3269 );
3270 }
3271
3272 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3274 let concatenated =
3275 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3276 .unwrap();
3277 assert_eq!(concatenated.num_rows(), num_rows as usize);
3278 for col in 0..4 {
3279 assert_eq!(
3280 concatenated.column(col).as_ref(),
3281 input_batch.column(col).as_ref(),
3282 "column {col} roundtrip mismatch"
3283 );
3284 }
3285 }
3286
3287 #[tokio::test]
3288 async fn test_byte_sized_batches_none_unchanged() {
3289 use arrow_array::Int32Array;
3290
3291 let num_rows: i32 = 1000;
3293 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
3294 .map(|col| {
3295 Arc::new(Int32Array::from_iter_values(
3296 (0..num_rows).map(move |row| row * 10 + col),
3297 )) as _
3298 })
3299 .collect();
3300
3301 let schema = Arc::new(ArrowSchema::new(vec![
3302 ArrowField::new("x", DataType::Int32, false),
3303 ArrowField::new("y", DataType::Int32, false),
3304 ]));
3305 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3306
3307 let batches = decode_batches_with_byte_limit(&input_batch, 250, None).await;
3309 assert_eq!(batches.len(), 4);
3310 for (i, batch) in batches.iter().enumerate() {
3311 assert_eq!(
3312 batch.num_rows(),
3313 250,
3314 "batch {i} should have 250 rows, got {}",
3315 batch.num_rows()
3316 );
3317 }
3318 }
3319
3320 #[tokio::test]
3321 async fn test_byte_sized_batches_feedback_convergence() {
3322 use arrow_array::StringArray;
3323
3324 let num_rows = 500;
3328 let value: String = "x".repeat(100);
3329 let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
3330 (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
3331 ))];
3332 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
3333 "s",
3334 DataType::Utf8,
3335 false,
3336 )]));
3337 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3338
3339 let target_bytes: u64 = 5000;
3343 let batches = decode_batches_with_byte_limit(
3344 &input_batch,
3345 1024,
3346 Some(target_bytes),
3347 )
3348 .await;
3349
3350 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3352 let concatenated =
3353 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3354 .unwrap();
3355 assert_eq!(concatenated.num_rows(), num_rows as usize);
3356 assert_eq!(
3357 concatenated.column(0).as_ref(),
3358 input_batch.column(0).as_ref()
3359 );
3360
3361 assert!(
3364 batches.len() >= 2,
3365 "need at least 2 batches to test convergence"
3366 );
3367 if batches.len() >= 3 {
3370 let second_batch_rows = batches[1].num_rows();
3371 let third_batch_rows = batches[2].num_rows();
3372 assert!(
3374 (40..=60).contains(&second_batch_rows),
3375 "second batch should be near 50 rows, got {second_batch_rows}"
3376 );
3377 assert!(
3378 (40..=60).contains(&third_batch_rows),
3379 "third batch should be near 50 rows, got {third_batch_rows}"
3380 );
3381 }
3382 }
3383}