1use std::collections::VecDeque;
216use std::sync::atomic::{AtomicU64, Ordering};
217use std::sync::{LazyLock, Once, OnceLock};
218use std::{ops::Range, sync::Arc};
219
220use arrow_array::cast::AsArray;
221use arrow_array::{ArrayRef, RecordBatch, RecordBatchIterator, RecordBatchReader};
222use arrow_schema::{ArrowError, DataType, Field as ArrowField, Fields, Schema as ArrowSchema};
223use bytes::Bytes;
224use futures::future::{BoxFuture, MaybeDone, maybe_done};
225use futures::stream::{self, BoxStream};
226use futures::{FutureExt, StreamExt};
227use lance_arrow::DataTypeExt;
228use lance_core::cache::{Context, DeepSizeOf, LanceCache};
229use lance_core::datatypes::{
230 BLOB_DESC_LANCE_FIELD, Field, Schema, validate_fixed_size_list_dimensions,
231};
232use lance_core::utils::futures::{FinallyStreamExt, StreamOnDropExt};
233use lance_core::utils::parse::parse_env_as_bool;
234use log::{debug, trace, warn};
235use prost::Message;
236use tokio::sync::mpsc::error::SendError;
237use tokio::sync::mpsc::{self, unbounded_channel};
238
239use lance_core::error::LanceOptionExt;
240use lance_core::{ArrowResult, Error, Result};
241use tracing::instrument;
242
243use crate::array_encoding::logical::list::OffsetPageInfo;
244use crate::array_encoding::logical::r#struct::{SimpleStructDecoder, SimpleStructScheduler};
245use crate::array_encoding::logical::{
246 binary::BinaryFieldScheduler, blob::BlobFieldScheduler, list::ListFieldScheduler,
247 primitive::PrimitiveFieldScheduler,
248};
249use crate::compression::{DecompressionStrategy, DefaultDecompressionStrategy};
250use crate::data::DataBlock;
251use crate::encoder::EncodedBatch;
252use crate::encodings::logical::fixed_size_list::StructuralFixedSizeListScheduler;
253use crate::encodings::logical::list::StructuralListScheduler;
254use crate::encodings::logical::map::StructuralMapScheduler;
255use crate::encodings::logical::primitive::StructuralPrimitiveFieldScheduler;
256use crate::encodings::logical::r#struct::{StructuralStructDecoder, StructuralStructScheduler};
257use crate::format::pb::{self, column_encoding};
258use crate::format::pb21;
259use crate::repdef::{CompositeRepDefUnraveler, RepDefUnraveler};
260use crate::{BufferScheduler, EncodingsIo};
261
262pub trait SchedulingJob: std::fmt::Debug {
263 fn schedule_next(
264 &mut self,
265 context: &mut SchedulerContext,
266 priority: &dyn PriorityRange,
267 ) -> Result<ScheduledScanLine>;
268
269 fn num_rows(&self) -> u64;
270}
271
272pub trait FieldScheduler: Send + Sync + std::fmt::Debug {
274 fn initialize<'a>(
275 &'a self,
276 filter: &'a FilterExpression,
277 context: &'a SchedulerContext,
278 ) -> BoxFuture<'a, Result<()>>;
279
280 fn schedule_ranges<'a>(
281 &'a self,
282 ranges: &[Range<u64>],
283 filter: &FilterExpression,
284 ) -> Result<Box<dyn SchedulingJob + 'a>>;
285
286 fn num_rows(&self) -> u64;
287}
288
289#[derive(Debug)]
290pub struct DecoderReady {
291 pub decoder: Box<dyn LogicalPageDecoder>,
292 pub path: VecDeque<u32>,
293}
294
295pub trait LogicalPageDecoder: std::fmt::Debug + Send {
297 fn accept_child(&mut self, _child: DecoderReady) -> Result<()> {
298 Err(Error::internal(format!(
299 "The decoder {:?} does not expect children but received a child",
300 self
301 )))
302 }
303
304 fn wait_for_loaded(&'_ mut self, loaded_need: u64) -> BoxFuture<'_, Result<()>>;
305
306 fn rows_loaded(&self) -> u64;
307
308 fn rows_unloaded(&self) -> u64 {
309 self.num_rows() - self.rows_loaded()
310 }
311
312 fn num_rows(&self) -> u64;
313
314 fn rows_drained(&self) -> u64;
315
316 fn rows_left(&self) -> u64 {
317 self.num_rows() - self.rows_drained()
318 }
319
320 fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
321
322 fn data_type(&self) -> &DataType;
323}
324
325const BATCH_SIZE_BYTES_WARNING: u64 = 10 * 1024 * 1024;
327const ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE: &str =
328 "LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE";
329const ENV_LANCE_READ_CACHE_REPETITION_INDEX: &str = "LANCE_READ_CACHE_REPETITION_INDEX";
330const ENV_LANCE_INLINE_SCHEDULING_THRESHOLD: &str = "LANCE_INLINE_SCHEDULING_THRESHOLD";
331
332const DEFAULT_INLINE_SCHEDULING_THRESHOLD: u64 = 16 * 1024;
335
336fn default_cache_repetition_index() -> bool {
337 static DEFAULT_CACHE_REPETITION_INDEX: OnceLock<bool> = OnceLock::new();
338 *DEFAULT_CACHE_REPETITION_INDEX
339 .get_or_init(|| parse_env_as_bool(ENV_LANCE_READ_CACHE_REPETITION_INDEX, true))
340}
341
342fn inline_scheduling_threshold() -> u64 {
343 static THRESHOLD: OnceLock<u64> = OnceLock::new();
344 *THRESHOLD.get_or_init(|| {
345 std::env::var(ENV_LANCE_INLINE_SCHEDULING_THRESHOLD)
346 .ok()
347 .and_then(|v| v.trim().parse::<u64>().ok())
348 .unwrap_or(DEFAULT_INLINE_SCHEDULING_THRESHOLD)
349 })
350}
351
352#[derive(Debug, Clone)]
359pub enum PageEncoding {
360 Legacy(pb::ArrayEncoding),
361 Structural(pb21::PageLayout),
362}
363
364impl DeepSizeOf for PageEncoding {
365 fn deep_size_of_children(&self, _context: &mut Context) -> usize {
366 match self {
367 Self::Legacy(encoding) => encoding.encoded_len() * 4,
368 Self::Structural(encoding) => encoding.encoded_len() * 4,
369 }
370 }
371}
372
373impl PageEncoding {
374 pub fn as_legacy(&self) -> &pb::ArrayEncoding {
375 match self {
376 Self::Legacy(enc) => enc,
377 Self::Structural(_) => panic!("Expected a legacy encoding"),
378 }
379 }
380
381 pub fn as_structural(&self) -> &pb21::PageLayout {
382 match self {
383 Self::Structural(enc) => enc,
384 Self::Legacy(_) => panic!("Expected a structural encoding"),
385 }
386 }
387
388 pub fn is_structural(&self) -> bool {
389 matches!(self, Self::Structural(_))
390 }
391}
392
393#[derive(Debug)]
397pub struct PageInfo {
398 pub num_rows: u64,
400 pub priority: u64,
404 pub encoding: PageEncoding,
406 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
408}
409
410impl DeepSizeOf for PageInfo {
411 fn deep_size_of_children(&self, context: &mut Context) -> usize {
412 self.encoding.deep_size_of_children(context)
413 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
414 }
415}
416
417#[derive(Debug, Clone)]
421pub struct ColumnInfo {
422 pub index: u32,
424 pub page_infos: Arc<[PageInfo]>,
426 pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
428 pub encoding: pb::ColumnEncoding,
429}
430
431impl DeepSizeOf for ColumnInfo {
432 fn deep_size_of_children(&self, context: &mut Context) -> usize {
433 self.page_infos.deep_size_of_children(context)
434 + self.buffer_offsets_and_sizes.deep_size_of_children(context)
435 + self.encoding.encoded_len() * 4
436 }
437}
438
439impl ColumnInfo {
440 pub fn new(
442 index: u32,
443 page_infos: Arc<[PageInfo]>,
444 buffer_offsets_and_sizes: Vec<(u64, u64)>,
445 encoding: pb::ColumnEncoding,
446 ) -> Self {
447 Self {
448 index,
449 page_infos,
450 buffer_offsets_and_sizes: buffer_offsets_and_sizes.into_boxed_slice().into(),
451 encoding,
452 }
453 }
454
455 pub fn is_structural(&self) -> bool {
456 self.page_infos
457 .first()
459 .map(|page| page.encoding.is_structural())
460 .unwrap_or(false)
461 }
462}
463
464enum RootScheduler {
465 Structural(Box<dyn StructuralFieldScheduler>),
466 Array(Arc<dyn FieldScheduler>),
467}
468
469impl RootScheduler {
470 fn as_array(&self) -> &Arc<dyn FieldScheduler> {
471 match self {
472 Self::Structural(_) => panic!("Expected an array scheduler"),
473 Self::Array(s) => s,
474 }
475 }
476
477 fn as_structural(&self) -> &dyn StructuralFieldScheduler {
478 match self {
479 Self::Structural(s) => s.as_ref(),
480 Self::Array(_) => panic!("Expected a structural scheduler"),
481 }
482 }
483}
484
485pub struct DecodeBatchScheduler {
507 root_scheduler: RootScheduler,
508 pub root_fields: Fields,
509 cache: Arc<LanceCache>,
510}
511
512pub struct ColumnInfoIter<'a> {
513 column_infos: Vec<Arc<ColumnInfo>>,
514 column_indices: &'a [u32],
515 column_info_pos: usize,
516 column_indices_pos: usize,
517}
518
519impl<'a> ColumnInfoIter<'a> {
520 pub fn new(column_infos: Vec<Arc<ColumnInfo>>, column_indices: &'a [u32]) -> Self {
521 let initial_pos = column_indices.first().copied().unwrap_or(0) as usize;
522 Self {
523 column_infos,
524 column_indices,
525 column_info_pos: initial_pos,
526 column_indices_pos: 0,
527 }
528 }
529
530 pub fn peek(&self) -> &Arc<ColumnInfo> {
531 &self.column_infos[self.column_info_pos]
532 }
533
534 pub fn peek_transform(&mut self, transform: impl FnOnce(Arc<ColumnInfo>) -> Arc<ColumnInfo>) {
535 let column_info = self.column_infos[self.column_info_pos].clone();
536 let transformed = transform(column_info);
537 self.column_infos[self.column_info_pos] = transformed;
538 }
539
540 pub fn expect_next(&mut self) -> Result<&Arc<ColumnInfo>> {
541 self.next().ok_or_else(|| {
542 Error::invalid_input(
543 "there were more fields in the schema than provided column indices / infos",
544 )
545 })
546 }
547
548 fn next(&mut self) -> Option<&Arc<ColumnInfo>> {
549 if self.column_info_pos < self.column_infos.len() {
550 let info = &self.column_infos[self.column_info_pos];
551 self.column_info_pos += 1;
552 Some(info)
553 } else {
554 None
555 }
556 }
557
558 pub(crate) fn next_top_level(&mut self) {
559 self.column_indices_pos += 1;
560 if self.column_indices_pos < self.column_indices.len() {
561 self.column_info_pos = self.column_indices[self.column_indices_pos] as usize;
562 } else {
563 self.column_info_pos = self.column_infos.len();
564 }
565 }
566}
567
568#[derive(Clone, Copy, Debug)]
570pub struct FileBuffers<'a> {
571 pub positions_and_sizes: &'a [(u64, u64)],
572}
573
574#[derive(Clone, Copy, Debug)]
576pub struct ColumnBuffers<'a, 'b> {
577 pub file_buffers: FileBuffers<'a>,
578 pub positions_and_sizes: &'b [(u64, u64)],
579}
580
581#[derive(Clone, Copy, Debug)]
583pub struct PageBuffers<'a, 'b, 'c> {
584 pub column_buffers: ColumnBuffers<'a, 'b>,
585 pub positions_and_sizes: &'c [(u64, u64)],
586}
587
588#[derive(Debug)]
590pub struct CoreFieldDecoderStrategy {
591 pub validate_data: bool,
592 pub decompressor_strategy: Arc<dyn DecompressionStrategy>,
593 pub cache_repetition_index: bool,
594}
595
596impl Default for CoreFieldDecoderStrategy {
597 fn default() -> Self {
598 Self {
599 validate_data: false,
600 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
601 cache_repetition_index: false,
602 }
603 }
604}
605
606impl CoreFieldDecoderStrategy {
607 pub fn with_cache_repetition_index(mut self, cache_repetition_index: bool) -> Self {
609 self.cache_repetition_index = cache_repetition_index;
610 self
611 }
612
613 pub fn from_decoder_config(config: &DecoderConfig) -> Self {
615 Self {
616 validate_data: config.validate_on_decode,
617 decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
618 cache_repetition_index: config.cache_repetition_index,
619 }
620 }
621
622 fn ensure_values_encoded(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
625 let column_encoding = column_info
626 .encoding
627 .column_encoding
628 .as_ref()
629 .ok_or_else(|| {
630 Error::invalid_input(format!(
631 "the column at index {} was missing a ColumnEncoding",
632 column_info.index
633 ))
634 })?;
635 if matches!(
636 column_encoding,
637 pb::column_encoding::ColumnEncoding::Values(_)
638 ) {
639 Ok(())
640 } else {
641 Err(Error::invalid_input(format!(
642 "the column at index {} mapping to the input field {} has column encoding {:?} and no decoder is registered to handle it",
643 column_info.index, field_name, column_encoding
644 )))
645 }
646 }
647
648 fn is_structural_primitive(data_type: &DataType) -> bool {
649 if data_type.is_primitive() {
650 true
651 } else {
652 match data_type {
653 DataType::Dictionary(_, value_type) => Self::is_structural_primitive(value_type),
655 DataType::Boolean
656 | DataType::Null
657 | DataType::FixedSizeBinary(_)
658 | DataType::Binary
659 | DataType::LargeBinary
660 | DataType::Utf8
661 | DataType::LargeUtf8 => true,
662 DataType::FixedSizeList(inner, _) => {
663 Self::is_structural_primitive(inner.data_type())
664 }
665 _ => false,
666 }
667 }
668 }
669
670 fn is_array_primitive(data_type: &DataType) -> bool {
671 if data_type.is_primitive() {
672 true
673 } else {
674 match data_type {
675 DataType::Boolean | DataType::Null | DataType::FixedSizeBinary(_) => true,
677 DataType::FixedSizeList(inner, _) => Self::is_array_primitive(inner.data_type()),
678 _ => false,
679 }
680 }
681 }
682
683 fn create_primitive_scheduler(
684 &self,
685 field: &Field,
686 column: &ColumnInfo,
687 buffers: FileBuffers,
688 ) -> Result<Box<dyn FieldScheduler>> {
689 Self::ensure_values_encoded(column, &field.name)?;
690 let column_buffers = ColumnBuffers {
692 file_buffers: buffers,
693 positions_and_sizes: &column.buffer_offsets_and_sizes,
694 };
695 Ok(Box::new(PrimitiveFieldScheduler::new(
696 column.index,
697 field.data_type(),
698 column.page_infos.clone(),
699 column_buffers,
700 self.validate_data,
701 )))
702 }
703
704 fn check_simple_struct(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
706 Self::ensure_values_encoded(column_info, field_name)?;
707 if column_info.page_infos.len() != 1 {
708 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()));
709 }
710 let encoding = &column_info.page_infos[0].encoding;
711 match encoding.as_legacy().array_encoding.as_ref().unwrap() {
712 pb::array_encoding::ArrayEncoding::Struct(_) => Ok(()),
713 _ => 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())),
714 }
715 }
716
717 fn check_packed_struct(column_info: &ColumnInfo) -> bool {
718 let encoding = &column_info.page_infos[0].encoding;
719 matches!(
720 encoding.as_legacy().array_encoding.as_ref().unwrap(),
721 pb::array_encoding::ArrayEncoding::PackedStruct(_)
722 )
723 }
724
725 fn create_list_scheduler(
726 &self,
727 list_field: &Field,
728 column_infos: &mut ColumnInfoIter,
729 buffers: FileBuffers,
730 offsets_column: &ColumnInfo,
731 ) -> Result<Box<dyn FieldScheduler>> {
732 Self::ensure_values_encoded(offsets_column, &list_field.name)?;
733 let offsets_column_buffers = ColumnBuffers {
734 file_buffers: buffers,
735 positions_and_sizes: &offsets_column.buffer_offsets_and_sizes,
736 };
737 let items_scheduler =
738 self.create_array_field_scheduler(&list_field.children[0], column_infos, buffers)?;
739
740 let (inner_infos, null_offset_adjustments): (Vec<_>, Vec<_>) = offsets_column
741 .page_infos
742 .iter()
743 .filter(|offsets_page| offsets_page.num_rows > 0)
744 .map(|offsets_page| {
745 if let Some(pb::array_encoding::ArrayEncoding::List(list_encoding)) =
746 &offsets_page.encoding.as_legacy().array_encoding
747 {
748 let inner = PageInfo {
749 buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
750 encoding: PageEncoding::Legacy(
751 list_encoding.offsets.as_ref().unwrap().as_ref().clone(),
752 ),
753 num_rows: offsets_page.num_rows,
754 priority: 0,
755 };
756 (
757 inner,
758 OffsetPageInfo {
759 offsets_in_page: offsets_page.num_rows,
760 null_offset_adjustment: list_encoding.null_offset_adjustment,
761 num_items_referenced_by_page: list_encoding.num_items,
762 },
763 )
764 } else {
765 panic!("Expected a list column");
767 }
768 })
769 .unzip();
770 let inner = Arc::new(PrimitiveFieldScheduler::new(
771 offsets_column.index,
772 DataType::UInt64,
773 Arc::from(inner_infos.into_boxed_slice()),
774 offsets_column_buffers,
775 self.validate_data,
776 )) as Arc<dyn FieldScheduler>;
777 let items_field = match list_field.data_type() {
778 DataType::List(inner) => inner,
779 DataType::LargeList(inner) => inner,
780 _ => unreachable!(),
781 };
782 let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
783 DataType::Int32
784 } else {
785 DataType::Int64
786 };
787 Ok(Box::new(ListFieldScheduler::new(
788 inner,
789 items_scheduler.into(),
790 items_field,
791 offset_type,
792 null_offset_adjustments,
793 )))
794 }
795
796 fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
797 if let column_encoding::ColumnEncoding::Blob(blob) =
798 column_info.encoding.column_encoding.as_ref().unwrap()
799 {
800 let mut column_info = column_info.clone();
801 column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
802 Some(column_info)
803 } else {
804 None
805 }
806 }
807
808 fn create_structural_field_scheduler(
809 &self,
810 field: &Field,
811 column_infos: &mut ColumnInfoIter,
812 ) -> Result<Box<dyn StructuralFieldScheduler>> {
813 let data_type = field.data_type();
814 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
815 if Self::is_structural_primitive(&data_type) {
816 let column_info = column_infos.expect_next()?;
817 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
818 column_info.as_ref(),
819 self.decompressor_strategy.as_ref(),
820 self.cache_repetition_index,
821 field,
822 )?);
823
824 column_infos.next_top_level();
826
827 return Ok(scheduler);
828 }
829 match &data_type {
830 DataType::Struct(fields) => {
831 if field.is_packed_struct() {
832 let column_info = column_infos.expect_next()?;
834 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
835 column_info.as_ref(),
836 self.decompressor_strategy.as_ref(),
837 self.cache_repetition_index,
838 field,
839 )?);
840
841 column_infos.next_top_level();
843
844 return Ok(scheduler);
845 }
846 if field.is_blob() {
848 let column_info = column_infos.peek();
849 if column_info.page_infos.iter().any(|page| {
850 matches!(
851 page.encoding,
852 PageEncoding::Structural(pb21::PageLayout {
853 layout: Some(pb21::page_layout::Layout::BlobLayout(_))
854 })
855 )
856 }) {
857 let column_info = column_infos.expect_next()?;
858 let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
859 column_info.as_ref(),
860 self.decompressor_strategy.as_ref(),
861 self.cache_repetition_index,
862 field,
863 )?);
864 column_infos.next_top_level();
865 return Ok(scheduler);
866 }
867 }
868
869 let mut child_schedulers = Vec::with_capacity(field.children.len());
870 for field in field.children.iter() {
871 let field_scheduler =
872 self.create_structural_field_scheduler(field, column_infos)?;
873 child_schedulers.push(field_scheduler);
874 }
875
876 let fields = fields.clone();
877 Ok(
878 Box::new(StructuralStructScheduler::new(child_schedulers, fields))
879 as Box<dyn StructuralFieldScheduler>,
880 )
881 }
882 DataType::List(_) | DataType::LargeList(_) => {
883 let child = field.children.first().expect_ok()?;
884 let child_scheduler =
885 self.create_structural_field_scheduler(child, column_infos)?;
886 Ok(Box::new(StructuralListScheduler::new(child_scheduler))
887 as Box<dyn StructuralFieldScheduler>)
888 }
889 DataType::FixedSizeList(inner, dimension)
890 if matches!(inner.data_type(), DataType::Struct(_)) =>
891 {
892 let child = field.children.first().expect_ok()?;
893 let child_scheduler =
894 self.create_structural_field_scheduler(child, column_infos)?;
895 Ok(Box::new(StructuralFixedSizeListScheduler::new(
896 child_scheduler,
897 *dimension,
898 )) as Box<dyn StructuralFieldScheduler>)
899 }
900 DataType::Map(_, keys_sorted) => {
901 if *keys_sorted {
905 return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
906 }
907 let entries_child = field.children.first().expect_ok()?;
908 let child_scheduler =
909 self.create_structural_field_scheduler(entries_child, column_infos)?;
910 Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
911 as Box<dyn StructuralFieldScheduler>)
912 }
913 _ => todo!("create_structural_field_scheduler for {}", data_type),
914 }
915 }
916
917 fn create_array_field_scheduler(
918 &self,
919 field: &Field,
920 column_infos: &mut ColumnInfoIter,
921 buffers: FileBuffers,
922 ) -> Result<Box<dyn FieldScheduler>> {
923 let data_type = field.data_type();
924 validate_fixed_size_list_dimensions(&field.name, &data_type)?;
925 if Self::is_array_primitive(&data_type) {
926 let column_info = column_infos.expect_next()?;
927 let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
928 return Ok(scheduler);
929 } else if data_type.is_binary_like() {
930 let column_info = column_infos.expect_next()?.clone();
931 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
933 let desc_scheduler =
934 self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
935 let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
936 return Ok(blob_scheduler);
937 }
938 if let Some(page_info) = column_info.page_infos.first() {
939 if matches!(
940 page_info.encoding.as_legacy(),
941 pb::ArrayEncoding {
942 array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
943 }
944 ) {
945 let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
946 DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
947 } else {
948 DataType::LargeList(Arc::new(ArrowField::new(
949 "item",
950 DataType::UInt8,
951 false,
952 )))
953 };
954 let list_field = Field::try_from(ArrowField::new(
955 field.name.clone(),
956 list_type,
957 field.nullable,
958 ))
959 .unwrap();
960 let list_scheduler = self.create_list_scheduler(
961 &list_field,
962 column_infos,
963 buffers,
964 &column_info,
965 )?;
966 let binary_scheduler = Box::new(BinaryFieldScheduler::new(
967 list_scheduler.into(),
968 field.data_type(),
969 ));
970 return Ok(binary_scheduler);
971 } else {
972 let scheduler =
973 self.create_primitive_scheduler(field, &column_info, buffers)?;
974 return Ok(scheduler);
975 }
976 } else {
977 return self.create_primitive_scheduler(field, &column_info, buffers);
978 }
979 }
980 match &data_type {
981 DataType::FixedSizeList(inner, _dimension) => {
982 if Self::is_array_primitive(inner.data_type()) {
985 let primitive_col = column_infos.expect_next()?;
986 let scheduler =
987 self.create_primitive_scheduler(field, primitive_col, buffers)?;
988 Ok(scheduler)
989 } else {
990 todo!()
991 }
992 }
993 DataType::Dictionary(_key_type, value_type) => {
994 if Self::is_array_primitive(value_type) || value_type.is_binary_like() {
995 let primitive_col = column_infos.expect_next()?;
996 let scheduler =
997 self.create_primitive_scheduler(field, primitive_col, buffers)?;
998 Ok(scheduler)
999 } else {
1000 Err(Error::not_supported_source(
1001 format!(
1002 "No way to decode into a dictionary field of type {}",
1003 value_type
1004 )
1005 .into(),
1006 ))
1007 }
1008 }
1009 DataType::List(_) | DataType::LargeList(_) => {
1010 let offsets_column = column_infos.expect_next()?.clone();
1011 column_infos.next_top_level();
1012 self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
1013 }
1014 DataType::Struct(fields) => {
1015 let column_info = column_infos.expect_next()?;
1016
1017 if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1019 return self.create_primitive_scheduler(field, &blob_col, buffers);
1021 }
1022
1023 if Self::check_packed_struct(column_info) {
1024 self.create_primitive_scheduler(field, column_info, buffers)
1026 } else {
1027 Self::check_simple_struct(column_info, &field.name).unwrap();
1029 let num_rows = column_info
1030 .page_infos
1031 .iter()
1032 .map(|page| page.num_rows)
1033 .sum();
1034 let mut child_schedulers = Vec::with_capacity(field.children.len());
1035 for field in &field.children {
1036 column_infos.next_top_level();
1037 let field_scheduler =
1038 self.create_array_field_scheduler(field, column_infos, buffers)?;
1039 child_schedulers.push(Arc::from(field_scheduler));
1040 }
1041
1042 let fields = fields.clone();
1043 Ok(Box::new(SimpleStructScheduler::new(
1044 child_schedulers,
1045 fields,
1046 num_rows,
1047 )))
1048 }
1049 }
1050 _ => todo!(),
1052 }
1053 }
1054}
1055
1056fn root_column(num_rows: u64) -> ColumnInfo {
1058 let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
1059 let final_page_num_rows = num_rows % (u32::MAX as u64);
1060 let root_pages = (0..num_root_pages)
1061 .map(|i| PageInfo {
1062 num_rows: if i == num_root_pages - 1 {
1063 final_page_num_rows
1064 } else {
1065 u64::MAX
1066 },
1067 encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1068 array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1069 pb::SimpleStruct {},
1070 )),
1071 }),
1072 priority: 0, buffer_offsets_and_sizes: Arc::new([]),
1074 })
1075 .collect::<Vec<_>>();
1076 ColumnInfo {
1077 buffer_offsets_and_sizes: Arc::new([]),
1078 encoding: pb::ColumnEncoding {
1079 column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1080 },
1081 index: u32::MAX,
1082 page_infos: Arc::from(root_pages),
1083 }
1084}
1085
1086pub enum RootDecoder {
1087 Structural(StructuralStructDecoder),
1088 Array(SimpleStructDecoder),
1089}
1090
1091impl RootDecoder {
1092 pub fn into_structural(self) -> StructuralStructDecoder {
1093 match self {
1094 Self::Structural(decoder) => decoder,
1095 Self::Array(_) => panic!("Expected a structural decoder"),
1096 }
1097 }
1098
1099 pub fn into_array(self) -> SimpleStructDecoder {
1100 match self {
1101 Self::Array(decoder) => decoder,
1102 Self::Structural(_) => panic!("Expected an array decoder"),
1103 }
1104 }
1105}
1106
1107impl DecodeBatchScheduler {
1108 #[allow(clippy::too_many_arguments)]
1111 pub async fn try_new<'a>(
1112 schema: &'a Schema,
1113 column_indices: &[u32],
1114 column_infos: &[Arc<ColumnInfo>],
1115 file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1116 num_rows: u64,
1117 _decoder_plugins: Arc<DecoderPlugins>,
1118 io: Arc<dyn EncodingsIo>,
1119 cache: Arc<LanceCache>,
1120 filter: &FilterExpression,
1121 decoder_config: &DecoderConfig,
1122 ) -> Result<Self> {
1123 assert!(num_rows > 0);
1124 let buffers = FileBuffers {
1125 positions_and_sizes: file_buffer_positions_and_sizes,
1126 };
1127 let arrow_schema = ArrowSchema::from(schema);
1128 let root_fields = arrow_schema.fields().clone();
1129 let root_type = DataType::Struct(root_fields.clone());
1130 let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1131 root_field.children.clone_from(&schema.fields);
1135 root_field
1136 .metadata
1137 .insert("__lance_decoder_root".to_string(), "true".to_string());
1138
1139 if column_infos.is_empty() || column_infos[0].is_structural() {
1140 let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1141
1142 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1143 let mut root_scheduler =
1144 strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1145
1146 let context = SchedulerContext::new(io, cache.clone());
1147 root_scheduler.initialize(filter, &context).await?;
1148
1149 Ok(Self {
1150 root_scheduler: RootScheduler::Structural(root_scheduler),
1151 root_fields,
1152 cache,
1153 })
1154 } else {
1155 let mut columns = Vec::with_capacity(column_infos.len() + 1);
1158 columns.push(Arc::new(root_column(num_rows)));
1159 columns.extend(column_infos.iter().cloned());
1160
1161 let adjusted_column_indices = [0_u32]
1162 .into_iter()
1163 .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1164 .collect::<Vec<_>>();
1165 let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1166 let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1167 let root_scheduler =
1168 strategy.create_array_field_scheduler(&root_field, &mut column_iter, buffers)?;
1169
1170 let context = SchedulerContext::new(io, cache.clone());
1171 root_scheduler.initialize(filter, &context).await?;
1172
1173 Ok(Self {
1174 root_scheduler: RootScheduler::Array(root_scheduler.into()),
1175 root_fields,
1176 cache,
1177 })
1178 }
1179 }
1180
1181 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
1182 pub fn from_scheduler(
1183 root_scheduler: Arc<dyn FieldScheduler>,
1184 root_fields: Fields,
1185 cache: Arc<LanceCache>,
1186 ) -> Self {
1187 Self {
1188 root_scheduler: RootScheduler::Array(root_scheduler),
1189 root_fields,
1190 cache,
1191 }
1192 }
1193
1194 fn do_schedule_ranges_structural(
1195 &mut self,
1196 ranges: &[Range<u64>],
1197 filter: &FilterExpression,
1198 io: Arc<dyn EncodingsIo>,
1199 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1200 ) {
1201 let root_scheduler = self.root_scheduler.as_structural();
1202 let mut context = SchedulerContext::new(io, self.cache.clone());
1203 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1204 if let Err(schedule_ranges_err) = maybe_root_job {
1205 schedule_action(Err(schedule_ranges_err));
1206 return;
1207 }
1208 let mut root_job = maybe_root_job.unwrap();
1209 let mut num_rows_scheduled = 0;
1210 loop {
1211 let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1212 if let Err(err) = maybe_next_scan_lines {
1213 schedule_action(Err(err));
1214 return;
1215 }
1216 let next_scan_lines = maybe_next_scan_lines.unwrap();
1217 if next_scan_lines.is_empty() {
1218 return;
1219 }
1220 for next_scan_line in next_scan_lines {
1221 trace!(
1222 "Scheduled scan line of {} rows and {} decoders",
1223 next_scan_line.rows_scheduled,
1224 next_scan_line.decoders.len()
1225 );
1226 num_rows_scheduled += next_scan_line.rows_scheduled;
1227 if !schedule_action(Ok(DecoderMessage {
1228 scheduled_so_far: num_rows_scheduled,
1229 decoders: next_scan_line.decoders,
1230 })) {
1231 return;
1233 }
1234 }
1235 }
1236 }
1237
1238 fn do_schedule_ranges_array(
1239 &mut self,
1240 ranges: &[Range<u64>],
1241 filter: &FilterExpression,
1242 io: Arc<dyn EncodingsIo>,
1243 mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1244 priority: Option<Box<dyn PriorityRange>>,
1248 ) {
1249 let root_scheduler = self.root_scheduler.as_array();
1250 let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1251 trace!(
1252 "Scheduling {} ranges across {}..{} ({} rows){}",
1253 ranges.len(),
1254 ranges.first().unwrap().start,
1255 ranges.last().unwrap().end,
1256 rows_requested,
1257 priority
1258 .as_ref()
1259 .map(|p| format!(" (priority={:?})", p))
1260 .unwrap_or_default()
1261 );
1262
1263 let mut context = SchedulerContext::new(io, self.cache.clone());
1264 let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1265 if let Err(schedule_ranges_err) = maybe_root_job {
1266 schedule_action(Err(schedule_ranges_err));
1267 return;
1268 }
1269 let mut root_job = maybe_root_job.unwrap();
1270 let mut num_rows_scheduled = 0;
1271 let mut rows_to_schedule = root_job.num_rows();
1272 let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1273 trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1274 while rows_to_schedule > 0 {
1275 let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1276 if let Err(schedule_next_err) = maybe_next_scan_line {
1277 schedule_action(Err(schedule_next_err));
1278 return;
1279 }
1280 let next_scan_line = maybe_next_scan_line.unwrap();
1281 priority.advance(next_scan_line.rows_scheduled);
1282 num_rows_scheduled += next_scan_line.rows_scheduled;
1283 rows_to_schedule -= next_scan_line.rows_scheduled;
1284 trace!(
1285 "Scheduled scan line of {} rows and {} decoders",
1286 next_scan_line.rows_scheduled,
1287 next_scan_line.decoders.len()
1288 );
1289 if !schedule_action(Ok(DecoderMessage {
1290 scheduled_so_far: num_rows_scheduled,
1291 decoders: next_scan_line.decoders,
1292 })) {
1293 return;
1295 }
1296
1297 trace!("Finished scheduling {} ranges", ranges.len());
1298 }
1299 }
1300
1301 fn do_schedule_ranges(
1302 &mut self,
1303 ranges: &[Range<u64>],
1304 filter: &FilterExpression,
1305 io: Arc<dyn EncodingsIo>,
1306 schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1307 priority: Option<Box<dyn PriorityRange>>,
1311 ) {
1312 match &self.root_scheduler {
1313 RootScheduler::Array(_) => {
1314 self.do_schedule_ranges_array(ranges, filter, io, schedule_action, priority)
1315 }
1316 RootScheduler::Structural(_) => {
1317 self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1318 }
1319 }
1320 }
1321
1322 pub fn schedule_ranges_to_vec(
1325 &mut self,
1326 ranges: &[Range<u64>],
1327 filter: &FilterExpression,
1328 io: Arc<dyn EncodingsIo>,
1329 priority: Option<Box<dyn PriorityRange>>,
1330 ) -> Result<Vec<DecoderMessage>> {
1331 let mut decode_messages = Vec::new();
1332 self.do_schedule_ranges(
1333 ranges,
1334 filter,
1335 io,
1336 |msg| {
1337 decode_messages.push(msg);
1338 true
1339 },
1340 priority,
1341 );
1342 decode_messages.into_iter().collect::<Result<Vec<_>>>()
1343 }
1344
1345 #[instrument(level = "debug", skip_all)]
1355 pub fn schedule_ranges(
1356 &mut self,
1357 ranges: &[Range<u64>],
1358 filter: &FilterExpression,
1359 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1360 scheduler: Arc<dyn EncodingsIo>,
1361 ) {
1362 self.do_schedule_ranges(
1363 ranges,
1364 filter,
1365 scheduler,
1366 |msg| {
1367 match sink.send(msg) {
1368 Ok(_) => true,
1369 Err(SendError { .. }) => {
1370 debug!(
1373 "schedule_ranges aborting early since decoder appears to have been dropped"
1374 );
1375 false
1376 }
1377 }
1378 },
1379 None,
1380 )
1381 }
1382
1383 #[instrument(level = "debug", skip_all)]
1391 pub fn schedule_range(
1392 &mut self,
1393 range: Range<u64>,
1394 filter: &FilterExpression,
1395 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1396 scheduler: Arc<dyn EncodingsIo>,
1397 ) {
1398 self.schedule_ranges(&[range], filter, sink, scheduler)
1399 }
1400
1401 pub fn schedule_take(
1409 &mut self,
1410 indices: &[u64],
1411 filter: &FilterExpression,
1412 sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1413 scheduler: Arc<dyn EncodingsIo>,
1414 ) {
1415 debug_assert!(indices.windows(2).all(|w| w[0] < w[1]));
1416 if indices.is_empty() {
1417 return;
1418 }
1419 trace!("Scheduling take of {} rows", indices.len());
1420 let ranges = Self::indices_to_ranges(indices);
1421 self.schedule_ranges(&ranges, filter, sink, scheduler)
1422 }
1423
1424 fn indices_to_ranges(indices: &[u64]) -> Vec<Range<u64>> {
1426 let mut ranges = Vec::new();
1427 let mut start = indices[0];
1428
1429 for window in indices.windows(2) {
1430 if window[1] != window[0] + 1 {
1431 ranges.push(start..window[0] + 1);
1432 start = window[1];
1433 }
1434 }
1435
1436 ranges.push(start..*indices.last().unwrap() + 1);
1437 ranges
1438 }
1439}
1440
1441pub struct ReadBatchTask {
1442 pub task: BoxFuture<'static, Result<RecordBatch>>,
1443 pub num_rows: u32,
1444}
1445
1446pub struct BatchDecodeStream {
1448 context: DecoderContext,
1449 root_decoder: SimpleStructDecoder,
1450 rows_remaining: u64,
1451 rows_per_batch: u32,
1452 rows_scheduled: u64,
1453 rows_drained: u64,
1454 scheduler_exhausted: bool,
1455 emitted_batch_size_warning: Arc<Once>,
1456}
1457
1458impl BatchDecodeStream {
1459 pub fn new(
1469 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1470 rows_per_batch: u32,
1471 num_rows: u64,
1472 root_decoder: SimpleStructDecoder,
1473 ) -> Self {
1474 Self {
1475 context: DecoderContext::new(scheduled),
1476 root_decoder,
1477 rows_remaining: num_rows,
1478 rows_per_batch,
1479 rows_scheduled: 0,
1480 rows_drained: 0,
1481 scheduler_exhausted: false,
1482 emitted_batch_size_warning: Arc::new(Once::new()),
1483 }
1484 }
1485
1486 fn accept_decoder(&mut self, decoder: DecoderReady) -> Result<()> {
1487 if decoder.path.is_empty() {
1488 Ok(())
1490 } else {
1491 self.root_decoder.accept_child(decoder)
1492 }
1493 }
1494
1495 #[instrument(level = "debug", skip_all)]
1496 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1497 if self.scheduler_exhausted {
1498 return Ok(self.rows_scheduled);
1499 }
1500 while self.rows_scheduled < scheduled_need {
1501 let next_message = self.context.source.recv().await;
1502 match next_message {
1503 Some(scan_line) => {
1504 let scan_line = scan_line?;
1505 self.rows_scheduled = scan_line.scheduled_so_far;
1506 for message in scan_line.decoders {
1507 self.accept_decoder(message.into_array())?;
1508 }
1509 }
1510 None => {
1511 self.scheduler_exhausted = true;
1515 return Ok(self.rows_scheduled);
1516 }
1517 }
1518 }
1519 Ok(scheduled_need)
1520 }
1521
1522 #[instrument(level = "debug", skip_all)]
1523 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1524 trace!(
1525 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1526 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1527 );
1528 if self.rows_remaining == 0 {
1529 return Ok(None);
1530 }
1531
1532 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1533 self.rows_remaining -= to_take;
1534
1535 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1536 trace!(
1537 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1538 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1539 );
1540 if scheduled_need > 0 {
1541 let desired_scheduled = scheduled_need + self.rows_scheduled;
1542 trace!(
1543 "Draining from scheduler (desire at least {} scheduled rows)",
1544 desired_scheduled
1545 );
1546 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1547 if actually_scheduled < desired_scheduled {
1548 let under_scheduled = desired_scheduled - actually_scheduled;
1549 to_take -= under_scheduled;
1550 }
1551 }
1552
1553 if to_take == 0 {
1554 return Ok(None);
1555 }
1556
1557 let loaded_need = self.rows_drained + to_take - 1;
1559 trace!(
1560 "Waiting for I/O (desire at least {} fully loaded rows)",
1561 loaded_need
1562 );
1563 self.root_decoder.wait_for_loaded(loaded_need).await?;
1564
1565 let next_task = self.root_decoder.drain(to_take)?;
1566 self.rows_drained += to_take;
1567 Ok(Some(next_task))
1568 }
1569
1570 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1571 let stream = futures::stream::unfold(self, |mut slf| async move {
1572 let next_task = match slf.next_batch_task().await {
1573 Ok(Some(next_task)) => next_task,
1574 Ok(None) => return None,
1575 Err(err) => {
1576 slf.rows_remaining = 0;
1577 return Some((
1578 ReadBatchTask {
1579 task: async move { Err(err) }.boxed(),
1580 num_rows: 0,
1581 },
1582 slf,
1583 ));
1584 }
1585 };
1586 let num_rows = next_task.num_rows;
1587 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1588 let task = async move {
1589 let (batch, _data_size) =
1593 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1594 .await
1595 .map_err(|err| Error::wrapped(err.into()))??;
1596 Ok(batch)
1597 };
1598 debug_assert!(num_rows <= u32::MAX as u64);
1600 Some((
1601 ReadBatchTask {
1602 task: task.boxed(),
1603 num_rows: num_rows as u32,
1604 },
1605 slf,
1606 ))
1607 });
1608 stream.boxed()
1609 }
1610}
1611
1612enum RootDecoderMessage {
1615 LoadedPage(LoadedPageShard),
1616 ArrayPage(DecoderReady),
1617}
1618trait RootDecoderType {
1619 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1620 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1621 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1622}
1623impl RootDecoderType for StructuralStructDecoder {
1624 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1625 let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1626 unreachable!()
1627 };
1628 self.accept_page(loaded_page)
1629 }
1630 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1631 self.drain_batch_task(num_rows)
1632 }
1633 fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1634 Ok(())
1636 }
1637}
1638impl RootDecoderType for SimpleStructDecoder {
1639 fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1640 let RootDecoderMessage::ArrayPage(array_page) = message else {
1641 unreachable!()
1642 };
1643 self.accept_child(array_page)
1644 }
1645 fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1646 self.drain(num_rows)
1647 }
1648 fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1649 runtime.block_on(self.wait_for_loaded(loaded_need))
1650 }
1651}
1652
1653struct BatchDecodeIterator<T: RootDecoderType> {
1655 messages: VecDeque<Result<DecoderMessage>>,
1656 root_decoder: T,
1657 rows_remaining: u64,
1658 rows_per_batch: u32,
1659 rows_scheduled: u64,
1660 rows_drained: u64,
1661 emitted_batch_size_warning: Arc<Once>,
1662 wait_for_io_runtime: tokio::runtime::Runtime,
1666 schema: Arc<ArrowSchema>,
1667}
1668
1669impl<T: RootDecoderType> BatchDecodeIterator<T> {
1670 pub fn new(
1672 messages: VecDeque<Result<DecoderMessage>>,
1673 rows_per_batch: u32,
1674 num_rows: u64,
1675 root_decoder: T,
1676 schema: Arc<ArrowSchema>,
1677 ) -> Self {
1678 Self {
1679 messages,
1680 root_decoder,
1681 rows_remaining: num_rows,
1682 rows_per_batch,
1683 rows_scheduled: 0,
1684 rows_drained: 0,
1685 wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1686 .build()
1687 .unwrap(),
1688 emitted_batch_size_warning: Arc::new(Once::new()),
1689 schema,
1690 }
1691 }
1692
1693 fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1698 match maybe_done(unloaded_page.0) {
1699 MaybeDone::Done(loaded_page) => loaded_page,
1701 MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1703 MaybeDone::Gone => unreachable!(),
1704 }
1705 }
1706
1707 #[instrument(level = "debug", skip_all)]
1712 fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1713 while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1714 let message = self.messages.pop_front().unwrap()?;
1715 self.rows_scheduled = message.scheduled_so_far;
1716 for decoder_message in message.decoders {
1717 match decoder_message {
1718 MessageType::UnloadedPage(unloaded_page) => {
1719 let loaded_page = self.wait_for_page(unloaded_page)?;
1720 self.root_decoder
1721 .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1722 }
1723 MessageType::DecoderReady(decoder_ready) => {
1724 if !decoder_ready.path.is_empty() {
1726 self.root_decoder
1727 .accept_message(RootDecoderMessage::ArrayPage(decoder_ready))?;
1728 }
1729 }
1730 }
1731 }
1732 }
1733
1734 let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1735
1736 self.root_decoder
1737 .wait(loaded_need, &self.wait_for_io_runtime)?;
1738 Ok(self.rows_scheduled)
1739 }
1740
1741 #[instrument(level = "debug", skip_all)]
1742 fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1743 trace!(
1744 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1745 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1746 );
1747 if self.rows_remaining == 0 {
1748 return Ok(None);
1749 }
1750
1751 let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1752 self.rows_remaining -= to_take;
1753
1754 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1755 trace!(
1756 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1757 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1758 );
1759 if scheduled_need > 0 {
1760 let desired_scheduled = scheduled_need + self.rows_scheduled;
1761 trace!(
1762 "Draining from scheduler (desire at least {} scheduled rows)",
1763 desired_scheduled
1764 );
1765 let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1766 if actually_scheduled < desired_scheduled {
1767 let under_scheduled = desired_scheduled - actually_scheduled;
1768 to_take -= under_scheduled;
1769 }
1770 }
1771
1772 if to_take == 0 {
1773 return Ok(None);
1774 }
1775
1776 let next_task = self.root_decoder.drain_batch(to_take)?;
1777
1778 self.rows_drained += to_take;
1779
1780 let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1781
1782 Ok(Some(batch))
1783 }
1784}
1785
1786impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1787 type Item = ArrowResult<RecordBatch>;
1788
1789 fn next(&mut self) -> Option<Self::Item> {
1790 self.next_batch_task()
1791 .transpose()
1792 .map(|r| r.map_err(ArrowError::from))
1793 }
1794}
1795
1796impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1797 fn schema(&self) -> Arc<ArrowSchema> {
1798 self.schema.clone()
1799 }
1800}
1801
1802fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1813 if let Some(w) = data_type.byte_width_opt() {
1814 return w as f64;
1815 }
1816 match data_type {
1817 DataType::Boolean => 1.0 / 8.0,
1818 DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1819 DataType::Struct(fields) => fields
1820 .iter()
1821 .map(|f| estimate_bytes_per_row(f.data_type()))
1822 .sum(),
1823 DataType::List(child) | DataType::LargeList(child) => {
1824 5.0 * estimate_bytes_per_row(child.data_type())
1825 }
1826 DataType::FixedSizeList(child, dim) => {
1827 *dim as f64 * estimate_bytes_per_row(child.data_type())
1828 }
1829 DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
1830 DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
1831 _ => 64.0,
1832 }
1833}
1834
1835pub struct StructuralBatchDecodeStream {
1837 context: DecoderContext,
1838 root_decoder: StructuralStructDecoder,
1839 rows_remaining: u64,
1840 rows_per_batch: u32,
1841 rows_scheduled: u64,
1842 rows_drained: u64,
1843 scheduler_exhausted: bool,
1844 emitted_batch_size_warning: Arc<Once>,
1845 spawn_batch_decode_tasks: bool,
1853 batch_size_bytes: Option<u64>,
1855 schema_bytes_per_row: f64,
1858 bytes_per_row_feedback: Arc<AtomicU64>,
1861}
1862
1863impl StructuralBatchDecodeStream {
1864 pub fn new(
1874 scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1875 rows_per_batch: u32,
1876 num_rows: u64,
1877 root_decoder: StructuralStructDecoder,
1878 spawn_batch_decode_tasks: bool,
1879 batch_size_bytes: Option<u64>,
1880 ) -> Self {
1881 let schema_bytes_per_row = if batch_size_bytes.is_some() {
1882 estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
1883 } else {
1884 0.0
1885 };
1886 Self {
1887 context: DecoderContext::new(scheduled),
1888 root_decoder,
1889 rows_remaining: num_rows,
1890 rows_per_batch,
1891 rows_scheduled: 0,
1892 rows_drained: 0,
1893 scheduler_exhausted: false,
1894 emitted_batch_size_warning: Arc::new(Once::new()),
1895 spawn_batch_decode_tasks,
1896 batch_size_bytes,
1897 schema_bytes_per_row,
1898 bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
1899 }
1900 }
1901
1902 #[instrument(level = "debug", skip_all)]
1903 async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1904 if self.scheduler_exhausted {
1905 return Ok(self.rows_scheduled);
1906 }
1907 while self.rows_scheduled < scheduled_need {
1908 let next_message = self.context.source.recv().await;
1909 match next_message {
1910 Some(scan_line) => {
1911 let scan_line = scan_line?;
1912 self.rows_scheduled = scan_line.scheduled_so_far;
1913 for message in scan_line.decoders {
1914 let unloaded_page = message.into_structural();
1915 let loaded_page = unloaded_page.0.await?;
1916 self.root_decoder.accept_page(loaded_page)?;
1917 }
1918 }
1919 None => {
1920 self.scheduler_exhausted = true;
1924 return Ok(self.rows_scheduled);
1925 }
1926 }
1927 }
1928 Ok(scheduled_need)
1929 }
1930
1931 #[instrument(level = "debug", skip_all)]
1932 async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1933 trace!(
1934 "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1935 self.rows_remaining, self.rows_drained, self.rows_scheduled,
1936 );
1937 if self.rows_remaining == 0 {
1938 return Ok(None);
1939 }
1940
1941 let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
1942 let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
1943 let bpr = if feedback > 0 {
1944 feedback as f64
1945 } else {
1946 self.schema_bytes_per_row
1947 };
1948 let rows = (batch_size_bytes as f64 / bpr) as u64;
1949 self.rows_remaining.min(rows.max(1))
1950 } else {
1951 self.rows_remaining.min(self.rows_per_batch as u64)
1952 };
1953 self.rows_remaining -= to_take;
1954
1955 let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1956 trace!(
1957 "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1958 scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1959 );
1960 if scheduled_need > 0 {
1961 let desired_scheduled = scheduled_need + self.rows_scheduled;
1962 trace!(
1963 "Draining from scheduler (desire at least {} scheduled rows)",
1964 desired_scheduled
1965 );
1966 let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1967 if actually_scheduled < desired_scheduled {
1968 let under_scheduled = desired_scheduled - actually_scheduled;
1969 to_take -= under_scheduled;
1970 }
1971 }
1972
1973 if to_take == 0 {
1974 return Ok(None);
1975 }
1976
1977 let next_task = self.root_decoder.drain_batch_task(to_take)?;
1978 self.rows_drained += to_take;
1979 Ok(Some(next_task))
1980 }
1981
1982 pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1983 let stream = futures::stream::unfold(self, |mut slf| async move {
1984 let next_task = match slf.next_batch_task().await {
1985 Ok(Some(next_task)) => next_task,
1986 Ok(None) => return None,
1987 Err(err) => {
1988 slf.rows_remaining = 0;
1989 return Some((
1990 ReadBatchTask {
1991 task: async move { Err(err) }.boxed(),
1992 num_rows: 0,
1993 },
1994 slf,
1995 ));
1996 }
1997 };
1998 let num_rows = next_task.num_rows;
1999 let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
2000 let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
2001 let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
2004 let task = async move {
2005 let (batch, data_size) = if spawn_batch_decode_tasks {
2006 tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
2007 .await
2008 .map_err(|err| Error::wrapped(err.into()))??
2009 } else {
2010 next_task.into_batch(emitted_batch_size_warning)?
2011 };
2012 let num_rows = batch.num_rows() as u64;
2013 if let Some(bpr) = data_size.checked_div(num_rows) {
2014 let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
2015 let next = if prev == 0 || bpr >= prev {
2016 bpr
2019 } else {
2020 (prev + bpr) / 2
2024 };
2025 bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
2026 }
2027 Ok(batch)
2028 };
2029 debug_assert!(num_rows <= u32::MAX as u64);
2031 Some((
2032 ReadBatchTask {
2033 task: task.boxed(),
2034 num_rows: num_rows as u32,
2035 },
2036 slf,
2037 ))
2038 });
2039 stream.boxed()
2040 }
2041}
2042
2043#[derive(Debug)]
2044pub enum RequestedRows {
2045 Ranges(Vec<Range<u64>>),
2046 Indices(Vec<u64>),
2047}
2048
2049impl RequestedRows {
2050 pub fn num_rows(&self) -> u64 {
2051 match self {
2052 Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
2053 Self::Indices(indices) => indices.len() as u64,
2054 }
2055 }
2056
2057 pub fn trim_empty_ranges(mut self) -> Self {
2058 if let Self::Ranges(ranges) = &mut self {
2059 ranges.retain(|r| !r.is_empty());
2060 }
2061 self
2062 }
2063}
2064
2065#[derive(Debug, Clone)]
2067pub struct DecoderConfig {
2068 pub cache_repetition_index: bool,
2075 pub validate_on_decode: bool,
2077 pub inline_scheduling: Option<bool>,
2095}
2096
2097impl Default for DecoderConfig {
2098 fn default() -> Self {
2099 Self {
2100 cache_repetition_index: default_cache_repetition_index(),
2101 validate_on_decode: false,
2102 inline_scheduling: None,
2103 }
2104 }
2105}
2106
2107#[derive(Debug, Clone)]
2108pub struct SchedulerDecoderConfig {
2109 pub decoder_plugins: Arc<DecoderPlugins>,
2110 pub batch_size: u32,
2111 pub io: Arc<dyn EncodingsIo>,
2112 pub cache: Arc<LanceCache>,
2113 pub decoder_config: DecoderConfig,
2115 pub batch_size_bytes: Option<u64>,
2120}
2121
2122fn check_scheduler_on_drop(
2123 stream: BoxStream<'static, ReadBatchTask>,
2124 scheduler_handle: tokio::task::JoinHandle<()>,
2125) -> BoxStream<'static, ReadBatchTask> {
2126 let abort_handle = scheduler_handle.abort_handle();
2130 let mut scheduler_handle = Some(scheduler_handle);
2131 let check_scheduler = stream::unfold((), move |_| {
2132 let handle = scheduler_handle.take();
2133 async move {
2134 if let Some(handle) = handle {
2135 handle.await.unwrap();
2136 }
2137 None
2138 }
2139 });
2140 stream
2141 .chain(check_scheduler)
2142 .on_drop(move || {
2143 abort_handle.abort();
2151 })
2152 .boxed()
2153}
2154
2155#[allow(clippy::too_many_arguments)]
2156pub fn create_decode_stream(
2157 schema: &Schema,
2158 num_rows: u64,
2159 batch_size: u32,
2160 is_structural: bool,
2161 should_validate: bool,
2162 spawn_structural_batch_decode_tasks: bool,
2163 rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2164 batch_size_bytes: Option<u64>,
2165) -> Result<BoxStream<'static, ReadBatchTask>> {
2166 if is_structural {
2167 let arrow_schema = ArrowSchema::from(schema);
2168 let structural_decoder = StructuralStructDecoder::new(
2169 arrow_schema.fields,
2170 should_validate,
2171 true,
2172 )?;
2173 Ok(StructuralBatchDecodeStream::new(
2174 rx,
2175 batch_size,
2176 num_rows,
2177 structural_decoder,
2178 spawn_structural_batch_decode_tasks,
2179 batch_size_bytes,
2180 )
2181 .into_stream())
2182 } else {
2183 if batch_size_bytes.is_some() {
2184 warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2185 }
2186 let arrow_schema = ArrowSchema::from(schema);
2187 let root_fields = arrow_schema.fields;
2188
2189 let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2190 Ok(BatchDecodeStream::new(rx, batch_size, num_rows, simple_struct_decoder).into_stream())
2191 }
2192}
2193
2194pub fn create_decode_iterator(
2198 schema: &Schema,
2199 num_rows: u64,
2200 batch_size: u32,
2201 should_validate: bool,
2202 is_structural: bool,
2203 messages: VecDeque<Result<DecoderMessage>>,
2204) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2205 let arrow_schema = Arc::new(ArrowSchema::from(schema));
2206 let root_fields = arrow_schema.fields.clone();
2207 if is_structural {
2208 let simple_struct_decoder =
2209 StructuralStructDecoder::new(root_fields, should_validate, true)?;
2210 Ok(Box::new(BatchDecodeIterator::new(
2211 messages,
2212 batch_size,
2213 num_rows,
2214 simple_struct_decoder,
2215 arrow_schema,
2216 )))
2217 } else {
2218 let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2219 Ok(Box::new(BatchDecodeIterator::new(
2220 messages,
2221 batch_size,
2222 num_rows,
2223 root_decoder,
2224 arrow_schema,
2225 )))
2226 }
2227}
2228
2229async fn create_scheduler_decoder(
2230 column_infos: Vec<Arc<ColumnInfo>>,
2231 requested_rows: RequestedRows,
2232 filter: FilterExpression,
2233 column_indices: Vec<u32>,
2234 target_schema: Arc<Schema>,
2235 config: SchedulerDecoderConfig,
2236) -> Result<BoxStream<'static, ReadBatchTask>> {
2237 let num_rows = requested_rows.num_rows();
2238
2239 let is_structural = column_infos[0].is_structural();
2240 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2241 let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2242 Some("always") => true,
2243 Some("never") => false,
2244 _ => matches!(requested_rows, RequestedRows::Ranges(_)),
2245 };
2246
2247 let (tx, rx) = mpsc::unbounded_channel();
2248
2249 let decode_stream = create_decode_stream(
2250 &target_schema,
2251 num_rows,
2252 config.batch_size,
2253 is_structural,
2254 config.decoder_config.validate_on_decode,
2255 spawn_structural_batch_decode_tasks,
2256 rx,
2257 config.batch_size_bytes,
2258 )?;
2259
2260 let mut decode_scheduler = DecodeBatchScheduler::try_new(
2265 target_schema.as_ref(),
2266 &column_indices,
2267 &column_infos,
2268 &vec![],
2269 num_rows,
2270 config.decoder_plugins,
2271 config.io.clone(),
2272 config.cache,
2273 &filter,
2274 &config.decoder_config,
2275 )
2276 .await?;
2277
2278 let inline_scheduling = config
2284 .decoder_config
2285 .inline_scheduling
2286 .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2287
2288 if inline_scheduling {
2289 match requested_rows {
2290 RequestedRows::Ranges(ranges) => {
2291 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2292 }
2293 RequestedRows::Indices(indices) => {
2294 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2295 }
2296 }
2297 Ok(decode_stream)
2298 } else {
2299 let scheduling = async move {
2303 match requested_rows {
2304 RequestedRows::Ranges(ranges) => {
2305 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2306 }
2307 RequestedRows::Indices(indices) => {
2308 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2309 }
2310 }
2311 };
2312 let scheduler_handle = tokio::task::spawn(scheduling);
2313 Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2314 }
2315}
2316
2317pub async fn schedule_and_decode(
2332 column_infos: Vec<Arc<ColumnInfo>>,
2333 requested_rows: RequestedRows,
2334 filter: FilterExpression,
2335 column_indices: Vec<u32>,
2336 target_schema: Arc<Schema>,
2337 config: SchedulerDecoderConfig,
2338) -> Result<BoxStream<'static, ReadBatchTask>> {
2339 if requested_rows.num_rows() == 0 {
2340 return Ok(stream::empty().boxed());
2341 }
2342
2343 let requested_rows = requested_rows.trim_empty_ranges();
2346
2347 let io = config.io.clone();
2348
2349 let stream = create_scheduler_decoder(
2350 column_infos,
2351 requested_rows,
2352 filter,
2353 column_indices,
2354 target_schema,
2355 config,
2356 )
2357 .await?;
2358
2359 Ok(stream.finally(move || drop(io)).boxed())
2362}
2363
2364pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2365 tokio::runtime::Builder::new_current_thread()
2366 .build()
2367 .unwrap()
2368});
2369
2370pub fn schedule_and_decode_blocking(
2385 column_infos: Vec<Arc<ColumnInfo>>,
2386 requested_rows: RequestedRows,
2387 filter: FilterExpression,
2388 column_indices: Vec<u32>,
2389 target_schema: Arc<Schema>,
2390 config: SchedulerDecoderConfig,
2391) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2392 if requested_rows.num_rows() == 0 {
2393 let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2394 return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2395 }
2396
2397 let num_rows = requested_rows.num_rows();
2398 let is_structural = column_infos[0].is_structural();
2399
2400 let (tx, mut rx) = mpsc::unbounded_channel();
2401
2402 let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new(
2405 target_schema.as_ref(),
2406 &column_indices,
2407 &column_infos,
2408 &vec![],
2409 num_rows,
2410 config.decoder_plugins,
2411 config.io.clone(),
2412 config.cache,
2413 &filter,
2414 &config.decoder_config,
2415 ))?;
2416
2417 match requested_rows {
2419 RequestedRows::Ranges(ranges) => {
2420 decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2421 }
2422 RequestedRows::Indices(indices) => {
2423 decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2424 }
2425 }
2426
2427 let mut messages = Vec::new();
2429 while rx
2430 .recv_many(&mut messages, usize::MAX)
2431 .now_or_never()
2432 .unwrap()
2433 != 0
2434 {}
2435
2436 let decode_iterator = create_decode_iterator(
2438 &target_schema,
2439 num_rows,
2440 config.batch_size,
2441 config.decoder_config.validate_on_decode,
2442 is_structural,
2443 messages.into(),
2444 )?;
2445
2446 Ok(decode_iterator)
2447}
2448
2449pub trait PrimitivePageDecoder: Send + Sync {
2461 fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2493}
2494
2495pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2504 fn schedule_ranges(
2516 &self,
2517 ranges: &[Range<u64>],
2518 scheduler: &Arc<dyn EncodingsIo>,
2519 top_level_row: u64,
2520 ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2521}
2522
2523pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2525 fn advance(&mut self, num_rows: u64);
2526 fn current_priority(&self) -> u64;
2527 fn box_clone(&self) -> Box<dyn PriorityRange>;
2528}
2529
2530#[derive(Debug)]
2533pub struct SimplePriorityRange {
2534 priority: u64,
2535}
2536
2537impl SimplePriorityRange {
2538 fn new(priority: u64) -> Self {
2539 Self { priority }
2540 }
2541}
2542
2543impl PriorityRange for SimplePriorityRange {
2544 fn advance(&mut self, num_rows: u64) {
2545 self.priority += num_rows;
2546 }
2547
2548 fn current_priority(&self) -> u64 {
2549 self.priority
2550 }
2551
2552 fn box_clone(&self) -> Box<dyn PriorityRange> {
2553 Box::new(Self {
2554 priority: self.priority,
2555 })
2556 }
2557}
2558
2559pub struct ListPriorityRange {
2572 base: Box<dyn PriorityRange>,
2573 offsets: Arc<[u64]>,
2574 cur_index_into_offsets: usize,
2575 cur_position: u64,
2576}
2577
2578impl ListPriorityRange {
2579 pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2580 Self {
2581 base,
2582 offsets,
2583 cur_index_into_offsets: 0,
2584 cur_position: 0,
2585 }
2586 }
2587}
2588
2589impl std::fmt::Debug for ListPriorityRange {
2590 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2591 f.debug_struct("ListPriorityRange")
2592 .field("base", &self.base)
2593 .field("offsets.len()", &self.offsets.len())
2594 .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2595 .field("cur_position", &self.cur_position)
2596 .finish()
2597 }
2598}
2599
2600impl PriorityRange for ListPriorityRange {
2601 fn advance(&mut self, num_rows: u64) {
2602 self.cur_position += num_rows;
2605 let mut idx_into_offsets = self.cur_index_into_offsets;
2606 while idx_into_offsets + 1 < self.offsets.len()
2607 && self.offsets[idx_into_offsets + 1] <= self.cur_position
2608 {
2609 idx_into_offsets += 1;
2610 }
2611 let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2612 self.cur_index_into_offsets = idx_into_offsets;
2613 self.base.advance(base_rows_advanced as u64);
2614 }
2615
2616 fn current_priority(&self) -> u64 {
2617 self.base.current_priority()
2618 }
2619
2620 fn box_clone(&self) -> Box<dyn PriorityRange> {
2621 Box::new(Self {
2622 base: self.base.box_clone(),
2623 offsets: self.offsets.clone(),
2624 cur_index_into_offsets: self.cur_index_into_offsets,
2625 cur_position: self.cur_position,
2626 })
2627 }
2628}
2629
2630pub struct SchedulerContext {
2632 recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
2633 io: Arc<dyn EncodingsIo>,
2634 cache: Arc<LanceCache>,
2635 name: String,
2636 path: Vec<u32>,
2637 path_names: Vec<String>,
2638}
2639
2640pub struct ScopedSchedulerContext<'a> {
2641 pub context: &'a mut SchedulerContext,
2642}
2643
2644impl<'a> ScopedSchedulerContext<'a> {
2645 pub fn pop(self) -> &'a mut SchedulerContext {
2646 self.context.pop();
2647 self.context
2648 }
2649}
2650
2651impl SchedulerContext {
2652 pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
2653 Self {
2654 io,
2655 cache,
2656 recv: None,
2657 name: "".to_string(),
2658 path: Vec::new(),
2659 path_names: Vec::new(),
2660 }
2661 }
2662
2663 pub fn io(&self) -> &Arc<dyn EncodingsIo> {
2664 &self.io
2665 }
2666
2667 pub fn cache(&self) -> &Arc<LanceCache> {
2668 &self.cache
2669 }
2670
2671 pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
2672 self.path.push(index);
2673 self.path_names.push(name.to_string());
2674 ScopedSchedulerContext { context: self }
2675 }
2676
2677 pub fn pop(&mut self) {
2678 self.path.pop();
2679 self.path_names.pop();
2680 }
2681
2682 pub fn path_name(&self) -> String {
2683 let path = self.path_names.join("/");
2684 if self.recv.is_some() {
2685 format!("TEMP({}){}", self.name, path)
2686 } else {
2687 format!("ROOT{}", path)
2688 }
2689 }
2690
2691 pub fn current_path(&self) -> VecDeque<u32> {
2692 VecDeque::from_iter(self.path.iter().copied())
2693 }
2694
2695 #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
2696 pub fn locate_decoder(&mut self, decoder: Box<dyn LogicalPageDecoder>) -> DecoderReady {
2697 trace!(
2698 "Scheduling decoder of type {:?} for {:?}",
2699 decoder.data_type(),
2700 self.path,
2701 );
2702 DecoderReady {
2703 decoder,
2704 path: self.current_path(),
2705 }
2706 }
2707}
2708
2709pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
2710
2711impl std::fmt::Debug for UnloadedPageShard {
2712 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2713 f.debug_struct("UnloadedPage").finish()
2714 }
2715}
2716
2717#[derive(Debug)]
2718pub struct ScheduledScanLine {
2719 pub rows_scheduled: u64,
2720 pub decoders: Vec<MessageType>,
2721}
2722
2723pub trait StructuralSchedulingJob: std::fmt::Debug {
2724 fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
2731}
2732
2733pub struct FilterExpression(pub Bytes);
2741
2742impl FilterExpression {
2743 pub fn no_filter() -> Self {
2748 Self(Bytes::new())
2749 }
2750
2751 pub fn is_noop(&self) -> bool {
2753 self.0.is_empty()
2754 }
2755}
2756
2757pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
2758 fn initialize<'a>(
2759 &'a mut self,
2760 filter: &'a FilterExpression,
2761 context: &'a SchedulerContext,
2762 ) -> BoxFuture<'a, Result<()>>;
2763 fn schedule_ranges<'a>(
2764 &'a self,
2765 ranges: &[Range<u64>],
2766 filter: &FilterExpression,
2767 ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
2768}
2769
2770pub trait DecodeArrayTask: Send {
2772 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
2774}
2775
2776impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
2777 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
2778 let decoded_array = StructuralDecodeArrayTask::decode(*self)?;
2779 decoded_array.repdef.ensure_exhausted()?;
2780 Ok((decoded_array.array, decoded_array.data_size))
2781 }
2782}
2783
2784pub struct NextDecodeTask {
2789 pub task: Box<dyn DecodeArrayTask>,
2791 pub num_rows: u64,
2793}
2794
2795impl NextDecodeTask {
2796 #[instrument(name = "task_to_batch", level = "debug", skip_all)]
2801 fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
2802 let (struct_arr, data_size) = self.task.decode()?;
2803 let batch = RecordBatch::from(struct_arr.as_struct());
2804 if data_size > BATCH_SIZE_BYTES_WARNING {
2805 emitted_batch_size_warning.call_once(|| {
2806 let size_mb = data_size / 1024 / 1024;
2807 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);
2808 });
2809 }
2810 Ok((batch, data_size))
2811 }
2812}
2813
2814#[derive(Debug)]
2818pub enum MessageType {
2819 DecoderReady(DecoderReady),
2824 UnloadedPage(UnloadedPageShard),
2828}
2829
2830impl MessageType {
2831 pub fn into_array(self) -> DecoderReady {
2832 match self {
2833 Self::DecoderReady(decoder) => decoder,
2834 Self::UnloadedPage(_) => {
2835 panic!("Expected DecoderReady but got UnloadedPage")
2836 }
2837 }
2838 }
2839
2840 pub fn into_structural(self) -> UnloadedPageShard {
2841 match self {
2842 Self::UnloadedPage(unloaded) => unloaded,
2843 Self::DecoderReady(_) => {
2844 panic!("Expected UnloadedPage but got DecoderReady")
2845 }
2846 }
2847 }
2848}
2849
2850pub struct DecoderMessage {
2851 pub scheduled_so_far: u64,
2852 pub decoders: Vec<MessageType>,
2853}
2854
2855pub struct DecoderContext {
2856 source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2857}
2858
2859impl DecoderContext {
2860 pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
2861 Self { source }
2862 }
2863}
2864
2865pub struct DecodedPage {
2866 pub data: DataBlock,
2867 pub repdef: RepDefUnraveler,
2868}
2869
2870pub trait DecodePageTask: Send + std::fmt::Debug {
2871 fn decode(self: Box<Self>) -> Result<DecodedPage>;
2873}
2874
2875pub trait StructuralPageDecoder: std::fmt::Debug + Send {
2876 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
2877 fn num_rows(&self) -> u64;
2878}
2879
2880#[derive(Debug)]
2881pub struct LoadedPageShard {
2882 pub decoder: Box<dyn StructuralPageDecoder>,
2884 pub path: VecDeque<u32>,
2903}
2904
2905pub struct DecodedArray {
2906 pub array: ArrayRef,
2907 pub repdef: CompositeRepDefUnraveler,
2908 pub data_size: u64,
2910}
2911
2912pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
2913 fn decode(self: Box<Self>) -> Result<DecodedArray>;
2914}
2915
2916pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
2917 fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
2922 fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
2924 fn data_type(&self) -> &DataType;
2926}
2927
2928#[derive(Debug, Default)]
2929pub struct DecoderPlugins {}
2930
2931#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2933pub enum EncodedBatchLayout {
2934 Array,
2936 Structural,
2938}
2939
2940pub async fn decode_batch(
2942 batch: &EncodedBatch,
2943 filter: &FilterExpression,
2944 decoder_plugins: Arc<DecoderPlugins>,
2945 should_validate: bool,
2946 layout: EncodedBatchLayout,
2947 cache: Option<Arc<LanceCache>>,
2948) -> Result<RecordBatch> {
2949 let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
2954 let cache = if let Some(cache) = cache {
2955 cache
2956 } else {
2957 Arc::new(lance_core::cache::LanceCache::with_capacity(
2958 128 * 1024 * 1024,
2959 ))
2960 };
2961 let mut decode_scheduler = DecodeBatchScheduler::try_new(
2962 batch.schema.as_ref(),
2963 &batch.top_level_columns,
2964 &batch.page_table,
2965 &vec![],
2966 batch.num_rows,
2967 decoder_plugins,
2968 io_scheduler.clone(),
2969 cache,
2970 filter,
2971 &DecoderConfig::default(),
2972 )
2973 .await?;
2974 let (tx, rx) = unbounded_channel();
2975 decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
2976 let is_structural = layout == EncodedBatchLayout::Structural;
2977 let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2978 let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
2979 let mut decode_stream = create_decode_stream(
2980 &batch.schema,
2981 batch.num_rows,
2982 batch.num_rows as u32,
2983 is_structural,
2984 should_validate,
2985 spawn_structural_batch_decode_tasks,
2986 rx,
2987 None,
2988 )?;
2989 decode_stream.next().await.unwrap().task.await
2990}
2991
2992#[cfg(test)]
2993mod tests {
2995 use super::*;
2996 use std::collections::VecDeque;
2997
2998 #[derive(Debug)]
2999 struct FailingPageDecoder {
3000 page_data_type: DataType,
3001 total_rows: u64,
3002 load_error_message: &'static str,
3003 }
3004
3005 impl FailingPageDecoder {
3006 fn new(
3007 page_data_type: DataType,
3008 total_rows: u64,
3009 load_error_message: &'static str,
3010 ) -> Self {
3011 Self {
3012 page_data_type,
3013 total_rows,
3014 load_error_message,
3015 }
3016 }
3017 }
3018
3019 impl LogicalPageDecoder for FailingPageDecoder {
3020 fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
3021 let load_error_message = self.load_error_message;
3022 async move { Err(Error::io(load_error_message)) }.boxed()
3023 }
3024
3025 fn rows_loaded(&self) -> u64 {
3026 0
3027 }
3028
3029 fn num_rows(&self) -> u64 {
3030 self.total_rows
3031 }
3032
3033 fn rows_drained(&self) -> u64 {
3034 0
3035 }
3036
3037 fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
3038 Err(Error::internal(format!(
3039 "failing page decoder should not be drained after load error \
3040 (requested_rows={})",
3041 requested_rows
3042 )))
3043 }
3044
3045 fn data_type(&self) -> &DataType {
3046 &self.page_data_type
3047 }
3048 }
3049
3050 struct InvalidInputDecodeTask;
3051
3052 impl DecodeArrayTask for InvalidInputDecodeTask {
3053 fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3054 Err(Error::invalid_input_source("malformed sparse page".into()))
3055 }
3056 }
3057
3058 #[test]
3059 fn next_decode_task_preserves_invalid_input_errors() {
3060 let err = NextDecodeTask {
3061 task: Box::new(InvalidInputDecodeTask),
3062 num_rows: 0,
3063 }
3064 .into_batch(Arc::new(Once::new()))
3065 .unwrap_err();
3066 assert!(matches!(err, Error::InvalidInput { .. }));
3067 }
3068
3069 #[test]
3070 fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
3071 use arrow_schema::Field as ArrowField;
3078
3079 let zero_dim = DataType::FixedSizeList(
3080 Arc::new(ArrowField::new("item", DataType::Float32, true)),
3081 0,
3082 );
3083 let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3084 let strategy = CoreFieldDecoderStrategy::default();
3085
3086 let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3087 let err = strategy
3088 .create_structural_field_scheduler(&field, &mut structural_columns)
3089 .unwrap_err();
3090 assert!(
3091 err.to_string()
3092 .contains("dimension must be a positive integer"),
3093 "unexpected error: {}",
3094 err
3095 );
3096
3097 let mut array_columns = ColumnInfoIter::new(vec![], &[]);
3098 let err = strategy
3099 .create_array_field_scheduler(
3100 &field,
3101 &mut array_columns,
3102 FileBuffers {
3103 positions_and_sizes: &[],
3104 },
3105 )
3106 .unwrap_err();
3107 assert!(
3108 err.to_string()
3109 .contains("dimension must be a positive integer"),
3110 "unexpected error: {}",
3111 err
3112 );
3113 }
3114
3115 #[tokio::test]
3116 async fn test_array_stream_stops_on_load_error() {
3117 use arrow_schema::Field as ArrowField;
3118
3119 let rows_per_batch = 1;
3120 let total_rows = 2;
3121 let scheduled_rows = 1;
3122 let page_rows = 1;
3123 let batch_readahead = 2;
3124 let load_error_message = "simulated page load failure";
3125 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3126 let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3127 let (tx, rx) = unbounded_channel();
3128
3129 tx.send(Ok(DecoderMessage {
3130 scheduled_so_far: scheduled_rows,
3131 decoders: vec![MessageType::DecoderReady(DecoderReady {
3132 decoder: Box::new(FailingPageDecoder::new(
3133 DataType::Float32,
3134 page_rows,
3135 load_error_message,
3136 )),
3137 path: VecDeque::from([0]),
3138 })],
3139 }))
3140 .unwrap();
3141 drop(tx);
3142
3143 let stream =
3144 BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3145 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3146
3147 let err = batches
3148 .next()
3149 .await
3150 .expect("stream should emit the array page-load error")
3151 .unwrap_err();
3152 assert!(
3153 err.to_string().contains(load_error_message),
3154 "unexpected error: {}",
3155 err
3156 );
3157 assert!(
3158 batches.next().await.is_none(),
3159 "stream should stop after the array page-load error"
3160 );
3161 }
3162
3163 #[tokio::test]
3164 async fn test_structural_stream_stops_on_load_error() {
3165 let rows_per_batch = 1;
3166 let total_rows = 2;
3167 let scheduled_rows = 1;
3168 let batch_readahead = 2;
3169 let load_error_message = "simulated page load failure";
3170 let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3171 let root_decoder = StructuralStructDecoder::new(fields, false, true).unwrap();
3172 let (tx, rx) = unbounded_channel();
3173 let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3174
3175 tx.send(Ok(DecoderMessage {
3176 scheduled_so_far: scheduled_rows,
3177 decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3178 }))
3179 .unwrap();
3180 drop(tx);
3181
3182 let stream = StructuralBatchDecodeStream::new(
3183 rx,
3184 rows_per_batch,
3185 total_rows,
3186 root_decoder,
3187 true,
3188 None,
3189 )
3190 .into_stream();
3191 let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3192
3193 let err = batches
3194 .next()
3195 .await
3196 .expect("stream should emit the page-load error")
3197 .unwrap_err();
3198 assert!(
3199 err.to_string().contains(load_error_message),
3200 "unexpected error: {}",
3201 err
3202 );
3203 assert!(
3204 batches.next().await.is_none(),
3205 "stream should stop after the page-load error"
3206 );
3207 }
3208
3209 #[test]
3210 fn test_coalesce_indices_to_ranges_with_single_index() {
3211 let indices = vec![1];
3212 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3213 assert_eq!(ranges, vec![1..2]);
3214 }
3215
3216 #[test]
3217 fn test_coalesce_indices_to_ranges() {
3218 let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3219 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3220 assert_eq!(ranges, vec![1..10]);
3221 }
3222
3223 #[test]
3224 fn test_coalesce_indices_to_ranges_with_gaps() {
3225 let indices = vec![1, 2, 3, 5, 6, 7, 9];
3226 let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3227 assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3228 }
3229
3230 #[test]
3231 fn test_estimate_bytes_per_row() {
3232 assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3233 assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3234 assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3235 assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3236 assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3237 assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3238 let struct_type = DataType::Struct(Fields::from(vec![
3240 ArrowField::new("a", DataType::Int32, false),
3241 ArrowField::new("b", DataType::Int32, false),
3242 ArrowField::new("c", DataType::Int32, false),
3243 ArrowField::new("d", DataType::Int32, false),
3244 ]));
3245 assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3246 }
3247
3248 async fn decode_batches_with_byte_limit(
3251 batch: &RecordBatch,
3252 batch_size: u32,
3253 batch_size_bytes: Option<u64>,
3254 ) -> Vec<RecordBatch> {
3255 use crate::{
3256 encoder::{EncodingOptions, encode_batch},
3257 testing::{TestEncoding, test_encoding_strategy},
3258 };
3259
3260 let version = TestEncoding::StructuralU16;
3261 let options = EncodingOptions::default();
3262 let strategy = test_encoding_strategy(version);
3263 let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3264 let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3265 .await
3266 .unwrap();
3267
3268 let io_scheduler =
3269 Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
3270 let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
3271 128 * 1024 * 1024,
3272 ));
3273 let decoder_plugins = Arc::new(DecoderPlugins::default());
3274
3275 let mut decode_scheduler = DecodeBatchScheduler::try_new(
3276 encoded.schema.as_ref(),
3277 &encoded.top_level_columns,
3278 &encoded.page_table,
3279 &vec![],
3280 encoded.num_rows,
3281 decoder_plugins,
3282 io_scheduler.clone(),
3283 cache,
3284 &FilterExpression::no_filter(),
3285 &DecoderConfig::default(),
3286 )
3287 .await
3288 .unwrap();
3289
3290 let (tx, rx) = unbounded_channel();
3291 decode_scheduler.schedule_range(
3292 0..encoded.num_rows,
3293 &FilterExpression::no_filter(),
3294 tx,
3295 io_scheduler,
3296 );
3297
3298 let mut decode_stream = create_decode_stream(
3299 &encoded.schema,
3300 encoded.num_rows,
3301 batch_size,
3302 true,
3303 true,
3304 true,
3305 rx,
3306 batch_size_bytes,
3307 )
3308 .unwrap();
3309
3310 let mut batches = Vec::new();
3311 while let Some(task) = decode_stream.next().await {
3312 batches.push(task.task.await.unwrap());
3313 }
3314 batches
3315 }
3316
3317 #[tokio::test]
3318 async fn test_byte_sized_batches_fixed_width() {
3319 use arrow_array::Int32Array;
3320
3321 let num_rows: i32 = 1000;
3323 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
3324 .map(|col| {
3325 Arc::new(Int32Array::from_iter_values(
3326 (0..num_rows).map(move |row| row * 10 + col),
3327 )) as _
3328 })
3329 .collect();
3330
3331 let schema = Arc::new(ArrowSchema::new(vec![
3332 ArrowField::new("a", DataType::Int32, false),
3333 ArrowField::new("b", DataType::Int32, false),
3334 ArrowField::new("c", DataType::Int32, false),
3335 ArrowField::new("d", DataType::Int32, false),
3336 ]));
3337 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3338
3339 let batches =
3341 decode_batches_with_byte_limit(&input_batch, 1024, Some(1600)).await;
3342
3343 assert_eq!(batches.len(), 10);
3345 for (i, batch) in batches.iter().enumerate() {
3346 assert_eq!(
3347 batch.num_rows(),
3348 100,
3349 "batch {i} should have 100 rows, got {}",
3350 batch.num_rows()
3351 );
3352 }
3353
3354 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3356 let concatenated =
3357 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3358 .unwrap();
3359 assert_eq!(concatenated.num_rows(), num_rows as usize);
3360 for col in 0..4 {
3361 assert_eq!(
3362 concatenated.column(col).as_ref(),
3363 input_batch.column(col).as_ref(),
3364 "column {col} roundtrip mismatch"
3365 );
3366 }
3367 }
3368
3369 #[tokio::test]
3370 async fn test_byte_sized_batches_none_unchanged() {
3371 use arrow_array::Int32Array;
3372
3373 let num_rows: i32 = 1000;
3375 let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
3376 .map(|col| {
3377 Arc::new(Int32Array::from_iter_values(
3378 (0..num_rows).map(move |row| row * 10 + col),
3379 )) as _
3380 })
3381 .collect();
3382
3383 let schema = Arc::new(ArrowSchema::new(vec![
3384 ArrowField::new("x", DataType::Int32, false),
3385 ArrowField::new("y", DataType::Int32, false),
3386 ]));
3387 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3388
3389 let batches = decode_batches_with_byte_limit(&input_batch, 250, None).await;
3391 assert_eq!(batches.len(), 4);
3392 for (i, batch) in batches.iter().enumerate() {
3393 assert_eq!(
3394 batch.num_rows(),
3395 250,
3396 "batch {i} should have 250 rows, got {}",
3397 batch.num_rows()
3398 );
3399 }
3400 }
3401
3402 #[tokio::test]
3403 async fn test_byte_sized_batches_feedback_convergence() {
3404 use arrow_array::StringArray;
3405
3406 let num_rows = 500;
3410 let value: String = "x".repeat(100);
3411 let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
3412 (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
3413 ))];
3414 let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
3415 "s",
3416 DataType::Utf8,
3417 false,
3418 )]));
3419 let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3420
3421 let target_bytes: u64 = 5000;
3425 let batches = decode_batches_with_byte_limit(
3426 &input_batch,
3427 1024,
3428 Some(target_bytes),
3429 )
3430 .await;
3431
3432 let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3434 let concatenated =
3435 arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3436 .unwrap();
3437 assert_eq!(concatenated.num_rows(), num_rows as usize);
3438 assert_eq!(
3439 concatenated.column(0).as_ref(),
3440 input_batch.column(0).as_ref()
3441 );
3442
3443 assert!(
3446 batches.len() >= 2,
3447 "need at least 2 batches to test convergence"
3448 );
3449 if batches.len() >= 3 {
3452 let second_batch_rows = batches[1].num_rows();
3453 let third_batch_rows = batches[2].num_rows();
3454 assert!(
3456 (40..=60).contains(&second_batch_rows),
3457 "second batch should be near 50 rows, got {second_batch_rows}"
3458 );
3459 assert!(
3460 (40..=60).contains(&third_batch_rows),
3461 "third batch should be near 50 rows, got {third_batch_rows}"
3462 );
3463 }
3464 }
3465}