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::metadata;
259use crate::format::pb::{self, column_encoding};
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)]
422pub enum PageEncoding {
423 Legacy(pb::ArrayEncoding),
425 Structural(metadata::PageLayout),
427 Planar(crate::format::pb23::PlanarLayout),
429}
430
431impl From<metadata::PageLayout> for PageEncoding {
432 fn from(layout: metadata::PageLayout) -> Self {
433 Self::Structural(layout)
434 }
435}
436
437impl From<crate::format::pb21::PageLayout> for PageEncoding {
438 fn from(layout: crate::format::pb21::PageLayout) -> Self {
439 Self::Structural(layout.into())
440 }
441}
442
443impl DeepSizeOf for PageEncoding {
444 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
445 match self {
446 Self::Legacy(encoding) => encoding.encoded_len() * 4,
447 Self::Structural(encoding) => encoding.estimated_size(),
448 Self::Planar(encoding) => encoding.encoded_len() * 4,
449 }
450 }
451}
452
453impl PageEncoding {
454 pub fn as_legacy(&self) -> &pb::ArrayEncoding {
455 match self {
456 Self::Legacy(enc) => enc,
457 Self::Structural(_) | Self::Planar(_) => panic!("Expected a legacy encoding"),
458 }
459 }
460
461 pub fn as_structural(&self) -> &metadata::PageLayout {
462 match self {
463 Self::Structural(enc) => enc,
464 Self::Legacy(_) | Self::Planar(_) => panic!("Expected a structural encoding"),
465 }
466 }
467
468 pub fn is_structural(&self) -> bool {
469 matches!(self, Self::Structural(_) | Self::Planar(_))
470 }
471}
472
473#[derive(Debug)]
477pub struct PageInfo {
478 pub num_rows: u64,
480 pub priority: u64,
484 pub encoding: PageEncoding,
486 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
488}
489
490impl DeepSizeOf for PageInfo {
491 fn deep_size_of_children(&self, context: &mut Context) -> usize {
492 self.encoding.deep_size_of_children(context)
493 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
494 }
495}
496
497#[derive(Debug, Clone)]
501pub struct ColumnInfo {
502 pub index: u32,
504 pub page_infos: Arc<[PageInfo]>,
506 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
508 pub encoding: pb::ColumnEncoding,
509}
510
511impl DeepSizeOf for ColumnInfo {
512 fn deep_size_of_children(&self, context: &mut Context) -> usize {
513 self.page_infos.deep_size_of_children(context)
514 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
515 + self.encoding.encoded_len() * 4
516 }
517}
518
519impl ColumnInfo {
520 pub fn new(
522 index: u32,
523 page_infos: Arc<[PageInfo]>,
524 buffer_offsets_and_sizes: Vec<(u64, u64)>,
525 encoding: pb::ColumnEncoding,
526 ) -> Self {
527 Self {
528 index,
529 page_infos,
530 buffer_offsets_and_sizes: buffer_offsets_and_sizes.into_boxed_slice().into(),
531 encoding,
532 }
533 }
534
535 pub fn is_structural(&self) -> bool {
536 self.page_infos
537 .first()
539 .map(|page| page.encoding.is_structural())
540 .unwrap_or(false)
541 }
542}
543
544enum RootScheduler {
545 Structural(Box<dyn StructuralFieldScheduler>),
546 Array(Arc<dyn FieldScheduler>),
547}
548
549impl RootScheduler {
550 fn as_array(&self) -> &Arc<dyn FieldScheduler> {
551 match self {
552 Self::Structural(_) => panic!("Expected an array scheduler"),
553 Self::Array(s) => s,
554 }
555 }
556
557 fn as_structural(&self) -> &dyn StructuralFieldScheduler {
558 match self {
559 Self::Structural(s) => s.as_ref(),
560 Self::Array(_) => panic!("Expected a structural scheduler"),
561 }
562 }
563}
564
565pub struct DecodeBatchScheduler {
587 root_scheduler: RootScheduler,
588 pub root_fields: Fields,
589 cache: Arc<LanceCache>,
590}
591
592pub struct ColumnInfoIter<'a> {
593 column_infos: Vec<Arc<ColumnInfo>>,
594 column_indices: &'a [u32],
595 column_info_pos: usize,
596 column_indices_pos: usize,
597}
598
599impl<'a> ColumnInfoIter<'a> {
600 pub fn new(column_infos: Vec<Arc<ColumnInfo>>, column_indices: &'a [u32]) -> Self {
601 let initial_pos = column_indices.first().copied().unwrap_or(0) as usize;
602 Self {
603 column_infos,
604 column_indices,
605 column_info_pos: initial_pos,
606 column_indices_pos: 0,
607 }
608 }
609
610 pub fn peek(&self) -> &Arc<ColumnInfo> {
611 &self.column_infos[self.column_info_pos]
612 }
613
614 pub fn peek_transform(&mut self, transform: impl FnOnce(Arc<ColumnInfo>) -> Arc<ColumnInfo>) {
615 let column_info = self.column_infos[self.column_info_pos].clone();
616 let transformed = transform(column_info);
617 self.column_infos[self.column_info_pos] = transformed;
618 }
619
620 pub fn expect_next(&mut self) -> Result<&Arc<ColumnInfo>> {
621 self.next().ok_or_else(|| {
622 Error::invalid_input(
623 "there were more fields in the schema than provided column indices / infos",
624 )
625 })
626 }
627
628 fn next(&mut self) -> Option<&Arc<ColumnInfo>> {
629 if self.column_info_pos < self.column_infos.len() {
630 let info = &self.column_infos[self.column_info_pos];
631 self.column_info_pos += 1;
632 Some(info)
633 } else {
634 None
635 }
636 }
637
638 pub(crate) fn next_top_level(&mut self) {
639 self.column_indices_pos += 1;
640 if self.column_indices_pos < self.column_indices.len() {
641 self.column_info_pos = self.column_indices[self.column_indices_pos] as usize;
642 } else {
643 self.column_info_pos = self.column_infos.len();
644 }
645 }
646}
647
648#[derive(Clone, Copy, Debug)]
650pub struct FileBuffers<'a> {
651 pub positions_and_sizes: &'a [(u64, u64)],
652}
653
654#[derive(Clone, Copy, Debug)]
656pub struct ColumnBuffers<'a, 'b> {
657 pub file_buffers: FileBuffers<'a>,
658 pub positions_and_sizes: &'b [(u64, u64)],
659}
660
661#[derive(Clone, Copy, Debug)]
663pub struct PageBuffers<'a, 'b, 'c> {
664 pub column_buffers: ColumnBuffers<'a, 'b>,
665 pub positions_and_sizes: &'c [(u64, u64)],
666}
667
668#[derive(Debug)]
670pub struct CoreFieldDecoderStrategy {
671 pub validate_data: bool,
672 pub decompressor_strategy: Arc<dyn DecompressionStrategy>,
673 pub cache_repetition_index: bool,
674}
675
676impl Default for CoreFieldDecoderStrategy {
677 fn default() -> Self {
678 Self {
679 validate_data: false,
680 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
681 cache_repetition_index: false,
682 }
683 }
684}
685
686impl CoreFieldDecoderStrategy {
687 pub fn with_cache_repetition_index(mut self, cache_repetition_index: bool) -> Self {
689 self.cache_repetition_index = cache_repetition_index;
690 self
691 }
692
693 pub fn from_decoder_config(config: &DecoderConfig) -> Self {
695 Self {
696 validate_data: config.validate_on_decode,
697 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
698 cache_repetition_index: config.cache_repetition_index,
699 }
700 }
701
702 fn ensure_values_encoded(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
705 let column_encoding = column_info
706 .encoding
707 .column_encoding
708 .as_ref()
709 .ok_or_else(|| {
710 Error::invalid_input(format!(
711 "the column at index {} was missing a ColumnEncoding",
712 column_info.index
713 ))
714 })?;
715 if matches!(
716 column_encoding,
717 pb::column_encoding::ColumnEncoding::Values(_)
718 ) {
719 Ok(())
720 } else {
721 Err(Error::invalid_input(format!(
722 "the column at index {} mapping to the input field {} has column encoding {:?} and no decoder is registered to handle it",
723 column_info.index, field_name, column_encoding
724 )))
725 }
726 }
727
728 fn is_structural_primitive(data_type: &DataType) -> bool {
729 if data_type.is_primitive() {
730 true
731 } else {
732 match data_type {
733 DataType::Dictionary(_, value_type) => Self::is_structural_primitive(value_type),
735 DataType::Boolean
736 | DataType::Null
737 | DataType::FixedSizeBinary(_)
738 | DataType::Binary
739 | DataType::LargeBinary
740 | DataType::Utf8
741 | DataType::LargeUtf8 => true,
742 DataType::FixedSizeList(inner, _) => {
743 Self::is_structural_primitive(inner.data_type())
744 }
745 _ => false,
746 }
747 }
748 }
749
750 fn is_array_primitive(data_type: &DataType) -> bool {
751 if data_type.is_primitive() {
752 true
753 } else {
754 match data_type {
755 DataType::Boolean | DataType::Null | DataType::FixedSizeBinary(_) => true,
757 DataType::FixedSizeList(inner, _) => Self::is_array_primitive(inner.data_type()),
758 _ => false,
759 }
760 }
761 }
762
763 fn create_primitive_scheduler(
764 &self,
765 field: &Field,
766 column: &ColumnInfo,
767 buffers: FileBuffers,
768 ) -> Result<Box<dyn FieldScheduler>> {
769 Self::ensure_values_encoded(column, &field.name)?;
770 let column_buffers = ColumnBuffers {
772 file_buffers: buffers,
773 positions_and_sizes: &column.buffer_offsets_and_sizes,
774 };
775 Ok(Box::new(PrimitiveFieldScheduler::new(
776 column.index,
777 field.data_type(),
778 column.page_infos.clone(),
779 column_buffers,
780 self.validate_data,
781 )))
782 }
783
784 fn check_simple_struct(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
786 Self::ensure_values_encoded(column_info, field_name)?;
787 if column_info.page_infos.len() != 1 {
788 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()));
789 }
790 let encoding = &column_info.page_infos[0].encoding;
791 match encoding.as_legacy().array_encoding.as_ref().unwrap() {
792 pb::array_encoding::ArrayEncoding::Struct(_) => Ok(()),
793 _ => 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())),
794 }
795 }
796
797 fn check_packed_struct(column_info: &ColumnInfo) -> bool {
798 let encoding = &column_info.page_infos[0].encoding;
799 matches!(
800 encoding.as_legacy().array_encoding.as_ref().unwrap(),
801 pb::array_encoding::ArrayEncoding::PackedStruct(_)
802 )
803 }
804
805 fn create_list_scheduler(
806 &self,
807 list_field: &Field,
808 column_infos: &mut ColumnInfoIter,
809 buffers: FileBuffers,
810 offsets_column: &ColumnInfo,
811 ) -> Result<Box<dyn FieldScheduler>> {
812 Self::ensure_values_encoded(offsets_column, &list_field.name)?;
813 let offsets_column_buffers = ColumnBuffers {
814 file_buffers: buffers,
815 positions_and_sizes: &offsets_column.buffer_offsets_and_sizes,
816 };
817 let items_scheduler =
818 self.create_array_field_scheduler(&list_field.children[0], column_infos, buffers)?;
819
820 let mut inner_infos = Vec::with_capacity(offsets_column.page_infos.len());
821 let mut null_offset_adjustments = Vec::with_capacity(offsets_column.page_infos.len());
822 for (page_index, offsets_page) in offsets_column
823 .page_infos
824 .iter()
825 .enumerate()
826 .filter(|(_, offsets_page)| offsets_page.num_rows > 0)
827 {
828 let PageEncoding::Legacy(pb::ArrayEncoding {
829 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(list_encoding)),
830 }) = &offsets_page.encoding
831 else {
832 return Err(Error::invalid_input(format!(
833 "expected list encoding for field '{}' in column {}, page {} but got {:?}",
834 list_field.name, offsets_column.index, page_index, offsets_page.encoding
835 )));
836 };
837 let offsets_encoding = list_encoding.offsets.as_ref().ok_or_else(|| {
838 Error::invalid_input(format!(
839 "list encoding for field '{}' in column {}, page {} is missing its offsets encoding",
840 list_field.name, offsets_column.index, page_index
841 ))
842 })?;
843 inner_infos.push(PageInfo {
844 buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
845 encoding: PageEncoding::Legacy(offsets_encoding.as_ref().clone()),
846 num_rows: offsets_page.num_rows,
847 priority: 0,
848 });
849 null_offset_adjustments.push(OffsetPageInfo {
850 offsets_in_page: offsets_page.num_rows,
851 null_offset_adjustment: list_encoding.null_offset_adjustment,
852 num_items_referenced_by_page: list_encoding.num_items,
853 });
854 }
855 let inner = Arc::new(PrimitiveFieldScheduler::new(
856 offsets_column.index,
857 DataType::UInt64,
858 Arc::from(inner_infos.into_boxed_slice()),
859 offsets_column_buffers,
860 self.validate_data,
861 )) as Arc<dyn FieldScheduler>;
862 let items_field = match list_field.data_type() {
863 DataType::List(inner) => inner,
864 DataType::LargeList(inner) => inner,
865 _ => unreachable!(),
866 };
867 let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
868 DataType::Int32
869 } else {
870 DataType::Int64
871 };
872 Ok(Box::new(ListFieldScheduler::new(
873 inner,
874 items_scheduler.into(),
875 items_field,
876 offset_type,
877 null_offset_adjustments,
878 )))
879 }
880
881 fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
882 if let column_encoding::ColumnEncoding::Blob(blob) =
883 column_info.encoding.column_encoding.as_ref().unwrap()
884 {
885 let mut column_info = column_info.clone();
886 column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
887 Some(column_info)
888 } else {
889 None
890 }
891 }
892
893 fn create_structural_field_scheduler(
894 &self,
895 field: &Field,
896 column_infos: &mut ColumnInfoIter,
897 ) -> Result<Box<dyn StructuralFieldScheduler>> {
898 let data_type = field.data_type();
899 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
900 if Self::is_structural_primitive(&data_type) {
901 let column_info = column_infos.expect_next()?;
902 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
903 column_info.clone(),
904 self.decompressor_strategy.clone(),
905 self.cache_repetition_index,
906 field,
907 )?);
908
909 column_infos.next_top_level();
911
912 return Ok(scheduler);
913 }
914 match &data_type {
915 DataType::Struct(fields) => {
916 if field.is_packed_struct() {
917 let column_info = column_infos.expect_next()?;
919 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
920 column_info.clone(),
921 self.decompressor_strategy.clone(),
922 self.cache_repetition_index,
923 field,
924 )?);
925
926 column_infos.next_top_level();
928
929 return Ok(scheduler);
930 }
931 if field.is_blob() {
933 let column_info = column_infos.peek();
934 if column_info.page_infos.iter().any(|page| {
935 matches!(
936 page.encoding,
937 PageEncoding::Structural(metadata::PageLayout {
938 layout: Some(metadata::page_layout::Layout::BlobLayout(_))
939 })
940 )
941 }) {
942 let column_info = column_infos.expect_next()?;
943 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
944 column_info.clone(),
945 self.decompressor_strategy.clone(),
946 self.cache_repetition_index,
947 field,
948 )?);
949 column_infos.next_top_level();
950 return Ok(scheduler);
951 }
952 }
953
954 let mut child_schedulers = Vec::with_capacity(field.children.len());
955 for field in field.children.iter() {
956 let field_scheduler =
957 self.create_structural_field_scheduler(field, column_infos)?;
958 child_schedulers.push(field_scheduler);
959 }
960
961 let fields = fields.clone();
962 Ok(
963 Box::new(StructuralStructScheduler::new(child_schedulers, fields))
964 as Box<dyn StructuralFieldScheduler>,
965 )
966 }
967 DataType::List(_) | DataType::LargeList(_) => {
968 let child = field.children.first().expect_ok()?;
969 let child_scheduler =
970 self.create_structural_field_scheduler(child, column_infos)?;
971 Ok(Box::new(StructuralListScheduler::new(child_scheduler))
972 as Box<dyn StructuralFieldScheduler>)
973 }
974 DataType::FixedSizeList(inner, dimension)
975 if matches!(inner.data_type(), DataType::Struct(_)) =>
976 {
977 let child = field.children.first().expect_ok()?;
978 let child_scheduler =
979 self.create_structural_field_scheduler(child, column_infos)?;
980 Ok(Box::new(StructuralFixedSizeListScheduler::new(
981 child_scheduler,
982 *dimension,
983 )) as Box<dyn StructuralFieldScheduler>)
984 }
985 DataType::Map(_, keys_sorted) => {
986 if *keys_sorted {
990 return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
991 }
992 let entries_child = field.children.first().expect_ok()?;
993 let child_scheduler =
994 self.create_structural_field_scheduler(entries_child, column_infos)?;
995 Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
996 as Box<dyn StructuralFieldScheduler>)
997 }
998 _ => todo!("create_structural_field_scheduler for {}", data_type),
999 }
1000 }
1001
1002 fn create_array_field_scheduler(
1003 &self,
1004 field: &Field,
1005 column_infos: &mut ColumnInfoIter,
1006 buffers: FileBuffers,
1007 ) -> Result<Box<dyn FieldScheduler>> {
1008 let data_type = field.data_type();
1009 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
1010 if Self::is_array_primitive(&data_type) {
1011 let column_info = column_infos.expect_next()?;
1012 let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
1013 return Ok(scheduler);
1014 } else if data_type.is_binary_like() {
1015 let column_info = column_infos.expect_next()?.clone();
1016 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1018 let desc_scheduler =
1019 self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
1020 let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
1021 return Ok(blob_scheduler);
1022 }
1023 if let Some(page_info) = column_info.page_infos.first() {
1024 if matches!(
1025 page_info.encoding.as_legacy(),
1026 pb::ArrayEncoding {
1027 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
1028 }
1029 ) {
1030 let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
1031 DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
1032 } else {
1033 DataType::LargeList(Arc::new(ArrowField::new(
1034 "item",
1035 DataType::UInt8,
1036 false,
1037 )))
1038 };
1039 let list_field = Field::try_from(ArrowField::new(
1040 field.name.clone(),
1041 list_type,
1042 field.nullable,
1043 ))
1044 .unwrap();
1045 let list_scheduler = self.create_list_scheduler(
1046 &list_field,
1047 column_infos,
1048 buffers,
1049 &column_info,
1050 )?;
1051 let binary_scheduler = Box::new(BinaryFieldScheduler::new(
1052 list_scheduler.into(),
1053 field.data_type(),
1054 ));
1055 return Ok(binary_scheduler);
1056 } else {
1057 let scheduler =
1058 self.create_primitive_scheduler(field, &column_info, buffers)?;
1059 return Ok(scheduler);
1060 }
1061 } else {
1062 return self.create_primitive_scheduler(field, &column_info, buffers);
1063 }
1064 }
1065 match &data_type {
1066 DataType::FixedSizeList(inner, _dimension) => {
1067 if Self::is_array_primitive(inner.data_type()) {
1070 let primitive_col = column_infos.expect_next()?;
1071 let scheduler =
1072 self.create_primitive_scheduler(field, primitive_col, buffers)?;
1073 Ok(scheduler)
1074 } else {
1075 todo!()
1076 }
1077 }
1078 DataType::Dictionary(_key_type, value_type) => {
1079 if Self::is_array_primitive(value_type) || value_type.is_binary_like() {
1080 let primitive_col = column_infos.expect_next()?;
1081 let scheduler =
1082 self.create_primitive_scheduler(field, primitive_col, buffers)?;
1083 Ok(scheduler)
1084 } else {
1085 Err(Error::not_supported_source(
1086 format!(
1087 "No way to decode into a dictionary field of type {}",
1088 value_type
1089 )
1090 .into(),
1091 ))
1092 }
1093 }
1094 DataType::List(_) | DataType::LargeList(_) => {
1095 let offsets_column = column_infos.expect_next()?.clone();
1096 column_infos.next_top_level();
1097 self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
1098 }
1099 DataType::Struct(fields) => {
1100 let column_info = column_infos.expect_next()?;
1101
1102 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1104 return self.create_primitive_scheduler(field, &blob_col, buffers);
1106 }
1107
1108 if Self::check_packed_struct(column_info) {
1109 self.create_primitive_scheduler(field, column_info, buffers)
1111 } else {
1112 Self::check_simple_struct(column_info, &field.name).unwrap();
1114 let num_rows = column_info
1115 .page_infos
1116 .iter()
1117 .map(|page| page.num_rows)
1118 .sum();
1119 let mut child_schedulers = Vec::with_capacity(field.children.len());
1120 for field in &field.children {
1121 column_infos.next_top_level();
1122 let field_scheduler =
1123 self.create_array_field_scheduler(field, column_infos, buffers)?;
1124 child_schedulers.push(Arc::from(field_scheduler));
1125 }
1126
1127 let fields = fields.clone();
1128 Ok(Box::new(SimpleStructScheduler::new(
1129 child_schedulers,
1130 fields,
1131 num_rows,
1132 )))
1133 }
1134 }
1135 _ => todo!(),
1137 }
1138 }
1139}
1140
1141fn root_column(num_rows: u64) -> ColumnInfo {
1143 let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
1144 let final_page_num_rows = num_rows % (u32::MAX as u64);
1145 let root_pages = (0..num_root_pages)
1146 .map(|i| PageInfo {
1147 num_rows: if i == num_root_pages - 1 {
1148 final_page_num_rows
1149 } else {
1150 u64::MAX
1151 },
1152 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1153 array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1154 pb::SimpleStruct {},
1155 )),
1156 }),
1157 priority: 0, buffer_offsets_and_sizes: Arc::new([]),
1159 })
1160 .collect::<Vec<_>>();
1161 ColumnInfo {
1162 buffer_offsets_and_sizes: Arc::new([]),
1163 encoding: pb::ColumnEncoding {
1164 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1165 },
1166 index: u32::MAX,
1167 page_infos: Arc::from(root_pages),
1168 }
1169}
1170
1171pub enum RootDecoder {
1172 Structural(StructuralStructDecoder),
1173 Array(SimpleStructDecoder),
1174}
1175
1176impl RootDecoder {
1177 pub fn into_structural(self) -> StructuralStructDecoder {
1178 match self {
1179 Self::Structural(decoder) => decoder,
1180 Self::Array(_) => panic!("Expected a structural decoder"),
1181 }
1182 }
1183
1184 pub fn into_array(self) -> SimpleStructDecoder {
1185 match self {
1186 Self::Array(decoder) => decoder,
1187 Self::Structural(_) => panic!("Expected an array decoder"),
1188 }
1189 }
1190}
1191
1192impl DecodeBatchScheduler {
1193 #[allow(clippy::too_many_arguments)]
1196 pub async fn try_new<'a>(
1197 schema: &'a Schema,
1198 column_indices: &[u32],
1199 column_infos: &[Arc<ColumnInfo>],
1200 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1201 num_rows: u64,
1202 decoder_plugins: Arc<DecoderPlugins>,
1203 io: Arc<dyn EncodingsIo>,
1204 cache: Arc<LanceCache>,
1205 filter: &FilterExpression,
1206 decoder_config: &DecoderConfig,
1207 ) -> Result<Self> {
1208 Self::try_new_with_ranges(
1209 schema,
1210 column_indices,
1211 column_infos,
1212 file_buffer_positions_and_sizes,
1213 num_rows,
1214 decoder_plugins,
1215 io,
1216 cache,
1217 None,
1218 filter,
1219 decoder_config,
1220 )
1221 .await
1222 }
1223
1224 #[allow(clippy::too_many_arguments)]
1226 pub(crate) async fn try_new_with_ranges<'a>(
1227 schema: &'a Schema,
1228 column_indices: &[u32],
1229 column_infos: &[Arc<ColumnInfo>],
1230 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1231 num_rows: u64,
1232 _decoder_plugins: Arc<DecoderPlugins>,
1233 io: Arc<dyn EncodingsIo>,
1234 cache: Arc<LanceCache>,
1235 requested_ranges: Option<&[Range<u64>]>,
1239 filter: &FilterExpression,
1240 decoder_config: &DecoderConfig,
1241 ) -> Result<Self> {
1242 assert!(num_rows > 0);
1243 let buffers = FileBuffers {
1244 positions_and_sizes: file_buffer_positions_and_sizes,
1245 };
1246 let arrow_schema = ArrowSchema::from(schema);
1247 let root_fields = arrow_schema.fields().clone();
1248 let root_type = DataType::Struct(root_fields.clone());
1249 let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1250 root_field.children.clone_from(&schema.fields);
1254 root_field
1255 .metadata
1256 .insert("__lance_decoder_root".to_string(), "true".to_string());
1257
1258 if column_infos.is_empty() || column_infos[0].is_structural() {
1259 let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1260
1261 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1262 let mut root_scheduler =
1263 strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1264
1265 let context = SchedulerContext::new(io, cache.clone());
1266 root_scheduler
1267 .initialize(requested_ranges, filter, &context)
1268 .await?;
1269
1270 Ok(Self {
1271 root_scheduler: RootScheduler::Structural(root_scheduler),
1272 root_fields,
1273 cache,
1274 })
1275 } else {
1276 let mut columns = Vec::with_capacity(column_infos.len() + 1);
1279 columns.push(Arc::new(root_column(num_rows)));
1280 columns.extend(column_infos.iter().cloned());
1281
1282 let adjusted_column_indices = [0_u32]
1283 .into_iter()
1284 .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1285 .collect::<Vec<_>>();
1286 let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1287 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1288 let root_scheduler =
1289 strategy.create_array_field_scheduler(&root_field, &mut column_iter, buffers)?;
1290
1291 let context = SchedulerContext::new(io, cache.clone());
1292 root_scheduler.initialize(filter, &context).await?;
1293
1294 Ok(Self {
1295 root_scheduler: RootScheduler::Array(root_scheduler.into()),
1296 root_fields,
1297 cache,
1298 })
1299 }
1300 }
1301
1302 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
1303 pub fn from_scheduler(
1304 root_scheduler: Arc<dyn FieldScheduler>,
1305 root_fields: Fields,
1306 cache: Arc<LanceCache>,
1307 ) -> Self {
1308 Self {
1309 root_scheduler: RootScheduler::Array(root_scheduler),
1310 root_fields,
1311 cache,
1312 }
1313 }
1314
1315 fn do_schedule_ranges_structural(
1316 &mut self,
1317 ranges: &[Range<u64>],
1318 filter: &FilterExpression,
1319 io: Arc<dyn EncodingsIo>,
1320 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1321 ) {
1322 let root_scheduler = self.root_scheduler.as_structural();
1323 let mut context = SchedulerContext::new(io, self.cache.clone());
1324 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1325 if let Err(schedule_ranges_err) = maybe_root_job {
1326 schedule_action(Err(schedule_ranges_err));
1327 return;
1328 }
1329 let mut root_job = maybe_root_job.unwrap();
1330 let mut num_rows_scheduled = 0;
1331 loop {
1332 let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1333 if let Err(err) = maybe_next_scan_lines {
1334 schedule_action(Err(err));
1335 return;
1336 }
1337 let next_scan_lines = maybe_next_scan_lines.unwrap();
1338 context.flush_io();
1339 if next_scan_lines.is_empty() {
1340 return;
1341 }
1342 for next_scan_line in next_scan_lines {
1343 trace!(
1344 "Scheduled scan line of {} rows and {} decoders",
1345 next_scan_line.rows_scheduled,
1346 next_scan_line.decoders.len()
1347 );
1348 num_rows_scheduled += next_scan_line.rows_scheduled;
1349 if !schedule_action(Ok(DecoderMessage {
1350 scheduled_so_far: num_rows_scheduled,
1351 decoders: next_scan_line.decoders,
1352 })) {
1353 return;
1355 }
1356 }
1357 }
1358 }
1359
1360 fn do_schedule_ranges_array(
1361 &mut self,
1362 ranges: &[Range<u64>],
1363 filter: &FilterExpression,
1364 io: Arc<dyn EncodingsIo>,
1365 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1366 priority: Option<Box<dyn PriorityRange>>,
1370 ) {
1371 let root_scheduler = self.root_scheduler.as_array();
1372 let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1373 trace!(
1374 "Scheduling {} ranges across {}..{} ({} rows){}",
1375 ranges.len(),
1376 ranges.first().unwrap().start,
1377 ranges.last().unwrap().end,
1378 rows_requested,
1379 priority
1380 .as_ref()
1381 .map(|p| format!(" (priority={:?})", p))
1382 .unwrap_or_default()
1383 );
1384
1385 let mut context = SchedulerContext::new(io, self.cache.clone());
1386 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1387 if let Err(schedule_ranges_err) = maybe_root_job {
1388 schedule_action(Err(schedule_ranges_err));
1389 return;
1390 }
1391 let mut root_job = maybe_root_job.unwrap();
1392 let mut num_rows_scheduled = 0;
1393 let mut rows_to_schedule = root_job.num_rows();
1394 let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1395 trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1396 while rows_to_schedule > 0 {
1397 let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1398 if let Err(schedule_next_err) = maybe_next_scan_line {
1399 schedule_action(Err(schedule_next_err));
1400 return;
1401 }
1402 let next_scan_line = maybe_next_scan_line.unwrap();
1403 context.flush_io();
1404 priority.advance(next_scan_line.rows_scheduled);
1405 num_rows_scheduled += next_scan_line.rows_scheduled;
1406 rows_to_schedule -= next_scan_line.rows_scheduled;
1407 trace!(
1408 "Scheduled scan line of {} rows and {} decoders",
1409 next_scan_line.rows_scheduled,
1410 next_scan_line.decoders.len()
1411 );
1412 if !schedule_action(Ok(DecoderMessage {
1413 scheduled_so_far: num_rows_scheduled,
1414 decoders: next_scan_line.decoders,
1415 })) {
1416 return;
1418 }
1419
1420 trace!("Finished scheduling {} ranges", ranges.len());
1421 }
1422 }
1423
1424 fn do_schedule_ranges(
1425 &mut self,
1426 ranges: &[Range<u64>],
1427 filter: &FilterExpression,
1428 io: Arc<dyn EncodingsIo>,
1429 schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1430 priority: Option<Box<dyn PriorityRange>>,
1434 ) {
1435 match &self.root_scheduler {
1436 RootScheduler::Array(_) => {
1437 self.do_schedule_ranges_array(ranges, filter, io, schedule_action, priority)
1438 }
1439 RootScheduler::Structural(_) => {
1440 self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1441 }
1442 }
1443 }
1444
1445 pub fn schedule_ranges_to_vec(
1448 &mut self,
1449 ranges: &[Range<u64>],
1450 filter: &FilterExpression,
1451 io: Arc<dyn EncodingsIo>,
1452 priority: Option<Box<dyn PriorityRange>>,
1453 ) -> Result<Vec<DecoderMessage>> {
1454 let mut decode_messages = Vec::new();
1455 self.do_schedule_ranges(
1456 ranges,
1457 filter,
1458 io,
1459 |msg| {
1460 decode_messages.push(msg);
1461 true
1462 },
1463 priority,
1464 );
1465 decode_messages.into_iter().collect::<Result<Vec<_>>>()
1466 }
1467
1468 #[instrument(level = "debug", skip_all)]
1478 pub fn schedule_ranges(
1479 &mut self,
1480 ranges: &[Range<u64>],
1481 filter: &FilterExpression,
1482 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1483 scheduler: Arc<dyn EncodingsIo>,
1484 ) {
1485 self.do_schedule_ranges(
1486 ranges,
1487 filter,
1488 scheduler,
1489 |msg| {
1490 match sink.send(msg) {
1491 Ok(_) => true,
1492 Err(SendError { .. }) => {
1493 debug!(
1496 "schedule_ranges aborting early since decoder appears to have been dropped"
1497 );
1498 false
1499 }
1500 }
1501 },
1502 None,
1503 )
1504 }
1505
1506 #[instrument(level = "debug", skip_all)]
1514 pub fn schedule_range(
1515 &mut self,
1516 range: Range<u64>,
1517 filter: &FilterExpression,
1518 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1519 scheduler: Arc<dyn EncodingsIo>,
1520 ) {
1521 self.schedule_ranges(&[range], filter, sink, scheduler)
1522 }
1523
1524 pub fn schedule_take(
1532 &mut self,
1533 indices: &[u64],
1534 filter: &FilterExpression,
1535 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1536 scheduler: Arc<dyn EncodingsIo>,
1537 ) {
1538 if indices.is_empty() {
1539 return;
1540 }
1541 trace!("Scheduling take of {} rows", indices.len());
1542 let ranges = match RequestedRows::indices_to_ranges(indices) {
1543 Ok(ranges) => ranges,
1544 Err(error) => {
1545 if let Err(SendError { .. }) = sink.send(Err(error)) {
1546 debug!(
1547 "schedule_take could not report invalid indices because the decoder was dropped"
1548 );
1549 }
1550 return;
1551 }
1552 };
1553 self.schedule_ranges(&ranges, filter, sink, scheduler)
1554 }
1555}
1556
1557pub struct ReadBatchTask {
1558 pub task: BoxFuture<'static, Result<RecordBatch>>,
1559 pub num_rows: u32,
1560}
1561
1562pub struct BatchDecodeStream {
1564 context: DecoderContext,
1565 root_decoder: SimpleStructDecoder,
1566 rows_remaining: u64,
1567 rows_per_batch: u32,
1568 rows_scheduled: u64,
1569 rows_drained: u64,
1570 scheduler_exhausted: bool,
1571 emitted_batch_size_warning: Arc<Once>,
1572 limit_i32_offset_batch_size: bool,
1573}
1574
1575impl BatchDecodeStream {
1576 pub fn new(
1586 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1587 rows_per_batch: u32,
1588 num_rows: u64,
1589 root_decoder: SimpleStructDecoder,
1590 ) -> Self {
1591 Self::new_with_i32_offset_limit(scheduled, rows_per_batch, num_rows, root_decoder, true)
1592 }
1593
1594 fn new_with_i32_offset_limit(
1595 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1596 rows_per_batch: u32,
1597 num_rows: u64,
1598 root_decoder: SimpleStructDecoder,
1599 limit_i32_offset_batch_size: bool,
1600 ) -> Self {
1601 Self {
1602 context: DecoderContext::new(scheduled),
1603 root_decoder,
1604 rows_remaining: num_rows,
1605 rows_per_batch,
1606 rows_scheduled: 0,
1607 rows_drained: 0,
1608 scheduler_exhausted: false,
1609 emitted_batch_size_warning: Arc::new(Once::new()),
1610 limit_i32_offset_batch_size,
1611 }
1612 }
1613
1614 fn accept_decoder(&mut self, decoder: DecoderReady) -> Result<()> {
1615 if decoder.path.is_empty() {
1616 Ok(())
1618 } else {
1619 self.root_decoder.accept_child(decoder)
1620 }
1621 }
1622
1623 #[instrument(level = "debug", skip_all)]
1624 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1625 if self.scheduler_exhausted {
1626 return Ok(self.rows_scheduled);
1627 }
1628 while self.rows_scheduled < scheduled_need {
1629 let next_message = self.context.source.recv().await;
1630 match next_message {
1631 Some(scan_line) => {
1632 let scan_line = scan_line?;
1633 self.rows_scheduled = scan_line.scheduled_so_far;
1634 for message in scan_line.decoders {
1635 self.accept_decoder(message.into_array())?;
1636 }
1637 }
1638 None => {
1639 self.scheduler_exhausted = true;
1643 return Ok(self.rows_scheduled);
1644 }
1645 }
1646 }
1647 Ok(scheduled_need)
1648 }
1649
1650 #[instrument(level = "debug", skip_all)]
1651 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1652 trace!(
1653 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1654 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1655 );
1656 if self.rows_remaining == 0 {
1657 return Ok(None);
1658 }
1659
1660 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1661 self.rows_remaining -= to_take;
1662
1663 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1664 trace!(
1665 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1666 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1667 );
1668 if scheduled_need > 0 {
1669 let desired_scheduled = scheduled_need + self.rows_scheduled;
1670 trace!(
1671 "Draining from scheduler (desire at least {} scheduled rows)",
1672 desired_scheduled
1673 );
1674 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1675 if actually_scheduled < desired_scheduled {
1676 let under_scheduled = desired_scheduled - actually_scheduled;
1677 to_take -= under_scheduled;
1678 }
1679 }
1680
1681 if to_take == 0 {
1682 return Ok(None);
1683 }
1684
1685 let loaded_need = self.rows_drained + to_take - 1;
1687 trace!(
1688 "Waiting for I/O (desire at least {} fully loaded rows)",
1689 loaded_need
1690 );
1691 self.root_decoder.wait_for_loaded(loaded_need).await?;
1692
1693 if self.limit_i32_offset_batch_size && has_i32_offsets(self.root_decoder.data_type()) {
1694 let limit = LogicalPageDecoder::max_rows_to_drain(
1695 &self.root_decoder,
1696 to_take,
1697 I32_OFFSET_BYTE_BUDGET,
1698 )?;
1699 let clamped = clamp_rows_to_i32_budget(limit, to_take);
1700 self.rows_remaining += to_take - clamped;
1701 to_take = clamped;
1702 }
1703
1704 let next_task = self.root_decoder.drain(to_take)?;
1705 self.rows_drained += to_take;
1706 Ok(Some(next_task))
1707 }
1708
1709 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1710 let stream = futures::stream::unfold(self, |mut slf| async move {
1711 let next_task = match slf.next_batch_task().await {
1712 Ok(Some(next_task)) => next_task,
1713 Ok(None) => return None,
1714 Err(err) => {
1715 slf.rows_remaining = 0;
1716 return Some((
1717 ReadBatchTask {
1718 task: async move { Err(err) }.boxed(),
1719 num_rows: 0,
1720 },
1721 slf,
1722 ));
1723 }
1724 };
1725 let num_rows = next_task.num_rows;
1726 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1727 let task = async move {
1728 let (batch, _data_size) =
1732 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1733 .await
1734 .map_err(|err| Error::wrapped(err.into()))??;
1735 Ok(batch)
1736 };
1737 debug_assert!(num_rows <= u32::MAX as u64);
1739 Some((
1740 ReadBatchTask {
1741 task: task.boxed(),
1742 num_rows: num_rows as u32,
1743 },
1744 slf,
1745 ))
1746 });
1747 stream.boxed()
1748 }
1749}
1750
1751enum RootDecoderMessage {
1754 LoadedPage(LoadedPageShard),
1755 ArrayPage(DecoderReady),
1756}
1757trait RootDecoderType {
1758 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1759 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1760 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1761 fn max_rows_to_drain(&self, num_rows: u64, _byte_budget: u64) -> Result<DrainLimit> {
1764 Ok(DrainLimit {
1765 rows: num_rows,
1766 bytes: 0,
1767 })
1768 }
1769}
1770impl RootDecoderType for StructuralStructDecoder {
1771 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1772 let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1773 unreachable!()
1774 };
1775 self.accept_page(loaded_page)
1776 }
1777 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1778 self.drain_batch_task(num_rows)
1779 }
1780 fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1781 Ok(())
1783 }
1784}
1785impl RootDecoderType for SimpleStructDecoder {
1786 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1787 let RootDecoderMessage::ArrayPage(array_page) = message else {
1788 unreachable!()
1789 };
1790 self.accept_child(array_page)
1791 }
1792 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1793 self.drain(num_rows)
1794 }
1795 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1796 runtime.block_on(self.wait_for_loaded(loaded_need))
1797 }
1798 fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
1799 if !has_i32_offsets(LogicalPageDecoder::data_type(self)) {
1800 return Ok(DrainLimit {
1801 rows: num_rows,
1802 bytes: 0,
1803 });
1804 }
1805 LogicalPageDecoder::max_rows_to_drain(self, num_rows, byte_budget)
1806 }
1807}
1808
1809struct BatchDecodeIterator<T: RootDecoderType> {
1811 messages: VecDeque<Result<DecoderMessage>>,
1812 root_decoder: T,
1813 rows_remaining: u64,
1814 rows_per_batch: u32,
1815 rows_scheduled: u64,
1816 rows_drained: u64,
1817 emitted_batch_size_warning: Arc<Once>,
1818 wait_for_io_runtime: tokio::runtime::Runtime,
1822 schema: Arc<ArrowSchema>,
1823}
1824
1825impl<T: RootDecoderType> BatchDecodeIterator<T> {
1826 pub fn new(
1828 messages: VecDeque<Result<DecoderMessage>>,
1829 rows_per_batch: u32,
1830 num_rows: u64,
1831 root_decoder: T,
1832 schema: Arc<ArrowSchema>,
1833 ) -> Self {
1834 Self {
1835 messages,
1836 root_decoder,
1837 rows_remaining: num_rows,
1838 rows_per_batch,
1839 rows_scheduled: 0,
1840 rows_drained: 0,
1841 wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1842 .build()
1843 .unwrap(),
1844 emitted_batch_size_warning: Arc::new(Once::new()),
1845 schema,
1846 }
1847 }
1848
1849 fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1854 match maybe_done(unloaded_page.0) {
1855 MaybeDone::Done(loaded_page) => loaded_page,
1857 MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1859 MaybeDone::Gone => unreachable!(),
1860 }
1861 }
1862
1863 #[instrument(level = "debug", skip_all)]
1868 fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1869 while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1870 let message = self.messages.pop_front().unwrap()?;
1871 self.rows_scheduled = message.scheduled_so_far;
1872 for decoder_message in message.decoders {
1873 match decoder_message {
1874 MessageType::UnloadedPage(unloaded_page) => {
1875 let loaded_page = self.wait_for_page(unloaded_page)?;
1876 self.root_decoder
1877 .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1878 }
1879 MessageType::DecoderReady(decoder_ready) => {
1880 if !decoder_ready.path.is_empty() {
1882 self.root_decoder
1883 .accept_message(RootDecoderMessage::ArrayPage(decoder_ready))?;
1884 }
1885 }
1886 }
1887 }
1888 }
1889
1890 let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1891
1892 self.root_decoder
1893 .wait(loaded_need, &self.wait_for_io_runtime)?;
1894 Ok(self.rows_scheduled)
1895 }
1896
1897 #[instrument(level = "debug", skip_all)]
1898 fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1899 trace!(
1900 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1901 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1902 );
1903 if self.rows_remaining == 0 {
1904 return Ok(None);
1905 }
1906
1907 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1908 self.rows_remaining -= to_take;
1909
1910 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1911 trace!(
1912 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1913 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1914 );
1915 if scheduled_need > 0 {
1916 let desired_scheduled = scheduled_need + self.rows_scheduled;
1917 trace!(
1918 "Draining from scheduler (desire at least {} scheduled rows)",
1919 desired_scheduled
1920 );
1921 let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1922 if actually_scheduled < desired_scheduled {
1923 let under_scheduled = desired_scheduled - actually_scheduled;
1924 to_take -= under_scheduled;
1925 }
1926 }
1927
1928 if to_take == 0 {
1929 return Ok(None);
1930 }
1931
1932 let limit = self
1933 .root_decoder
1934 .max_rows_to_drain(to_take, I32_OFFSET_BYTE_BUDGET)?;
1935 let clamped = clamp_rows_to_i32_budget(limit, to_take);
1936 self.rows_remaining += to_take - clamped;
1937 to_take = clamped;
1938
1939 let next_task = self.root_decoder.drain_batch(to_take)?;
1940
1941 self.rows_drained += to_take;
1942
1943 let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1944
1945 Ok(Some(batch))
1946 }
1947}
1948
1949impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1950 type Item = ArrowResult<RecordBatch>;
1951
1952 fn next(&mut self) -> Option<Self::Item> {
1953 let next = self.next_batch_task().transpose();
1954 if let Some(Err(_)) = &next {
1955 self.rows_remaining = 0;
1957 }
1958 next.map(|r| r.map_err(ArrowError::from))
1959 }
1960}
1961
1962impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1963 fn schema(&self) -> Arc<ArrowSchema> {
1964 self.schema.clone()
1965 }
1966}
1967
1968pub fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1984 if let Some(w) = data_type.byte_width_opt() {
1985 return w as f64;
1986 }
1987 match data_type {
1988 DataType::Boolean => 1.0 / 8.0,
1989 DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1990 DataType::Struct(fields) => fields
1991 .iter()
1992 .map(|f| estimate_bytes_per_row(f.data_type()))
1993 .sum(),
1994 DataType::List(child) | DataType::LargeList(child) => {
1995 5.0 * estimate_bytes_per_row(child.data_type())
1996 }
1997 DataType::FixedSizeList(child, dim) => {
1998 *dim as f64 * estimate_bytes_per_row(child.data_type())
1999 }
2000 DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
2001 DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
2002 _ => 64.0,
2003 }
2004}
2005
2006pub struct StructuralBatchDecodeStream {
2008 context: DecoderContext,
2009 root_decoder: StructuralStructDecoder,
2010 rows_remaining: u64,
2011 rows_per_batch: u32,
2012 rows_scheduled: u64,
2013 rows_drained: u64,
2014 scheduler_exhausted: bool,
2015 emitted_batch_size_warning: Arc<Once>,
2016 spawn_batch_decode_tasks: bool,
2024 batch_size_bytes: Option<u64>,
2027 schema_bytes_per_row: f64,
2030 bytes_per_row_feedback: Arc<AtomicU64>,
2033}
2034
2035impl StructuralBatchDecodeStream {
2036 pub fn new(
2046 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2047 rows_per_batch: u32,
2048 num_rows: u64,
2049 root_decoder: StructuralStructDecoder,
2050 spawn_batch_decode_tasks: bool,
2051 batch_size_bytes: Option<u64>,
2052 ) -> Self {
2053 let schema_bytes_per_row = if batch_size_bytes.is_some() {
2054 estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
2055 } else {
2056 0.0
2057 };
2058 Self {
2059 context: DecoderContext::new(scheduled),
2060 root_decoder,
2061 rows_remaining: num_rows,
2062 rows_per_batch,
2063 rows_scheduled: 0,
2064 rows_drained: 0,
2065 scheduler_exhausted: false,
2066 emitted_batch_size_warning: Arc::new(Once::new()),
2067 spawn_batch_decode_tasks,
2068 batch_size_bytes,
2069 schema_bytes_per_row,
2070 bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
2071 }
2072 }
2073
2074 #[instrument(level = "debug", skip_all)]
2075 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
2076 if self.scheduler_exhausted {
2077 return Ok(self.rows_scheduled);
2078 }
2079 while self.rows_scheduled < scheduled_need {
2080 let next_message = self.context.source.recv().await;
2081 match next_message {
2082 Some(scan_line) => {
2083 let scan_line = scan_line?;
2084 self.rows_scheduled = scan_line.scheduled_so_far;
2085 for message in scan_line.decoders {
2086 let unloaded_page = message.into_structural();
2087 let loaded_page = unloaded_page.0.await?;
2088 self.root_decoder.accept_page(loaded_page)?;
2089 }
2090 }
2091 None => {
2092 self.scheduler_exhausted = true;
2096 return Ok(self.rows_scheduled);
2097 }
2098 }
2099 }
2100 Ok(scheduled_need)
2101 }
2102
2103 #[instrument(level = "debug", skip_all)]
2104 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
2105 trace!(
2106 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
2107 self.rows_remaining, self.rows_drained, self.rows_scheduled,
2108 );
2109 if self.rows_remaining == 0 {
2110 return Ok(None);
2111 }
2112
2113 let row_limit = self.rows_remaining.min(self.rows_per_batch as u64);
2114 let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
2115 let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
2116 let bpr = if feedback > 0 {
2117 feedback as f64
2118 } else {
2119 self.schema_bytes_per_row
2120 };
2121 let rows = (batch_size_bytes as f64 / bpr) as u64;
2122 row_limit.min(rows.max(1))
2123 } else {
2124 row_limit
2125 };
2126 self.rows_remaining -= to_take;
2127
2128 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
2129 trace!(
2130 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
2131 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
2132 );
2133 if scheduled_need > 0 {
2134 let desired_scheduled = scheduled_need + self.rows_scheduled;
2135 trace!(
2136 "Draining from scheduler (desire at least {} scheduled rows)",
2137 desired_scheduled
2138 );
2139 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
2140 if actually_scheduled < desired_scheduled {
2141 let under_scheduled = desired_scheduled - actually_scheduled;
2142 to_take -= under_scheduled;
2143 }
2144 }
2145
2146 if to_take == 0 {
2147 return Ok(None);
2148 }
2149
2150 let next_task = self.root_decoder.drain_batch_task(to_take)?;
2151 self.rows_drained += to_take;
2152 Ok(Some(next_task))
2153 }
2154
2155 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
2156 let stream = futures::stream::unfold(self, |mut slf| async move {
2157 let next_task = match slf.next_batch_task().await {
2158 Ok(Some(next_task)) => next_task,
2159 Ok(None) => return None,
2160 Err(err) => {
2161 slf.rows_remaining = 0;
2162 return Some((
2163 ReadBatchTask {
2164 task: async move { Err(err) }.boxed(),
2165 num_rows: 0,
2166 },
2167 slf,
2168 ));
2169 }
2170 };
2171 let num_rows = next_task.num_rows;
2172 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
2173 let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
2174 let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
2177 let task = async move {
2178 let (batch, data_size) = if spawn_batch_decode_tasks {
2179 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
2180 .await
2181 .map_err(|err| Error::wrapped(err.into()))??
2182 } else {
2183 next_task.into_batch(emitted_batch_size_warning)?
2184 };
2185 let num_rows = batch.num_rows() as u64;
2186 if let Some(bpr) = data_size.checked_div(num_rows) {
2187 let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
2188 let next = if prev == 0 || bpr >= prev {
2189 bpr
2192 } else {
2193 (prev + bpr) / 2
2197 };
2198 bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
2199 }
2200 Ok(batch)
2201 };
2202 debug_assert!(num_rows <= u32::MAX as u64);
2204 Some((
2205 ReadBatchTask {
2206 task: task.boxed(),
2207 num_rows: num_rows as u32,
2208 },
2209 slf,
2210 ))
2211 });
2212 stream.boxed()
2213 }
2214}
2215
2216#[derive(Debug)]
2217pub enum RequestedRows {
2218 Ranges(Vec<Range<u64>>),
2219 Indices(Vec<u64>),
2220}
2221
2222impl RequestedRows {
2223 pub fn num_rows(&self) -> u64 {
2224 match self {
2225 Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
2226 Self::Indices(indices) => indices.len() as u64,
2227 }
2228 }
2229
2230 pub fn trim_empty_ranges(mut self) -> Self {
2231 if let Self::Ranges(ranges) = &mut self {
2232 ranges.retain(|r| !r.is_empty());
2233 }
2234 self
2235 }
2236
2237 fn into_ranges(self) -> Result<Vec<Range<u64>>> {
2238 match self {
2239 Self::Ranges(ranges) => Ok(ranges),
2240 Self::Indices(indices) => Self::indices_to_ranges(&indices),
2241 }
2242 }
2243
2244 fn indices_to_ranges(indices: &[u64]) -> Result<Vec<Range<u64>>> {
2246 if indices.is_empty() {
2247 return Ok(Vec::new());
2248 }
2249
2250 let mut ranges = Vec::with_capacity(indices.len());
2251 let mut start = indices[0];
2252
2253 for (index, pair) in indices.windows(2).enumerate() {
2254 if pair[0] >= pair[1] {
2255 return Err(Error::invalid_input(format!(
2256 "Requested row indices are not strictly increasing at index {index}: {} then {}",
2257 pair[0], pair[1]
2258 )));
2259 }
2260 let end = pair[0]
2261 .checked_add(1)
2262 .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2263 if pair[1] != end {
2264 ranges.push(start..end);
2265 start = pair[1];
2266 }
2267 }
2268
2269 let end = indices
2270 .last()
2271 .copied()
2272 .and_then(|index| index.checked_add(1))
2273 .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2274 ranges.push(start..end);
2275 Ok(ranges)
2276 }
2277}
2278
2279#[derive(Debug, Clone)]
2281pub struct DecoderConfig {
2282 pub cache_repetition_index: bool,
2289 pub validate_on_decode: bool,
2291 pub inline_scheduling: Option<bool>,
2309}
2310
2311impl Default for DecoderConfig {
2312 fn default() -> Self {
2313 Self {
2314 cache_repetition_index: default_cache_repetition_index(),
2315 validate_on_decode: false,
2316 inline_scheduling: None,
2317 }
2318 }
2319}
2320
2321#[derive(Debug, Clone)]
2322pub struct SchedulerDecoderConfig {
2323 pub decoder_plugins: Arc<DecoderPlugins>,
2324 pub batch_size: u32,
2325 pub io: Arc<dyn EncodingsIo>,
2326 pub cache: Arc<LanceCache>,
2327 pub decoder_config: DecoderConfig,
2329 pub batch_size_bytes: Option<u64>,
2335}
2336
2337fn check_scheduler_on_drop(
2338 stream: BoxStream<'static, ReadBatchTask>,
2339 scheduler_handle: tokio::task::JoinHandle<()>,
2340) -> BoxStream<'static, ReadBatchTask> {
2341 let abort_handle = scheduler_handle.abort_handle();
2345 let mut scheduler_handle = Some(scheduler_handle);
2346 let check_scheduler = stream::unfold((), move |_| {
2347 let handle = scheduler_handle.take();
2348 async move {
2349 if let Some(handle) = handle {
2350 handle.await.unwrap();
2351 }
2352 None
2353 }
2354 });
2355 stream
2356 .chain(check_scheduler)
2357 .on_drop(move || {
2358 abort_handle.abort();
2366 })
2367 .boxed()
2368}
2369
2370#[allow(clippy::too_many_arguments)]
2371pub fn create_decode_stream(
2372 schema: &Schema,
2373 num_rows: u64,
2374 batch_size: u32,
2375 is_structural: bool,
2376 should_validate: bool,
2377 spawn_structural_batch_decode_tasks: bool,
2378 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2379 batch_size_bytes: Option<u64>,
2380) -> Result<BoxStream<'static, ReadBatchTask>> {
2381 create_decode_stream_with_i32_offset_limit(
2382 schema,
2383 num_rows,
2384 batch_size,
2385 is_structural,
2386 should_validate,
2387 spawn_structural_batch_decode_tasks,
2388 rx,
2389 batch_size_bytes,
2390 true,
2391 )
2392}
2393
2394#[allow(clippy::too_many_arguments)]
2395fn create_decode_stream_with_i32_offset_limit(
2396 schema: &Schema,
2397 num_rows: u64,
2398 batch_size: u32,
2399 is_structural: bool,
2400 should_validate: bool,
2401 spawn_structural_batch_decode_tasks: bool,
2402 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2403 batch_size_bytes: Option<u64>,
2404 limit_i32_offset_batch_size: bool,
2405) -> Result<BoxStream<'static, ReadBatchTask>> {
2406 if is_structural {
2407 let arrow_schema = ArrowSchema::from(schema);
2408 let structural_decoder = StructuralStructDecoder::new(
2409 arrow_schema.fields,
2410 should_validate,
2411 true,
2412 false,
2413 )?;
2414 Ok(StructuralBatchDecodeStream::new(
2415 rx,
2416 batch_size,
2417 num_rows,
2418 structural_decoder,
2419 spawn_structural_batch_decode_tasks,
2420 batch_size_bytes,
2421 )
2422 .into_stream())
2423 } else {
2424 if batch_size_bytes.is_some() {
2425 warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2426 }
2427 let arrow_schema = ArrowSchema::from(schema);
2428 let root_fields = arrow_schema.fields;
2429
2430 let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2431 Ok(BatchDecodeStream::new_with_i32_offset_limit(
2432 rx,
2433 batch_size,
2434 num_rows,
2435 simple_struct_decoder,
2436 limit_i32_offset_batch_size,
2437 )
2438 .into_stream())
2439 }
2440}
2441
2442pub fn create_decode_iterator(
2446 schema: &Schema,
2447 num_rows: u64,
2448 batch_size: u32,
2449 should_validate: bool,
2450 is_structural: bool,
2451 messages: VecDeque<Result<DecoderMessage>>,
2452) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2453 let arrow_schema = Arc::new(ArrowSchema::from(schema));
2454 let root_fields = arrow_schema.fields.clone();
2455 if is_structural {
2456 let simple_struct_decoder = StructuralStructDecoder::new(
2457 root_fields,
2458 should_validate,
2459 true,
2460 false,
2461 )?;
2462 Ok(Box::new(BatchDecodeIterator::new(
2463 messages,
2464 batch_size,
2465 num_rows,
2466 simple_struct_decoder,
2467 arrow_schema,
2468 )))
2469 } else {
2470 let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2471 Ok(Box::new(BatchDecodeIterator::new(
2472 messages,
2473 batch_size,
2474 num_rows,
2475 root_decoder,
2476 arrow_schema,
2477 )))
2478 }
2479}
2480
2481async fn create_scheduler_decoder(
2482 column_infos: Vec<Arc<ColumnInfo>>,
2483 requested_rows: RequestedRows,
2484 filter: FilterExpression,
2485 column_indices: Vec<u32>,
2486 target_schema: Arc<Schema>,
2487 config: SchedulerDecoderConfig,
2488) -> Result<BoxStream<'static, ReadBatchTask>> {
2489 let num_rows = requested_rows.num_rows();
2490 let is_range_request = matches!(&requested_rows, RequestedRows::Ranges(_));
2491
2492 let is_structural = column_infos[0].is_structural();
2493 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2494 let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2495 Some("always") => true,
2496 Some("never") => false,
2497 _ => is_range_request,
2498 };
2499
2500 let (tx, rx) = mpsc::unbounded_channel();
2501
2502 let decode_stream = create_decode_stream(
2503 &target_schema,
2504 num_rows,
2505 config.batch_size,
2506 is_structural,
2507 config.decoder_config.validate_on_decode,
2508 spawn_structural_batch_decode_tasks,
2509 rx,
2510 config.batch_size_bytes,
2511 )?;
2512
2513 let requested_ranges = requested_rows.into_ranges()?;
2519 let mut decode_scheduler = DecodeBatchScheduler::try_new_with_ranges(
2520 target_schema.as_ref(),
2521 &column_indices,
2522 &column_infos,
2523 &vec![],
2524 num_rows,
2525 config.decoder_plugins,
2526 config.io.clone(),
2527 config.cache,
2528 Some(&requested_ranges),
2529 &filter,
2530 &config.decoder_config,
2531 )
2532 .await?;
2533
2534 let inline_scheduling = config
2540 .decoder_config
2541 .inline_scheduling
2542 .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2543
2544 if inline_scheduling {
2545 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2546 Ok(decode_stream)
2547 } else {
2548 let scheduling = async move {
2552 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io)
2553 };
2554 let scheduler_handle = tokio::task::spawn(scheduling);
2555 Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2556 }
2557}
2558
2559pub async fn schedule_and_decode(
2574 column_infos: Vec<Arc<ColumnInfo>>,
2575 requested_rows: RequestedRows,
2576 filter: FilterExpression,
2577 column_indices: Vec<u32>,
2578 target_schema: Arc<Schema>,
2579 config: SchedulerDecoderConfig,
2580) -> Result<BoxStream<'static, ReadBatchTask>> {
2581 if requested_rows.num_rows() == 0 {
2582 return Ok(stream::empty().boxed());
2583 }
2584
2585 let requested_rows = requested_rows.trim_empty_ranges();
2588
2589 let io = config.io.clone();
2590
2591 let stream = create_scheduler_decoder(
2592 column_infos,
2593 requested_rows,
2594 filter,
2595 column_indices,
2596 target_schema,
2597 config,
2598 )
2599 .await?;
2600
2601 Ok(stream.finally(move || drop(io)).boxed())
2604}
2605
2606pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2607 tokio::runtime::Builder::new_current_thread()
2608 .build()
2609 .unwrap()
2610});
2611
2612pub fn schedule_and_decode_blocking(
2627 column_infos: Vec<Arc<ColumnInfo>>,
2628 requested_rows: RequestedRows,
2629 filter: FilterExpression,
2630 column_indices: Vec<u32>,
2631 target_schema: Arc<Schema>,
2632 config: SchedulerDecoderConfig,
2633) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2634 if requested_rows.num_rows() == 0 {
2635 let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2636 return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2637 }
2638
2639 let requested_rows = requested_rows.trim_empty_ranges();
2640 let num_rows = requested_rows.num_rows();
2641 let is_structural = column_infos[0].is_structural();
2642
2643 let (tx, mut rx) = mpsc::unbounded_channel();
2644
2645 let requested_ranges = requested_rows.into_ranges()?;
2649 let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new_with_ranges(
2650 target_schema.as_ref(),
2651 &column_indices,
2652 &column_infos,
2653 &vec![],
2654 num_rows,
2655 config.decoder_plugins,
2656 config.io.clone(),
2657 config.cache,
2658 Some(&requested_ranges),
2659 &filter,
2660 &config.decoder_config,
2661 ))?;
2662
2663 decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2665
2666 let mut messages = Vec::new();
2668 while rx
2669 .recv_many(&mut messages, usize::MAX)
2670 .now_or_never()
2671 .unwrap()
2672 != 0
2673 {}
2674
2675 let decode_iterator = create_decode_iterator(
2677 &target_schema,
2678 num_rows,
2679 config.batch_size,
2680 config.decoder_config.validate_on_decode,
2681 is_structural,
2682 messages.into(),
2683 )?;
2684
2685 Ok(decode_iterator)
2686}
2687
2688pub trait PrimitivePageDecoder: Send + Sync {
2700 fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2732
2733 fn variable_width_bytes(&self, _rows_to_skip: u64, _num_rows: u64) -> Result<Option<u64>> {
2740 Ok(None)
2741 }
2742}
2743
2744pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2753 fn schedule_ranges(
2765 &self,
2766 ranges: &[Range<u64>],
2767 scheduler: &Arc<dyn EncodingsIo>,
2768 top_level_row: u64,
2769 ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2770}
2771
2772pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2774 fn advance(&mut self, num_rows: u64);
2775 fn current_priority(&self) -> u64;
2776 fn box_clone(&self) -> Box<dyn PriorityRange>;
2777}
2778
2779#[derive(Debug)]
2782pub struct SimplePriorityRange {
2783 priority: u64,
2784}
2785
2786impl SimplePriorityRange {
2787 fn new(priority: u64) -> Self {
2788 Self { priority }
2789 }
2790}
2791
2792impl PriorityRange for SimplePriorityRange {
2793 fn advance(&mut self, num_rows: u64) {
2794 self.priority += num_rows;
2795 }
2796
2797 fn current_priority(&self) -> u64 {
2798 self.priority
2799 }
2800
2801 fn box_clone(&self) -> Box<dyn PriorityRange> {
2802 Box::new(Self {
2803 priority: self.priority,
2804 })
2805 }
2806}
2807
2808pub struct ListPriorityRange {
2821 base: Box<dyn PriorityRange>,
2822 offsets: Arc<[u64]>,
2823 cur_index_into_offsets: usize,
2824 cur_position: u64,
2825}
2826
2827impl ListPriorityRange {
2828 pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2829 Self {
2830 base,
2831 offsets,
2832 cur_index_into_offsets: 0,
2833 cur_position: 0,
2834 }
2835 }
2836}
2837
2838impl std::fmt::Debug for ListPriorityRange {
2839 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2840 f.debug_struct("ListPriorityRange")
2841 .field("base", &self.base)
2842 .field("offsets.len()", &self.offsets.len())
2843 .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2844 .field("cur_position", &self.cur_position)
2845 .finish()
2846 }
2847}
2848
2849impl PriorityRange for ListPriorityRange {
2850 fn advance(&mut self, num_rows: u64) {
2851 self.cur_position += num_rows;
2854 let mut idx_into_offsets = self.cur_index_into_offsets;
2855 while idx_into_offsets + 1 < self.offsets.len()
2856 && self.offsets[idx_into_offsets + 1] <= self.cur_position
2857 {
2858 idx_into_offsets += 1;
2859 }
2860 let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2861 self.cur_index_into_offsets = idx_into_offsets;
2862 self.base.advance(base_rows_advanced as u64);
2863 }
2864
2865 fn current_priority(&self) -> u64 {
2866 self.base.current_priority()
2867 }
2868
2869 fn box_clone(&self) -> Box<dyn PriorityRange> {
2870 Box::new(Self {
2871 base: self.base.box_clone(),
2872 offsets: self.offsets.clone(),
2873 cur_index_into_offsets: self.cur_index_into_offsets,
2874 cur_position: self.cur_position,
2875 })
2876 }
2877}
2878
2879pub(crate) const IO_REQUEST_BATCH_BYTES: u64 = 8 * 1024 * 1024;
2886
2887type SharedRead = Shared<BoxFuture<'static, std::result::Result<Arc<Vec<Bytes>>, CloneableError>>>;
2888
2889enum SubmittedRead {
2891 Alone(BoxFuture<'static, Result<Vec<Bytes>>>),
2893 Shared(SharedRead, Vec<usize>),
2895}
2896
2897struct PendingRead {
2898 ranges: Vec<Range<u64>>,
2899 priority: u64,
2900 tx: oneshot::Sender<SubmittedRead>,
2901}
2902
2903pub(crate) struct RequestBatch {
2922 inner: Arc<dyn EncodingsIo>,
2923 pending: Arc<Mutex<Vec<PendingRead>>>,
2924}
2925
2926impl std::fmt::Debug for RequestBatch {
2927 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2928 f.debug_struct("RequestBatch").finish_non_exhaustive()
2929 }
2930}
2931
2932impl RequestBatch {
2933 pub(crate) fn new(inner: Arc<dyn EncodingsIo>) -> Self {
2934 Self {
2935 inner,
2936 pending: Arc::new(Mutex::new(Vec::new())),
2937 }
2938 }
2939
2940 fn queued_bytes(pending: &[PendingRead]) -> u64 {
2941 pending
2942 .iter()
2943 .flat_map(|read| &read.ranges)
2944 .map(|range| range.end - range.start)
2945 .sum()
2946 }
2947
2948 pub(crate) fn pending_bytes(&self) -> u64 {
2950 Self::queued_bytes(&self.pending.lock().unwrap())
2951 }
2952
2953 pub(crate) fn flush(&self) {
2955 Self::flush_pending(&self.inner, &self.pending);
2956 }
2957
2958 fn flush_pending(inner: &Arc<dyn EncodingsIo>, pending: &Mutex<Vec<PendingRead>>) {
2959 let pending = std::mem::take(&mut *pending.lock().unwrap());
2960 Self::submit_batch(inner, pending);
2961 }
2962
2963 fn submit_batch(inner: &Arc<dyn EncodingsIo>, pending: Vec<PendingRead>) {
2966 if pending.is_empty() {
2967 return;
2968 }
2969 if pending.len() == 1 {
2970 let read = pending.into_iter().next().unwrap();
2971 let _ = read.tx.send(SubmittedRead::Alone(
2972 inner.submit_request(read.ranges, read.priority),
2973 ));
2974 return;
2975 }
2976 let priority = pending.iter().map(|read| read.priority).min().unwrap();
2978 let mut ordered = pending
2979 .iter()
2980 .enumerate()
2981 .flat_map(|(read_idx, read)| {
2982 read.ranges
2983 .iter()
2984 .enumerate()
2985 .map(move |(range_idx, range)| (range.clone(), read_idx, range_idx))
2986 })
2987 .collect::<Vec<_>>();
2988 ordered.sort_by_key(|(range, _, _)| (range.start, range.end));
2989 let ranges = ordered
2990 .iter()
2991 .map(|(range, _, _)| range.clone())
2992 .collect::<Vec<_>>();
2993 let mut positions = pending
2995 .iter()
2996 .map(|read| vec![0; read.ranges.len()])
2997 .collect::<Vec<_>>();
2998 for (position, (_, read_idx, range_idx)) in ordered.iter().enumerate() {
2999 positions[*read_idx][*range_idx] = position;
3000 }
3001 let batched: SharedRead = inner
3002 .submit_request(ranges, priority)
3003 .map(|result| result.map(Arc::new).map_err(CloneableError::new))
3004 .boxed()
3005 .shared();
3006 for (read, read_positions) in pending.into_iter().zip(positions) {
3007 let _ = read
3009 .tx
3010 .send(SubmittedRead::Shared(batched.clone(), read_positions));
3011 }
3012 }
3013}
3014
3015impl EncodingsIo for RequestBatch {
3016 fn submit_request(
3017 &self,
3018 ranges: Vec<Range<u64>>,
3019 priority: u64,
3020 ) -> BoxFuture<'static, Result<Vec<Bytes>>> {
3021 let mut pending = self.pending.lock().unwrap();
3022 let bytes = ranges
3025 .iter()
3026 .map(|range| range.end - range.start)
3027 .sum::<u64>();
3028 if !pending.is_empty() && Self::queued_bytes(&pending) + bytes > IO_REQUEST_BATCH_BYTES {
3029 Self::submit_batch(&self.inner, std::mem::take(&mut *pending));
3030 }
3031 let (tx, mut rx) = oneshot::channel();
3032 pending.push(PendingRead {
3033 ranges,
3034 priority,
3035 tx,
3036 });
3037 drop(pending);
3038 let inner = self.inner.clone();
3039 let pending = self.pending.clone();
3040 async move {
3041 let submitted = match rx.try_recv() {
3042 Ok(Some(submitted)) => submitted,
3044 _ => {
3045 tokio::task::yield_now().await;
3049 Self::flush_pending(&inner, &pending);
3050 rx.await.map_err(|_| {
3051 Error::internal("a batched read was dropped before the batch was submitted")
3052 })?
3053 }
3054 };
3055 match submitted {
3056 SubmittedRead::Alone(request) => request.await,
3057 SubmittedRead::Shared(batched, positions) => {
3058 let bytes = batched.await.map_err(|err| err.into_inner())?;
3059 Ok(positions
3060 .into_iter()
3061 .map(|position| bytes[position].clone())
3062 .collect())
3063 }
3064 }
3065 }
3066 .boxed()
3067 }
3068
3069 fn with_bypass_backpressure(&self) -> Option<Arc<dyn EncodingsIo>> {
3070 self.inner.with_bypass_backpressure()
3071 }
3072
3073 fn with_io_stats(
3074 &self,
3075 stats: Arc<dyn lance_core::utils::io_stats::IoStatsRecorder>,
3076 ) -> Option<Arc<dyn EncodingsIo>> {
3077 self.inner.with_io_stats(stats)
3078 }
3079}
3080
3081pub struct SchedulerContext {
3082 recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
3083 io: Arc<dyn EncodingsIo>,
3084 batch: Arc<RequestBatch>,
3086 cache: Arc<LanceCache>,
3087 name: String,
3088 path: Vec<u32>,
3089 path_names: Vec<String>,
3090}
3091
3092pub struct ScopedSchedulerContext<'a> {
3093 pub context: &'a mut SchedulerContext,
3094}
3095
3096impl<'a> ScopedSchedulerContext<'a> {
3097 pub fn pop(self) -> &'a mut SchedulerContext {
3098 self.context.pop();
3099 self.context
3100 }
3101}
3102
3103impl SchedulerContext {
3104 pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
3105 let batch = Arc::new(RequestBatch::new(io));
3106 Self {
3107 io: batch.clone(),
3108 batch,
3109 cache,
3110 recv: None,
3111 name: "".to_string(),
3112 path: Vec::new(),
3113 path_names: Vec::new(),
3114 }
3115 }
3116
3117 pub fn io(&self) -> &Arc<dyn EncodingsIo> {
3118 &self.io
3119 }
3120
3121 pub fn flush_io(&self) {
3123 self.batch.flush();
3124 }
3125
3126 pub fn cache(&self) -> &Arc<LanceCache> {
3127 &self.cache
3128 }
3129
3130 pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
3131 self.path.push(index);
3132 self.path_names.push(name.to_string());
3133 ScopedSchedulerContext { context: self }
3134 }
3135
3136 pub fn pop(&mut self) {
3137 self.path.pop();
3138 self.path_names.pop();
3139 }
3140
3141 pub fn path_name(&self) -> String {
3142 let path = self.path_names.join("/");
3143 if self.recv.is_some() {
3144 format!("TEMP({}){}", self.name, path)
3145 } else {
3146 format!("ROOT{}", path)
3147 }
3148 }
3149
3150 pub fn current_path(&self) -> VecDeque<u32> {
3151 VecDeque::from_iter(self.path.iter().copied())
3152 }
3153
3154 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
3155 pub fn locate_decoder(&mut self, decoder: Box<dyn LogicalPageDecoder>) -> DecoderReady {
3156 trace!(
3157 "Scheduling decoder of type {:?} for {:?}",
3158 decoder.data_type(),
3159 self.path,
3160 );
3161 DecoderReady {
3162 decoder,
3163 path: self.current_path(),
3164 }
3165 }
3166}
3167
3168pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
3169
3170impl std::fmt::Debug for UnloadedPageShard {
3171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3172 f.debug_struct("UnloadedPage").finish()
3173 }
3174}
3175
3176#[derive(Debug)]
3177pub struct ScheduledScanLine {
3178 pub rows_scheduled: u64,
3179 pub decoders: Vec<MessageType>,
3180}
3181
3182pub trait StructuralSchedulingJob: std::fmt::Debug {
3183 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
3190}
3191
3192pub struct FilterExpression(pub Bytes);
3200
3201impl FilterExpression {
3202 pub fn no_filter() -> Self {
3207 Self(Bytes::new())
3208 }
3209
3210 pub fn is_noop(&self) -> bool {
3212 self.0.is_empty()
3213 }
3214}
3215
3216pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
3217 fn initialize<'a>(
3223 &'a mut self,
3224 requested_ranges: Option<&'a [Range<u64>]>,
3225 filter: &'a FilterExpression,
3226 context: &'a SchedulerContext,
3227 ) -> BoxFuture<'a, Result<()>>;
3228 fn schedule_ranges<'a>(
3229 &'a self,
3230 ranges: &[Range<u64>],
3231 filter: &FilterExpression,
3232 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
3233}
3234
3235pub trait DecodeArrayTask: Send {
3237 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
3239}
3240
3241impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
3242 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3243 let decoded_array = StructuralDecodeArrayTask::decode(*self)?;
3244 decoded_array.repdef.ensure_exhausted()?;
3245 Ok((decoded_array.array, decoded_array.data_size))
3246 }
3247}
3248
3249pub struct NextDecodeTask {
3254 pub task: Box<dyn DecodeArrayTask>,
3256 pub num_rows: u64,
3258}
3259
3260impl NextDecodeTask {
3261 #[instrument(name = "task_to_batch", level = "debug", skip_all)]
3266 fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
3267 let (struct_arr, data_size) = self.task.decode()?;
3268 let batch = RecordBatch::from(struct_arr.as_struct());
3269 if data_size > BATCH_SIZE_BYTES_WARNING {
3270 emitted_batch_size_warning.call_once(|| {
3271 let size_mb = data_size / 1024 / 1024;
3272 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);
3273 });
3274 }
3275 Ok((batch, data_size))
3276 }
3277}
3278
3279#[derive(Debug)]
3283pub enum MessageType {
3284 DecoderReady(DecoderReady),
3289 UnloadedPage(UnloadedPageShard),
3293}
3294
3295impl MessageType {
3296 pub fn into_array(self) -> DecoderReady {
3297 match self {
3298 Self::DecoderReady(decoder) => decoder,
3299 Self::UnloadedPage(_) => {
3300 panic!("Expected DecoderReady but got UnloadedPage")
3301 }
3302 }
3303 }
3304
3305 pub fn into_structural(self) -> UnloadedPageShard {
3306 match self {
3307 Self::UnloadedPage(unloaded) => unloaded,
3308 Self::DecoderReady(_) => {
3309 panic!("Expected UnloadedPage but got DecoderReady")
3310 }
3311 }
3312 }
3313}
3314
3315pub struct DecoderMessage {
3316 pub scheduled_so_far: u64,
3317 pub decoders: Vec<MessageType>,
3318}
3319
3320pub struct DecoderContext {
3321 source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
3322}
3323
3324impl DecoderContext {
3325 pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
3326 Self { source }
3327 }
3328}
3329
3330pub struct DecodedPage {
3331 pub data: DataBlock,
3332 pub repdef: RepDefUnraveler,
3333}
3334
3335pub trait DecodePageTask: Send + std::fmt::Debug {
3336 fn decode(self: Box<Self>) -> Result<DecodedPage>;
3338}
3339
3340pub trait StructuralPageDecoder: std::fmt::Debug + Send {
3341 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
3342 fn num_rows(&self) -> u64;
3343 fn decoded_bytes(&self, _num_rows: u64) -> Result<u64> {
3346 Err(Error::not_supported(
3347 "decoded_bytes is not implemented for this page decoder".to_string(),
3348 ))
3349 }
3350}
3351
3352#[derive(Debug)]
3353pub struct LoadedPageShard {
3354 pub decoder: Box<dyn StructuralPageDecoder>,
3356 pub path: VecDeque<u32>,
3375}
3376
3377pub struct DecodedArray {
3378 pub array: ArrayRef,
3379 pub repdef: CompositeRepDefUnraveler,
3380 pub data_size: u64,
3382}
3383
3384pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
3385 fn decode(self: Box<Self>) -> Result<DecodedArray>;
3386}
3387
3388pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
3389 fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
3394 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
3396 fn data_type(&self) -> &DataType;
3398 fn plan_decoded_bytes(&self, _rows_remaining: u64) -> Result<[u64; 8]> {
3408 Err(Error::not_supported(
3409 "decoded_bytes is not implemented for this field decoder".to_string(),
3410 ))
3411 }
3412}
3413
3414#[derive(Debug, Default)]
3415pub struct DecoderPlugins {}
3416
3417#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3419pub enum EncodedBatchLayout {
3420 Array,
3422 Structural,
3424}
3425
3426pub async fn decode_batch(
3428 batch: &EncodedBatch,
3429 filter: &FilterExpression,
3430 decoder_plugins: Arc<DecoderPlugins>,
3431 should_validate: bool,
3432 layout: EncodedBatchLayout,
3433 cache: Option<Arc<LanceCache>>,
3434) -> Result<RecordBatch> {
3435 let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
3440 let cache = if let Some(cache) = cache {
3441 cache
3442 } else {
3443 Arc::new(lance_core::cache::LanceCache::with_capacity(
3444 128 * 1024 * 1024,
3445 ))
3446 };
3447 let mut decode_scheduler = DecodeBatchScheduler::try_new(
3448 batch.schema.as_ref(),
3449 &batch.top_level_columns,
3450 &batch.page_table,
3451 &vec![],
3452 batch.num_rows,
3453 decoder_plugins,
3454 io_scheduler.clone(),
3455 cache,
3456 filter,
3457 &DecoderConfig::default(),
3458 )
3459 .await?;
3460 let (tx, rx) = unbounded_channel();
3461 decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
3462 let is_structural = layout == EncodedBatchLayout::Structural;
3463 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
3464 let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
3465 let mut decode_stream = create_decode_stream_with_i32_offset_limit(
3468 &batch.schema,
3469 batch.num_rows,
3470 batch.num_rows as u32,
3471 is_structural,
3472 should_validate,
3473 spawn_structural_batch_decode_tasks,
3474 rx,
3475 None,
3476 false,
3477 )?;
3478 decode_stream.next().await.unwrap().task.await
3479}
3480
3481#[cfg(test)]
3482mod tests {
3484 use super::*;
3485 use arrow_array::StringArray;
3486 use std::collections::VecDeque;
3487
3488 struct StaticArrayDecodeTask(ArrayRef);
3489
3490 impl DecodeArrayTask for StaticArrayDecodeTask {
3491 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3492 Ok((self.0, 0))
3493 }
3494 }
3495
3496 #[derive(Debug)]
3497 struct StaticArrayPageDecoder {
3498 array: ArrayRef,
3499 rows_drained: u64,
3500 fake_bytes_per_row: Option<u64>,
3503 }
3504
3505 impl StaticArrayPageDecoder {
3506 fn new(values: &[&str]) -> Self {
3507 Self {
3508 array: Arc::new(StringArray::from(values.to_vec())),
3509 rows_drained: 0,
3510 fake_bytes_per_row: None,
3511 }
3512 }
3513
3514 fn with_fake_bytes_per_row(values: &[&str], fake_bytes_per_row: u64) -> Self {
3515 Self {
3516 fake_bytes_per_row: Some(fake_bytes_per_row),
3517 ..Self::new(values)
3518 }
3519 }
3520
3521 fn row_bytes(&self, row: usize) -> u64 {
3522 self.fake_bytes_per_row.unwrap_or_else(|| {
3523 self.array
3524 .as_any()
3525 .downcast_ref::<StringArray>()
3526 .unwrap()
3527 .value(row)
3528 .len() as u64
3529 })
3530 }
3531 }
3532
3533 impl LogicalPageDecoder for StaticArrayPageDecoder {
3534 fn wait_for_loaded(&mut self, _loaded_need: u64) -> BoxFuture<'_, Result<()>> {
3535 std::future::ready(Ok(())).boxed()
3536 }
3537
3538 fn rows_loaded(&self) -> u64 {
3539 self.array.len() as u64
3540 }
3541
3542 fn num_rows(&self) -> u64 {
3543 self.array.len() as u64
3544 }
3545
3546 fn rows_drained(&self) -> u64 {
3547 self.rows_drained
3548 }
3549
3550 fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
3551 let array = self
3552 .array
3553 .slice(self.rows_drained as usize, num_rows as usize);
3554 self.rows_drained += num_rows;
3555 Ok(NextDecodeTask {
3556 task: Box::new(StaticArrayDecodeTask(array)),
3557 num_rows,
3558 })
3559 }
3560
3561 fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
3562 let start = self.rows_drained as usize;
3563 let mut rows = 0u64;
3564 let mut bytes = 0u64;
3565 for row in start..start + num_rows as usize {
3566 let row_bytes = self.row_bytes(row);
3567 if bytes + row_bytes > byte_budget {
3568 break;
3569 }
3570 bytes += row_bytes;
3571 rows += 1;
3572 }
3573 Ok(DrainLimit { rows, bytes })
3574 }
3575
3576 fn data_type(&self) -> &DataType {
3577 self.array.data_type()
3578 }
3579 }
3580
3581 async fn array_stream_batch_sizes(
3582 pages: Vec<StaticArrayPageDecoder>,
3583 batch_size: u32,
3584 ) -> Vec<usize> {
3585 let num_rows = pages.iter().map(|page| page.num_rows()).sum::<u64>();
3586 let fields = Fields::from(vec![ArrowField::new("value", DataType::Utf8, false)]);
3587 let root_decoder = SimpleStructDecoder::new(fields, num_rows);
3588 let (tx, rx) = unbounded_channel();
3589 let pages = pages
3590 .into_iter()
3591 .map(|page| {
3592 MessageType::DecoderReady(DecoderReady {
3593 decoder: Box::new(page),
3594 path: VecDeque::from([0]),
3595 })
3596 })
3597 .collect();
3598 tx.send(Ok(DecoderMessage {
3599 scheduled_so_far: num_rows,
3600 decoders: pages,
3601 }))
3602 .unwrap();
3603 drop(tx);
3604
3605 let mut stream =
3606 BatchDecodeStream::new(rx, batch_size, num_rows, root_decoder).into_stream();
3607 let mut batch_sizes = Vec::new();
3608 while let Some(task) = stream.next().await {
3609 batch_sizes.push(task.task.await.unwrap().num_rows());
3610 }
3611 batch_sizes
3612 }
3613
3614 #[tokio::test]
3615 async fn test_array_stream_spans_i32_offset_pages_within_budget() {
3616 let batch_sizes = array_stream_batch_sizes(
3618 vec![
3619 StaticArrayPageDecoder::new(&["a", "b"]),
3620 StaticArrayPageDecoder::new(&["c", "d"]),
3621 ],
3622 4,
3623 )
3624 .await;
3625 assert_eq!(batch_sizes, vec![4]);
3626 }
3627
3628 #[tokio::test]
3629 async fn test_array_stream_splits_when_i32_offset_budget_exhausted() {
3630 const FAKE_ROW_BYTES: u64 = 800 * 1024 * 1024;
3633 let batch_sizes = array_stream_batch_sizes(
3634 vec![
3635 StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3636 StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3637 StaticArrayPageDecoder::with_fake_bytes_per_row(&["c"], FAKE_ROW_BYTES),
3638 ],
3639 4,
3640 )
3641 .await;
3642 assert_eq!(batch_sizes, vec![2, 1]);
3643 }
3644
3645 #[tokio::test]
3646 async fn test_array_stream_emits_over_budget_rows_alone() {
3647 const FAKE_ROW_BYTES: u64 = 3 * 1024 * 1024 * 1024;
3650 let batch_sizes = array_stream_batch_sizes(
3651 vec![
3652 StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3653 StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3654 ],
3655 4,
3656 )
3657 .await;
3658 assert_eq!(batch_sizes, vec![1, 1]);
3659 }
3660
3661 #[test]
3662 fn requested_row_indices_convert_to_checked_ranges() {
3663 let requested_rows = RequestedRows::Indices(vec![1, 2, 5, 8, 9]);
3664 let ranges = requested_rows.into_ranges().unwrap();
3665 assert_eq!(ranges, [1..3, 5..6, 8..10]);
3666
3667 let error = RequestedRows::Indices(vec![2, 2])
3668 .into_ranges()
3669 .unwrap_err();
3670 assert!(matches!(error, Error::InvalidInput { .. }));
3671 assert!(error.to_string().contains("not strictly increasing"));
3672
3673 let error = RequestedRows::Indices(vec![u64::MAX])
3674 .into_ranges()
3675 .unwrap_err();
3676 assert!(matches!(error, Error::InvalidInput { .. }));
3677 assert!(error.to_string().contains("u64::MAX"));
3678 }
3679
3680 #[derive(Debug)]
3681 struct FailingPageDecoder {
3682 page_data_type: DataType,
3683 total_rows: u64,
3684 load_error_message: &'static str,
3685 }
3686
3687 impl FailingPageDecoder {
3688 fn new(
3689 page_data_type: DataType,
3690 total_rows: u64,
3691 load_error_message: &'static str,
3692 ) -> Self {
3693 Self {
3694 page_data_type,
3695 total_rows,
3696 load_error_message,
3697 }
3698 }
3699 }
3700
3701 impl LogicalPageDecoder for FailingPageDecoder {
3702 fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
3703 let load_error_message = self.load_error_message;
3704 async move { Err(Error::io(load_error_message)) }.boxed()
3705 }
3706
3707 fn rows_loaded(&self) -> u64 {
3708 0
3709 }
3710
3711 fn num_rows(&self) -> u64 {
3712 self.total_rows
3713 }
3714
3715 fn rows_drained(&self) -> u64 {
3716 0
3717 }
3718
3719 fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
3720 Err(Error::internal(format!(
3721 "failing page decoder should not be drained after load error \
3722 (requested_rows={})",
3723 requested_rows
3724 )))
3725 }
3726
3727 fn data_type(&self) -> &DataType {
3728 &self.page_data_type
3729 }
3730 }
3731
3732 struct InvalidInputDecodeTask;
3733
3734 impl DecodeArrayTask for InvalidInputDecodeTask {
3735 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3736 Err(Error::invalid_input_source("malformed sparse page".into()))
3737 }
3738 }
3739
3740 #[test]
3741 fn next_decode_task_preserves_invalid_input_errors() {
3742 let err = NextDecodeTask {
3743 task: Box::new(InvalidInputDecodeTask),
3744 num_rows: 0,
3745 }
3746 .into_batch(Arc::new(Once::new()))
3747 .unwrap_err();
3748 assert!(matches!(err, Error::InvalidInput { .. }));
3749 }
3750
3751 #[test]
3752 fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
3753 use arrow_schema::Field as ArrowField;
3760
3761 let zero_dim = DataType::FixedSizeList(
3762 Arc::new(ArrowField::new("item", DataType::Float32, true)),
3763 0,
3764 );
3765 let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3766 let strategy = CoreFieldDecoderStrategy::default();
3767
3768 let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3769 let err = strategy
3770 .create_structural_field_scheduler(&field, &mut structural_columns)
3771 .unwrap_err();
3772 assert!(
3773 err.to_string()
3774 .contains("dimension must be a positive integer"),
3775 "unexpected error: {}",
3776 err
3777 );
3778
3779 let mut array_columns = ColumnInfoIter::new(vec![], &[]);
3780 let err = strategy
3781 .create_array_field_scheduler(
3782 &field,
3783 &mut array_columns,
3784 FileBuffers {
3785 positions_and_sizes: &[],
3786 },
3787 )
3788 .unwrap_err();
3789 assert!(
3790 err.to_string()
3791 .contains("dimension must be a positive integer"),
3792 "unexpected error: {}",
3793 err
3794 );
3795 }
3796
3797 #[test]
3798 fn test_list_page_with_non_list_encoding_returns_error() {
3799 let item = Arc::new(ArrowField::new("item", DataType::Int32, true));
3800 let list = DataType::List(item);
3801 let field = Field::try_from(&ArrowField::new("values", list, true)).unwrap();
3802 let values_encoding = pb::ColumnEncoding {
3803 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
3804 };
3805 let offsets_column = Arc::new(ColumnInfo::new(
3806 0,
3807 Arc::new([PageInfo {
3808 num_rows: 1,
3809 priority: 0,
3810 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
3811 array_encoding: Some(pb::array_encoding::ArrayEncoding::Flat(
3812 pb::Flat::default(),
3813 )),
3814 }),
3815 buffer_offsets_and_sizes: Arc::new([]),
3816 }]),
3817 vec![],
3818 values_encoding.clone(),
3819 ));
3820 let items_column = Arc::new(ColumnInfo::new(1, Arc::new([]), vec![], values_encoding));
3821 let column_indices = [0, 1];
3822 let mut columns = ColumnInfoIter::new(vec![offsets_column, items_column], &column_indices);
3823
3824 let err = CoreFieldDecoderStrategy::default()
3825 .create_array_field_scheduler(
3826 &field,
3827 &mut columns,
3828 FileBuffers {
3829 positions_and_sizes: &[],
3830 },
3831 )
3832 .unwrap_err();
3833
3834 assert!(matches!(err, Error::InvalidInput { .. }));
3835 assert!(
3836 err.to_string()
3837 .contains("expected list encoding for field 'values' in column 0, page 0 but got"),
3838 "unexpected error: {err}"
3839 );
3840 }
3841
3842 #[tokio::test]
3843 async fn test_array_stream_stops_on_load_error() {
3844 use arrow_schema::Field as ArrowField;
3845
3846 let rows_per_batch = 1;
3847 let total_rows = 2;
3848 let scheduled_rows = 1;
3849 let page_rows = 1;
3850 let batch_readahead = 2;
3851 let load_error_message = "simulated page load failure";
3852 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3853 let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3854 let (tx, rx) = unbounded_channel();
3855
3856 tx.send(Ok(DecoderMessage {
3857 scheduled_so_far: scheduled_rows,
3858 decoders: vec![MessageType::DecoderReady(DecoderReady {
3859 decoder: Box::new(FailingPageDecoder::new(
3860 DataType::Float32,
3861 page_rows,
3862 load_error_message,
3863 )),
3864 path: VecDeque::from([0]),
3865 })],
3866 }))
3867 .unwrap();
3868 drop(tx);
3869
3870 let stream =
3871 BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3872 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3873
3874 let err = batches
3875 .next()
3876 .await
3877 .expect("stream should emit the array page-load error")
3878 .unwrap_err();
3879 assert!(
3880 err.to_string().contains(load_error_message),
3881 "unexpected error: {}",
3882 err
3883 );
3884 assert!(
3885 batches.next().await.is_none(),
3886 "stream should stop after the array page-load error"
3887 );
3888 }
3889
3890 #[tokio::test]
3891 async fn test_structural_stream_stops_on_load_error() {
3892 let rows_per_batch = 1;
3893 let total_rows = 2;
3894 let scheduled_rows = 1;
3895 let batch_readahead = 2;
3896 let load_error_message = "simulated page load failure";
3897 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3898 let root_decoder = StructuralStructDecoder::new(
3899 fields, false, true, false,
3900 )
3901 .unwrap();
3902 let (tx, rx) = unbounded_channel();
3903 let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3904
3905 tx.send(Ok(DecoderMessage {
3906 scheduled_so_far: scheduled_rows,
3907 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3908 }))
3909 .unwrap();
3910 drop(tx);
3911
3912 let stream = StructuralBatchDecodeStream::new(
3913 rx,
3914 rows_per_batch,
3915 total_rows,
3916 root_decoder,
3917 true,
3918 None,
3919 )
3920 .into_stream();
3921 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3922
3923 let err = batches
3924 .next()
3925 .await
3926 .expect("stream should emit the page-load error")
3927 .unwrap_err();
3928 assert!(
3929 err.to_string().contains(load_error_message),
3930 "unexpected error: {}",
3931 err
3932 );
3933 assert!(
3934 batches.next().await.is_none(),
3935 "stream should stop after the page-load error"
3936 );
3937 }
3938
3939 #[test]
3940 fn test_coalesce_indices_to_ranges_with_single_index() {
3941 let indices = vec![1];
3942 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3943 assert_eq!(ranges, vec![1..2]);
3944 }
3945
3946 #[test]
3947 fn test_coalesce_indices_to_ranges() {
3948 let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3949 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3950 assert_eq!(ranges, vec![1..10]);
3951 }
3952
3953 #[test]
3954 fn test_coalesce_indices_to_ranges_with_gaps() {
3955 let indices = vec![1, 2, 3, 5, 6, 7, 9];
3956 let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3957 assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3958 }
3959
3960 #[test]
3961 fn test_estimate_bytes_per_row() {
3962 assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3963 assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3964 assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3965 assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3966 assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3967 assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3968 let struct_type = DataType::Struct(Fields::from(vec![
3970 ArrowField::new("a", DataType::Int32, false),
3971 ArrowField::new("b", DataType::Int32, false),
3972 ArrowField::new("c", DataType::Int32, false),
3973 ArrowField::new("d", DataType::Int32, false),
3974 ]));
3975 assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3976 }
3977
3978 async fn decode_batches_with_byte_limit(
3981 batch: &RecordBatch,
3982 batch_size: u32,
3983 batch_size_bytes: Option<u64>,
3984 ) -> Vec<RecordBatch> {
3985 use crate::{
3986 encoder::{EncodingOptions, encode_batch},
3987 testing::{TestEncoding, test_encoding_strategy},
3988 };
3989
3990 let version = TestEncoding::StructuralU16;
3991 let options = EncodingOptions::default();
3992 let strategy = test_encoding_strategy(version);
3993 let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3994 let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3995 .await
3996 .unwrap();
3997
3998 let io_scheduler =
3999 Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
4000 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
4001 128 * 1024 * 1024,
4002 ));
4003 let decoder_plugins = Arc::new(DecoderPlugins::default());
4004
4005 let mut decode_scheduler = DecodeBatchScheduler::try_new(
4006 encoded.schema.as_ref(),
4007 &encoded.top_level_columns,
4008 &encoded.page_table,
4009 &vec![],
4010 encoded.num_rows,
4011 decoder_plugins,
4012 io_scheduler.clone(),
4013 cache,
4014 &FilterExpression::no_filter(),
4015 &DecoderConfig::default(),
4016 )
4017 .await
4018 .unwrap();
4019
4020 let (tx, rx) = unbounded_channel();
4021 decode_scheduler.schedule_range(
4022 0..encoded.num_rows,
4023 &FilterExpression::no_filter(),
4024 tx,
4025 io_scheduler,
4026 );
4027
4028 let mut decode_stream = create_decode_stream(
4029 &encoded.schema,
4030 encoded.num_rows,
4031 batch_size,
4032 true,
4033 true,
4034 true,
4035 rx,
4036 batch_size_bytes,
4037 )
4038 .unwrap();
4039
4040 let mut batches = Vec::new();
4041 while let Some(task) = decode_stream.next().await {
4042 batches.push(task.task.await.unwrap());
4043 }
4044 batches
4045 }
4046
4047 #[tokio::test]
4048 async fn test_byte_sized_batches_fixed_width() {
4049 use arrow_array::Int32Array;
4050
4051 let num_rows: i32 = 1000;
4053 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
4054 .map(|col| {
4055 Arc::new(Int32Array::from_iter_values(
4056 (0..num_rows).map(move |row| row * 10 + col),
4057 )) as _
4058 })
4059 .collect();
4060
4061 let schema = Arc::new(ArrowSchema::new(vec![
4062 ArrowField::new("a", DataType::Int32, false),
4063 ArrowField::new("b", DataType::Int32, false),
4064 ArrowField::new("c", DataType::Int32, false),
4065 ArrowField::new("d", DataType::Int32, false),
4066 ]));
4067 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4068
4069 let batches =
4071 decode_batches_with_byte_limit(&input_batch, 1024, Some(1600)).await;
4072
4073 assert_eq!(batches.len(), 10);
4075 for (i, batch) in batches.iter().enumerate() {
4076 assert_eq!(
4077 batch.num_rows(),
4078 100,
4079 "batch {i} should have 100 rows, got {}",
4080 batch.num_rows()
4081 );
4082 }
4083
4084 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4086 let concatenated =
4087 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4088 .unwrap();
4089 assert_eq!(concatenated.num_rows(), num_rows as usize);
4090 for col in 0..4 {
4091 assert_eq!(
4092 concatenated.column(col).as_ref(),
4093 input_batch.column(col).as_ref(),
4094 "column {col} roundtrip mismatch"
4095 );
4096 }
4097 }
4098
4099 #[tokio::test]
4100 async fn test_byte_sized_batches_none_unchanged() {
4101 use arrow_array::Int32Array;
4102
4103 let num_rows: i32 = 1000;
4105 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
4106 .map(|col| {
4107 Arc::new(Int32Array::from_iter_values(
4108 (0..num_rows).map(move |row| row * 10 + col),
4109 )) as _
4110 })
4111 .collect();
4112
4113 let schema = Arc::new(ArrowSchema::new(vec![
4114 ArrowField::new("x", DataType::Int32, false),
4115 ArrowField::new("y", DataType::Int32, false),
4116 ]));
4117 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4118
4119 let batches = decode_batches_with_byte_limit(&input_batch, 250, None).await;
4121 assert_eq!(batches.len(), 4);
4122 for (i, batch) in batches.iter().enumerate() {
4123 assert_eq!(
4124 batch.num_rows(),
4125 250,
4126 "batch {i} should have 250 rows, got {}",
4127 batch.num_rows()
4128 );
4129 }
4130 }
4131
4132 #[tokio::test]
4133 async fn test_byte_sized_batches_respect_row_limit() {
4134 use arrow_array::Int32Array;
4135
4136 let num_rows: i32 = 1000;
4137 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4138 "x",
4139 DataType::Int32,
4140 false,
4141 )]));
4142 let input_batch = RecordBatch::try_new(
4143 schema,
4144 vec![Arc::new(Int32Array::from_iter_values(0..num_rows))],
4145 )
4146 .unwrap();
4147
4148 let batches =
4150 decode_batches_with_byte_limit(&input_batch, 100, Some(10_000)).await;
4151 assert_eq!(batches.len(), 10);
4152 assert!(batches.iter().all(|batch| batch.num_rows() == 100));
4153 }
4154
4155 #[tokio::test]
4156 async fn test_byte_sized_batches_feedback_convergence() {
4157 use arrow_array::StringArray;
4158
4159 let num_rows = 500;
4163 let value: String = "x".repeat(100);
4164 let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
4165 (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
4166 ))];
4167 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4168 "s",
4169 DataType::Utf8,
4170 false,
4171 )]));
4172 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4173
4174 let target_bytes: u64 = 5000;
4178 let batches = decode_batches_with_byte_limit(
4179 &input_batch,
4180 1024,
4181 Some(target_bytes),
4182 )
4183 .await;
4184
4185 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4187 let concatenated =
4188 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4189 .unwrap();
4190 assert_eq!(concatenated.num_rows(), num_rows as usize);
4191 assert_eq!(
4192 concatenated.column(0).as_ref(),
4193 input_batch.column(0).as_ref()
4194 );
4195
4196 assert!(
4199 batches.len() >= 2,
4200 "need at least 2 batches to test convergence"
4201 );
4202 if batches.len() >= 3 {
4205 let second_batch_rows = batches[1].num_rows();
4206 let third_batch_rows = batches[2].num_rows();
4207 assert!(
4209 (40..=60).contains(&second_batch_rows),
4210 "second batch should be near 50 rows, got {second_batch_rows}"
4211 );
4212 assert!(
4213 (40..=60).contains(&third_batch_rows),
4214 "third batch should be near 50 rows, got {third_batch_rows}"
4215 );
4216 }
4217 }
4218}