1use std::collections::VecDeque;
216use std::sync::atomic::{AtomicU64, Ordering};
217use std::sync::{LazyLock, Mutex, 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::channel::oneshot;
225use futures::future::{BoxFuture, MaybeDone, Shared, maybe_done};
226use futures::stream::{self, BoxStream};
227use futures::{FutureExt, StreamExt};
228use lance_arrow::DataTypeExt;
229use lance_core::cache::{Context, DeepSizeOf, LanceCache};
230use lance_core::datatypes::{
231 BLOB_DESC_LANCE_FIELD, Field, Schema, validate_fixed_size_list_dimensions,
232};
233use lance_core::utils::futures::{FinallyStreamExt, StreamOnDropExt};
234use lance_core::utils::parse::parse_env_as_bool;
235use log::{debug, trace, warn};
236use prost::Message;
237use tokio::sync::mpsc::error::SendError;
238use tokio::sync::mpsc::{self, unbounded_channel};
239
240use lance_core::error::{CloneableError, LanceOptionExt};
241use lance_core::{ArrowResult, Error, Result};
242use tracing::instrument;
243
244use crate::array_encoding::logical::list::OffsetPageInfo;
245use crate::array_encoding::logical::r#struct::{SimpleStructDecoder, SimpleStructScheduler};
246use crate::array_encoding::logical::{
247 binary::BinaryFieldScheduler, blob::BlobFieldScheduler, list::ListFieldScheduler,
248 primitive::PrimitiveFieldScheduler,
249};
250use crate::compression::{DecompressionStrategy, DefaultDecompressionStrategy};
251use crate::data::DataBlock;
252use crate::encoder::EncodedBatch;
253use crate::encodings::logical::fixed_size_list::StructuralFixedSizeListScheduler;
254use crate::encodings::logical::list::StructuralListScheduler;
255use crate::encodings::logical::map::StructuralMapScheduler;
256use crate::encodings::logical::primitive::StructuralPrimitiveFieldScheduler;
257use crate::encodings::logical::r#struct::{StructuralStructDecoder, StructuralStructScheduler};
258use crate::format::pb::{self, column_encoding};
259use crate::format::pb21;
260use crate::repdef::{CompositeRepDefUnraveler, RepDefUnraveler};
261use crate::{BufferScheduler, EncodingsIo};
262
263pub const CANDIDATE_BATCH_SIZES: [u32; 8] = [1, 4, 16, 64, 256, 1024, 4096, 16384];
266
267pub trait SchedulingJob: std::fmt::Debug {
268 fn schedule_next(
269 &mut self,
270 context: &mut SchedulerContext,
271 priority: &dyn PriorityRange,
272 ) -> Result<ScheduledScanLine>;
273
274 fn num_rows(&self) -> u64;
275}
276
277pub trait FieldScheduler: Send + Sync + std::fmt::Debug {
279 fn initialize<'a>(
280 &'a self,
281 filter: &'a FilterExpression,
282 context: &'a SchedulerContext,
283 ) -> BoxFuture<'a, Result<()>>;
284
285 fn schedule_ranges<'a>(
286 &'a self,
287 ranges: &[Range<u64>],
288 filter: &FilterExpression,
289 ) -> Result<Box<dyn SchedulingJob + 'a>>;
290
291 fn num_rows(&self) -> u64;
292}
293
294#[derive(Debug)]
295pub struct DecoderReady {
296 pub decoder: Box<dyn LogicalPageDecoder>,
297 pub path: VecDeque<u32>,
298}
299
300pub trait LogicalPageDecoder: std::fmt::Debug + Send {
302 fn accept_child(&mut self, _child: DecoderReady) -> Result<()> {
303 Err(Error::internal(format!(
304 "The decoder {:?} does not expect children but received a child",
305 self
306 )))
307 }
308
309 fn wait_for_loaded(&'_ mut self, loaded_need: u64) -> BoxFuture<'_, Result<()>>;
310
311 fn rows_loaded(&self) -> u64;
312
313 fn rows_unloaded(&self) -> u64 {
314 self.num_rows() - self.rows_loaded()
315 }
316
317 fn num_rows(&self) -> u64;
318
319 fn rows_drained(&self) -> u64;
320
321 fn rows_left(&self) -> u64 {
322 self.num_rows() - self.rows_drained()
323 }
324
325 fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
326
327 fn max_rows_to_drain(&self, num_rows: u64, _byte_budget: u64) -> Result<DrainLimit> {
335 Ok(DrainLimit {
336 rows: num_rows,
337 bytes: 0,
338 })
339 }
340
341 fn data_type(&self) -> &DataType;
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub struct DrainLimit {
347 pub rows: u64,
349 pub bytes: u64,
352}
353
354pub(crate) const I32_OFFSET_BYTE_BUDGET: u64 = i32::MAX as u64;
359
360fn clamp_rows_to_i32_budget(limit: DrainLimit, to_take: u64) -> u64 {
367 if to_take == 0 {
368 return 0;
369 }
370 limit.rows.clamp(1, to_take)
371}
372
373pub(crate) fn has_i32_offsets(data_type: &DataType) -> bool {
375 match data_type {
376 DataType::Binary | DataType::Utf8 | DataType::List(_) | DataType::ListView(_) => true,
377 DataType::Map(_, _) => true,
378 DataType::LargeList(field)
379 | DataType::LargeListView(field)
380 | DataType::FixedSizeList(field, _) => has_i32_offsets(field.data_type()),
381 DataType::Struct(fields) => fields
382 .iter()
383 .any(|field| has_i32_offsets(field.data_type())),
384 DataType::Dictionary(_, values) => has_i32_offsets(values),
385 DataType::RunEndEncoded(_, values) => has_i32_offsets(values.data_type()),
386 _ => false,
387 }
388}
389
390const BATCH_SIZE_BYTES_WARNING: u64 = 10 * 1024 * 1024;
392const ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE: &str =
393 "LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE";
394const ENV_LANCE_READ_CACHE_REPETITION_INDEX: &str = "LANCE_READ_CACHE_REPETITION_INDEX";
395const ENV_LANCE_INLINE_SCHEDULING_THRESHOLD: &str = "LANCE_INLINE_SCHEDULING_THRESHOLD";
396
397const DEFAULT_INLINE_SCHEDULING_THRESHOLD: u64 = 16 * 1024;
400
401fn default_cache_repetition_index() -> bool {
402 static DEFAULT_CACHE_REPETITION_INDEX: OnceLock<bool> = OnceLock::new();
403 *DEFAULT_CACHE_REPETITION_INDEX
404 .get_or_init(|| parse_env_as_bool(ENV_LANCE_READ_CACHE_REPETITION_INDEX, true))
405}
406
407fn inline_scheduling_threshold() -> u64 {
408 static THRESHOLD: OnceLock<u64> = OnceLock::new();
409 *THRESHOLD.get_or_init(|| {
410 std::env::var(ENV_LANCE_INLINE_SCHEDULING_THRESHOLD)
411 .ok()
412 .and_then(|v| v.trim().parse::<u64>().ok())
413 .unwrap_or(DEFAULT_INLINE_SCHEDULING_THRESHOLD)
414 })
415}
416
417#[derive(Debug, Clone)]
424pub enum PageEncoding {
425 Legacy(pb::ArrayEncoding),
426 Structural(pb21::PageLayout),
427}
428
429impl DeepSizeOf for PageEncoding {
430 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
431 match self {
432 Self::Legacy(encoding) => encoding.encoded_len() * 4,
433 Self::Structural(encoding) => encoding.encoded_len() * 4,
434 }
435 }
436}
437
438impl PageEncoding {
439 pub fn as_legacy(&self) -> &pb::ArrayEncoding {
440 match self {
441 Self::Legacy(enc) => enc,
442 Self::Structural(_) => panic!("Expected a legacy encoding"),
443 }
444 }
445
446 pub fn as_structural(&self) -> &pb21::PageLayout {
447 match self {
448 Self::Structural(enc) => enc,
449 Self::Legacy(_) => panic!("Expected a structural encoding"),
450 }
451 }
452
453 pub fn is_structural(&self) -> bool {
454 matches!(self, Self::Structural(_))
455 }
456}
457
458#[derive(Debug)]
462pub struct PageInfo {
463 pub num_rows: u64,
465 pub priority: u64,
469 pub encoding: PageEncoding,
471 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
473}
474
475impl DeepSizeOf for PageInfo {
476 fn deep_size_of_children(&self, context: &mut Context) -> usize {
477 self.encoding.deep_size_of_children(context)
478 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
479 }
480}
481
482#[derive(Debug, Clone)]
486pub struct ColumnInfo {
487 pub index: u32,
489 pub page_infos: Arc<[PageInfo]>,
491 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
493 pub encoding: pb::ColumnEncoding,
494}
495
496impl DeepSizeOf for ColumnInfo {
497 fn deep_size_of_children(&self, context: &mut Context) -> usize {
498 self.page_infos.deep_size_of_children(context)
499 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
500 + self.encoding.encoded_len() * 4
501 }
502}
503
504impl ColumnInfo {
505 pub fn new(
507 index: u32,
508 page_infos: Arc<[PageInfo]>,
509 buffer_offsets_and_sizes: Vec<(u64, u64)>,
510 encoding: pb::ColumnEncoding,
511 ) -> Self {
512 Self {
513 index,
514 page_infos,
515 buffer_offsets_and_sizes: buffer_offsets_and_sizes.into_boxed_slice().into(),
516 encoding,
517 }
518 }
519
520 pub fn is_structural(&self) -> bool {
521 self.page_infos
522 .first()
524 .map(|page| page.encoding.is_structural())
525 .unwrap_or(false)
526 }
527}
528
529enum RootScheduler {
530 Structural(Box<dyn StructuralFieldScheduler>),
531 Array(Arc<dyn FieldScheduler>),
532}
533
534impl RootScheduler {
535 fn as_array(&self) -> &Arc<dyn FieldScheduler> {
536 match self {
537 Self::Structural(_) => panic!("Expected an array scheduler"),
538 Self::Array(s) => s,
539 }
540 }
541
542 fn as_structural(&self) -> &dyn StructuralFieldScheduler {
543 match self {
544 Self::Structural(s) => s.as_ref(),
545 Self::Array(_) => panic!("Expected a structural scheduler"),
546 }
547 }
548}
549
550pub struct DecodeBatchScheduler {
572 root_scheduler: RootScheduler,
573 pub root_fields: Fields,
574 cache: Arc<LanceCache>,
575}
576
577pub struct ColumnInfoIter<'a> {
578 column_infos: Vec<Arc<ColumnInfo>>,
579 column_indices: &'a [u32],
580 column_info_pos: usize,
581 column_indices_pos: usize,
582}
583
584impl<'a> ColumnInfoIter<'a> {
585 pub fn new(column_infos: Vec<Arc<ColumnInfo>>, column_indices: &'a [u32]) -> Self {
586 let initial_pos = column_indices.first().copied().unwrap_or(0) as usize;
587 Self {
588 column_infos,
589 column_indices,
590 column_info_pos: initial_pos,
591 column_indices_pos: 0,
592 }
593 }
594
595 pub fn peek(&self) -> &Arc<ColumnInfo> {
596 &self.column_infos[self.column_info_pos]
597 }
598
599 pub fn peek_transform(&mut self, transform: impl FnOnce(Arc<ColumnInfo>) -> Arc<ColumnInfo>) {
600 let column_info = self.column_infos[self.column_info_pos].clone();
601 let transformed = transform(column_info);
602 self.column_infos[self.column_info_pos] = transformed;
603 }
604
605 pub fn expect_next(&mut self) -> Result<&Arc<ColumnInfo>> {
606 self.next().ok_or_else(|| {
607 Error::invalid_input(
608 "there were more fields in the schema than provided column indices / infos",
609 )
610 })
611 }
612
613 fn next(&mut self) -> Option<&Arc<ColumnInfo>> {
614 if self.column_info_pos < self.column_infos.len() {
615 let info = &self.column_infos[self.column_info_pos];
616 self.column_info_pos += 1;
617 Some(info)
618 } else {
619 None
620 }
621 }
622
623 pub(crate) fn next_top_level(&mut self) {
624 self.column_indices_pos += 1;
625 if self.column_indices_pos < self.column_indices.len() {
626 self.column_info_pos = self.column_indices[self.column_indices_pos] as usize;
627 } else {
628 self.column_info_pos = self.column_infos.len();
629 }
630 }
631}
632
633#[derive(Clone, Copy, Debug)]
635pub struct FileBuffers<'a> {
636 pub positions_and_sizes: &'a [(u64, u64)],
637}
638
639#[derive(Clone, Copy, Debug)]
641pub struct ColumnBuffers<'a, 'b> {
642 pub file_buffers: FileBuffers<'a>,
643 pub positions_and_sizes: &'b [(u64, u64)],
644}
645
646#[derive(Clone, Copy, Debug)]
648pub struct PageBuffers<'a, 'b, 'c> {
649 pub column_buffers: ColumnBuffers<'a, 'b>,
650 pub positions_and_sizes: &'c [(u64, u64)],
651}
652
653#[derive(Debug)]
655pub struct CoreFieldDecoderStrategy {
656 pub validate_data: bool,
657 pub decompressor_strategy: Arc<dyn DecompressionStrategy>,
658 pub cache_repetition_index: bool,
659}
660
661impl Default for CoreFieldDecoderStrategy {
662 fn default() -> Self {
663 Self {
664 validate_data: false,
665 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
666 cache_repetition_index: false,
667 }
668 }
669}
670
671impl CoreFieldDecoderStrategy {
672 pub fn with_cache_repetition_index(mut self, cache_repetition_index: bool) -> Self {
674 self.cache_repetition_index = cache_repetition_index;
675 self
676 }
677
678 pub fn from_decoder_config(config: &DecoderConfig) -> Self {
680 Self {
681 validate_data: config.validate_on_decode,
682 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
683 cache_repetition_index: config.cache_repetition_index,
684 }
685 }
686
687 fn ensure_values_encoded(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
690 let column_encoding = column_info
691 .encoding
692 .column_encoding
693 .as_ref()
694 .ok_or_else(|| {
695 Error::invalid_input(format!(
696 "the column at index {} was missing a ColumnEncoding",
697 column_info.index
698 ))
699 })?;
700 if matches!(
701 column_encoding,
702 pb::column_encoding::ColumnEncoding::Values(_)
703 ) {
704 Ok(())
705 } else {
706 Err(Error::invalid_input(format!(
707 "the column at index {} mapping to the input field {} has column encoding {:?} and no decoder is registered to handle it",
708 column_info.index, field_name, column_encoding
709 )))
710 }
711 }
712
713 fn is_structural_primitive(data_type: &DataType) -> bool {
714 if data_type.is_primitive() {
715 true
716 } else {
717 match data_type {
718 DataType::Dictionary(_, value_type) => Self::is_structural_primitive(value_type),
720 DataType::Boolean
721 | DataType::Null
722 | DataType::FixedSizeBinary(_)
723 | DataType::Binary
724 | DataType::LargeBinary
725 | DataType::Utf8
726 | DataType::LargeUtf8 => true,
727 DataType::FixedSizeList(inner, _) => {
728 Self::is_structural_primitive(inner.data_type())
729 }
730 _ => false,
731 }
732 }
733 }
734
735 fn is_array_primitive(data_type: &DataType) -> bool {
736 if data_type.is_primitive() {
737 true
738 } else {
739 match data_type {
740 DataType::Boolean | DataType::Null | DataType::FixedSizeBinary(_) => true,
742 DataType::FixedSizeList(inner, _) => Self::is_array_primitive(inner.data_type()),
743 _ => false,
744 }
745 }
746 }
747
748 fn create_primitive_scheduler(
749 &self,
750 field: &Field,
751 column: &ColumnInfo,
752 buffers: FileBuffers,
753 ) -> Result<Box<dyn FieldScheduler>> {
754 Self::ensure_values_encoded(column, &field.name)?;
755 let column_buffers = ColumnBuffers {
757 file_buffers: buffers,
758 positions_and_sizes: &column.buffer_offsets_and_sizes,
759 };
760 Ok(Box::new(PrimitiveFieldScheduler::new(
761 column.index,
762 field.data_type(),
763 column.page_infos.clone(),
764 column_buffers,
765 self.validate_data,
766 )))
767 }
768
769 fn check_simple_struct(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
771 Self::ensure_values_encoded(column_info, field_name)?;
772 if column_info.page_infos.len() != 1 {
773 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()));
774 }
775 let encoding = &column_info.page_infos[0].encoding;
776 match encoding.as_legacy().array_encoding.as_ref().unwrap() {
777 pb::array_encoding::ArrayEncoding::Struct(_) => Ok(()),
778 _ => 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())),
779 }
780 }
781
782 fn check_packed_struct(column_info: &ColumnInfo) -> bool {
783 let encoding = &column_info.page_infos[0].encoding;
784 matches!(
785 encoding.as_legacy().array_encoding.as_ref().unwrap(),
786 pb::array_encoding::ArrayEncoding::PackedStruct(_)
787 )
788 }
789
790 fn create_list_scheduler(
791 &self,
792 list_field: &Field,
793 column_infos: &mut ColumnInfoIter,
794 buffers: FileBuffers,
795 offsets_column: &ColumnInfo,
796 ) -> Result<Box<dyn FieldScheduler>> {
797 Self::ensure_values_encoded(offsets_column, &list_field.name)?;
798 let offsets_column_buffers = ColumnBuffers {
799 file_buffers: buffers,
800 positions_and_sizes: &offsets_column.buffer_offsets_and_sizes,
801 };
802 let items_scheduler =
803 self.create_array_field_scheduler(&list_field.children[0], column_infos, buffers)?;
804
805 let mut inner_infos = Vec::with_capacity(offsets_column.page_infos.len());
806 let mut null_offset_adjustments = Vec::with_capacity(offsets_column.page_infos.len());
807 for (page_index, offsets_page) in offsets_column
808 .page_infos
809 .iter()
810 .enumerate()
811 .filter(|(_, offsets_page)| offsets_page.num_rows > 0)
812 {
813 let PageEncoding::Legacy(pb::ArrayEncoding {
814 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(list_encoding)),
815 }) = &offsets_page.encoding
816 else {
817 return Err(Error::invalid_input(format!(
818 "expected list encoding for field '{}' in column {}, page {} but got {:?}",
819 list_field.name, offsets_column.index, page_index, offsets_page.encoding
820 )));
821 };
822 let offsets_encoding = list_encoding.offsets.as_ref().ok_or_else(|| {
823 Error::invalid_input(format!(
824 "list encoding for field '{}' in column {}, page {} is missing its offsets encoding",
825 list_field.name, offsets_column.index, page_index
826 ))
827 })?;
828 inner_infos.push(PageInfo {
829 buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
830 encoding: PageEncoding::Legacy(offsets_encoding.as_ref().clone()),
831 num_rows: offsets_page.num_rows,
832 priority: 0,
833 });
834 null_offset_adjustments.push(OffsetPageInfo {
835 offsets_in_page: offsets_page.num_rows,
836 null_offset_adjustment: list_encoding.null_offset_adjustment,
837 num_items_referenced_by_page: list_encoding.num_items,
838 });
839 }
840 let inner = Arc::new(PrimitiveFieldScheduler::new(
841 offsets_column.index,
842 DataType::UInt64,
843 Arc::from(inner_infos.into_boxed_slice()),
844 offsets_column_buffers,
845 self.validate_data,
846 )) as Arc<dyn FieldScheduler>;
847 let items_field = match list_field.data_type() {
848 DataType::List(inner) => inner,
849 DataType::LargeList(inner) => inner,
850 _ => unreachable!(),
851 };
852 let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
853 DataType::Int32
854 } else {
855 DataType::Int64
856 };
857 Ok(Box::new(ListFieldScheduler::new(
858 inner,
859 items_scheduler.into(),
860 items_field,
861 offset_type,
862 null_offset_adjustments,
863 )))
864 }
865
866 fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
867 if let column_encoding::ColumnEncoding::Blob(blob) =
868 column_info.encoding.column_encoding.as_ref().unwrap()
869 {
870 let mut column_info = column_info.clone();
871 column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
872 Some(column_info)
873 } else {
874 None
875 }
876 }
877
878 fn create_structural_field_scheduler(
879 &self,
880 field: &Field,
881 column_infos: &mut ColumnInfoIter,
882 ) -> Result<Box<dyn StructuralFieldScheduler>> {
883 let data_type = field.data_type();
884 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
885 if Self::is_structural_primitive(&data_type) {
886 let column_info = column_infos.expect_next()?;
887 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
888 column_info.clone(),
889 self.decompressor_strategy.clone(),
890 self.cache_repetition_index,
891 field,
892 )?);
893
894 column_infos.next_top_level();
896
897 return Ok(scheduler);
898 }
899 match &data_type {
900 DataType::Struct(fields) => {
901 if field.is_packed_struct() {
902 let column_info = column_infos.expect_next()?;
904 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
905 column_info.clone(),
906 self.decompressor_strategy.clone(),
907 self.cache_repetition_index,
908 field,
909 )?);
910
911 column_infos.next_top_level();
913
914 return Ok(scheduler);
915 }
916 if field.is_blob() {
918 let column_info = column_infos.peek();
919 if column_info.page_infos.iter().any(|page| {
920 matches!(
921 page.encoding,
922 PageEncoding::Structural(pb21::PageLayout {
923 layout: Some(pb21::page_layout::Layout::BlobLayout(_))
924 })
925 )
926 }) {
927 let column_info = column_infos.expect_next()?;
928 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
929 column_info.clone(),
930 self.decompressor_strategy.clone(),
931 self.cache_repetition_index,
932 field,
933 )?);
934 column_infos.next_top_level();
935 return Ok(scheduler);
936 }
937 }
938
939 let mut child_schedulers = Vec::with_capacity(field.children.len());
940 for field in field.children.iter() {
941 let field_scheduler =
942 self.create_structural_field_scheduler(field, column_infos)?;
943 child_schedulers.push(field_scheduler);
944 }
945
946 let fields = fields.clone();
947 Ok(
948 Box::new(StructuralStructScheduler::new(child_schedulers, fields))
949 as Box<dyn StructuralFieldScheduler>,
950 )
951 }
952 DataType::List(_) | DataType::LargeList(_) => {
953 let child = field.children.first().expect_ok()?;
954 let child_scheduler =
955 self.create_structural_field_scheduler(child, column_infos)?;
956 Ok(Box::new(StructuralListScheduler::new(child_scheduler))
957 as Box<dyn StructuralFieldScheduler>)
958 }
959 DataType::FixedSizeList(inner, dimension)
960 if matches!(inner.data_type(), DataType::Struct(_)) =>
961 {
962 let child = field.children.first().expect_ok()?;
963 let child_scheduler =
964 self.create_structural_field_scheduler(child, column_infos)?;
965 Ok(Box::new(StructuralFixedSizeListScheduler::new(
966 child_scheduler,
967 *dimension,
968 )) as Box<dyn StructuralFieldScheduler>)
969 }
970 DataType::Map(_, keys_sorted) => {
971 if *keys_sorted {
975 return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
976 }
977 let entries_child = field.children.first().expect_ok()?;
978 let child_scheduler =
979 self.create_structural_field_scheduler(entries_child, column_infos)?;
980 Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
981 as Box<dyn StructuralFieldScheduler>)
982 }
983 _ => todo!("create_structural_field_scheduler for {}", data_type),
984 }
985 }
986
987 fn create_array_field_scheduler(
988 &self,
989 field: &Field,
990 column_infos: &mut ColumnInfoIter,
991 buffers: FileBuffers,
992 ) -> Result<Box<dyn FieldScheduler>> {
993 let data_type = field.data_type();
994 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
995 if Self::is_array_primitive(&data_type) {
996 let column_info = column_infos.expect_next()?;
997 let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
998 return Ok(scheduler);
999 } else if data_type.is_binary_like() {
1000 let column_info = column_infos.expect_next()?.clone();
1001 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1003 let desc_scheduler =
1004 self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
1005 let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
1006 return Ok(blob_scheduler);
1007 }
1008 if let Some(page_info) = column_info.page_infos.first() {
1009 if matches!(
1010 page_info.encoding.as_legacy(),
1011 pb::ArrayEncoding {
1012 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
1013 }
1014 ) {
1015 let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
1016 DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
1017 } else {
1018 DataType::LargeList(Arc::new(ArrowField::new(
1019 "item",
1020 DataType::UInt8,
1021 false,
1022 )))
1023 };
1024 let list_field = Field::try_from(ArrowField::new(
1025 field.name.clone(),
1026 list_type,
1027 field.nullable,
1028 ))
1029 .unwrap();
1030 let list_scheduler = self.create_list_scheduler(
1031 &list_field,
1032 column_infos,
1033 buffers,
1034 &column_info,
1035 )?;
1036 let binary_scheduler = Box::new(BinaryFieldScheduler::new(
1037 list_scheduler.into(),
1038 field.data_type(),
1039 ));
1040 return Ok(binary_scheduler);
1041 } else {
1042 let scheduler =
1043 self.create_primitive_scheduler(field, &column_info, buffers)?;
1044 return Ok(scheduler);
1045 }
1046 } else {
1047 return self.create_primitive_scheduler(field, &column_info, buffers);
1048 }
1049 }
1050 match &data_type {
1051 DataType::FixedSizeList(inner, _dimension) => {
1052 if Self::is_array_primitive(inner.data_type()) {
1055 let primitive_col = column_infos.expect_next()?;
1056 let scheduler =
1057 self.create_primitive_scheduler(field, primitive_col, buffers)?;
1058 Ok(scheduler)
1059 } else {
1060 todo!()
1061 }
1062 }
1063 DataType::Dictionary(_key_type, value_type) => {
1064 if Self::is_array_primitive(value_type) || value_type.is_binary_like() {
1065 let primitive_col = column_infos.expect_next()?;
1066 let scheduler =
1067 self.create_primitive_scheduler(field, primitive_col, buffers)?;
1068 Ok(scheduler)
1069 } else {
1070 Err(Error::not_supported_source(
1071 format!(
1072 "No way to decode into a dictionary field of type {}",
1073 value_type
1074 )
1075 .into(),
1076 ))
1077 }
1078 }
1079 DataType::List(_) | DataType::LargeList(_) => {
1080 let offsets_column = column_infos.expect_next()?.clone();
1081 column_infos.next_top_level();
1082 self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
1083 }
1084 DataType::Struct(fields) => {
1085 let column_info = column_infos.expect_next()?;
1086
1087 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1089 return self.create_primitive_scheduler(field, &blob_col, buffers);
1091 }
1092
1093 if Self::check_packed_struct(column_info) {
1094 self.create_primitive_scheduler(field, column_info, buffers)
1096 } else {
1097 Self::check_simple_struct(column_info, &field.name).unwrap();
1099 let num_rows = column_info
1100 .page_infos
1101 .iter()
1102 .map(|page| page.num_rows)
1103 .sum();
1104 let mut child_schedulers = Vec::with_capacity(field.children.len());
1105 for field in &field.children {
1106 column_infos.next_top_level();
1107 let field_scheduler =
1108 self.create_array_field_scheduler(field, column_infos, buffers)?;
1109 child_schedulers.push(Arc::from(field_scheduler));
1110 }
1111
1112 let fields = fields.clone();
1113 Ok(Box::new(SimpleStructScheduler::new(
1114 child_schedulers,
1115 fields,
1116 num_rows,
1117 )))
1118 }
1119 }
1120 _ => todo!(),
1122 }
1123 }
1124}
1125
1126fn root_column(num_rows: u64) -> ColumnInfo {
1128 let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
1129 let final_page_num_rows = num_rows % (u32::MAX as u64);
1130 let root_pages = (0..num_root_pages)
1131 .map(|i| PageInfo {
1132 num_rows: if i == num_root_pages - 1 {
1133 final_page_num_rows
1134 } else {
1135 u64::MAX
1136 },
1137 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1138 array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1139 pb::SimpleStruct {},
1140 )),
1141 }),
1142 priority: 0, buffer_offsets_and_sizes: Arc::new([]),
1144 })
1145 .collect::<Vec<_>>();
1146 ColumnInfo {
1147 buffer_offsets_and_sizes: Arc::new([]),
1148 encoding: pb::ColumnEncoding {
1149 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1150 },
1151 index: u32::MAX,
1152 page_infos: Arc::from(root_pages),
1153 }
1154}
1155
1156pub enum RootDecoder {
1157 Structural(StructuralStructDecoder),
1158 Array(SimpleStructDecoder),
1159}
1160
1161impl RootDecoder {
1162 pub fn into_structural(self) -> StructuralStructDecoder {
1163 match self {
1164 Self::Structural(decoder) => decoder,
1165 Self::Array(_) => panic!("Expected a structural decoder"),
1166 }
1167 }
1168
1169 pub fn into_array(self) -> SimpleStructDecoder {
1170 match self {
1171 Self::Array(decoder) => decoder,
1172 Self::Structural(_) => panic!("Expected an array decoder"),
1173 }
1174 }
1175}
1176
1177impl DecodeBatchScheduler {
1178 #[allow(clippy::too_many_arguments)]
1181 pub async fn try_new<'a>(
1182 schema: &'a Schema,
1183 column_indices: &[u32],
1184 column_infos: &[Arc<ColumnInfo>],
1185 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1186 num_rows: u64,
1187 decoder_plugins: Arc<DecoderPlugins>,
1188 io: Arc<dyn EncodingsIo>,
1189 cache: Arc<LanceCache>,
1190 filter: &FilterExpression,
1191 decoder_config: &DecoderConfig,
1192 ) -> Result<Self> {
1193 Self::try_new_with_ranges(
1194 schema,
1195 column_indices,
1196 column_infos,
1197 file_buffer_positions_and_sizes,
1198 num_rows,
1199 decoder_plugins,
1200 io,
1201 cache,
1202 None,
1203 filter,
1204 decoder_config,
1205 )
1206 .await
1207 }
1208
1209 #[allow(clippy::too_many_arguments)]
1211 pub(crate) async fn try_new_with_ranges<'a>(
1212 schema: &'a Schema,
1213 column_indices: &[u32],
1214 column_infos: &[Arc<ColumnInfo>],
1215 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1216 num_rows: u64,
1217 _decoder_plugins: Arc<DecoderPlugins>,
1218 io: Arc<dyn EncodingsIo>,
1219 cache: Arc<LanceCache>,
1220 requested_ranges: Option<&[Range<u64>]>,
1224 filter: &FilterExpression,
1225 decoder_config: &DecoderConfig,
1226 ) -> Result<Self> {
1227 assert!(num_rows > 0);
1228 let buffers = FileBuffers {
1229 positions_and_sizes: file_buffer_positions_and_sizes,
1230 };
1231 let arrow_schema = ArrowSchema::from(schema);
1232 let root_fields = arrow_schema.fields().clone();
1233 let root_type = DataType::Struct(root_fields.clone());
1234 let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1235 root_field.children.clone_from(&schema.fields);
1239 root_field
1240 .metadata
1241 .insert("__lance_decoder_root".to_string(), "true".to_string());
1242
1243 if column_infos.is_empty() || column_infos[0].is_structural() {
1244 let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1245
1246 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1247 let mut root_scheduler =
1248 strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1249
1250 let context = SchedulerContext::new(io, cache.clone());
1251 root_scheduler
1252 .initialize(requested_ranges, filter, &context)
1253 .await?;
1254
1255 Ok(Self {
1256 root_scheduler: RootScheduler::Structural(root_scheduler),
1257 root_fields,
1258 cache,
1259 })
1260 } else {
1261 let mut columns = Vec::with_capacity(column_infos.len() + 1);
1264 columns.push(Arc::new(root_column(num_rows)));
1265 columns.extend(column_infos.iter().cloned());
1266
1267 let adjusted_column_indices = [0_u32]
1268 .into_iter()
1269 .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1270 .collect::<Vec<_>>();
1271 let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1272 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1273 let root_scheduler =
1274 strategy.create_array_field_scheduler(&root_field, &mut column_iter, buffers)?;
1275
1276 let context = SchedulerContext::new(io, cache.clone());
1277 root_scheduler.initialize(filter, &context).await?;
1278
1279 Ok(Self {
1280 root_scheduler: RootScheduler::Array(root_scheduler.into()),
1281 root_fields,
1282 cache,
1283 })
1284 }
1285 }
1286
1287 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
1288 pub fn from_scheduler(
1289 root_scheduler: Arc<dyn FieldScheduler>,
1290 root_fields: Fields,
1291 cache: Arc<LanceCache>,
1292 ) -> Self {
1293 Self {
1294 root_scheduler: RootScheduler::Array(root_scheduler),
1295 root_fields,
1296 cache,
1297 }
1298 }
1299
1300 fn do_schedule_ranges_structural(
1301 &mut self,
1302 ranges: &[Range<u64>],
1303 filter: &FilterExpression,
1304 io: Arc<dyn EncodingsIo>,
1305 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1306 ) {
1307 let root_scheduler = self.root_scheduler.as_structural();
1308 let mut context = SchedulerContext::new(io, self.cache.clone());
1309 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1310 if let Err(schedule_ranges_err) = maybe_root_job {
1311 schedule_action(Err(schedule_ranges_err));
1312 return;
1313 }
1314 let mut root_job = maybe_root_job.unwrap();
1315 let mut num_rows_scheduled = 0;
1316 loop {
1317 let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1318 if let Err(err) = maybe_next_scan_lines {
1319 schedule_action(Err(err));
1320 return;
1321 }
1322 let next_scan_lines = maybe_next_scan_lines.unwrap();
1323 context.flush_io();
1324 if next_scan_lines.is_empty() {
1325 return;
1326 }
1327 for next_scan_line in next_scan_lines {
1328 trace!(
1329 "Scheduled scan line of {} rows and {} decoders",
1330 next_scan_line.rows_scheduled,
1331 next_scan_line.decoders.len()
1332 );
1333 num_rows_scheduled += next_scan_line.rows_scheduled;
1334 if !schedule_action(Ok(DecoderMessage {
1335 scheduled_so_far: num_rows_scheduled,
1336 decoders: next_scan_line.decoders,
1337 })) {
1338 return;
1340 }
1341 }
1342 }
1343 }
1344
1345 fn do_schedule_ranges_array(
1346 &mut self,
1347 ranges: &[Range<u64>],
1348 filter: &FilterExpression,
1349 io: Arc<dyn EncodingsIo>,
1350 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1351 priority: Option<Box<dyn PriorityRange>>,
1355 ) {
1356 let root_scheduler = self.root_scheduler.as_array();
1357 let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1358 trace!(
1359 "Scheduling {} ranges across {}..{} ({} rows){}",
1360 ranges.len(),
1361 ranges.first().unwrap().start,
1362 ranges.last().unwrap().end,
1363 rows_requested,
1364 priority
1365 .as_ref()
1366 .map(|p| format!(" (priority={:?})", p))
1367 .unwrap_or_default()
1368 );
1369
1370 let mut context = SchedulerContext::new(io, self.cache.clone());
1371 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1372 if let Err(schedule_ranges_err) = maybe_root_job {
1373 schedule_action(Err(schedule_ranges_err));
1374 return;
1375 }
1376 let mut root_job = maybe_root_job.unwrap();
1377 let mut num_rows_scheduled = 0;
1378 let mut rows_to_schedule = root_job.num_rows();
1379 let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1380 trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1381 while rows_to_schedule > 0 {
1382 let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1383 if let Err(schedule_next_err) = maybe_next_scan_line {
1384 schedule_action(Err(schedule_next_err));
1385 return;
1386 }
1387 let next_scan_line = maybe_next_scan_line.unwrap();
1388 context.flush_io();
1389 priority.advance(next_scan_line.rows_scheduled);
1390 num_rows_scheduled += next_scan_line.rows_scheduled;
1391 rows_to_schedule -= next_scan_line.rows_scheduled;
1392 trace!(
1393 "Scheduled scan line of {} rows and {} decoders",
1394 next_scan_line.rows_scheduled,
1395 next_scan_line.decoders.len()
1396 );
1397 if !schedule_action(Ok(DecoderMessage {
1398 scheduled_so_far: num_rows_scheduled,
1399 decoders: next_scan_line.decoders,
1400 })) {
1401 return;
1403 }
1404
1405 trace!("Finished scheduling {} ranges", ranges.len());
1406 }
1407 }
1408
1409 fn do_schedule_ranges(
1410 &mut self,
1411 ranges: &[Range<u64>],
1412 filter: &FilterExpression,
1413 io: Arc<dyn EncodingsIo>,
1414 schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1415 priority: Option<Box<dyn PriorityRange>>,
1419 ) {
1420 match &self.root_scheduler {
1421 RootScheduler::Array(_) => {
1422 self.do_schedule_ranges_array(ranges, filter, io, schedule_action, priority)
1423 }
1424 RootScheduler::Structural(_) => {
1425 self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1426 }
1427 }
1428 }
1429
1430 pub fn schedule_ranges_to_vec(
1433 &mut self,
1434 ranges: &[Range<u64>],
1435 filter: &FilterExpression,
1436 io: Arc<dyn EncodingsIo>,
1437 priority: Option<Box<dyn PriorityRange>>,
1438 ) -> Result<Vec<DecoderMessage>> {
1439 let mut decode_messages = Vec::new();
1440 self.do_schedule_ranges(
1441 ranges,
1442 filter,
1443 io,
1444 |msg| {
1445 decode_messages.push(msg);
1446 true
1447 },
1448 priority,
1449 );
1450 decode_messages.into_iter().collect::<Result<Vec<_>>>()
1451 }
1452
1453 #[instrument(level = "debug", skip_all)]
1463 pub fn schedule_ranges(
1464 &mut self,
1465 ranges: &[Range<u64>],
1466 filter: &FilterExpression,
1467 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1468 scheduler: Arc<dyn EncodingsIo>,
1469 ) {
1470 self.do_schedule_ranges(
1471 ranges,
1472 filter,
1473 scheduler,
1474 |msg| {
1475 match sink.send(msg) {
1476 Ok(_) => true,
1477 Err(SendError { .. }) => {
1478 debug!(
1481 "schedule_ranges aborting early since decoder appears to have been dropped"
1482 );
1483 false
1484 }
1485 }
1486 },
1487 None,
1488 )
1489 }
1490
1491 #[instrument(level = "debug", skip_all)]
1499 pub fn schedule_range(
1500 &mut self,
1501 range: Range<u64>,
1502 filter: &FilterExpression,
1503 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1504 scheduler: Arc<dyn EncodingsIo>,
1505 ) {
1506 self.schedule_ranges(&[range], filter, sink, scheduler)
1507 }
1508
1509 pub fn schedule_take(
1517 &mut self,
1518 indices: &[u64],
1519 filter: &FilterExpression,
1520 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1521 scheduler: Arc<dyn EncodingsIo>,
1522 ) {
1523 if indices.is_empty() {
1524 return;
1525 }
1526 trace!("Scheduling take of {} rows", indices.len());
1527 let ranges = match RequestedRows::indices_to_ranges(indices) {
1528 Ok(ranges) => ranges,
1529 Err(error) => {
1530 if let Err(SendError { .. }) = sink.send(Err(error)) {
1531 debug!(
1532 "schedule_take could not report invalid indices because the decoder was dropped"
1533 );
1534 }
1535 return;
1536 }
1537 };
1538 self.schedule_ranges(&ranges, filter, sink, scheduler)
1539 }
1540}
1541
1542pub struct ReadBatchTask {
1543 pub task: BoxFuture<'static, Result<RecordBatch>>,
1544 pub num_rows: u32,
1545}
1546
1547pub struct BatchDecodeStream {
1549 context: DecoderContext,
1550 root_decoder: SimpleStructDecoder,
1551 rows_remaining: u64,
1552 rows_per_batch: u32,
1553 rows_scheduled: u64,
1554 rows_drained: u64,
1555 scheduler_exhausted: bool,
1556 emitted_batch_size_warning: Arc<Once>,
1557 limit_i32_offset_batch_size: bool,
1558}
1559
1560impl BatchDecodeStream {
1561 pub fn new(
1571 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1572 rows_per_batch: u32,
1573 num_rows: u64,
1574 root_decoder: SimpleStructDecoder,
1575 ) -> Self {
1576 Self::new_with_i32_offset_limit(scheduled, rows_per_batch, num_rows, root_decoder, true)
1577 }
1578
1579 fn new_with_i32_offset_limit(
1580 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1581 rows_per_batch: u32,
1582 num_rows: u64,
1583 root_decoder: SimpleStructDecoder,
1584 limit_i32_offset_batch_size: bool,
1585 ) -> Self {
1586 Self {
1587 context: DecoderContext::new(scheduled),
1588 root_decoder,
1589 rows_remaining: num_rows,
1590 rows_per_batch,
1591 rows_scheduled: 0,
1592 rows_drained: 0,
1593 scheduler_exhausted: false,
1594 emitted_batch_size_warning: Arc::new(Once::new()),
1595 limit_i32_offset_batch_size,
1596 }
1597 }
1598
1599 fn accept_decoder(&mut self, decoder: DecoderReady) -> Result<()> {
1600 if decoder.path.is_empty() {
1601 Ok(())
1603 } else {
1604 self.root_decoder.accept_child(decoder)
1605 }
1606 }
1607
1608 #[instrument(level = "debug", skip_all)]
1609 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1610 if self.scheduler_exhausted {
1611 return Ok(self.rows_scheduled);
1612 }
1613 while self.rows_scheduled < scheduled_need {
1614 let next_message = self.context.source.recv().await;
1615 match next_message {
1616 Some(scan_line) => {
1617 let scan_line = scan_line?;
1618 self.rows_scheduled = scan_line.scheduled_so_far;
1619 for message in scan_line.decoders {
1620 self.accept_decoder(message.into_array())?;
1621 }
1622 }
1623 None => {
1624 self.scheduler_exhausted = true;
1628 return Ok(self.rows_scheduled);
1629 }
1630 }
1631 }
1632 Ok(scheduled_need)
1633 }
1634
1635 #[instrument(level = "debug", skip_all)]
1636 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1637 trace!(
1638 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1639 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1640 );
1641 if self.rows_remaining == 0 {
1642 return Ok(None);
1643 }
1644
1645 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1646 self.rows_remaining -= to_take;
1647
1648 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1649 trace!(
1650 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1651 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1652 );
1653 if scheduled_need > 0 {
1654 let desired_scheduled = scheduled_need + self.rows_scheduled;
1655 trace!(
1656 "Draining from scheduler (desire at least {} scheduled rows)",
1657 desired_scheduled
1658 );
1659 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1660 if actually_scheduled < desired_scheduled {
1661 let under_scheduled = desired_scheduled - actually_scheduled;
1662 to_take -= under_scheduled;
1663 }
1664 }
1665
1666 if to_take == 0 {
1667 return Ok(None);
1668 }
1669
1670 let loaded_need = self.rows_drained + to_take - 1;
1672 trace!(
1673 "Waiting for I/O (desire at least {} fully loaded rows)",
1674 loaded_need
1675 );
1676 self.root_decoder.wait_for_loaded(loaded_need).await?;
1677
1678 if self.limit_i32_offset_batch_size && has_i32_offsets(self.root_decoder.data_type()) {
1679 let limit = LogicalPageDecoder::max_rows_to_drain(
1680 &self.root_decoder,
1681 to_take,
1682 I32_OFFSET_BYTE_BUDGET,
1683 )?;
1684 let clamped = clamp_rows_to_i32_budget(limit, to_take);
1685 self.rows_remaining += to_take - clamped;
1686 to_take = clamped;
1687 }
1688
1689 let next_task = self.root_decoder.drain(to_take)?;
1690 self.rows_drained += to_take;
1691 Ok(Some(next_task))
1692 }
1693
1694 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1695 let stream = futures::stream::unfold(self, |mut slf| async move {
1696 let next_task = match slf.next_batch_task().await {
1697 Ok(Some(next_task)) => next_task,
1698 Ok(None) => return None,
1699 Err(err) => {
1700 slf.rows_remaining = 0;
1701 return Some((
1702 ReadBatchTask {
1703 task: async move { Err(err) }.boxed(),
1704 num_rows: 0,
1705 },
1706 slf,
1707 ));
1708 }
1709 };
1710 let num_rows = next_task.num_rows;
1711 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1712 let task = async move {
1713 let (batch, _data_size) =
1717 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1718 .await
1719 .map_err(|err| Error::wrapped(err.into()))??;
1720 Ok(batch)
1721 };
1722 debug_assert!(num_rows <= u32::MAX as u64);
1724 Some((
1725 ReadBatchTask {
1726 task: task.boxed(),
1727 num_rows: num_rows as u32,
1728 },
1729 slf,
1730 ))
1731 });
1732 stream.boxed()
1733 }
1734}
1735
1736enum RootDecoderMessage {
1739 LoadedPage(LoadedPageShard),
1740 ArrayPage(DecoderReady),
1741}
1742trait RootDecoderType {
1743 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1744 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1745 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1746 fn max_rows_to_drain(&self, num_rows: u64, _byte_budget: u64) -> Result<DrainLimit> {
1749 Ok(DrainLimit {
1750 rows: num_rows,
1751 bytes: 0,
1752 })
1753 }
1754}
1755impl RootDecoderType for StructuralStructDecoder {
1756 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1757 let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1758 unreachable!()
1759 };
1760 self.accept_page(loaded_page)
1761 }
1762 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1763 self.drain_batch_task(num_rows)
1764 }
1765 fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1766 Ok(())
1768 }
1769}
1770impl RootDecoderType for SimpleStructDecoder {
1771 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1772 let RootDecoderMessage::ArrayPage(array_page) = message else {
1773 unreachable!()
1774 };
1775 self.accept_child(array_page)
1776 }
1777 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1778 self.drain(num_rows)
1779 }
1780 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1781 runtime.block_on(self.wait_for_loaded(loaded_need))
1782 }
1783 fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
1784 if !has_i32_offsets(LogicalPageDecoder::data_type(self)) {
1785 return Ok(DrainLimit {
1786 rows: num_rows,
1787 bytes: 0,
1788 });
1789 }
1790 LogicalPageDecoder::max_rows_to_drain(self, num_rows, byte_budget)
1791 }
1792}
1793
1794struct BatchDecodeIterator<T: RootDecoderType> {
1796 messages: VecDeque<Result<DecoderMessage>>,
1797 root_decoder: T,
1798 rows_remaining: u64,
1799 rows_per_batch: u32,
1800 rows_scheduled: u64,
1801 rows_drained: u64,
1802 emitted_batch_size_warning: Arc<Once>,
1803 wait_for_io_runtime: tokio::runtime::Runtime,
1807 schema: Arc<ArrowSchema>,
1808}
1809
1810impl<T: RootDecoderType> BatchDecodeIterator<T> {
1811 pub fn new(
1813 messages: VecDeque<Result<DecoderMessage>>,
1814 rows_per_batch: u32,
1815 num_rows: u64,
1816 root_decoder: T,
1817 schema: Arc<ArrowSchema>,
1818 ) -> Self {
1819 Self {
1820 messages,
1821 root_decoder,
1822 rows_remaining: num_rows,
1823 rows_per_batch,
1824 rows_scheduled: 0,
1825 rows_drained: 0,
1826 wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1827 .build()
1828 .unwrap(),
1829 emitted_batch_size_warning: Arc::new(Once::new()),
1830 schema,
1831 }
1832 }
1833
1834 fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1839 match maybe_done(unloaded_page.0) {
1840 MaybeDone::Done(loaded_page) => loaded_page,
1842 MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1844 MaybeDone::Gone => unreachable!(),
1845 }
1846 }
1847
1848 #[instrument(level = "debug", skip_all)]
1853 fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1854 while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1855 let message = self.messages.pop_front().unwrap()?;
1856 self.rows_scheduled = message.scheduled_so_far;
1857 for decoder_message in message.decoders {
1858 match decoder_message {
1859 MessageType::UnloadedPage(unloaded_page) => {
1860 let loaded_page = self.wait_for_page(unloaded_page)?;
1861 self.root_decoder
1862 .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1863 }
1864 MessageType::DecoderReady(decoder_ready) => {
1865 if !decoder_ready.path.is_empty() {
1867 self.root_decoder
1868 .accept_message(RootDecoderMessage::ArrayPage(decoder_ready))?;
1869 }
1870 }
1871 }
1872 }
1873 }
1874
1875 let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1876
1877 self.root_decoder
1878 .wait(loaded_need, &self.wait_for_io_runtime)?;
1879 Ok(self.rows_scheduled)
1880 }
1881
1882 #[instrument(level = "debug", skip_all)]
1883 fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1884 trace!(
1885 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1886 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1887 );
1888 if self.rows_remaining == 0 {
1889 return Ok(None);
1890 }
1891
1892 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1893 self.rows_remaining -= to_take;
1894
1895 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1896 trace!(
1897 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1898 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1899 );
1900 if scheduled_need > 0 {
1901 let desired_scheduled = scheduled_need + self.rows_scheduled;
1902 trace!(
1903 "Draining from scheduler (desire at least {} scheduled rows)",
1904 desired_scheduled
1905 );
1906 let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1907 if actually_scheduled < desired_scheduled {
1908 let under_scheduled = desired_scheduled - actually_scheduled;
1909 to_take -= under_scheduled;
1910 }
1911 }
1912
1913 if to_take == 0 {
1914 return Ok(None);
1915 }
1916
1917 let limit = self
1918 .root_decoder
1919 .max_rows_to_drain(to_take, I32_OFFSET_BYTE_BUDGET)?;
1920 let clamped = clamp_rows_to_i32_budget(limit, to_take);
1921 self.rows_remaining += to_take - clamped;
1922 to_take = clamped;
1923
1924 let next_task = self.root_decoder.drain_batch(to_take)?;
1925
1926 self.rows_drained += to_take;
1927
1928 let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1929
1930 Ok(Some(batch))
1931 }
1932}
1933
1934impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1935 type Item = ArrowResult<RecordBatch>;
1936
1937 fn next(&mut self) -> Option<Self::Item> {
1938 let next = self.next_batch_task().transpose();
1939 if let Some(Err(_)) = &next {
1940 self.rows_remaining = 0;
1942 }
1943 next.map(|r| r.map_err(ArrowError::from))
1944 }
1945}
1946
1947impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1948 fn schema(&self) -> Arc<ArrowSchema> {
1949 self.schema.clone()
1950 }
1951}
1952
1953pub fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1969 if let Some(w) = data_type.byte_width_opt() {
1970 return w as f64;
1971 }
1972 match data_type {
1973 DataType::Boolean => 1.0 / 8.0,
1974 DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1975 DataType::Struct(fields) => fields
1976 .iter()
1977 .map(|f| estimate_bytes_per_row(f.data_type()))
1978 .sum(),
1979 DataType::List(child) | DataType::LargeList(child) => {
1980 5.0 * estimate_bytes_per_row(child.data_type())
1981 }
1982 DataType::FixedSizeList(child, dim) => {
1983 *dim as f64 * estimate_bytes_per_row(child.data_type())
1984 }
1985 DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
1986 DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
1987 _ => 64.0,
1988 }
1989}
1990
1991pub struct StructuralBatchDecodeStream {
1993 context: DecoderContext,
1994 root_decoder: StructuralStructDecoder,
1995 rows_remaining: u64,
1996 rows_per_batch: u32,
1997 rows_scheduled: u64,
1998 rows_drained: u64,
1999 scheduler_exhausted: bool,
2000 emitted_batch_size_warning: Arc<Once>,
2001 spawn_batch_decode_tasks: bool,
2009 batch_size_bytes: Option<u64>,
2012 schema_bytes_per_row: f64,
2015 bytes_per_row_feedback: Arc<AtomicU64>,
2018}
2019
2020impl StructuralBatchDecodeStream {
2021 pub fn new(
2031 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2032 rows_per_batch: u32,
2033 num_rows: u64,
2034 root_decoder: StructuralStructDecoder,
2035 spawn_batch_decode_tasks: bool,
2036 batch_size_bytes: Option<u64>,
2037 ) -> Self {
2038 let schema_bytes_per_row = if batch_size_bytes.is_some() {
2039 estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
2040 } else {
2041 0.0
2042 };
2043 Self {
2044 context: DecoderContext::new(scheduled),
2045 root_decoder,
2046 rows_remaining: num_rows,
2047 rows_per_batch,
2048 rows_scheduled: 0,
2049 rows_drained: 0,
2050 scheduler_exhausted: false,
2051 emitted_batch_size_warning: Arc::new(Once::new()),
2052 spawn_batch_decode_tasks,
2053 batch_size_bytes,
2054 schema_bytes_per_row,
2055 bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
2056 }
2057 }
2058
2059 #[instrument(level = "debug", skip_all)]
2060 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
2061 if self.scheduler_exhausted {
2062 return Ok(self.rows_scheduled);
2063 }
2064 while self.rows_scheduled < scheduled_need {
2065 let next_message = self.context.source.recv().await;
2066 match next_message {
2067 Some(scan_line) => {
2068 let scan_line = scan_line?;
2069 self.rows_scheduled = scan_line.scheduled_so_far;
2070 for message in scan_line.decoders {
2071 let unloaded_page = message.into_structural();
2072 let loaded_page = unloaded_page.0.await?;
2073 self.root_decoder.accept_page(loaded_page)?;
2074 }
2075 }
2076 None => {
2077 self.scheduler_exhausted = true;
2081 return Ok(self.rows_scheduled);
2082 }
2083 }
2084 }
2085 Ok(scheduled_need)
2086 }
2087
2088 #[instrument(level = "debug", skip_all)]
2089 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
2090 trace!(
2091 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
2092 self.rows_remaining, self.rows_drained, self.rows_scheduled,
2093 );
2094 if self.rows_remaining == 0 {
2095 return Ok(None);
2096 }
2097
2098 let row_limit = self.rows_remaining.min(self.rows_per_batch as u64);
2099 let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
2100 let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
2101 let bpr = if feedback > 0 {
2102 feedback as f64
2103 } else {
2104 self.schema_bytes_per_row
2105 };
2106 let rows = (batch_size_bytes as f64 / bpr) as u64;
2107 row_limit.min(rows.max(1))
2108 } else {
2109 row_limit
2110 };
2111 self.rows_remaining -= to_take;
2112
2113 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
2114 trace!(
2115 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
2116 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
2117 );
2118 if scheduled_need > 0 {
2119 let desired_scheduled = scheduled_need + self.rows_scheduled;
2120 trace!(
2121 "Draining from scheduler (desire at least {} scheduled rows)",
2122 desired_scheduled
2123 );
2124 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
2125 if actually_scheduled < desired_scheduled {
2126 let under_scheduled = desired_scheduled - actually_scheduled;
2127 to_take -= under_scheduled;
2128 }
2129 }
2130
2131 if to_take == 0 {
2132 return Ok(None);
2133 }
2134
2135 let next_task = self.root_decoder.drain_batch_task(to_take)?;
2136 self.rows_drained += to_take;
2137 Ok(Some(next_task))
2138 }
2139
2140 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
2141 let stream = futures::stream::unfold(self, |mut slf| async move {
2142 let next_task = match slf.next_batch_task().await {
2143 Ok(Some(next_task)) => next_task,
2144 Ok(None) => return None,
2145 Err(err) => {
2146 slf.rows_remaining = 0;
2147 return Some((
2148 ReadBatchTask {
2149 task: async move { Err(err) }.boxed(),
2150 num_rows: 0,
2151 },
2152 slf,
2153 ));
2154 }
2155 };
2156 let num_rows = next_task.num_rows;
2157 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
2158 let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
2159 let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
2162 let task = async move {
2163 let (batch, data_size) = if spawn_batch_decode_tasks {
2164 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
2165 .await
2166 .map_err(|err| Error::wrapped(err.into()))??
2167 } else {
2168 next_task.into_batch(emitted_batch_size_warning)?
2169 };
2170 let num_rows = batch.num_rows() as u64;
2171 if let Some(bpr) = data_size.checked_div(num_rows) {
2172 let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
2173 let next = if prev == 0 || bpr >= prev {
2174 bpr
2177 } else {
2178 (prev + bpr) / 2
2182 };
2183 bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
2184 }
2185 Ok(batch)
2186 };
2187 debug_assert!(num_rows <= u32::MAX as u64);
2189 Some((
2190 ReadBatchTask {
2191 task: task.boxed(),
2192 num_rows: num_rows as u32,
2193 },
2194 slf,
2195 ))
2196 });
2197 stream.boxed()
2198 }
2199}
2200
2201#[derive(Debug)]
2202pub enum RequestedRows {
2203 Ranges(Vec<Range<u64>>),
2204 Indices(Vec<u64>),
2205}
2206
2207impl RequestedRows {
2208 pub fn num_rows(&self) -> u64 {
2209 match self {
2210 Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
2211 Self::Indices(indices) => indices.len() as u64,
2212 }
2213 }
2214
2215 pub fn trim_empty_ranges(mut self) -> Self {
2216 if let Self::Ranges(ranges) = &mut self {
2217 ranges.retain(|r| !r.is_empty());
2218 }
2219 self
2220 }
2221
2222 fn into_ranges(self) -> Result<Vec<Range<u64>>> {
2223 match self {
2224 Self::Ranges(ranges) => Ok(ranges),
2225 Self::Indices(indices) => Self::indices_to_ranges(&indices),
2226 }
2227 }
2228
2229 fn indices_to_ranges(indices: &[u64]) -> Result<Vec<Range<u64>>> {
2231 if indices.is_empty() {
2232 return Ok(Vec::new());
2233 }
2234
2235 let mut ranges = Vec::with_capacity(indices.len());
2236 let mut start = indices[0];
2237
2238 for (index, pair) in indices.windows(2).enumerate() {
2239 if pair[0] >= pair[1] {
2240 return Err(Error::invalid_input(format!(
2241 "Requested row indices are not strictly increasing at index {index}: {} then {}",
2242 pair[0], pair[1]
2243 )));
2244 }
2245 let end = pair[0]
2246 .checked_add(1)
2247 .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2248 if pair[1] != end {
2249 ranges.push(start..end);
2250 start = pair[1];
2251 }
2252 }
2253
2254 let end = indices
2255 .last()
2256 .copied()
2257 .and_then(|index| index.checked_add(1))
2258 .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2259 ranges.push(start..end);
2260 Ok(ranges)
2261 }
2262}
2263
2264#[derive(Debug, Clone)]
2266pub struct DecoderConfig {
2267 pub cache_repetition_index: bool,
2274 pub validate_on_decode: bool,
2276 pub inline_scheduling: Option<bool>,
2294}
2295
2296impl Default for DecoderConfig {
2297 fn default() -> Self {
2298 Self {
2299 cache_repetition_index: default_cache_repetition_index(),
2300 validate_on_decode: false,
2301 inline_scheduling: None,
2302 }
2303 }
2304}
2305
2306#[derive(Debug, Clone)]
2307pub struct SchedulerDecoderConfig {
2308 pub decoder_plugins: Arc<DecoderPlugins>,
2309 pub batch_size: u32,
2310 pub io: Arc<dyn EncodingsIo>,
2311 pub cache: Arc<LanceCache>,
2312 pub decoder_config: DecoderConfig,
2314 pub batch_size_bytes: Option<u64>,
2320}
2321
2322fn check_scheduler_on_drop(
2323 stream: BoxStream<'static, ReadBatchTask>,
2324 scheduler_handle: tokio::task::JoinHandle<()>,
2325) -> BoxStream<'static, ReadBatchTask> {
2326 let abort_handle = scheduler_handle.abort_handle();
2330 let mut scheduler_handle = Some(scheduler_handle);
2331 let check_scheduler = stream::unfold((), move |_| {
2332 let handle = scheduler_handle.take();
2333 async move {
2334 if let Some(handle) = handle {
2335 handle.await.unwrap();
2336 }
2337 None
2338 }
2339 });
2340 stream
2341 .chain(check_scheduler)
2342 .on_drop(move || {
2343 abort_handle.abort();
2351 })
2352 .boxed()
2353}
2354
2355#[allow(clippy::too_many_arguments)]
2356pub fn create_decode_stream(
2357 schema: &Schema,
2358 num_rows: u64,
2359 batch_size: u32,
2360 is_structural: bool,
2361 should_validate: bool,
2362 spawn_structural_batch_decode_tasks: bool,
2363 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2364 batch_size_bytes: Option<u64>,
2365) -> Result<BoxStream<'static, ReadBatchTask>> {
2366 create_decode_stream_with_i32_offset_limit(
2367 schema,
2368 num_rows,
2369 batch_size,
2370 is_structural,
2371 should_validate,
2372 spawn_structural_batch_decode_tasks,
2373 rx,
2374 batch_size_bytes,
2375 true,
2376 )
2377}
2378
2379#[allow(clippy::too_many_arguments)]
2380fn create_decode_stream_with_i32_offset_limit(
2381 schema: &Schema,
2382 num_rows: u64,
2383 batch_size: u32,
2384 is_structural: bool,
2385 should_validate: bool,
2386 spawn_structural_batch_decode_tasks: bool,
2387 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2388 batch_size_bytes: Option<u64>,
2389 limit_i32_offset_batch_size: bool,
2390) -> Result<BoxStream<'static, ReadBatchTask>> {
2391 if is_structural {
2392 let arrow_schema = ArrowSchema::from(schema);
2393 let structural_decoder = StructuralStructDecoder::new(
2394 arrow_schema.fields,
2395 should_validate,
2396 true,
2397 false,
2398 )?;
2399 Ok(StructuralBatchDecodeStream::new(
2400 rx,
2401 batch_size,
2402 num_rows,
2403 structural_decoder,
2404 spawn_structural_batch_decode_tasks,
2405 batch_size_bytes,
2406 )
2407 .into_stream())
2408 } else {
2409 if batch_size_bytes.is_some() {
2410 warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2411 }
2412 let arrow_schema = ArrowSchema::from(schema);
2413 let root_fields = arrow_schema.fields;
2414
2415 let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2416 Ok(BatchDecodeStream::new_with_i32_offset_limit(
2417 rx,
2418 batch_size,
2419 num_rows,
2420 simple_struct_decoder,
2421 limit_i32_offset_batch_size,
2422 )
2423 .into_stream())
2424 }
2425}
2426
2427pub fn create_decode_iterator(
2431 schema: &Schema,
2432 num_rows: u64,
2433 batch_size: u32,
2434 should_validate: bool,
2435 is_structural: bool,
2436 messages: VecDeque<Result<DecoderMessage>>,
2437) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2438 let arrow_schema = Arc::new(ArrowSchema::from(schema));
2439 let root_fields = arrow_schema.fields.clone();
2440 if is_structural {
2441 let simple_struct_decoder = StructuralStructDecoder::new(
2442 root_fields,
2443 should_validate,
2444 true,
2445 false,
2446 )?;
2447 Ok(Box::new(BatchDecodeIterator::new(
2448 messages,
2449 batch_size,
2450 num_rows,
2451 simple_struct_decoder,
2452 arrow_schema,
2453 )))
2454 } else {
2455 let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2456 Ok(Box::new(BatchDecodeIterator::new(
2457 messages,
2458 batch_size,
2459 num_rows,
2460 root_decoder,
2461 arrow_schema,
2462 )))
2463 }
2464}
2465
2466async fn create_scheduler_decoder(
2467 column_infos: Vec<Arc<ColumnInfo>>,
2468 requested_rows: RequestedRows,
2469 filter: FilterExpression,
2470 column_indices: Vec<u32>,
2471 target_schema: Arc<Schema>,
2472 config: SchedulerDecoderConfig,
2473) -> Result<BoxStream<'static, ReadBatchTask>> {
2474 let num_rows = requested_rows.num_rows();
2475 let is_range_request = matches!(&requested_rows, RequestedRows::Ranges(_));
2476
2477 let is_structural = column_infos[0].is_structural();
2478 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2479 let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2480 Some("always") => true,
2481 Some("never") => false,
2482 _ => is_range_request,
2483 };
2484
2485 let (tx, rx) = mpsc::unbounded_channel();
2486
2487 let decode_stream = create_decode_stream(
2488 &target_schema,
2489 num_rows,
2490 config.batch_size,
2491 is_structural,
2492 config.decoder_config.validate_on_decode,
2493 spawn_structural_batch_decode_tasks,
2494 rx,
2495 config.batch_size_bytes,
2496 )?;
2497
2498 let requested_ranges = requested_rows.into_ranges()?;
2504 let mut decode_scheduler = DecodeBatchScheduler::try_new_with_ranges(
2505 target_schema.as_ref(),
2506 &column_indices,
2507 &column_infos,
2508 &vec![],
2509 num_rows,
2510 config.decoder_plugins,
2511 config.io.clone(),
2512 config.cache,
2513 Some(&requested_ranges),
2514 &filter,
2515 &config.decoder_config,
2516 )
2517 .await?;
2518
2519 let inline_scheduling = config
2525 .decoder_config
2526 .inline_scheduling
2527 .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2528
2529 if inline_scheduling {
2530 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2531 Ok(decode_stream)
2532 } else {
2533 let scheduling = async move {
2537 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io)
2538 };
2539 let scheduler_handle = tokio::task::spawn(scheduling);
2540 Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2541 }
2542}
2543
2544pub async fn schedule_and_decode(
2559 column_infos: Vec<Arc<ColumnInfo>>,
2560 requested_rows: RequestedRows,
2561 filter: FilterExpression,
2562 column_indices: Vec<u32>,
2563 target_schema: Arc<Schema>,
2564 config: SchedulerDecoderConfig,
2565) -> Result<BoxStream<'static, ReadBatchTask>> {
2566 if requested_rows.num_rows() == 0 {
2567 return Ok(stream::empty().boxed());
2568 }
2569
2570 let requested_rows = requested_rows.trim_empty_ranges();
2573
2574 let io = config.io.clone();
2575
2576 let stream = create_scheduler_decoder(
2577 column_infos,
2578 requested_rows,
2579 filter,
2580 column_indices,
2581 target_schema,
2582 config,
2583 )
2584 .await?;
2585
2586 Ok(stream.finally(move || drop(io)).boxed())
2589}
2590
2591pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2592 tokio::runtime::Builder::new_current_thread()
2593 .build()
2594 .unwrap()
2595});
2596
2597pub fn schedule_and_decode_blocking(
2612 column_infos: Vec<Arc<ColumnInfo>>,
2613 requested_rows: RequestedRows,
2614 filter: FilterExpression,
2615 column_indices: Vec<u32>,
2616 target_schema: Arc<Schema>,
2617 config: SchedulerDecoderConfig,
2618) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2619 if requested_rows.num_rows() == 0 {
2620 let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2621 return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2622 }
2623
2624 let requested_rows = requested_rows.trim_empty_ranges();
2625 let num_rows = requested_rows.num_rows();
2626 let is_structural = column_infos[0].is_structural();
2627
2628 let (tx, mut rx) = mpsc::unbounded_channel();
2629
2630 let requested_ranges = requested_rows.into_ranges()?;
2634 let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new_with_ranges(
2635 target_schema.as_ref(),
2636 &column_indices,
2637 &column_infos,
2638 &vec![],
2639 num_rows,
2640 config.decoder_plugins,
2641 config.io.clone(),
2642 config.cache,
2643 Some(&requested_ranges),
2644 &filter,
2645 &config.decoder_config,
2646 ))?;
2647
2648 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2650
2651 let mut messages = Vec::new();
2653 while rx
2654 .recv_many(&mut messages, usize::MAX)
2655 .now_or_never()
2656 .unwrap()
2657 != 0
2658 {}
2659
2660 let decode_iterator = create_decode_iterator(
2662 &target_schema,
2663 num_rows,
2664 config.batch_size,
2665 config.decoder_config.validate_on_decode,
2666 is_structural,
2667 messages.into(),
2668 )?;
2669
2670 Ok(decode_iterator)
2671}
2672
2673pub trait PrimitivePageDecoder: Send + Sync {
2685 fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2717
2718 fn variable_width_bytes(&self, _rows_to_skip: u64, _num_rows: u64) -> Result<Option<u64>> {
2725 Ok(None)
2726 }
2727}
2728
2729pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2738 fn schedule_ranges(
2750 &self,
2751 ranges: &[Range<u64>],
2752 scheduler: &Arc<dyn EncodingsIo>,
2753 top_level_row: u64,
2754 ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2755}
2756
2757pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2759 fn advance(&mut self, num_rows: u64);
2760 fn current_priority(&self) -> u64;
2761 fn box_clone(&self) -> Box<dyn PriorityRange>;
2762}
2763
2764#[derive(Debug)]
2767pub struct SimplePriorityRange {
2768 priority: u64,
2769}
2770
2771impl SimplePriorityRange {
2772 fn new(priority: u64) -> Self {
2773 Self { priority }
2774 }
2775}
2776
2777impl PriorityRange for SimplePriorityRange {
2778 fn advance(&mut self, num_rows: u64) {
2779 self.priority += num_rows;
2780 }
2781
2782 fn current_priority(&self) -> u64 {
2783 self.priority
2784 }
2785
2786 fn box_clone(&self) -> Box<dyn PriorityRange> {
2787 Box::new(Self {
2788 priority: self.priority,
2789 })
2790 }
2791}
2792
2793pub struct ListPriorityRange {
2806 base: Box<dyn PriorityRange>,
2807 offsets: Arc<[u64]>,
2808 cur_index_into_offsets: usize,
2809 cur_position: u64,
2810}
2811
2812impl ListPriorityRange {
2813 pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2814 Self {
2815 base,
2816 offsets,
2817 cur_index_into_offsets: 0,
2818 cur_position: 0,
2819 }
2820 }
2821}
2822
2823impl std::fmt::Debug for ListPriorityRange {
2824 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2825 f.debug_struct("ListPriorityRange")
2826 .field("base", &self.base)
2827 .field("offsets.len()", &self.offsets.len())
2828 .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2829 .field("cur_position", &self.cur_position)
2830 .finish()
2831 }
2832}
2833
2834impl PriorityRange for ListPriorityRange {
2835 fn advance(&mut self, num_rows: u64) {
2836 self.cur_position += num_rows;
2839 let mut idx_into_offsets = self.cur_index_into_offsets;
2840 while idx_into_offsets + 1 < self.offsets.len()
2841 && self.offsets[idx_into_offsets + 1] <= self.cur_position
2842 {
2843 idx_into_offsets += 1;
2844 }
2845 let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2846 self.cur_index_into_offsets = idx_into_offsets;
2847 self.base.advance(base_rows_advanced as u64);
2848 }
2849
2850 fn current_priority(&self) -> u64 {
2851 self.base.current_priority()
2852 }
2853
2854 fn box_clone(&self) -> Box<dyn PriorityRange> {
2855 Box::new(Self {
2856 base: self.base.box_clone(),
2857 offsets: self.offsets.clone(),
2858 cur_index_into_offsets: self.cur_index_into_offsets,
2859 cur_position: self.cur_position,
2860 })
2861 }
2862}
2863
2864pub(crate) const IO_REQUEST_BATCH_BYTES: u64 = 8 * 1024 * 1024;
2871
2872type SharedRead = Shared<BoxFuture<'static, std::result::Result<Arc<Vec<Bytes>>, CloneableError>>>;
2873
2874enum SubmittedRead {
2876 Alone(BoxFuture<'static, Result<Vec<Bytes>>>),
2878 Shared(SharedRead, Vec<usize>),
2880}
2881
2882struct PendingRead {
2883 ranges: Vec<Range<u64>>,
2884 priority: u64,
2885 tx: oneshot::Sender<SubmittedRead>,
2886}
2887
2888pub(crate) struct RequestBatch {
2907 inner: Arc<dyn EncodingsIo>,
2908 pending: Arc<Mutex<Vec<PendingRead>>>,
2909}
2910
2911impl std::fmt::Debug for RequestBatch {
2912 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2913 f.debug_struct("RequestBatch").finish_non_exhaustive()
2914 }
2915}
2916
2917impl RequestBatch {
2918 pub(crate) fn new(inner: Arc<dyn EncodingsIo>) -> Self {
2919 Self {
2920 inner,
2921 pending: Arc::new(Mutex::new(Vec::new())),
2922 }
2923 }
2924
2925 fn queued_bytes(pending: &[PendingRead]) -> u64 {
2926 pending
2927 .iter()
2928 .flat_map(|read| &read.ranges)
2929 .map(|range| range.end - range.start)
2930 .sum()
2931 }
2932
2933 pub(crate) fn pending_bytes(&self) -> u64 {
2935 Self::queued_bytes(&self.pending.lock().unwrap())
2936 }
2937
2938 pub(crate) fn flush(&self) {
2940 Self::flush_pending(&self.inner, &self.pending);
2941 }
2942
2943 fn flush_pending(inner: &Arc<dyn EncodingsIo>, pending: &Mutex<Vec<PendingRead>>) {
2944 let pending = std::mem::take(&mut *pending.lock().unwrap());
2945 Self::submit_batch(inner, pending);
2946 }
2947
2948 fn submit_batch(inner: &Arc<dyn EncodingsIo>, pending: Vec<PendingRead>) {
2951 if pending.is_empty() {
2952 return;
2953 }
2954 if pending.len() == 1 {
2955 let read = pending.into_iter().next().unwrap();
2956 let _ = read.tx.send(SubmittedRead::Alone(
2957 inner.submit_request(read.ranges, read.priority),
2958 ));
2959 return;
2960 }
2961 let priority = pending.iter().map(|read| read.priority).min().unwrap();
2963 let mut ordered = pending
2964 .iter()
2965 .enumerate()
2966 .flat_map(|(read_idx, read)| {
2967 read.ranges
2968 .iter()
2969 .enumerate()
2970 .map(move |(range_idx, range)| (range.clone(), read_idx, range_idx))
2971 })
2972 .collect::<Vec<_>>();
2973 ordered.sort_by_key(|(range, _, _)| (range.start, range.end));
2974 let ranges = ordered
2975 .iter()
2976 .map(|(range, _, _)| range.clone())
2977 .collect::<Vec<_>>();
2978 let mut positions = pending
2980 .iter()
2981 .map(|read| vec![0; read.ranges.len()])
2982 .collect::<Vec<_>>();
2983 for (position, (_, read_idx, range_idx)) in ordered.iter().enumerate() {
2984 positions[*read_idx][*range_idx] = position;
2985 }
2986 let batched: SharedRead = inner
2987 .submit_request(ranges, priority)
2988 .map(|result| result.map(Arc::new).map_err(CloneableError))
2989 .boxed()
2990 .shared();
2991 for (read, read_positions) in pending.into_iter().zip(positions) {
2992 let _ = read
2994 .tx
2995 .send(SubmittedRead::Shared(batched.clone(), read_positions));
2996 }
2997 }
2998}
2999
3000impl EncodingsIo for RequestBatch {
3001 fn submit_request(
3002 &self,
3003 ranges: Vec<Range<u64>>,
3004 priority: u64,
3005 ) -> BoxFuture<'static, Result<Vec<Bytes>>> {
3006 let mut pending = self.pending.lock().unwrap();
3007 let bytes = ranges
3010 .iter()
3011 .map(|range| range.end - range.start)
3012 .sum::<u64>();
3013 if !pending.is_empty() && Self::queued_bytes(&pending) + bytes > IO_REQUEST_BATCH_BYTES {
3014 Self::submit_batch(&self.inner, std::mem::take(&mut *pending));
3015 }
3016 let (tx, mut rx) = oneshot::channel();
3017 pending.push(PendingRead {
3018 ranges,
3019 priority,
3020 tx,
3021 });
3022 drop(pending);
3023 let inner = self.inner.clone();
3024 let pending = self.pending.clone();
3025 async move {
3026 let submitted = match rx.try_recv() {
3027 Ok(Some(submitted)) => submitted,
3029 _ => {
3030 tokio::task::yield_now().await;
3034 Self::flush_pending(&inner, &pending);
3035 rx.await.map_err(|_| {
3036 Error::internal("a batched read was dropped before the batch was submitted")
3037 })?
3038 }
3039 };
3040 match submitted {
3041 SubmittedRead::Alone(request) => request.await,
3042 SubmittedRead::Shared(batched, positions) => {
3043 let bytes = batched.await.map_err(|err| err.0)?;
3044 Ok(positions
3045 .into_iter()
3046 .map(|position| bytes[position].clone())
3047 .collect())
3048 }
3049 }
3050 }
3051 .boxed()
3052 }
3053
3054 fn with_bypass_backpressure(&self) -> Option<Arc<dyn EncodingsIo>> {
3055 self.inner.with_bypass_backpressure()
3056 }
3057
3058 fn with_io_stats(
3059 &self,
3060 stats: Arc<dyn lance_core::utils::io_stats::IoStatsRecorder>,
3061 ) -> Option<Arc<dyn EncodingsIo>> {
3062 self.inner.with_io_stats(stats)
3063 }
3064}
3065
3066pub struct SchedulerContext {
3067 recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
3068 io: Arc<dyn EncodingsIo>,
3069 batch: Arc<RequestBatch>,
3071 cache: Arc<LanceCache>,
3072 name: String,
3073 path: Vec<u32>,
3074 path_names: Vec<String>,
3075}
3076
3077pub struct ScopedSchedulerContext<'a> {
3078 pub context: &'a mut SchedulerContext,
3079}
3080
3081impl<'a> ScopedSchedulerContext<'a> {
3082 pub fn pop(self) -> &'a mut SchedulerContext {
3083 self.context.pop();
3084 self.context
3085 }
3086}
3087
3088impl SchedulerContext {
3089 pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
3090 let batch = Arc::new(RequestBatch::new(io));
3091 Self {
3092 io: batch.clone(),
3093 batch,
3094 cache,
3095 recv: None,
3096 name: "".to_string(),
3097 path: Vec::new(),
3098 path_names: Vec::new(),
3099 }
3100 }
3101
3102 pub fn io(&self) -> &Arc<dyn EncodingsIo> {
3103 &self.io
3104 }
3105
3106 pub fn flush_io(&self) {
3108 self.batch.flush();
3109 }
3110
3111 pub fn cache(&self) -> &Arc<LanceCache> {
3112 &self.cache
3113 }
3114
3115 pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
3116 self.path.push(index);
3117 self.path_names.push(name.to_string());
3118 ScopedSchedulerContext { context: self }
3119 }
3120
3121 pub fn pop(&mut self) {
3122 self.path.pop();
3123 self.path_names.pop();
3124 }
3125
3126 pub fn path_name(&self) -> String {
3127 let path = self.path_names.join("/");
3128 if self.recv.is_some() {
3129 format!("TEMP({}){}", self.name, path)
3130 } else {
3131 format!("ROOT{}", path)
3132 }
3133 }
3134
3135 pub fn current_path(&self) -> VecDeque<u32> {
3136 VecDeque::from_iter(self.path.iter().copied())
3137 }
3138
3139 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
3140 pub fn locate_decoder(&mut self, decoder: Box<dyn LogicalPageDecoder>) -> DecoderReady {
3141 trace!(
3142 "Scheduling decoder of type {:?} for {:?}",
3143 decoder.data_type(),
3144 self.path,
3145 );
3146 DecoderReady {
3147 decoder,
3148 path: self.current_path(),
3149 }
3150 }
3151}
3152
3153pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
3154
3155impl std::fmt::Debug for UnloadedPageShard {
3156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3157 f.debug_struct("UnloadedPage").finish()
3158 }
3159}
3160
3161#[derive(Debug)]
3162pub struct ScheduledScanLine {
3163 pub rows_scheduled: u64,
3164 pub decoders: Vec<MessageType>,
3165}
3166
3167pub trait StructuralSchedulingJob: std::fmt::Debug {
3168 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
3175}
3176
3177pub struct FilterExpression(pub Bytes);
3185
3186impl FilterExpression {
3187 pub fn no_filter() -> Self {
3192 Self(Bytes::new())
3193 }
3194
3195 pub fn is_noop(&self) -> bool {
3197 self.0.is_empty()
3198 }
3199}
3200
3201pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
3202 fn initialize<'a>(
3208 &'a mut self,
3209 requested_ranges: Option<&'a [Range<u64>]>,
3210 filter: &'a FilterExpression,
3211 context: &'a SchedulerContext,
3212 ) -> BoxFuture<'a, Result<()>>;
3213 fn schedule_ranges<'a>(
3214 &'a self,
3215 ranges: &[Range<u64>],
3216 filter: &FilterExpression,
3217 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
3218}
3219
3220pub trait DecodeArrayTask: Send {
3222 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
3224}
3225
3226impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
3227 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3228 let decoded_array = StructuralDecodeArrayTask::decode(*self)?;
3229 decoded_array.repdef.ensure_exhausted()?;
3230 Ok((decoded_array.array, decoded_array.data_size))
3231 }
3232}
3233
3234pub struct NextDecodeTask {
3239 pub task: Box<dyn DecodeArrayTask>,
3241 pub num_rows: u64,
3243}
3244
3245impl NextDecodeTask {
3246 #[instrument(name = "task_to_batch", level = "debug", skip_all)]
3251 fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
3252 let (struct_arr, data_size) = self.task.decode()?;
3253 let batch = RecordBatch::from(struct_arr.as_struct());
3254 if data_size > BATCH_SIZE_BYTES_WARNING {
3255 emitted_batch_size_warning.call_once(|| {
3256 let size_mb = data_size / 1024 / 1024;
3257 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);
3258 });
3259 }
3260 Ok((batch, data_size))
3261 }
3262}
3263
3264#[derive(Debug)]
3268pub enum MessageType {
3269 DecoderReady(DecoderReady),
3274 UnloadedPage(UnloadedPageShard),
3278}
3279
3280impl MessageType {
3281 pub fn into_array(self) -> DecoderReady {
3282 match self {
3283 Self::DecoderReady(decoder) => decoder,
3284 Self::UnloadedPage(_) => {
3285 panic!("Expected DecoderReady but got UnloadedPage")
3286 }
3287 }
3288 }
3289
3290 pub fn into_structural(self) -> UnloadedPageShard {
3291 match self {
3292 Self::UnloadedPage(unloaded) => unloaded,
3293 Self::DecoderReady(_) => {
3294 panic!("Expected UnloadedPage but got DecoderReady")
3295 }
3296 }
3297 }
3298}
3299
3300pub struct DecoderMessage {
3301 pub scheduled_so_far: u64,
3302 pub decoders: Vec<MessageType>,
3303}
3304
3305pub struct DecoderContext {
3306 source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
3307}
3308
3309impl DecoderContext {
3310 pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
3311 Self { source }
3312 }
3313}
3314
3315pub struct DecodedPage {
3316 pub data: DataBlock,
3317 pub repdef: RepDefUnraveler,
3318}
3319
3320pub trait DecodePageTask: Send + std::fmt::Debug {
3321 fn decode(self: Box<Self>) -> Result<DecodedPage>;
3323}
3324
3325pub trait StructuralPageDecoder: std::fmt::Debug + Send {
3326 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
3327 fn num_rows(&self) -> u64;
3328 fn decoded_bytes(&self, _num_rows: u64) -> Result<u64> {
3331 Err(Error::not_supported(
3332 "decoded_bytes is not implemented for this page decoder".to_string(),
3333 ))
3334 }
3335}
3336
3337#[derive(Debug)]
3338pub struct LoadedPageShard {
3339 pub decoder: Box<dyn StructuralPageDecoder>,
3341 pub path: VecDeque<u32>,
3360}
3361
3362pub struct DecodedArray {
3363 pub array: ArrayRef,
3364 pub repdef: CompositeRepDefUnraveler,
3365 pub data_size: u64,
3367}
3368
3369pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
3370 fn decode(self: Box<Self>) -> Result<DecodedArray>;
3371}
3372
3373pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
3374 fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
3379 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
3381 fn data_type(&self) -> &DataType;
3383 fn plan_decoded_bytes(&self, _rows_remaining: u64) -> Result<[u64; 8]> {
3393 Err(Error::not_supported(
3394 "decoded_bytes is not implemented for this field decoder".to_string(),
3395 ))
3396 }
3397}
3398
3399#[derive(Debug, Default)]
3400pub struct DecoderPlugins {}
3401
3402#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3404pub enum EncodedBatchLayout {
3405 Array,
3407 Structural,
3409}
3410
3411pub async fn decode_batch(
3413 batch: &EncodedBatch,
3414 filter: &FilterExpression,
3415 decoder_plugins: Arc<DecoderPlugins>,
3416 should_validate: bool,
3417 layout: EncodedBatchLayout,
3418 cache: Option<Arc<LanceCache>>,
3419) -> Result<RecordBatch> {
3420 let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
3425 let cache = if let Some(cache) = cache {
3426 cache
3427 } else {
3428 Arc::new(lance_core::cache::LanceCache::with_capacity(
3429 128 * 1024 * 1024,
3430 ))
3431 };
3432 let mut decode_scheduler = DecodeBatchScheduler::try_new(
3433 batch.schema.as_ref(),
3434 &batch.top_level_columns,
3435 &batch.page_table,
3436 &vec![],
3437 batch.num_rows,
3438 decoder_plugins,
3439 io_scheduler.clone(),
3440 cache,
3441 filter,
3442 &DecoderConfig::default(),
3443 )
3444 .await?;
3445 let (tx, rx) = unbounded_channel();
3446 decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
3447 let is_structural = layout == EncodedBatchLayout::Structural;
3448 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
3449 let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
3450 let mut decode_stream = create_decode_stream_with_i32_offset_limit(
3453 &batch.schema,
3454 batch.num_rows,
3455 batch.num_rows as u32,
3456 is_structural,
3457 should_validate,
3458 spawn_structural_batch_decode_tasks,
3459 rx,
3460 None,
3461 false,
3462 )?;
3463 decode_stream.next().await.unwrap().task.await
3464}
3465
3466#[cfg(test)]
3467mod tests {
3469 use super::*;
3470 use arrow_array::StringArray;
3471 use std::collections::VecDeque;
3472
3473 struct StaticArrayDecodeTask(ArrayRef);
3474
3475 impl DecodeArrayTask for StaticArrayDecodeTask {
3476 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3477 Ok((self.0, 0))
3478 }
3479 }
3480
3481 #[derive(Debug)]
3482 struct StaticArrayPageDecoder {
3483 array: ArrayRef,
3484 rows_drained: u64,
3485 fake_bytes_per_row: Option<u64>,
3488 }
3489
3490 impl StaticArrayPageDecoder {
3491 fn new(values: &[&str]) -> Self {
3492 Self {
3493 array: Arc::new(StringArray::from(values.to_vec())),
3494 rows_drained: 0,
3495 fake_bytes_per_row: None,
3496 }
3497 }
3498
3499 fn with_fake_bytes_per_row(values: &[&str], fake_bytes_per_row: u64) -> Self {
3500 Self {
3501 fake_bytes_per_row: Some(fake_bytes_per_row),
3502 ..Self::new(values)
3503 }
3504 }
3505
3506 fn row_bytes(&self, row: usize) -> u64 {
3507 self.fake_bytes_per_row.unwrap_or_else(|| {
3508 self.array
3509 .as_any()
3510 .downcast_ref::<StringArray>()
3511 .unwrap()
3512 .value(row)
3513 .len() as u64
3514 })
3515 }
3516 }
3517
3518 impl LogicalPageDecoder for StaticArrayPageDecoder {
3519 fn wait_for_loaded(&mut self, _loaded_need: u64) -> BoxFuture<'_, Result<()>> {
3520 std::future::ready(Ok(())).boxed()
3521 }
3522
3523 fn rows_loaded(&self) -> u64 {
3524 self.array.len() as u64
3525 }
3526
3527 fn num_rows(&self) -> u64 {
3528 self.array.len() as u64
3529 }
3530
3531 fn rows_drained(&self) -> u64 {
3532 self.rows_drained
3533 }
3534
3535 fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
3536 let array = self
3537 .array
3538 .slice(self.rows_drained as usize, num_rows as usize);
3539 self.rows_drained += num_rows;
3540 Ok(NextDecodeTask {
3541 task: Box::new(StaticArrayDecodeTask(array)),
3542 num_rows,
3543 })
3544 }
3545
3546 fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
3547 let start = self.rows_drained as usize;
3548 let mut rows = 0u64;
3549 let mut bytes = 0u64;
3550 for row in start..start + num_rows as usize {
3551 let row_bytes = self.row_bytes(row);
3552 if bytes + row_bytes > byte_budget {
3553 break;
3554 }
3555 bytes += row_bytes;
3556 rows += 1;
3557 }
3558 Ok(DrainLimit { rows, bytes })
3559 }
3560
3561 fn data_type(&self) -> &DataType {
3562 self.array.data_type()
3563 }
3564 }
3565
3566 async fn array_stream_batch_sizes(
3567 pages: Vec<StaticArrayPageDecoder>,
3568 batch_size: u32,
3569 ) -> Vec<usize> {
3570 let num_rows = pages.iter().map(|page| page.num_rows()).sum::<u64>();
3571 let fields = Fields::from(vec![ArrowField::new("value", DataType::Utf8, false)]);
3572 let root_decoder = SimpleStructDecoder::new(fields, num_rows);
3573 let (tx, rx) = unbounded_channel();
3574 let pages = pages
3575 .into_iter()
3576 .map(|page| {
3577 MessageType::DecoderReady(DecoderReady {
3578 decoder: Box::new(page),
3579 path: VecDeque::from([0]),
3580 })
3581 })
3582 .collect();
3583 tx.send(Ok(DecoderMessage {
3584 scheduled_so_far: num_rows,
3585 decoders: pages,
3586 }))
3587 .unwrap();
3588 drop(tx);
3589
3590 let mut stream =
3591 BatchDecodeStream::new(rx, batch_size, num_rows, root_decoder).into_stream();
3592 let mut batch_sizes = Vec::new();
3593 while let Some(task) = stream.next().await {
3594 batch_sizes.push(task.task.await.unwrap().num_rows());
3595 }
3596 batch_sizes
3597 }
3598
3599 #[tokio::test]
3600 async fn test_array_stream_spans_i32_offset_pages_within_budget() {
3601 let batch_sizes = array_stream_batch_sizes(
3603 vec![
3604 StaticArrayPageDecoder::new(&["a", "b"]),
3605 StaticArrayPageDecoder::new(&["c", "d"]),
3606 ],
3607 4,
3608 )
3609 .await;
3610 assert_eq!(batch_sizes, vec![4]);
3611 }
3612
3613 #[tokio::test]
3614 async fn test_array_stream_splits_when_i32_offset_budget_exhausted() {
3615 const FAKE_ROW_BYTES: u64 = 800 * 1024 * 1024;
3618 let batch_sizes = array_stream_batch_sizes(
3619 vec![
3620 StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3621 StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3622 StaticArrayPageDecoder::with_fake_bytes_per_row(&["c"], FAKE_ROW_BYTES),
3623 ],
3624 4,
3625 )
3626 .await;
3627 assert_eq!(batch_sizes, vec![2, 1]);
3628 }
3629
3630 #[tokio::test]
3631 async fn test_array_stream_emits_over_budget_rows_alone() {
3632 const FAKE_ROW_BYTES: u64 = 3 * 1024 * 1024 * 1024;
3635 let batch_sizes = array_stream_batch_sizes(
3636 vec![
3637 StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3638 StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3639 ],
3640 4,
3641 )
3642 .await;
3643 assert_eq!(batch_sizes, vec![1, 1]);
3644 }
3645
3646 #[test]
3647 fn requested_row_indices_convert_to_checked_ranges() {
3648 let requested_rows = RequestedRows::Indices(vec![1, 2, 5, 8, 9]);
3649 let ranges = requested_rows.into_ranges().unwrap();
3650 assert_eq!(ranges, [1..3, 5..6, 8..10]);
3651
3652 let error = RequestedRows::Indices(vec![2, 2])
3653 .into_ranges()
3654 .unwrap_err();
3655 assert!(matches!(error, Error::InvalidInput { .. }));
3656 assert!(error.to_string().contains("not strictly increasing"));
3657
3658 let error = RequestedRows::Indices(vec![u64::MAX])
3659 .into_ranges()
3660 .unwrap_err();
3661 assert!(matches!(error, Error::InvalidInput { .. }));
3662 assert!(error.to_string().contains("u64::MAX"));
3663 }
3664
3665 #[derive(Debug)]
3666 struct FailingPageDecoder {
3667 page_data_type: DataType,
3668 total_rows: u64,
3669 load_error_message: &'static str,
3670 }
3671
3672 impl FailingPageDecoder {
3673 fn new(
3674 page_data_type: DataType,
3675 total_rows: u64,
3676 load_error_message: &'static str,
3677 ) -> Self {
3678 Self {
3679 page_data_type,
3680 total_rows,
3681 load_error_message,
3682 }
3683 }
3684 }
3685
3686 impl LogicalPageDecoder for FailingPageDecoder {
3687 fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
3688 let load_error_message = self.load_error_message;
3689 async move { Err(Error::io(load_error_message)) }.boxed()
3690 }
3691
3692 fn rows_loaded(&self) -> u64 {
3693 0
3694 }
3695
3696 fn num_rows(&self) -> u64 {
3697 self.total_rows
3698 }
3699
3700 fn rows_drained(&self) -> u64 {
3701 0
3702 }
3703
3704 fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
3705 Err(Error::internal(format!(
3706 "failing page decoder should not be drained after load error \
3707 (requested_rows={})",
3708 requested_rows
3709 )))
3710 }
3711
3712 fn data_type(&self) -> &DataType {
3713 &self.page_data_type
3714 }
3715 }
3716
3717 struct InvalidInputDecodeTask;
3718
3719 impl DecodeArrayTask for InvalidInputDecodeTask {
3720 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3721 Err(Error::invalid_input_source("malformed sparse page".into()))
3722 }
3723 }
3724
3725 #[test]
3726 fn next_decode_task_preserves_invalid_input_errors() {
3727 let err = NextDecodeTask {
3728 task: Box::new(InvalidInputDecodeTask),
3729 num_rows: 0,
3730 }
3731 .into_batch(Arc::new(Once::new()))
3732 .unwrap_err();
3733 assert!(matches!(err, Error::InvalidInput { .. }));
3734 }
3735
3736 #[test]
3737 fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
3738 use arrow_schema::Field as ArrowField;
3745
3746 let zero_dim = DataType::FixedSizeList(
3747 Arc::new(ArrowField::new("item", DataType::Float32, true)),
3748 0,
3749 );
3750 let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3751 let strategy = CoreFieldDecoderStrategy::default();
3752
3753 let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3754 let err = strategy
3755 .create_structural_field_scheduler(&field, &mut structural_columns)
3756 .unwrap_err();
3757 assert!(
3758 err.to_string()
3759 .contains("dimension must be a positive integer"),
3760 "unexpected error: {}",
3761 err
3762 );
3763
3764 let mut array_columns = ColumnInfoIter::new(vec![], &[]);
3765 let err = strategy
3766 .create_array_field_scheduler(
3767 &field,
3768 &mut array_columns,
3769 FileBuffers {
3770 positions_and_sizes: &[],
3771 },
3772 )
3773 .unwrap_err();
3774 assert!(
3775 err.to_string()
3776 .contains("dimension must be a positive integer"),
3777 "unexpected error: {}",
3778 err
3779 );
3780 }
3781
3782 #[test]
3783 fn test_list_page_with_non_list_encoding_returns_error() {
3784 let item = Arc::new(ArrowField::new("item", DataType::Int32, true));
3785 let list = DataType::List(item);
3786 let field = Field::try_from(&ArrowField::new("values", list, true)).unwrap();
3787 let values_encoding = pb::ColumnEncoding {
3788 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
3789 };
3790 let offsets_column = Arc::new(ColumnInfo::new(
3791 0,
3792 Arc::new([PageInfo {
3793 num_rows: 1,
3794 priority: 0,
3795 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
3796 array_encoding: Some(pb::array_encoding::ArrayEncoding::Flat(
3797 pb::Flat::default(),
3798 )),
3799 }),
3800 buffer_offsets_and_sizes: Arc::new([]),
3801 }]),
3802 vec![],
3803 values_encoding.clone(),
3804 ));
3805 let items_column = Arc::new(ColumnInfo::new(1, Arc::new([]), vec![], values_encoding));
3806 let column_indices = [0, 1];
3807 let mut columns = ColumnInfoIter::new(vec![offsets_column, items_column], &column_indices);
3808
3809 let err = CoreFieldDecoderStrategy::default()
3810 .create_array_field_scheduler(
3811 &field,
3812 &mut columns,
3813 FileBuffers {
3814 positions_and_sizes: &[],
3815 },
3816 )
3817 .unwrap_err();
3818
3819 assert!(matches!(err, Error::InvalidInput { .. }));
3820 assert!(
3821 err.to_string()
3822 .contains("expected list encoding for field 'values' in column 0, page 0 but got"),
3823 "unexpected error: {err}"
3824 );
3825 }
3826
3827 #[tokio::test]
3828 async fn test_array_stream_stops_on_load_error() {
3829 use arrow_schema::Field as ArrowField;
3830
3831 let rows_per_batch = 1;
3832 let total_rows = 2;
3833 let scheduled_rows = 1;
3834 let page_rows = 1;
3835 let batch_readahead = 2;
3836 let load_error_message = "simulated page load failure";
3837 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3838 let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3839 let (tx, rx) = unbounded_channel();
3840
3841 tx.send(Ok(DecoderMessage {
3842 scheduled_so_far: scheduled_rows,
3843 decoders: vec![MessageType::DecoderReady(DecoderReady {
3844 decoder: Box::new(FailingPageDecoder::new(
3845 DataType::Float32,
3846 page_rows,
3847 load_error_message,
3848 )),
3849 path: VecDeque::from([0]),
3850 })],
3851 }))
3852 .unwrap();
3853 drop(tx);
3854
3855 let stream =
3856 BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3857 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3858
3859 let err = batches
3860 .next()
3861 .await
3862 .expect("stream should emit the array page-load error")
3863 .unwrap_err();
3864 assert!(
3865 err.to_string().contains(load_error_message),
3866 "unexpected error: {}",
3867 err
3868 );
3869 assert!(
3870 batches.next().await.is_none(),
3871 "stream should stop after the array page-load error"
3872 );
3873 }
3874
3875 #[tokio::test]
3876 async fn test_structural_stream_stops_on_load_error() {
3877 let rows_per_batch = 1;
3878 let total_rows = 2;
3879 let scheduled_rows = 1;
3880 let batch_readahead = 2;
3881 let load_error_message = "simulated page load failure";
3882 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3883 let root_decoder = StructuralStructDecoder::new(
3884 fields, false, true, false,
3885 )
3886 .unwrap();
3887 let (tx, rx) = unbounded_channel();
3888 let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3889
3890 tx.send(Ok(DecoderMessage {
3891 scheduled_so_far: scheduled_rows,
3892 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3893 }))
3894 .unwrap();
3895 drop(tx);
3896
3897 let stream = StructuralBatchDecodeStream::new(
3898 rx,
3899 rows_per_batch,
3900 total_rows,
3901 root_decoder,
3902 true,
3903 None,
3904 )
3905 .into_stream();
3906 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3907
3908 let err = batches
3909 .next()
3910 .await
3911 .expect("stream should emit the page-load error")
3912 .unwrap_err();
3913 assert!(
3914 err.to_string().contains(load_error_message),
3915 "unexpected error: {}",
3916 err
3917 );
3918 assert!(
3919 batches.next().await.is_none(),
3920 "stream should stop after the page-load error"
3921 );
3922 }
3923
3924 #[test]
3925 fn test_coalesce_indices_to_ranges_with_single_index() {
3926 let indices = vec![1];
3927 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3928 assert_eq!(ranges, vec![1..2]);
3929 }
3930
3931 #[test]
3932 fn test_coalesce_indices_to_ranges() {
3933 let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3934 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3935 assert_eq!(ranges, vec![1..10]);
3936 }
3937
3938 #[test]
3939 fn test_coalesce_indices_to_ranges_with_gaps() {
3940 let indices = vec![1, 2, 3, 5, 6, 7, 9];
3941 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3942 assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3943 }
3944
3945 #[test]
3946 fn test_estimate_bytes_per_row() {
3947 assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3948 assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3949 assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3950 assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3951 assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3952 assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3953 let struct_type = DataType::Struct(Fields::from(vec![
3955 ArrowField::new("a", DataType::Int32, false),
3956 ArrowField::new("b", DataType::Int32, false),
3957 ArrowField::new("c", DataType::Int32, false),
3958 ArrowField::new("d", DataType::Int32, false),
3959 ]));
3960 assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3961 }
3962
3963 async fn decode_batches_with_byte_limit(
3966 batch: &RecordBatch,
3967 batch_size: u32,
3968 batch_size_bytes: Option<u64>,
3969 ) -> Vec<RecordBatch> {
3970 use crate::{
3971 encoder::{EncodingOptions, encode_batch},
3972 testing::{TestEncoding, test_encoding_strategy},
3973 };
3974
3975 let version = TestEncoding::StructuralU16;
3976 let options = EncodingOptions::default();
3977 let strategy = test_encoding_strategy(version);
3978 let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3979 let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3980 .await
3981 .unwrap();
3982
3983 let io_scheduler =
3984 Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
3985 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
3986 128 * 1024 * 1024,
3987 ));
3988 let decoder_plugins = Arc::new(DecoderPlugins::default());
3989
3990 let mut decode_scheduler = DecodeBatchScheduler::try_new(
3991 encoded.schema.as_ref(),
3992 &encoded.top_level_columns,
3993 &encoded.page_table,
3994 &vec![],
3995 encoded.num_rows,
3996 decoder_plugins,
3997 io_scheduler.clone(),
3998 cache,
3999 &FilterExpression::no_filter(),
4000 &DecoderConfig::default(),
4001 )
4002 .await
4003 .unwrap();
4004
4005 let (tx, rx) = unbounded_channel();
4006 decode_scheduler.schedule_range(
4007 0..encoded.num_rows,
4008 &FilterExpression::no_filter(),
4009 tx,
4010 io_scheduler,
4011 );
4012
4013 let mut decode_stream = create_decode_stream(
4014 &encoded.schema,
4015 encoded.num_rows,
4016 batch_size,
4017 true,
4018 true,
4019 true,
4020 rx,
4021 batch_size_bytes,
4022 )
4023 .unwrap();
4024
4025 let mut batches = Vec::new();
4026 while let Some(task) = decode_stream.next().await {
4027 batches.push(task.task.await.unwrap());
4028 }
4029 batches
4030 }
4031
4032 #[tokio::test]
4033 async fn test_byte_sized_batches_fixed_width() {
4034 use arrow_array::Int32Array;
4035
4036 let num_rows: i32 = 1000;
4038 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
4039 .map(|col| {
4040 Arc::new(Int32Array::from_iter_values(
4041 (0..num_rows).map(move |row| row * 10 + col),
4042 )) as _
4043 })
4044 .collect();
4045
4046 let schema = Arc::new(ArrowSchema::new(vec![
4047 ArrowField::new("a", DataType::Int32, false),
4048 ArrowField::new("b", DataType::Int32, false),
4049 ArrowField::new("c", DataType::Int32, false),
4050 ArrowField::new("d", DataType::Int32, false),
4051 ]));
4052 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4053
4054 let batches =
4056 decode_batches_with_byte_limit(&input_batch, 1024, Some(1600)).await;
4057
4058 assert_eq!(batches.len(), 10);
4060 for (i, batch) in batches.iter().enumerate() {
4061 assert_eq!(
4062 batch.num_rows(),
4063 100,
4064 "batch {i} should have 100 rows, got {}",
4065 batch.num_rows()
4066 );
4067 }
4068
4069 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4071 let concatenated =
4072 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4073 .unwrap();
4074 assert_eq!(concatenated.num_rows(), num_rows as usize);
4075 for col in 0..4 {
4076 assert_eq!(
4077 concatenated.column(col).as_ref(),
4078 input_batch.column(col).as_ref(),
4079 "column {col} roundtrip mismatch"
4080 );
4081 }
4082 }
4083
4084 #[tokio::test]
4085 async fn test_byte_sized_batches_none_unchanged() {
4086 use arrow_array::Int32Array;
4087
4088 let num_rows: i32 = 1000;
4090 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
4091 .map(|col| {
4092 Arc::new(Int32Array::from_iter_values(
4093 (0..num_rows).map(move |row| row * 10 + col),
4094 )) as _
4095 })
4096 .collect();
4097
4098 let schema = Arc::new(ArrowSchema::new(vec![
4099 ArrowField::new("x", DataType::Int32, false),
4100 ArrowField::new("y", DataType::Int32, false),
4101 ]));
4102 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4103
4104 let batches = decode_batches_with_byte_limit(&input_batch, 250, None).await;
4106 assert_eq!(batches.len(), 4);
4107 for (i, batch) in batches.iter().enumerate() {
4108 assert_eq!(
4109 batch.num_rows(),
4110 250,
4111 "batch {i} should have 250 rows, got {}",
4112 batch.num_rows()
4113 );
4114 }
4115 }
4116
4117 #[tokio::test]
4118 async fn test_byte_sized_batches_respect_row_limit() {
4119 use arrow_array::Int32Array;
4120
4121 let num_rows: i32 = 1000;
4122 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4123 "x",
4124 DataType::Int32,
4125 false,
4126 )]));
4127 let input_batch = RecordBatch::try_new(
4128 schema,
4129 vec![Arc::new(Int32Array::from_iter_values(0..num_rows))],
4130 )
4131 .unwrap();
4132
4133 let batches =
4135 decode_batches_with_byte_limit(&input_batch, 100, Some(10_000)).await;
4136 assert_eq!(batches.len(), 10);
4137 assert!(batches.iter().all(|batch| batch.num_rows() == 100));
4138 }
4139
4140 #[tokio::test]
4141 async fn test_byte_sized_batches_feedback_convergence() {
4142 use arrow_array::StringArray;
4143
4144 let num_rows = 500;
4148 let value: String = "x".repeat(100);
4149 let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
4150 (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
4151 ))];
4152 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4153 "s",
4154 DataType::Utf8,
4155 false,
4156 )]));
4157 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4158
4159 let target_bytes: u64 = 5000;
4163 let batches = decode_batches_with_byte_limit(
4164 &input_batch,
4165 1024,
4166 Some(target_bytes),
4167 )
4168 .await;
4169
4170 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4172 let concatenated =
4173 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4174 .unwrap();
4175 assert_eq!(concatenated.num_rows(), num_rows as usize);
4176 assert_eq!(
4177 concatenated.column(0).as_ref(),
4178 input_batch.column(0).as_ref()
4179 );
4180
4181 assert!(
4184 batches.len() >= 2,
4185 "need at least 2 batches to test convergence"
4186 );
4187 if batches.len() >= 3 {
4190 let second_batch_rows = batches[1].num_rows();
4191 let third_batch_rows = batches[2].num_rows();
4192 assert!(
4194 (40..=60).contains(&second_batch_rows),
4195 "second batch should be near 50 rows, got {second_batch_rows}"
4196 );
4197 assert!(
4198 (40..=60).contains(&third_batch_rows),
4199 "third batch should be near 50 rows, got {third_batch_rows}"
4200 );
4201 }
4202 }
4203}