Skip to main content

lance_encoding/
decoder.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Utilities and traits for scheduling & decoding data
5//!
6//! Reading data involves two steps: scheduling and decoding.  The
7//! scheduling step is responsible for figuring out what data is needed
8//! and issuing the appropriate I/O requests.  The decoding step is
9//! responsible for taking the loaded data and turning it into Arrow
10//! arrays.
11//!
12//! # Scheduling
13//!
14//! Scheduling is split into `FieldScheduler` and `PageScheduler`.
15//! There is one field scheduler for each output field, which may map to many
16//! columns of actual data.  A field scheduler is responsible for figuring out
17//! the order in which pages should be scheduled.  Field schedulers then delegate
18//! to page schedulers to figure out the I/O requests that need to be made for
19//! the page.
20//!
21//! Page schedulers also create the decoders that will be used to decode the
22//! scheduled data.
23//!
24//! # Decoding
25//!
26//! Decoders are split into `PhysicalPageDecoder` and
27//! [`LogicalPageDecoder`].  Note that both physical and logical decoding
28//! happens on a per-page basis.  There is no concept of a "field decoder" or
29//! "column decoder".
30//!
31//! The physical decoders handle lower level encodings.  They have a few advantages:
32//!
33//!  * They do not need to decode into an Arrow array and so they don't need
34//!    to be enveloped into the Arrow filesystem (e.g. Arrow doesn't have a
35//!    bit-packed type.  We can use variable-length binary but that is kind
36//!    of awkward)
37//!  * They can decode into an existing allocation.  This can allow for "page
38//!    bridging".  If we are trying to decode into a batch of 1024 rows and
39//!    the rows 0..1024 are spread across two pages then we can avoid a memory
40//!    copy by allocating once and decoding each page into the outer allocation.
41//!    (note: page bridging is not actually implemented yet)
42//!
43//! However, there are some limitations for physical decoders:
44//!
45//!  * They are constrained to a single column
46//!  * The API is more complex
47//!
48//! The logical decoders are designed to map one or more columns of Lance
49//! data into an Arrow array.
50//!
51//! Typically, a "logical encoding" will have both a logical decoder and a field scheduler.
52//! Meanwhile, a "physical encoding" will have a physical decoder but no corresponding field
53//! scheduler.
54//!
55//!
56//! # General notes
57//!
58//! Encodings are typically nested into each other to form a tree.  The top of the tree is
59//! the user requested schema.  Each field in that schema is assigned to one top-level logical
60//! encoding.  That encoding can then contain other logical encodings or physical encodings.
61//! Physical encodings can also contain other physical encodings.
62//!
63//! So, for example, a single field in the Arrow schema might have the type `List<UInt32>`
64//!
65//! The encoding tree could then be:
66//!
67//! root: List (logical encoding)
68//!  - indices: Primitive (logical encoding)
69//!    - column: Basic (physical encoding)
70//!      - validity: Bitmap (physical encoding)
71//!      - values: RLE (physical encoding)
72//!        - runs: Value (physical encoding)
73//!        - values: Value (physical encoding)
74//!  - items: Primitive (logical encoding)
75//!    - column: Basic (physical encoding)
76//!      - values: Value (physical encoding)
77//!
78//! Note that, in this example, root.items.column does not have a validity because there were
79//! no nulls in the page.
80//!
81//! ## Multiple buffers or multiple columns?
82//!
83//! Note that there are many different ways we can write encodings.  For example, we might
84//! store primitive fields in a single column with two buffers (one for validity and one for
85//! values)
86//!
87//! On the other hand, we could also store a primitive field as two different columns.  One
88//! that yields a non-nullable boolean array and one that yields a non-nullable array of items.
89//! Then we could combine these two arrays into a single array where the boolean array is the
90//! bitmap.  There are a few subtle differences between the approaches:
91//!
92//! * Storing things as multiple buffers within the same column is generally more efficient and
93//!   easier to schedule.  For example, in-batch coalescing is very easy but can only be done
94//!   on data that is in the same page.
95//! * When things are stored in multiple columns you have to worry about their pages not being
96//!   in sync.  In our previous validity / values example this means we might have to do some
97//!   memory copies to get the validity array and values arrays to be the same length as
98//!   decode.
99//! * When things are stored in a single column, projection is impossible.  For example, if we
100//!   tried to store all the struct fields in a single column with lots of buffers then we wouldn't
101//!   be able to read back individual fields of the struct.
102//!
103//! The fixed size list decoding is an interesting example because it is actually both a physical
104//! encoding and a logical encoding.  A fixed size list of a physical encoding is, itself, a physical
105//! encoding (e.g. a fixed size list of doubles).  However, a fixed size list of a logical encoding
106//! is a logical encoding (e.g. a fixed size list of structs).
107//!
108//! # The scheduling loop
109//!
110//! Reading a Lance file involves both scheduling and decoding.  Its generally expected that these
111//! will run as two separate threads.
112//!
113//! ```text
114//!
115//!                                    I/O PARALLELISM
116//!                       Issues
117//!                       Requests   ┌─────────────────┐
118//!                                  │                 │        Wait for
119//!                       ┌──────────►   I/O Service   ├─────►  Enough I/O ◄─┐
120//!                       │          │                 │        For batch    │
121//!                       │          └─────────────────┘             │3      │
122//!                       │                                          │       │
123//!                       │                                          │       │2
124//! ┌─────────────────────┴─┐                              ┌─────────▼───────┴┐
125//! │                       │                              │                  │Poll
126//! │       Batch Decode    │ Decode tasks sent via channel│   Batch Decode   │1
127//! │       Scheduler       ├─────────────────────────────►│   Stream         ◄─────
128//! │                       │                              │                  │
129//! └─────▲─────────────┬───┘                              └─────────┬────────┘
130//!       │             │                                            │4
131//!       │             │                                            │
132//!       └─────────────┘                                   ┌────────┴────────┐
133//!  Caller of schedule_range                Buffer polling │                 │
134//!  will be scheduler thread                to achieve CPU │ Decode Batch    ├────►
135//!  and schedule one decode                 parallelism    │ Task            │
136//!  task (and all needed I/O)               (thread per    │                 │
137//!  per logical page                         batch)        └─────────────────┘
138//! ```
139//!
140//! The scheduling thread will work through the file from the
141//! start to the end as quickly as possible.  Data is scheduled one page at a time in a row-major
142//! fashion.  For example, imagine we have a file with the following page structure:
143//!
144//! ```text
145//! Score (Float32)     | C0P0 |
146//! Id (16-byte UUID)   | C1P0 | C1P1 | C1P2 | C1P3 |
147//! Vector (4096 bytes) | C2P0 | C2P1 | C2P2 | C2P3 | .. | C2P1024 |
148//! ```
149//!
150//! This would be quite common as each of these pages has the same number of bytes.  Let's pretend
151//! each page is 1MiB and so there are 256Ki rows of data.  Each page of `Score` has 256Ki rows.
152//! Each page of `Id` has 64Ki rows.  Each page of `Vector` has 256 rows.  The scheduler would then
153//! schedule in the following order:
154//!
155//! C0 P0
156//! C1 P0
157//! C2 P0
158//! C2 P1
159//! ... (254 pages omitted)
160//! C2 P255
161//! C1 P1
162//! C2 P256
163//! ... (254 pages omitted)
164//! C2 P511
165//! C1 P2
166//! C2 P512
167//! ... (254 pages omitted)
168//! C2 P767
169//! C1 P3
170//! C2 P768
171//! ... (254 pages omitted)
172//! C2 P1024
173//!
174//! This is the ideal scheduling order because it means we can decode complete rows as quickly as possible.
175//! Note that the scheduler thread does not need to wait for I/O to happen at any point.  As soon as it starts
176//! it will start scheduling one page of I/O after another until it has scheduled the entire file's worth of
177//! I/O.  This is slightly different than other file readers which have "row group parallelism" and will
178//! typically only schedule X row groups worth of reads at a time.
179//!
180//! In the near future there will be a backpressure mechanism and so it may need to stop/pause if the compute
181//! falls behind.
182//!
183//! ## Indirect I/O
184//!
185//! Regrettably, there are times where we cannot know exactly what data we need until we have partially decoded
186//! the file.  This happens when we have variable sized list data.  In that case the scheduling task for that
187//! page will only schedule the first part of the read (loading the list offsets).  It will then immediately
188//! spawn a new tokio task to wait for that I/O and decode the list offsets.  That follow-up task is not part
189//! of the scheduling loop or the decode loop.  It is a free task.  Once the list offsets are decoded we submit
190//! a follow-up I/O task.  This task is scheduled at a high priority because the decoder is going to need it soon.
191//!
192//! # The decode loop
193//!
194//! As soon as the scheduler starts we can start decoding.  Each time we schedule a page we
195//! push a decoder for that page's data into a channel.  The decode loop
196//! ([`BatchDecodeStream`]) reads from that channel.  Each time it receives a decoder it
197//! waits until the decoder has all of its data.  Then it grabs the next decoder.  Once it has
198//! enough loaded decoders to complete a batch worth of rows it will spawn a "decode batch task".
199//!
200//! These batch decode tasks perform the actual CPU work of decoding the loaded data into Arrow
201//! arrays.  This may involve signifciant CPU processing like decompression or arithmetic in order
202//! to restore the data to its correct in-memory representation.
203//!
204//! ## Batch size
205//!
206//! The `BatchDecodeStream` is configured with a batch size.  This does not need to have any
207//! relation to the page size(s) used to write the data.  This keeps our compute work completely
208//! independent of our I/O work.  We suggest using small batch sizes:
209//!
210//!  * Batches should fit in CPU cache (at least L3)
211//!  * More batches means more opportunity for parallelism
212//!  * The "batch overhead" is very small in Lance compared to other formats because it has no
213//!    relation to the way the data is stored.
214
215use 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
272/// Schedules the I/O needed to decode one field.
273pub 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
295/// Stateful decoder for one logical page.
296pub 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
325// If users are getting batches over 10MiB large then it's time to reduce the batch size
326const 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
332// If a request is for at most this many rows we skip the scheduler-task spawn
333// and run scheduling inline as part of the `schedule_and_decode` await.
334const 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/// Top-level encoding message for a page. Wraps both the v2.0
353/// [`pb::ArrayEncoding`] grammar and the structural [`pb21::PageLayout`] grammar.
354///
355/// A file should only use one or the other and never both.
356/// 2.0 decoders can always assume this is pb::ArrayEncoding
357/// and 2.1+ decoders can always assume this is pb::PageLayout
358#[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/// Metadata describing a page in a file
394///
395/// This is typically created by reading the metadata section of a Lance file
396#[derive(Debug)]
397pub struct PageInfo {
398    /// The number of rows in the page
399    pub num_rows: u64,
400    /// The priority (top level row number) of the page
401    ///
402    /// This is only set in 2.1 files and will be 0 for 2.0 files
403    pub priority: u64,
404    /// The encoding that explains the buffers in the page
405    pub encoding: PageEncoding,
406    /// The offsets and sizes of the buffers in the file
407    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/// Metadata describing a column in a file
418///
419/// This is typically created by reading the metadata section of a Lance file
420#[derive(Debug, Clone)]
421pub struct ColumnInfo {
422    /// The index of the column in the file
423    pub index: u32,
424    /// The metadata for each page in the column
425    pub page_infos: Arc<[PageInfo]>,
426    /// File positions and their sizes of the column-level buffers
427    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    /// Create a new instance
441    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            // Can just look at the first since all should be the same
458            .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
485/// The scheduler for decoding batches
486///
487/// Lance decoding is done in two steps, scheduling, and decoding.  The
488/// scheduling tends to be lightweight and should quickly figure what data
489/// is needed from the disk issue the appropriate I/O requests.  A decode task is
490/// created to eventually decode the data (once it is loaded) and scheduling
491/// moves on to scheduling the next page.
492///
493/// Meanwhile, it's expected that a decode stream will be setup to run at the
494/// same time.  Decode tasks take the data that is loaded and turn it into
495/// Arrow arrays.
496///
497/// This approach allows us to keep our I/O parallelism and CPU parallelism
498/// completely separate since those are often two very different values.
499///
500/// Backpressure should be achieved via the I/O service.  Requests that are
501/// issued will pile up if the decode stream is not polling quickly enough.
502/// The [`crate::EncodingsIo::submit_request`] function should return a pending
503/// future once there are too many I/O requests in flight.
504///
505/// TODO: Implement backpressure
506pub 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/// These contain the file buffers shared across the entire file
569#[derive(Clone, Copy, Debug)]
570pub struct FileBuffers<'a> {
571    pub positions_and_sizes: &'a [(u64, u64)],
572}
573
574/// These contain the file buffers and also buffers specific to a column
575#[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/// These contain the file & column buffers and also buffers specific to a page
582#[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/// The core decoder strategy handles all the various Arrow types
589#[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    /// Create a new strategy with cache_repetition_index enabled
608    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    /// Create a new strategy from decoder config
614    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    /// This is just a sanity check to ensure there is no "wrapped encodings"
623    /// that haven't been handled.
624    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::is_primitive doesn't consider these primitive but we do
654                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::is_primitive doesn't consider these primitive but we do
676                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        // Primitive fields map to a single column
691        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    /// Helper method to verify the page encoding of a struct header column
705    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 mut inner_infos = Vec::with_capacity(offsets_column.page_infos.len());
741        let mut null_offset_adjustments = Vec::with_capacity(offsets_column.page_infos.len());
742        for (page_index, offsets_page) in offsets_column
743            .page_infos
744            .iter()
745            .enumerate()
746            .filter(|(_, offsets_page)| offsets_page.num_rows > 0)
747        {
748            let PageEncoding::Legacy(pb::ArrayEncoding {
749                array_encoding: Some(pb::array_encoding::ArrayEncoding::List(list_encoding)),
750            }) = &offsets_page.encoding
751            else {
752                return Err(Error::invalid_input(format!(
753                    "expected list encoding for field '{}' in column {}, page {} but got {:?}",
754                    list_field.name, offsets_column.index, page_index, offsets_page.encoding
755                )));
756            };
757            let offsets_encoding = list_encoding.offsets.as_ref().ok_or_else(|| {
758                Error::invalid_input(format!(
759                    "list encoding for field '{}' in column {}, page {} is missing its offsets encoding",
760                    list_field.name, offsets_column.index, page_index
761                ))
762            })?;
763            inner_infos.push(PageInfo {
764                buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
765                encoding: PageEncoding::Legacy(offsets_encoding.as_ref().clone()),
766                num_rows: offsets_page.num_rows,
767                priority: 0,
768            });
769            null_offset_adjustments.push(OffsetPageInfo {
770                offsets_in_page: offsets_page.num_rows,
771                null_offset_adjustment: list_encoding.null_offset_adjustment,
772                num_items_referenced_by_page: list_encoding.num_items,
773            });
774        }
775        let inner = Arc::new(PrimitiveFieldScheduler::new(
776            offsets_column.index,
777            DataType::UInt64,
778            Arc::from(inner_infos.into_boxed_slice()),
779            offsets_column_buffers,
780            self.validate_data,
781        )) as Arc<dyn FieldScheduler>;
782        let items_field = match list_field.data_type() {
783            DataType::List(inner) => inner,
784            DataType::LargeList(inner) => inner,
785            _ => unreachable!(),
786        };
787        let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
788            DataType::Int32
789        } else {
790            DataType::Int64
791        };
792        Ok(Box::new(ListFieldScheduler::new(
793            inner,
794            items_scheduler.into(),
795            items_field,
796            offset_type,
797            null_offset_adjustments,
798        )))
799    }
800
801    fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
802        if let column_encoding::ColumnEncoding::Blob(blob) =
803            column_info.encoding.column_encoding.as_ref().unwrap()
804        {
805            let mut column_info = column_info.clone();
806            column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
807            Some(column_info)
808        } else {
809            None
810        }
811    }
812
813    fn create_structural_field_scheduler(
814        &self,
815        field: &Field,
816        column_infos: &mut ColumnInfoIter,
817    ) -> Result<Box<dyn StructuralFieldScheduler>> {
818        let data_type = field.data_type();
819        validate_fixed_size_list_dimensions(&field.name, &data_type)?;
820        if Self::is_structural_primitive(&data_type) {
821            let column_info = column_infos.expect_next()?;
822            let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
823                column_info.as_ref(),
824                self.decompressor_strategy.as_ref(),
825                self.cache_repetition_index,
826                field,
827            )?);
828
829            // advance to the next top level column
830            column_infos.next_top_level();
831
832            return Ok(scheduler);
833        }
834        match &data_type {
835            DataType::Struct(fields) => {
836                if field.is_packed_struct() {
837                    // Packed struct
838                    let column_info = column_infos.expect_next()?;
839                    let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
840                        column_info.as_ref(),
841                        self.decompressor_strategy.as_ref(),
842                        self.cache_repetition_index,
843                        field,
844                    )?);
845
846                    // advance to the next top level column
847                    column_infos.next_top_level();
848
849                    return Ok(scheduler);
850                }
851                // Maybe a blob descriptions struct?
852                if field.is_blob() {
853                    let column_info = column_infos.peek();
854                    if column_info.page_infos.iter().any(|page| {
855                        matches!(
856                            page.encoding,
857                            PageEncoding::Structural(pb21::PageLayout {
858                                layout: Some(pb21::page_layout::Layout::BlobLayout(_))
859                            })
860                        )
861                    }) {
862                        let column_info = column_infos.expect_next()?;
863                        let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new(
864                            column_info.as_ref(),
865                            self.decompressor_strategy.as_ref(),
866                            self.cache_repetition_index,
867                            field,
868                        )?);
869                        column_infos.next_top_level();
870                        return Ok(scheduler);
871                    }
872                }
873
874                let mut child_schedulers = Vec::with_capacity(field.children.len());
875                for field in field.children.iter() {
876                    let field_scheduler =
877                        self.create_structural_field_scheduler(field, column_infos)?;
878                    child_schedulers.push(field_scheduler);
879                }
880
881                let fields = fields.clone();
882                Ok(
883                    Box::new(StructuralStructScheduler::new(child_schedulers, fields))
884                        as Box<dyn StructuralFieldScheduler>,
885                )
886            }
887            DataType::List(_) | DataType::LargeList(_) => {
888                let child = field.children.first().expect_ok()?;
889                let child_scheduler =
890                    self.create_structural_field_scheduler(child, column_infos)?;
891                Ok(Box::new(StructuralListScheduler::new(child_scheduler))
892                    as Box<dyn StructuralFieldScheduler>)
893            }
894            DataType::FixedSizeList(inner, dimension)
895                if matches!(inner.data_type(), DataType::Struct(_)) =>
896            {
897                let child = field.children.first().expect_ok()?;
898                let child_scheduler =
899                    self.create_structural_field_scheduler(child, column_infos)?;
900                Ok(Box::new(StructuralFixedSizeListScheduler::new(
901                    child_scheduler,
902                    *dimension,
903                )) as Box<dyn StructuralFieldScheduler>)
904            }
905            DataType::Map(_, keys_sorted) => {
906                // TODO: We only support keys_sorted=false for now,
907                //  because converting a rust arrow map field to the python arrow field will
908                //  lose the keys_sorted property.
909                if *keys_sorted {
910                    return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
911                }
912                let entries_child = field.children.first().expect_ok()?;
913                let child_scheduler =
914                    self.create_structural_field_scheduler(entries_child, column_infos)?;
915                Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
916                    as Box<dyn StructuralFieldScheduler>)
917            }
918            _ => todo!("create_structural_field_scheduler for {}", data_type),
919        }
920    }
921
922    fn create_array_field_scheduler(
923        &self,
924        field: &Field,
925        column_infos: &mut ColumnInfoIter,
926        buffers: FileBuffers,
927    ) -> Result<Box<dyn FieldScheduler>> {
928        let data_type = field.data_type();
929        validate_fixed_size_list_dimensions(&field.name, &data_type)?;
930        if Self::is_array_primitive(&data_type) {
931            let column_info = column_infos.expect_next()?;
932            let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
933            return Ok(scheduler);
934        } else if data_type.is_binary_like() {
935            let column_info = column_infos.expect_next()?.clone();
936            // Column is blob and user is asking for binary data
937            if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
938                let desc_scheduler =
939                    self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
940                let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
941                return Ok(blob_scheduler);
942            }
943            if let Some(page_info) = column_info.page_infos.first() {
944                if matches!(
945                    page_info.encoding.as_legacy(),
946                    pb::ArrayEncoding {
947                        array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
948                    }
949                ) {
950                    let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
951                        DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
952                    } else {
953                        DataType::LargeList(Arc::new(ArrowField::new(
954                            "item",
955                            DataType::UInt8,
956                            false,
957                        )))
958                    };
959                    let list_field = Field::try_from(ArrowField::new(
960                        field.name.clone(),
961                        list_type,
962                        field.nullable,
963                    ))
964                    .unwrap();
965                    let list_scheduler = self.create_list_scheduler(
966                        &list_field,
967                        column_infos,
968                        buffers,
969                        &column_info,
970                    )?;
971                    let binary_scheduler = Box::new(BinaryFieldScheduler::new(
972                        list_scheduler.into(),
973                        field.data_type(),
974                    ));
975                    return Ok(binary_scheduler);
976                } else {
977                    let scheduler =
978                        self.create_primitive_scheduler(field, &column_info, buffers)?;
979                    return Ok(scheduler);
980                }
981            } else {
982                return self.create_primitive_scheduler(field, &column_info, buffers);
983            }
984        }
985        match &data_type {
986            DataType::FixedSizeList(inner, _dimension) => {
987                // A fixed size list column could either be a physical or a logical decoder
988                // depending on the child data type.
989                if Self::is_array_primitive(inner.data_type()) {
990                    let primitive_col = column_infos.expect_next()?;
991                    let scheduler =
992                        self.create_primitive_scheduler(field, primitive_col, buffers)?;
993                    Ok(scheduler)
994                } else {
995                    todo!()
996                }
997            }
998            DataType::Dictionary(_key_type, value_type) => {
999                if Self::is_array_primitive(value_type) || value_type.is_binary_like() {
1000                    let primitive_col = column_infos.expect_next()?;
1001                    let scheduler =
1002                        self.create_primitive_scheduler(field, primitive_col, buffers)?;
1003                    Ok(scheduler)
1004                } else {
1005                    Err(Error::not_supported_source(
1006                        format!(
1007                            "No way to decode into a dictionary field of type {}",
1008                            value_type
1009                        )
1010                        .into(),
1011                    ))
1012                }
1013            }
1014            DataType::List(_) | DataType::LargeList(_) => {
1015                let offsets_column = column_infos.expect_next()?.clone();
1016                column_infos.next_top_level();
1017                self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
1018            }
1019            DataType::Struct(fields) => {
1020                let column_info = column_infos.expect_next()?;
1021
1022                // Column is blob and user is asking for descriptions
1023                if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1024                    // Can use primitive scheduler here since descriptions are always packed struct
1025                    return self.create_primitive_scheduler(field, &blob_col, buffers);
1026                }
1027
1028                if Self::check_packed_struct(column_info) {
1029                    // use packed struct encoding
1030                    self.create_primitive_scheduler(field, column_info, buffers)
1031                } else {
1032                    // use default struct encoding
1033                    Self::check_simple_struct(column_info, &field.name).unwrap();
1034                    let num_rows = column_info
1035                        .page_infos
1036                        .iter()
1037                        .map(|page| page.num_rows)
1038                        .sum();
1039                    let mut child_schedulers = Vec::with_capacity(field.children.len());
1040                    for field in &field.children {
1041                        column_infos.next_top_level();
1042                        let field_scheduler =
1043                            self.create_array_field_scheduler(field, column_infos, buffers)?;
1044                        child_schedulers.push(Arc::from(field_scheduler));
1045                    }
1046
1047                    let fields = fields.clone();
1048                    Ok(Box::new(SimpleStructScheduler::new(
1049                        child_schedulers,
1050                        fields,
1051                        num_rows,
1052                    )))
1053                }
1054            }
1055            // TODO: Still need support for RLE
1056            _ => todo!(),
1057        }
1058    }
1059}
1060
1061/// Create's a dummy ColumnInfo for the root column
1062fn root_column(num_rows: u64) -> ColumnInfo {
1063    let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
1064    let final_page_num_rows = num_rows % (u32::MAX as u64);
1065    let root_pages = (0..num_root_pages)
1066        .map(|i| PageInfo {
1067            num_rows: if i == num_root_pages - 1 {
1068                final_page_num_rows
1069            } else {
1070                u64::MAX
1071            },
1072            encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1073                array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1074                    pb::SimpleStruct {},
1075                )),
1076            }),
1077            priority: 0, // not used by the array scheduler
1078            buffer_offsets_and_sizes: Arc::new([]),
1079        })
1080        .collect::<Vec<_>>();
1081    ColumnInfo {
1082        buffer_offsets_and_sizes: Arc::new([]),
1083        encoding: pb::ColumnEncoding {
1084            column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1085        },
1086        index: u32::MAX,
1087        page_infos: Arc::from(root_pages),
1088    }
1089}
1090
1091pub enum RootDecoder {
1092    Structural(StructuralStructDecoder),
1093    Array(SimpleStructDecoder),
1094}
1095
1096impl RootDecoder {
1097    pub fn into_structural(self) -> StructuralStructDecoder {
1098        match self {
1099            Self::Structural(decoder) => decoder,
1100            Self::Array(_) => panic!("Expected a structural decoder"),
1101        }
1102    }
1103
1104    pub fn into_array(self) -> SimpleStructDecoder {
1105        match self {
1106            Self::Array(decoder) => decoder,
1107            Self::Structural(_) => panic!("Expected an array decoder"),
1108        }
1109    }
1110}
1111
1112impl DecodeBatchScheduler {
1113    /// Creates a new decode scheduler with the expected schema and the column
1114    /// metadata of the file.
1115    #[allow(clippy::too_many_arguments)]
1116    pub async fn try_new<'a>(
1117        schema: &'a Schema,
1118        column_indices: &[u32],
1119        column_infos: &[Arc<ColumnInfo>],
1120        file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1121        num_rows: u64,
1122        _decoder_plugins: Arc<DecoderPlugins>,
1123        io: Arc<dyn EncodingsIo>,
1124        cache: Arc<LanceCache>,
1125        filter: &FilterExpression,
1126        decoder_config: &DecoderConfig,
1127    ) -> Result<Self> {
1128        assert!(num_rows > 0);
1129        let buffers = FileBuffers {
1130            positions_and_sizes: file_buffer_positions_and_sizes,
1131        };
1132        let arrow_schema = ArrowSchema::from(schema);
1133        let root_fields = arrow_schema.fields().clone();
1134        let root_type = DataType::Struct(root_fields.clone());
1135        let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1136        // root_field.children and schema.fields should be identical at this point but the latter
1137        // has field ids and the former does not.  This line restores that.
1138        // TODO:  Is there another way to create the root field without forcing a trip through arrow?
1139        root_field.children.clone_from(&schema.fields);
1140        root_field
1141            .metadata
1142            .insert("__lance_decoder_root".to_string(), "true".to_string());
1143
1144        if column_infos.is_empty() || column_infos[0].is_structural() {
1145            let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1146
1147            let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1148            let mut root_scheduler =
1149                strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1150
1151            let context = SchedulerContext::new(io, cache.clone());
1152            root_scheduler.initialize(filter, &context).await?;
1153
1154            Ok(Self {
1155                root_scheduler: RootScheduler::Structural(root_scheduler),
1156                root_fields,
1157                cache,
1158            })
1159        } else {
1160            // The old encoding style expected a header column for structs and so we
1161            // need a header column for the top-level struct
1162            let mut columns = Vec::with_capacity(column_infos.len() + 1);
1163            columns.push(Arc::new(root_column(num_rows)));
1164            columns.extend(column_infos.iter().cloned());
1165
1166            let adjusted_column_indices = [0_u32]
1167                .into_iter()
1168                .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1169                .collect::<Vec<_>>();
1170            let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1171            let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1172            let root_scheduler =
1173                strategy.create_array_field_scheduler(&root_field, &mut column_iter, buffers)?;
1174
1175            let context = SchedulerContext::new(io, cache.clone());
1176            root_scheduler.initialize(filter, &context).await?;
1177
1178            Ok(Self {
1179                root_scheduler: RootScheduler::Array(root_scheduler.into()),
1180                root_fields,
1181                cache,
1182            })
1183        }
1184    }
1185
1186    #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
1187    pub fn from_scheduler(
1188        root_scheduler: Arc<dyn FieldScheduler>,
1189        root_fields: Fields,
1190        cache: Arc<LanceCache>,
1191    ) -> Self {
1192        Self {
1193            root_scheduler: RootScheduler::Array(root_scheduler),
1194            root_fields,
1195            cache,
1196        }
1197    }
1198
1199    fn do_schedule_ranges_structural(
1200        &mut self,
1201        ranges: &[Range<u64>],
1202        filter: &FilterExpression,
1203        io: Arc<dyn EncodingsIo>,
1204        mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1205    ) {
1206        let root_scheduler = self.root_scheduler.as_structural();
1207        let mut context = SchedulerContext::new(io, self.cache.clone());
1208        let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1209        if let Err(schedule_ranges_err) = maybe_root_job {
1210            schedule_action(Err(schedule_ranges_err));
1211            return;
1212        }
1213        let mut root_job = maybe_root_job.unwrap();
1214        let mut num_rows_scheduled = 0;
1215        loop {
1216            let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1217            if let Err(err) = maybe_next_scan_lines {
1218                schedule_action(Err(err));
1219                return;
1220            }
1221            let next_scan_lines = maybe_next_scan_lines.unwrap();
1222            if next_scan_lines.is_empty() {
1223                return;
1224            }
1225            for next_scan_line in next_scan_lines {
1226                trace!(
1227                    "Scheduled scan line of {} rows and {} decoders",
1228                    next_scan_line.rows_scheduled,
1229                    next_scan_line.decoders.len()
1230                );
1231                num_rows_scheduled += next_scan_line.rows_scheduled;
1232                if !schedule_action(Ok(DecoderMessage {
1233                    scheduled_so_far: num_rows_scheduled,
1234                    decoders: next_scan_line.decoders,
1235                })) {
1236                    // Decoder has disconnected
1237                    return;
1238                }
1239            }
1240        }
1241    }
1242
1243    fn do_schedule_ranges_array(
1244        &mut self,
1245        ranges: &[Range<u64>],
1246        filter: &FilterExpression,
1247        io: Arc<dyn EncodingsIo>,
1248        mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1249        // If specified, this will be used as the top_level_row for all scheduling
1250        // tasks.  This is used by list scheduling to ensure all items scheduling
1251        // tasks are scheduled at the same top level row.
1252        priority: Option<Box<dyn PriorityRange>>,
1253    ) {
1254        let root_scheduler = self.root_scheduler.as_array();
1255        let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1256        trace!(
1257            "Scheduling {} ranges across {}..{} ({} rows){}",
1258            ranges.len(),
1259            ranges.first().unwrap().start,
1260            ranges.last().unwrap().end,
1261            rows_requested,
1262            priority
1263                .as_ref()
1264                .map(|p| format!(" (priority={:?})", p))
1265                .unwrap_or_default()
1266        );
1267
1268        let mut context = SchedulerContext::new(io, self.cache.clone());
1269        let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1270        if let Err(schedule_ranges_err) = maybe_root_job {
1271            schedule_action(Err(schedule_ranges_err));
1272            return;
1273        }
1274        let mut root_job = maybe_root_job.unwrap();
1275        let mut num_rows_scheduled = 0;
1276        let mut rows_to_schedule = root_job.num_rows();
1277        let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1278        trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1279        while rows_to_schedule > 0 {
1280            let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1281            if let Err(schedule_next_err) = maybe_next_scan_line {
1282                schedule_action(Err(schedule_next_err));
1283                return;
1284            }
1285            let next_scan_line = maybe_next_scan_line.unwrap();
1286            priority.advance(next_scan_line.rows_scheduled);
1287            num_rows_scheduled += next_scan_line.rows_scheduled;
1288            rows_to_schedule -= next_scan_line.rows_scheduled;
1289            trace!(
1290                "Scheduled scan line of {} rows and {} decoders",
1291                next_scan_line.rows_scheduled,
1292                next_scan_line.decoders.len()
1293            );
1294            if !schedule_action(Ok(DecoderMessage {
1295                scheduled_so_far: num_rows_scheduled,
1296                decoders: next_scan_line.decoders,
1297            })) {
1298                // Decoder has disconnected
1299                return;
1300            }
1301
1302            trace!("Finished scheduling {} ranges", ranges.len());
1303        }
1304    }
1305
1306    fn do_schedule_ranges(
1307        &mut self,
1308        ranges: &[Range<u64>],
1309        filter: &FilterExpression,
1310        io: Arc<dyn EncodingsIo>,
1311        schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1312        // If specified, this will be used as the top_level_row for all scheduling
1313        // tasks.  This is used by list scheduling to ensure all items scheduling
1314        // tasks are scheduled at the same top level row.
1315        priority: Option<Box<dyn PriorityRange>>,
1316    ) {
1317        match &self.root_scheduler {
1318            RootScheduler::Array(_) => {
1319                self.do_schedule_ranges_array(ranges, filter, io, schedule_action, priority)
1320            }
1321            RootScheduler::Structural(_) => {
1322                self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1323            }
1324        }
1325    }
1326
1327    // This method is similar to schedule_ranges but instead of
1328    // sending the decoders to a channel it collects them all into a vector
1329    pub fn schedule_ranges_to_vec(
1330        &mut self,
1331        ranges: &[Range<u64>],
1332        filter: &FilterExpression,
1333        io: Arc<dyn EncodingsIo>,
1334        priority: Option<Box<dyn PriorityRange>>,
1335    ) -> Result<Vec<DecoderMessage>> {
1336        let mut decode_messages = Vec::new();
1337        self.do_schedule_ranges(
1338            ranges,
1339            filter,
1340            io,
1341            |msg| {
1342                decode_messages.push(msg);
1343                true
1344            },
1345            priority,
1346        );
1347        decode_messages.into_iter().collect::<Result<Vec<_>>>()
1348    }
1349
1350    /// Schedules the load of multiple ranges of rows
1351    ///
1352    /// Ranges must be non-overlapping and in sorted order
1353    ///
1354    /// # Arguments
1355    ///
1356    /// * `ranges` - The ranges of rows to load
1357    /// * `sink` - A channel to send the decode tasks
1358    /// * `scheduler` An I/O scheduler to issue I/O requests
1359    #[instrument(level = "debug", skip_all)]
1360    pub fn schedule_ranges(
1361        &mut self,
1362        ranges: &[Range<u64>],
1363        filter: &FilterExpression,
1364        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1365        scheduler: Arc<dyn EncodingsIo>,
1366    ) {
1367        self.do_schedule_ranges(
1368            ranges,
1369            filter,
1370            scheduler,
1371            |msg| {
1372                match sink.send(msg) {
1373                    Ok(_) => true,
1374                    Err(SendError { .. }) => {
1375                        // The receiver has gone away.  We can't do anything about it
1376                        // so just ignore the error.
1377                        debug!(
1378                        "schedule_ranges aborting early since decoder appears to have been dropped"
1379                    );
1380                        false
1381                    }
1382                }
1383            },
1384            None,
1385        )
1386    }
1387
1388    /// Schedules the load of a range of rows
1389    ///
1390    /// # Arguments
1391    ///
1392    /// * `range` - The range of rows to load
1393    /// * `sink` - A channel to send the decode tasks
1394    /// * `scheduler` An I/O scheduler to issue I/O requests
1395    #[instrument(level = "debug", skip_all)]
1396    pub fn schedule_range(
1397        &mut self,
1398        range: Range<u64>,
1399        filter: &FilterExpression,
1400        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1401        scheduler: Arc<dyn EncodingsIo>,
1402    ) {
1403        self.schedule_ranges(&[range], filter, sink, scheduler)
1404    }
1405
1406    /// Schedules the load of selected rows
1407    ///
1408    /// # Arguments
1409    ///
1410    /// * `indices` - The row indices to load (these must be in ascending order!)
1411    /// * `sink` - A channel to send the decode tasks
1412    /// * `scheduler` An I/O scheduler to issue I/O requests
1413    pub fn schedule_take(
1414        &mut self,
1415        indices: &[u64],
1416        filter: &FilterExpression,
1417        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1418        scheduler: Arc<dyn EncodingsIo>,
1419    ) {
1420        debug_assert!(indices.windows(2).all(|w| w[0] < w[1]));
1421        if indices.is_empty() {
1422            return;
1423        }
1424        trace!("Scheduling take of {} rows", indices.len());
1425        let ranges = Self::indices_to_ranges(indices);
1426        self.schedule_ranges(&ranges, filter, sink, scheduler)
1427    }
1428
1429    // coalesce continuous indices if possible (the input indices must be sorted and non-empty)
1430    fn indices_to_ranges(indices: &[u64]) -> Vec<Range<u64>> {
1431        let mut ranges = Vec::new();
1432        let mut start = indices[0];
1433
1434        for window in indices.windows(2) {
1435            if window[1] != window[0] + 1 {
1436                ranges.push(start..window[0] + 1);
1437                start = window[1];
1438            }
1439        }
1440
1441        ranges.push(start..*indices.last().unwrap() + 1);
1442        ranges
1443    }
1444}
1445
1446pub struct ReadBatchTask {
1447    pub task: BoxFuture<'static, Result<RecordBatch>>,
1448    pub num_rows: u32,
1449}
1450
1451/// A stream that takes scheduled jobs and generates decode tasks from them.
1452pub struct BatchDecodeStream {
1453    context: DecoderContext,
1454    root_decoder: SimpleStructDecoder,
1455    rows_remaining: u64,
1456    rows_per_batch: u32,
1457    rows_scheduled: u64,
1458    rows_drained: u64,
1459    scheduler_exhausted: bool,
1460    emitted_batch_size_warning: Arc<Once>,
1461}
1462
1463impl BatchDecodeStream {
1464    /// Create a new instance of a batch decode stream
1465    ///
1466    /// # Arguments
1467    ///
1468    /// * `scheduled` - an incoming stream of decode tasks from a `DecodeBatchScheduler`
1469    /// * `schema` - the schema of the data to create
1470    /// * `rows_per_batch` the number of rows to create before making a batch
1471    /// * `num_rows` the total number of rows scheduled
1472    /// * `num_columns` the total number of columns in the file
1473    pub fn new(
1474        scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1475        rows_per_batch: u32,
1476        num_rows: u64,
1477        root_decoder: SimpleStructDecoder,
1478    ) -> Self {
1479        Self {
1480            context: DecoderContext::new(scheduled),
1481            root_decoder,
1482            rows_remaining: num_rows,
1483            rows_per_batch,
1484            rows_scheduled: 0,
1485            rows_drained: 0,
1486            scheduler_exhausted: false,
1487            emitted_batch_size_warning: Arc::new(Once::new()),
1488        }
1489    }
1490
1491    fn accept_decoder(&mut self, decoder: DecoderReady) -> Result<()> {
1492        if decoder.path.is_empty() {
1493            // The root decoder we can ignore
1494            Ok(())
1495        } else {
1496            self.root_decoder.accept_child(decoder)
1497        }
1498    }
1499
1500    #[instrument(level = "debug", skip_all)]
1501    async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1502        if self.scheduler_exhausted {
1503            return Ok(self.rows_scheduled);
1504        }
1505        while self.rows_scheduled < scheduled_need {
1506            let next_message = self.context.source.recv().await;
1507            match next_message {
1508                Some(scan_line) => {
1509                    let scan_line = scan_line?;
1510                    self.rows_scheduled = scan_line.scheduled_so_far;
1511                    for message in scan_line.decoders {
1512                        self.accept_decoder(message.into_array())?;
1513                    }
1514                }
1515                None => {
1516                    // Schedule ended before we got all the data we expected.  This probably
1517                    // means some kind of pushdown filter was applied and we didn't load as
1518                    // much data as we thought we would.
1519                    self.scheduler_exhausted = true;
1520                    return Ok(self.rows_scheduled);
1521                }
1522            }
1523        }
1524        Ok(scheduled_need)
1525    }
1526
1527    #[instrument(level = "debug", skip_all)]
1528    async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1529        trace!(
1530            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1531            self.rows_remaining, self.rows_drained, self.rows_scheduled,
1532        );
1533        if self.rows_remaining == 0 {
1534            return Ok(None);
1535        }
1536
1537        let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1538        self.rows_remaining -= to_take;
1539
1540        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1541        trace!(
1542            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1543            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1544        );
1545        if scheduled_need > 0 {
1546            let desired_scheduled = scheduled_need + self.rows_scheduled;
1547            trace!(
1548                "Draining from scheduler (desire at least {} scheduled rows)",
1549                desired_scheduled
1550            );
1551            let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1552            if actually_scheduled < desired_scheduled {
1553                let under_scheduled = desired_scheduled - actually_scheduled;
1554                to_take -= under_scheduled;
1555            }
1556        }
1557
1558        if to_take == 0 {
1559            return Ok(None);
1560        }
1561
1562        // wait_for_loaded waits for *>* loaded_need (not >=) so we do a -1 here
1563        let loaded_need = self.rows_drained + to_take - 1;
1564        trace!(
1565            "Waiting for I/O (desire at least {} fully loaded rows)",
1566            loaded_need
1567        );
1568        self.root_decoder.wait_for_loaded(loaded_need).await?;
1569
1570        let next_task = self.root_decoder.drain(to_take)?;
1571        self.rows_drained += to_take;
1572        Ok(Some(next_task))
1573    }
1574
1575    pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1576        let stream = futures::stream::unfold(self, |mut slf| async move {
1577            let next_task = match slf.next_batch_task().await {
1578                Ok(Some(next_task)) => next_task,
1579                Ok(None) => return None,
1580                Err(err) => {
1581                    slf.rows_remaining = 0;
1582                    return Some((
1583                        ReadBatchTask {
1584                            task: async move { Err(err) }.boxed(),
1585                            num_rows: 0,
1586                        },
1587                        slf,
1588                    ));
1589                }
1590            };
1591            let num_rows = next_task.num_rows;
1592            let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1593            let task = async move {
1594                // Real decode work happens inside into_batch, which can block the current
1595                // thread for a long time. By spawning it as a new task, we allow Tokio's
1596                // worker threads to keep making progress.
1597                let (batch, _data_size) =
1598                    tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1599                        .await
1600                        .map_err(|err| Error::wrapped(err.into()))??;
1601                Ok(batch)
1602            };
1603            // This should be true since batch size is u32
1604            debug_assert!(num_rows <= u32::MAX as u64);
1605            Some((
1606                ReadBatchTask {
1607                    task: task.boxed(),
1608                    num_rows: num_rows as u32,
1609                },
1610                slf,
1611            ))
1612        });
1613        stream.boxed()
1614    }
1615}
1616
1617// Utility types to smooth out the differences between the 2.0 and 2.1 decoders so that
1618// we can have a single implementation of the batch decode iterator
1619enum RootDecoderMessage {
1620    LoadedPage(LoadedPageShard),
1621    ArrayPage(DecoderReady),
1622}
1623trait RootDecoderType {
1624    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1625    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1626    fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1627}
1628impl RootDecoderType for StructuralStructDecoder {
1629    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1630        let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1631            unreachable!()
1632        };
1633        self.accept_page(loaded_page)
1634    }
1635    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1636        self.drain_batch_task(num_rows)
1637    }
1638    fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1639        // Waiting happens elsewhere (not as part of the decoder)
1640        Ok(())
1641    }
1642}
1643impl RootDecoderType for SimpleStructDecoder {
1644    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1645        let RootDecoderMessage::ArrayPage(array_page) = message else {
1646            unreachable!()
1647        };
1648        self.accept_child(array_page)
1649    }
1650    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1651        self.drain(num_rows)
1652    }
1653    fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1654        runtime.block_on(self.wait_for_loaded(loaded_need))
1655    }
1656}
1657
1658/// A blocking batch decoder that performs synchronous decoding
1659struct BatchDecodeIterator<T: RootDecoderType> {
1660    messages: VecDeque<Result<DecoderMessage>>,
1661    root_decoder: T,
1662    rows_remaining: u64,
1663    rows_per_batch: u32,
1664    rows_scheduled: u64,
1665    rows_drained: u64,
1666    emitted_batch_size_warning: Arc<Once>,
1667    // Note: this is not the runtime on which I/O happens.
1668    // That's always in the scheduler.  This is just a runtime we use to
1669    // sleep the current thread if I/O is unready
1670    wait_for_io_runtime: tokio::runtime::Runtime,
1671    schema: Arc<ArrowSchema>,
1672}
1673
1674impl<T: RootDecoderType> BatchDecodeIterator<T> {
1675    /// Create a new instance of a batch decode iterator
1676    pub fn new(
1677        messages: VecDeque<Result<DecoderMessage>>,
1678        rows_per_batch: u32,
1679        num_rows: u64,
1680        root_decoder: T,
1681        schema: Arc<ArrowSchema>,
1682    ) -> Self {
1683        Self {
1684            messages,
1685            root_decoder,
1686            rows_remaining: num_rows,
1687            rows_per_batch,
1688            rows_scheduled: 0,
1689            rows_drained: 0,
1690            wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1691                .build()
1692                .unwrap(),
1693            emitted_batch_size_warning: Arc::new(Once::new()),
1694            schema,
1695        }
1696    }
1697
1698    /// Wait for a single page of data to finish loading
1699    ///
1700    /// If the data is not available this will perform a *blocking* wait (put
1701    /// the current thread to sleep)
1702    fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1703        match maybe_done(unloaded_page.0) {
1704            // Fast path, avoid all runtime shenanigans if the data is ready
1705            MaybeDone::Done(loaded_page) => loaded_page,
1706            // Slow path, we need to wait on I/O, enter the runtime
1707            MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1708            MaybeDone::Gone => unreachable!(),
1709        }
1710    }
1711
1712    /// Waits for I/O until `scheduled_need` rows have been loaded
1713    ///
1714    /// Note that `scheduled_need` is cumulative.  E.g. this method
1715    /// should be called with 5, 10, 15 and not 5, 5, 5
1716    #[instrument(level = "debug", skip_all)]
1717    fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1718        while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1719            let message = self.messages.pop_front().unwrap()?;
1720            self.rows_scheduled = message.scheduled_so_far;
1721            for decoder_message in message.decoders {
1722                match decoder_message {
1723                    MessageType::UnloadedPage(unloaded_page) => {
1724                        let loaded_page = self.wait_for_page(unloaded_page)?;
1725                        self.root_decoder
1726                            .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1727                    }
1728                    MessageType::DecoderReady(decoder_ready) => {
1729                        // The root decoder we can ignore
1730                        if !decoder_ready.path.is_empty() {
1731                            self.root_decoder
1732                                .accept_message(RootDecoderMessage::ArrayPage(decoder_ready))?;
1733                        }
1734                    }
1735                }
1736            }
1737        }
1738
1739        let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1740
1741        self.root_decoder
1742            .wait(loaded_need, &self.wait_for_io_runtime)?;
1743        Ok(self.rows_scheduled)
1744    }
1745
1746    #[instrument(level = "debug", skip_all)]
1747    fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1748        trace!(
1749            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1750            self.rows_remaining, self.rows_drained, self.rows_scheduled,
1751        );
1752        if self.rows_remaining == 0 {
1753            return Ok(None);
1754        }
1755
1756        let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1757        self.rows_remaining -= to_take;
1758
1759        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1760        trace!(
1761            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1762            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1763        );
1764        if scheduled_need > 0 {
1765            let desired_scheduled = scheduled_need + self.rows_scheduled;
1766            trace!(
1767                "Draining from scheduler (desire at least {} scheduled rows)",
1768                desired_scheduled
1769            );
1770            let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1771            if actually_scheduled < desired_scheduled {
1772                let under_scheduled = desired_scheduled - actually_scheduled;
1773                to_take -= under_scheduled;
1774            }
1775        }
1776
1777        if to_take == 0 {
1778            return Ok(None);
1779        }
1780
1781        let next_task = self.root_decoder.drain_batch(to_take)?;
1782
1783        self.rows_drained += to_take;
1784
1785        let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1786
1787        Ok(Some(batch))
1788    }
1789}
1790
1791impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1792    type Item = ArrowResult<RecordBatch>;
1793
1794    fn next(&mut self) -> Option<Self::Item> {
1795        self.next_batch_task()
1796            .transpose()
1797            .map(|r| r.map_err(ArrowError::from))
1798    }
1799}
1800
1801impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1802    fn schema(&self) -> Arc<ArrowSchema> {
1803        self.schema.clone()
1804    }
1805}
1806
1807/// Estimate the number of bytes per row for a given Arrow data type.
1808///
1809/// For fixed-width types this is exact. For variable-width types (strings,
1810/// binary, lists) a rough default is used. The estimate is used as a
1811/// starting point when `batch_size_bytes` is set; a post-decode feedback
1812/// loop corrects it after the first batch.
1813///
1814/// This estimate ignores validity bitmaps at the moment.  We can't infer
1815/// their presence simply from the data_type and their impact is probably
1816/// fairly negligible.
1817fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1818    if let Some(w) = data_type.byte_width_opt() {
1819        return w as f64;
1820    }
1821    match data_type {
1822        DataType::Boolean => 1.0 / 8.0,
1823        DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1824        DataType::Struct(fields) => fields
1825            .iter()
1826            .map(|f| estimate_bytes_per_row(f.data_type()))
1827            .sum(),
1828        DataType::List(child) | DataType::LargeList(child) => {
1829            5.0 * estimate_bytes_per_row(child.data_type())
1830        }
1831        DataType::FixedSizeList(child, dim) => {
1832            *dim as f64 * estimate_bytes_per_row(child.data_type())
1833        }
1834        DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
1835        DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
1836        _ => 64.0,
1837    }
1838}
1839
1840/// A stream that takes scheduled jobs and generates decode tasks from them.
1841pub struct StructuralBatchDecodeStream {
1842    context: DecoderContext,
1843    root_decoder: StructuralStructDecoder,
1844    rows_remaining: u64,
1845    rows_per_batch: u32,
1846    rows_scheduled: u64,
1847    rows_drained: u64,
1848    scheduler_exhausted: bool,
1849    emitted_batch_size_warning: Arc<Once>,
1850    // Decode scheduling policy selected at planning time.
1851    //
1852    // Performance tradeoff:
1853    // - true: spawn `into_batch` onto Tokio, which improves scan throughput by allowing
1854    //   more decode parallelism.
1855    // - false: run `into_batch` inline, which avoids Tokio scheduling overhead and is
1856    //   typically better for point lookups / small takes.
1857    spawn_batch_decode_tasks: bool,
1858    /// If set, target this many bytes per batch while retaining `rows_per_batch`
1859    /// as an independent upper bound.
1860    batch_size_bytes: Option<u64>,
1861    /// Schema-based estimate of bytes per row, computed once at construction.
1862    /// Only meaningful when `batch_size_bytes` is `Some`.
1863    schema_bytes_per_row: f64,
1864    /// Post-decode feedback: actual bytes-per-row measured from the most
1865    /// recently decoded batch.  Zero means no feedback yet (use schema estimate).
1866    bytes_per_row_feedback: Arc<AtomicU64>,
1867}
1868
1869impl StructuralBatchDecodeStream {
1870    /// Create a new instance of a batch decode stream
1871    ///
1872    /// # Arguments
1873    ///
1874    /// * `scheduled` - an incoming stream of decode tasks from a `DecodeBatchScheduler`
1875    /// * `schema` - the schema of the data to create
1876    /// * `rows_per_batch` the number of rows to create before making a batch
1877    /// * `num_rows` the total number of rows scheduled
1878    /// * `num_columns` the total number of columns in the file
1879    pub fn new(
1880        scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1881        rows_per_batch: u32,
1882        num_rows: u64,
1883        root_decoder: StructuralStructDecoder,
1884        spawn_batch_decode_tasks: bool,
1885        batch_size_bytes: Option<u64>,
1886    ) -> Self {
1887        let schema_bytes_per_row = if batch_size_bytes.is_some() {
1888            estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
1889        } else {
1890            0.0
1891        };
1892        Self {
1893            context: DecoderContext::new(scheduled),
1894            root_decoder,
1895            rows_remaining: num_rows,
1896            rows_per_batch,
1897            rows_scheduled: 0,
1898            rows_drained: 0,
1899            scheduler_exhausted: false,
1900            emitted_batch_size_warning: Arc::new(Once::new()),
1901            spawn_batch_decode_tasks,
1902            batch_size_bytes,
1903            schema_bytes_per_row,
1904            bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
1905        }
1906    }
1907
1908    #[instrument(level = "debug", skip_all)]
1909    async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1910        if self.scheduler_exhausted {
1911            return Ok(self.rows_scheduled);
1912        }
1913        while self.rows_scheduled < scheduled_need {
1914            let next_message = self.context.source.recv().await;
1915            match next_message {
1916                Some(scan_line) => {
1917                    let scan_line = scan_line?;
1918                    self.rows_scheduled = scan_line.scheduled_so_far;
1919                    for message in scan_line.decoders {
1920                        let unloaded_page = message.into_structural();
1921                        let loaded_page = unloaded_page.0.await?;
1922                        self.root_decoder.accept_page(loaded_page)?;
1923                    }
1924                }
1925                None => {
1926                    // Schedule ended before we got all the data we expected.  This probably
1927                    // means some kind of pushdown filter was applied and we didn't load as
1928                    // much data as we thought we would.
1929                    self.scheduler_exhausted = true;
1930                    return Ok(self.rows_scheduled);
1931                }
1932            }
1933        }
1934        Ok(scheduled_need)
1935    }
1936
1937    #[instrument(level = "debug", skip_all)]
1938    async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1939        trace!(
1940            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1941            self.rows_remaining, self.rows_drained, self.rows_scheduled,
1942        );
1943        if self.rows_remaining == 0 {
1944            return Ok(None);
1945        }
1946
1947        let row_limit = self.rows_remaining.min(self.rows_per_batch as u64);
1948        let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
1949            let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
1950            let bpr = if feedback > 0 {
1951                feedback as f64
1952            } else {
1953                self.schema_bytes_per_row
1954            };
1955            let rows = (batch_size_bytes as f64 / bpr) as u64;
1956            row_limit.min(rows.max(1))
1957        } else {
1958            row_limit
1959        };
1960        self.rows_remaining -= to_take;
1961
1962        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1963        trace!(
1964            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1965            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1966        );
1967        if scheduled_need > 0 {
1968            let desired_scheduled = scheduled_need + self.rows_scheduled;
1969            trace!(
1970                "Draining from scheduler (desire at least {} scheduled rows)",
1971                desired_scheduled
1972            );
1973            let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1974            if actually_scheduled < desired_scheduled {
1975                let under_scheduled = desired_scheduled - actually_scheduled;
1976                to_take -= under_scheduled;
1977            }
1978        }
1979
1980        if to_take == 0 {
1981            return Ok(None);
1982        }
1983
1984        let next_task = self.root_decoder.drain_batch_task(to_take)?;
1985        self.rows_drained += to_take;
1986        Ok(Some(next_task))
1987    }
1988
1989    pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1990        let stream = futures::stream::unfold(self, |mut slf| async move {
1991            let next_task = match slf.next_batch_task().await {
1992                Ok(Some(next_task)) => next_task,
1993                Ok(None) => return None,
1994                Err(err) => {
1995                    slf.rows_remaining = 0;
1996                    return Some((
1997                        ReadBatchTask {
1998                            task: async move { Err(err) }.boxed(),
1999                            num_rows: 0,
2000                        },
2001                        slf,
2002                    ));
2003                }
2004            };
2005            let num_rows = next_task.num_rows;
2006            let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
2007            let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
2008            // Capture the per-stream policy once so every emitted batch task follows the
2009            // same throughput-vs-overhead choice made by the scheduler.
2010            let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
2011            let task = async move {
2012                let (batch, data_size) = if spawn_batch_decode_tasks {
2013                    tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
2014                        .await
2015                        .map_err(|err| Error::wrapped(err.into()))??
2016                } else {
2017                    next_task.into_batch(emitted_batch_size_warning)?
2018                };
2019                let num_rows = batch.num_rows() as u64;
2020                if let Some(bpr) = data_size.checked_div(num_rows) {
2021                    let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
2022                    let next = if prev == 0 || bpr >= prev {
2023                        // First batch or actual size is larger than estimate:
2024                        // adopt immediately to avoid OOM.
2025                        bpr
2026                    } else {
2027                        // Actual size is smaller: degrade gradually toward
2028                        // the true value to avoid over-correcting on a
2029                        // single anomalous batch.
2030                        (prev + bpr) / 2
2031                    };
2032                    bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
2033                }
2034                Ok(batch)
2035            };
2036            // This should be true since batch size is u32
2037            debug_assert!(num_rows <= u32::MAX as u64);
2038            Some((
2039                ReadBatchTask {
2040                    task: task.boxed(),
2041                    num_rows: num_rows as u32,
2042                },
2043                slf,
2044            ))
2045        });
2046        stream.boxed()
2047    }
2048}
2049
2050#[derive(Debug)]
2051pub enum RequestedRows {
2052    Ranges(Vec<Range<u64>>),
2053    Indices(Vec<u64>),
2054}
2055
2056impl RequestedRows {
2057    pub fn num_rows(&self) -> u64 {
2058        match self {
2059            Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
2060            Self::Indices(indices) => indices.len() as u64,
2061        }
2062    }
2063
2064    pub fn trim_empty_ranges(mut self) -> Self {
2065        if let Self::Ranges(ranges) = &mut self {
2066            ranges.retain(|r| !r.is_empty());
2067        }
2068        self
2069    }
2070}
2071
2072/// Configuration for decoder behavior
2073#[derive(Debug, Clone)]
2074pub struct DecoderConfig {
2075    /// Whether to cache repetition indices for better performance.
2076    ///
2077    /// This defaults to the `LANCE_READ_CACHE_REPETITION_INDEX` environment variable
2078    /// when present and is enabled by default. Set the env var to a non-truthy
2079    /// value (for example `0` or `false`) to disable it. The env var is read
2080    /// once per process.
2081    pub cache_repetition_index: bool,
2082    /// Whether to validate decoded data
2083    pub validate_on_decode: bool,
2084    /// Override the strategy used to dispatch the scheduling work in
2085    /// [`schedule_and_decode`].
2086    ///
2087    /// `schedule_and_decode` always awaits the scheduler's `initialize` (which
2088    /// performs metadata I/O) before returning.  This flag controls what
2089    /// happens with the subsequent (synchronous) work of pushing decoder
2090    /// messages into the channel that feeds the decode stream.
2091    ///
2092    /// * `None` - default behavior: the scheduling work runs inline (as part
2093    ///   of the `schedule_and_decode` await) when the request is small
2094    ///   (controlled by the `LANCE_INLINE_SCHEDULING_THRESHOLD` env var) and
2095    ///   is dispatched onto a spawned task otherwise.
2096    /// * `Some(true)` - always run scheduling inline.  The await of
2097    ///   `schedule_and_decode` does not return until every decoder message
2098    ///   has been queued.
2099    /// * `Some(false)` - always spawn a task for scheduling so that it can
2100    ///   overlap with consumption of the decode stream.
2101    pub inline_scheduling: Option<bool>,
2102}
2103
2104impl Default for DecoderConfig {
2105    fn default() -> Self {
2106        Self {
2107            cache_repetition_index: default_cache_repetition_index(),
2108            validate_on_decode: false,
2109            inline_scheduling: None,
2110        }
2111    }
2112}
2113
2114#[derive(Debug, Clone)]
2115pub struct SchedulerDecoderConfig {
2116    pub decoder_plugins: Arc<DecoderPlugins>,
2117    pub batch_size: u32,
2118    pub io: Arc<dyn EncodingsIo>,
2119    pub cache: Arc<LanceCache>,
2120    /// Decoder configuration
2121    pub decoder_config: DecoderConfig,
2122    /// If set, target this many bytes per batch while retaining `batch_size` as
2123    /// an independent row-count upper bound.
2124    ///
2125    /// Only supported for v2.1+ (structural) files. For v2.0 files this
2126    /// option is ignored and a warning is logged.
2127    pub batch_size_bytes: Option<u64>,
2128}
2129
2130fn check_scheduler_on_drop(
2131    stream: BoxStream<'static, ReadBatchTask>,
2132    scheduler_handle: tokio::task::JoinHandle<()>,
2133) -> BoxStream<'static, ReadBatchTask> {
2134    // This is a bit weird but we create an "empty stream" that unwraps the scheduler handle (which
2135    // will panic if the scheduler panicked).  This let's us check if the scheduler panicked
2136    // when the stream finishes.
2137    let abort_handle = scheduler_handle.abort_handle();
2138    let mut scheduler_handle = Some(scheduler_handle);
2139    let check_scheduler = stream::unfold((), move |_| {
2140        let handle = scheduler_handle.take();
2141        async move {
2142            if let Some(handle) = handle {
2143                handle.await.unwrap();
2144            }
2145            None
2146        }
2147    });
2148    stream
2149        .chain(check_scheduler)
2150        .on_drop(move || {
2151            // Abort the scheduler task on early drop. The scheduler task holds
2152            // a reference to the I/O scheduler (via config.io) which keeps the
2153            // ScanScheduler alive. If the scheduler task is stuck waiting for
2154            // initialization I/O (which is blocked on backpressure that will
2155            // never drain because no one is consuming the stream), we need to
2156            // abort it so it releases its I/O reference and allows the
2157            // ScanScheduler to drop and cancel pending I/O.
2158            abort_handle.abort();
2159        })
2160        .boxed()
2161}
2162
2163#[allow(clippy::too_many_arguments)]
2164pub fn create_decode_stream(
2165    schema: &Schema,
2166    num_rows: u64,
2167    batch_size: u32,
2168    is_structural: bool,
2169    should_validate: bool,
2170    spawn_structural_batch_decode_tasks: bool,
2171    rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2172    batch_size_bytes: Option<u64>,
2173) -> Result<BoxStream<'static, ReadBatchTask>> {
2174    if is_structural {
2175        let arrow_schema = ArrowSchema::from(schema);
2176        let structural_decoder = StructuralStructDecoder::new(
2177            arrow_schema.fields,
2178            should_validate,
2179            /*is_root=*/ true,
2180        )?;
2181        Ok(StructuralBatchDecodeStream::new(
2182            rx,
2183            batch_size,
2184            num_rows,
2185            structural_decoder,
2186            spawn_structural_batch_decode_tasks,
2187            batch_size_bytes,
2188        )
2189        .into_stream())
2190    } else {
2191        if batch_size_bytes.is_some() {
2192            warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2193        }
2194        let arrow_schema = ArrowSchema::from(schema);
2195        let root_fields = arrow_schema.fields;
2196
2197        let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2198        Ok(BatchDecodeStream::new(rx, batch_size, num_rows, simple_struct_decoder).into_stream())
2199    }
2200}
2201
2202/// Creates a iterator that decodes a set of messages in a blocking fashion
2203///
2204/// See [`schedule_and_decode_blocking`] for more information.
2205pub fn create_decode_iterator(
2206    schema: &Schema,
2207    num_rows: u64,
2208    batch_size: u32,
2209    should_validate: bool,
2210    is_structural: bool,
2211    messages: VecDeque<Result<DecoderMessage>>,
2212) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2213    let arrow_schema = Arc::new(ArrowSchema::from(schema));
2214    let root_fields = arrow_schema.fields.clone();
2215    if is_structural {
2216        let simple_struct_decoder =
2217            StructuralStructDecoder::new(root_fields, should_validate, /*is_root=*/ true)?;
2218        Ok(Box::new(BatchDecodeIterator::new(
2219            messages,
2220            batch_size,
2221            num_rows,
2222            simple_struct_decoder,
2223            arrow_schema,
2224        )))
2225    } else {
2226        let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2227        Ok(Box::new(BatchDecodeIterator::new(
2228            messages,
2229            batch_size,
2230            num_rows,
2231            root_decoder,
2232            arrow_schema,
2233        )))
2234    }
2235}
2236
2237async fn create_scheduler_decoder(
2238    column_infos: Vec<Arc<ColumnInfo>>,
2239    requested_rows: RequestedRows,
2240    filter: FilterExpression,
2241    column_indices: Vec<u32>,
2242    target_schema: Arc<Schema>,
2243    config: SchedulerDecoderConfig,
2244) -> Result<BoxStream<'static, ReadBatchTask>> {
2245    let num_rows = requested_rows.num_rows();
2246
2247    let is_structural = column_infos[0].is_structural();
2248    let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2249    let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2250        Some("always") => true,
2251        Some("never") => false,
2252        _ => matches!(requested_rows, RequestedRows::Ranges(_)),
2253    };
2254
2255    let (tx, rx) = mpsc::unbounded_channel();
2256
2257    let decode_stream = create_decode_stream(
2258        &target_schema,
2259        num_rows,
2260        config.batch_size,
2261        is_structural,
2262        config.decoder_config.validate_on_decode,
2263        spawn_structural_batch_decode_tasks,
2264        rx,
2265        config.batch_size_bytes,
2266    )?;
2267
2268    // The scheduler's `initialize` may perform I/O to load column metadata
2269    // unless that metadata is already in the cache.  This metadata loading
2270    // happens as part of this call and should be parallelized if reading
2271    // multiple files.
2272    let mut decode_scheduler = DecodeBatchScheduler::try_new(
2273        target_schema.as_ref(),
2274        &column_indices,
2275        &column_infos,
2276        &vec![],
2277        num_rows,
2278        config.decoder_plugins,
2279        config.io.clone(),
2280        config.cache,
2281        &filter,
2282        &config.decoder_config,
2283    )
2284    .await?;
2285
2286    // For small requests the scheduling cost is dwarfed by the overhead of
2287    // spawning a task, so we run scheduling inline (still as part of this
2288    // await) before returning.  The threshold is configurable via
2289    // `LANCE_INLINE_SCHEDULING_THRESHOLD`, and callers can force either
2290    // strategy via `DecoderConfig::inline_scheduling`.
2291    let inline_scheduling = config
2292        .decoder_config
2293        .inline_scheduling
2294        .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2295
2296    if inline_scheduling {
2297        match requested_rows {
2298            RequestedRows::Ranges(ranges) => {
2299                decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2300            }
2301            RequestedRows::Indices(indices) => {
2302                decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2303            }
2304        }
2305        Ok(decode_stream)
2306    } else {
2307        // Spawn the (still synchronous) scheduling work so that decoder
2308        // messages can stream into the channel while the consumer is
2309        // already pulling from the decode stream.
2310        let scheduling = async move {
2311            match requested_rows {
2312                RequestedRows::Ranges(ranges) => {
2313                    decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2314                }
2315                RequestedRows::Indices(indices) => {
2316                    decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2317                }
2318            }
2319        };
2320        let scheduler_handle = tokio::task::spawn(scheduling);
2321        Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2322    }
2323}
2324
2325/// Initializes the scheduler, schedules the requested rows, and returns a
2326/// stream of decode tasks for the resulting batches.
2327///
2328/// This is a convenience function that creates both the scheduler and the
2329/// decoder, which can be a little tricky to get right.
2330///
2331/// # Why is this async?
2332///
2333/// Constructing the scheduler runs `initialize` which will perform I/O
2334/// unless the data required is already in the file metadata cache.
2335///
2336/// When `DecoderConfig::inline_scheduling` resolves to `true`, the
2337/// subsequent (synchronous) scheduling work also runs before this function
2338/// returns, leaving a fully primed decode stream.
2339pub async fn schedule_and_decode(
2340    column_infos: Vec<Arc<ColumnInfo>>,
2341    requested_rows: RequestedRows,
2342    filter: FilterExpression,
2343    column_indices: Vec<u32>,
2344    target_schema: Arc<Schema>,
2345    config: SchedulerDecoderConfig,
2346) -> Result<BoxStream<'static, ReadBatchTask>> {
2347    if requested_rows.num_rows() == 0 {
2348        return Ok(stream::empty().boxed());
2349    }
2350
2351    // If the user requested any ranges that are empty, ignore them.  They are pointless and
2352    // trying to read them has caused bugs in the past.
2353    let requested_rows = requested_rows.trim_empty_ranges();
2354
2355    let io = config.io.clone();
2356
2357    let stream = create_scheduler_decoder(
2358        column_infos,
2359        requested_rows,
2360        filter,
2361        column_indices,
2362        target_schema,
2363        config,
2364    )
2365    .await?;
2366
2367    // Keep the io alive until the stream is dropped or finishes.  Otherwise the
2368    // I/O drops as soon as the scheduling is finished and the I/O loop terminates.
2369    Ok(stream.finally(move || drop(io)).boxed())
2370}
2371
2372pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2373    tokio::runtime::Builder::new_current_thread()
2374        .build()
2375        .unwrap()
2376});
2377
2378/// Schedules and decodes the requested data in a blocking fashion
2379///
2380/// This function is a blocking version of [`schedule_and_decode`]. It schedules the requested data
2381/// and decodes it in the current thread.
2382///
2383/// This can be useful when the disk is fast (or the data is in memory) and the amount
2384/// of data is relatively small.  For example, when doing a take against NVMe or in-memory data.
2385///
2386/// This should NOT be used for full scans.  Even if the data is in memory this function will
2387/// not parallelize the decode and will be slower than the async version.  Full scans typically
2388/// make relatively few IOPs and so the asynchronous overhead is much smaller.
2389///
2390/// This method will first completely run the scheduling process.  Then it will run the
2391/// decode process.
2392pub fn schedule_and_decode_blocking(
2393    column_infos: Vec<Arc<ColumnInfo>>,
2394    requested_rows: RequestedRows,
2395    filter: FilterExpression,
2396    column_indices: Vec<u32>,
2397    target_schema: Arc<Schema>,
2398    config: SchedulerDecoderConfig,
2399) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2400    if requested_rows.num_rows() == 0 {
2401        let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2402        return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2403    }
2404
2405    let num_rows = requested_rows.num_rows();
2406    let is_structural = column_infos[0].is_structural();
2407
2408    let (tx, mut rx) = mpsc::unbounded_channel();
2409
2410    // Initialize the scheduler.  This is still "asynchronous" but we run it with a current-thread
2411    // runtime.
2412    let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new(
2413        target_schema.as_ref(),
2414        &column_indices,
2415        &column_infos,
2416        &vec![],
2417        num_rows,
2418        config.decoder_plugins,
2419        config.io.clone(),
2420        config.cache,
2421        &filter,
2422        &config.decoder_config,
2423    ))?;
2424
2425    // Schedule the requested rows
2426    match requested_rows {
2427        RequestedRows::Ranges(ranges) => {
2428            decode_scheduler.schedule_ranges(&ranges, &filter, tx, config.io)
2429        }
2430        RequestedRows::Indices(indices) => {
2431            decode_scheduler.schedule_take(&indices, &filter, tx, config.io)
2432        }
2433    }
2434
2435    // Drain the scheduler queue into a vec of decode messages
2436    let mut messages = Vec::new();
2437    while rx
2438        .recv_many(&mut messages, usize::MAX)
2439        .now_or_never()
2440        .unwrap()
2441        != 0
2442    {}
2443
2444    // Create a decoder to decode the messages
2445    let decode_iterator = create_decode_iterator(
2446        &target_schema,
2447        num_rows,
2448        config.batch_size,
2449        config.decoder_config.validate_on_decode,
2450        is_structural,
2451        messages.into(),
2452    )?;
2453
2454    Ok(decode_iterator)
2455}
2456
2457/// A decoder for single-column encodings of primitive data (this includes fixed size
2458/// lists of primitive data)
2459///
2460/// Physical decoders are able to decode into existing buffers for zero-copy operation.
2461///
2462/// Instances should be stateless and `Send` / `Sync`.  This is because multiple decode
2463/// tasks could reference the same page.  For example, imagine a page covers rows 0-2000
2464/// and the decoder stream has a batch size of 1024.  The decoder will be needed by both
2465/// the decode task for batch 0 and the decode task for batch 1.
2466///
2467/// See [`crate::decoder`] for more information
2468pub trait PrimitivePageDecoder: Send + Sync {
2469    /// Decode data into buffers
2470    ///
2471    /// This may be a simple zero-copy from a disk buffer or could involve complex decoding
2472    /// such as decompressing from some compressed representation.
2473    ///
2474    /// Capacity is stored as a tuple of (num_bytes: u64, is_needed: bool).  The `is_needed`
2475    /// portion only needs to be updated if the encoding has some concept of an "optional"
2476    /// buffer.
2477    ///
2478    /// Encodings can have any number of input or output buffers.  For example, a dictionary
2479    /// decoding will convert two buffers (indices + dictionary) into a single buffer
2480    ///
2481    /// Binary decodings have two output buffers (one for values, one for offsets)
2482    ///
2483    /// Other decodings could even expand the # of output buffers.  For example, we could decode
2484    /// fixed size strings into variable length strings going from one input buffer to multiple output
2485    /// buffers.
2486    ///
2487    /// Each Arrow data type typically has a fixed structure of buffers and the encoding chain will
2488    /// generally end at one of these structures.  However, intermediate structures may exist which
2489    /// do not correspond to any Arrow type at all.  For example, a bitpacking encoding will deal
2490    /// with buffers that have bits-per-value that is not a multiple of 8.
2491    ///
2492    /// The `primitive_array_from_buffers` method has an expected buffer layout for each arrow
2493    /// type (order matters) and encodings that aim to decode into arrow types should respect
2494    /// this layout.
2495    /// # Arguments
2496    ///
2497    /// * `rows_to_skip` - how many rows to skip (within the page) before decoding
2498    /// * `num_rows` - how many rows to decode
2499    /// * `all_null` - A mutable bool, set to true if a decoder determines all values are null
2500    fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2501}
2502
2503/// A scheduler for single-column encodings of primitive data
2504///
2505/// The scheduler is responsible for calculating what I/O is needed for the requested rows
2506///
2507/// Instances should be stateless and `Send` and `Sync`.  This is because instances can
2508/// be shared in follow-up I/O tasks.
2509///
2510/// See [`crate::decoder`] for more information
2511pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2512    /// Schedules a batch of I/O to load the data needed for the requested ranges
2513    ///
2514    /// Returns a future that will yield a decoder once the data has been loaded
2515    ///
2516    /// # Arguments
2517    ///
2518    /// * `range` - the range of row offsets (relative to start of page) requested
2519    ///   these must be ordered and must not overlap
2520    /// * `scheduler` - a scheduler to submit the I/O request to
2521    /// * `top_level_row` - the row offset of the top level field currently being
2522    ///   scheduled.  This can be used to assign priority to I/O requests
2523    fn schedule_ranges(
2524        &self,
2525        ranges: &[Range<u64>],
2526        scheduler: &Arc<dyn EncodingsIo>,
2527        top_level_row: u64,
2528    ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2529}
2530
2531/// A trait to control the priority of I/O
2532pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2533    fn advance(&mut self, num_rows: u64);
2534    fn current_priority(&self) -> u64;
2535    fn box_clone(&self) -> Box<dyn PriorityRange>;
2536}
2537
2538/// A simple priority scheme for top-level fields with no parent
2539/// repetition
2540#[derive(Debug)]
2541pub struct SimplePriorityRange {
2542    priority: u64,
2543}
2544
2545impl SimplePriorityRange {
2546    fn new(priority: u64) -> Self {
2547        Self { priority }
2548    }
2549}
2550
2551impl PriorityRange for SimplePriorityRange {
2552    fn advance(&mut self, num_rows: u64) {
2553        self.priority += num_rows;
2554    }
2555
2556    fn current_priority(&self) -> u64 {
2557        self.priority
2558    }
2559
2560    fn box_clone(&self) -> Box<dyn PriorityRange> {
2561        Box::new(Self {
2562            priority: self.priority,
2563        })
2564    }
2565}
2566
2567/// Determining the priority of a list request is tricky.  We want
2568/// the priority to be the top-level row.  So if we have a
2569/// `list<list<int>>` and each outer list has 10 rows and each inner
2570/// list has 5 rows then the priority of the 100th item is 1 because
2571/// it is the 5th item in the 10th item of the *second* row.
2572///
2573/// This structure allows us to keep track of this complicated priority
2574/// relationship.
2575///
2576/// There's a fair amount of bookkeeping involved here.
2577///
2578/// A better approach (using repetition levels) is coming in the future.
2579pub struct ListPriorityRange {
2580    base: Box<dyn PriorityRange>,
2581    offsets: Arc<[u64]>,
2582    cur_index_into_offsets: usize,
2583    cur_position: u64,
2584}
2585
2586impl ListPriorityRange {
2587    pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2588        Self {
2589            base,
2590            offsets,
2591            cur_index_into_offsets: 0,
2592            cur_position: 0,
2593        }
2594    }
2595}
2596
2597impl std::fmt::Debug for ListPriorityRange {
2598    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2599        f.debug_struct("ListPriorityRange")
2600            .field("base", &self.base)
2601            .field("offsets.len()", &self.offsets.len())
2602            .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2603            .field("cur_position", &self.cur_position)
2604            .finish()
2605    }
2606}
2607
2608impl PriorityRange for ListPriorityRange {
2609    fn advance(&mut self, num_rows: u64) {
2610        // We've scheduled X items.  Now walk through the offsets to
2611        // determine how many rows we've scheduled.
2612        self.cur_position += num_rows;
2613        let mut idx_into_offsets = self.cur_index_into_offsets;
2614        while idx_into_offsets + 1 < self.offsets.len()
2615            && self.offsets[idx_into_offsets + 1] <= self.cur_position
2616        {
2617            idx_into_offsets += 1;
2618        }
2619        let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2620        self.cur_index_into_offsets = idx_into_offsets;
2621        self.base.advance(base_rows_advanced as u64);
2622    }
2623
2624    fn current_priority(&self) -> u64 {
2625        self.base.current_priority()
2626    }
2627
2628    fn box_clone(&self) -> Box<dyn PriorityRange> {
2629        Box::new(Self {
2630            base: self.base.box_clone(),
2631            offsets: self.offsets.clone(),
2632            cur_index_into_offsets: self.cur_index_into_offsets,
2633            cur_position: self.cur_position,
2634        })
2635    }
2636}
2637
2638/// Contains the context for a scheduler
2639pub struct SchedulerContext {
2640    recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
2641    io: Arc<dyn EncodingsIo>,
2642    cache: Arc<LanceCache>,
2643    name: String,
2644    path: Vec<u32>,
2645    path_names: Vec<String>,
2646}
2647
2648pub struct ScopedSchedulerContext<'a> {
2649    pub context: &'a mut SchedulerContext,
2650}
2651
2652impl<'a> ScopedSchedulerContext<'a> {
2653    pub fn pop(self) -> &'a mut SchedulerContext {
2654        self.context.pop();
2655        self.context
2656    }
2657}
2658
2659impl SchedulerContext {
2660    pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
2661        Self {
2662            io,
2663            cache,
2664            recv: None,
2665            name: "".to_string(),
2666            path: Vec::new(),
2667            path_names: Vec::new(),
2668        }
2669    }
2670
2671    pub fn io(&self) -> &Arc<dyn EncodingsIo> {
2672        &self.io
2673    }
2674
2675    pub fn cache(&self) -> &Arc<LanceCache> {
2676        &self.cache
2677    }
2678
2679    pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
2680        self.path.push(index);
2681        self.path_names.push(name.to_string());
2682        ScopedSchedulerContext { context: self }
2683    }
2684
2685    pub fn pop(&mut self) {
2686        self.path.pop();
2687        self.path_names.pop();
2688    }
2689
2690    pub fn path_name(&self) -> String {
2691        let path = self.path_names.join("/");
2692        if self.recv.is_some() {
2693            format!("TEMP({}){}", self.name, path)
2694        } else {
2695            format!("ROOT{}", path)
2696        }
2697    }
2698
2699    pub fn current_path(&self) -> VecDeque<u32> {
2700        VecDeque::from_iter(self.path.iter().copied())
2701    }
2702
2703    #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
2704    pub fn locate_decoder(&mut self, decoder: Box<dyn LogicalPageDecoder>) -> DecoderReady {
2705        trace!(
2706            "Scheduling decoder of type {:?} for {:?}",
2707            decoder.data_type(),
2708            self.path,
2709        );
2710        DecoderReady {
2711            decoder,
2712            path: self.current_path(),
2713        }
2714    }
2715}
2716
2717pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
2718
2719impl std::fmt::Debug for UnloadedPageShard {
2720    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2721        f.debug_struct("UnloadedPage").finish()
2722    }
2723}
2724
2725#[derive(Debug)]
2726pub struct ScheduledScanLine {
2727    pub rows_scheduled: u64,
2728    pub decoders: Vec<MessageType>,
2729}
2730
2731pub trait StructuralSchedulingJob: std::fmt::Debug {
2732    /// Schedule the next batch of data
2733    ///
2734    /// Normally this equates to scheduling the next page of data into one task.  Very large pages
2735    /// might be split into multiple scan lines.  Each scan line has one or more rows.
2736    ///
2737    /// If a scheduler ends early it may return an empty vector.
2738    fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
2739}
2740
2741/// A filter expression to apply to the data
2742///
2743/// The core decoders do not currently take advantage of filtering in
2744/// any way.  In order to maintain the abstraction we represent filters
2745/// as an arbitrary byte sequence.
2746///
2747/// We recommend that encodings use Substrait for filters.
2748pub struct FilterExpression(pub Bytes);
2749
2750impl FilterExpression {
2751    /// Create a filter expression that does not filter any data
2752    ///
2753    /// This is currently represented by an empty byte array.  Encoders
2754    /// that are "filter aware" should make sure they handle this case.
2755    pub fn no_filter() -> Self {
2756        Self(Bytes::new())
2757    }
2758
2759    /// Returns true if the filter is the same as the [`Self::no_filter`] filter
2760    pub fn is_noop(&self) -> bool {
2761        self.0.is_empty()
2762    }
2763}
2764
2765pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
2766    fn initialize<'a>(
2767        &'a mut self,
2768        filter: &'a FilterExpression,
2769        context: &'a SchedulerContext,
2770    ) -> BoxFuture<'a, Result<()>>;
2771    fn schedule_ranges<'a>(
2772        &'a self,
2773        ranges: &[Range<u64>],
2774        filter: &FilterExpression,
2775    ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
2776}
2777
2778/// A trait for tasks that decode data into an Arrow array
2779pub trait DecodeArrayTask: Send {
2780    /// Decodes the data into an Arrow array and its data size in bytes
2781    fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
2782}
2783
2784impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
2785    fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
2786        let decoded_array = StructuralDecodeArrayTask::decode(*self)?;
2787        decoded_array.repdef.ensure_exhausted()?;
2788        Ok((decoded_array.array, decoded_array.data_size))
2789    }
2790}
2791
2792/// A task to decode data into an Arrow record batch
2793///
2794/// It has a child `task` which decodes a struct array with no nulls.
2795/// This is then converted into a record batch.
2796pub struct NextDecodeTask {
2797    /// The decode task itself
2798    pub task: Box<dyn DecodeArrayTask>,
2799    /// The number of rows that will be created
2800    pub num_rows: u64,
2801}
2802
2803impl NextDecodeTask {
2804    // Run the task and produce a record batch
2805    //
2806    // If the batch is very large this function will log a warning message
2807    // suggesting the user try a smaller batch size.
2808    #[instrument(name = "task_to_batch", level = "debug", skip_all)]
2809    fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
2810        let (struct_arr, data_size) = self.task.decode()?;
2811        let batch = RecordBatch::from(struct_arr.as_struct());
2812        if data_size > BATCH_SIZE_BYTES_WARNING {
2813            emitted_batch_size_warning.call_once(|| {
2814                let size_mb = data_size / 1024 / 1024;
2815                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);
2816            });
2817        }
2818        Ok((batch, data_size))
2819    }
2820}
2821
2822// An envelope to wrap both 2.0 style messages and 2.1 style messages so we can
2823// share some code paths between the two.  Decoders can safely unwrap into whatever
2824// style they expect since a file will be either all-2.0 or all-2.1
2825#[derive(Debug)]
2826pub enum MessageType {
2827    // The older v2.0 scheduler/decoder used a scheme where the message was the
2828    // decoder itself.  The messages were not sent in priority order and the decoder
2829    // had to wait for I/O, figuring out the correct priority.  This was a lot of
2830    // complexity.
2831    DecoderReady(DecoderReady),
2832    // Starting in 2.1 we use a simpler scheme where the scheduling happens in priority
2833    // order and the message is an unloaded decoder.  These can be awaited, in order, and
2834    // the decoder does not have to worry about waiting for I/O.
2835    UnloadedPage(UnloadedPageShard),
2836}
2837
2838impl MessageType {
2839    pub fn into_array(self) -> DecoderReady {
2840        match self {
2841            Self::DecoderReady(decoder) => decoder,
2842            Self::UnloadedPage(_) => {
2843                panic!("Expected DecoderReady but got UnloadedPage")
2844            }
2845        }
2846    }
2847
2848    pub fn into_structural(self) -> UnloadedPageShard {
2849        match self {
2850            Self::UnloadedPage(unloaded) => unloaded,
2851            Self::DecoderReady(_) => {
2852                panic!("Expected UnloadedPage but got DecoderReady")
2853            }
2854        }
2855    }
2856}
2857
2858pub struct DecoderMessage {
2859    pub scheduled_so_far: u64,
2860    pub decoders: Vec<MessageType>,
2861}
2862
2863pub struct DecoderContext {
2864    source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2865}
2866
2867impl DecoderContext {
2868    pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
2869        Self { source }
2870    }
2871}
2872
2873pub struct DecodedPage {
2874    pub data: DataBlock,
2875    pub repdef: RepDefUnraveler,
2876}
2877
2878pub trait DecodePageTask: Send + std::fmt::Debug {
2879    /// Decodes the data into an Arrow array
2880    fn decode(self: Box<Self>) -> Result<DecodedPage>;
2881}
2882
2883pub trait StructuralPageDecoder: std::fmt::Debug + Send {
2884    fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
2885    fn num_rows(&self) -> u64;
2886}
2887
2888#[derive(Debug)]
2889pub struct LoadedPageShard {
2890    // The decoder that is ready to be decoded
2891    pub decoder: Box<dyn StructuralPageDecoder>,
2892    // The path to the decoder, the first value is the column index
2893    // following values, if present, are nested child indices
2894    //
2895    // For example, a path of [1, 1, 0] would mean to grab the second
2896    // column, then the second child, and then the first child.
2897    //
2898    // It could represent x in the following schema:
2899    //
2900    // score: float64
2901    // points: struct
2902    //   color: string
2903    //   location: struct
2904    //     x: float64
2905    //
2906    // Currently, only struct decoders have "children" although other
2907    // decoders may at some point as well.  List children are only
2908    // handled through indirect I/O at the moment and so they don't
2909    // need to be represented (yet)
2910    pub path: VecDeque<u32>,
2911}
2912
2913pub struct DecodedArray {
2914    pub array: ArrayRef,
2915    pub repdef: CompositeRepDefUnraveler,
2916    /// The number of bytes of data in this array (excluding Arrow overhead).
2917    pub data_size: u64,
2918}
2919
2920pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
2921    fn decode(self: Box<Self>) -> Result<DecodedArray>;
2922}
2923
2924pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
2925    /// Add a newly scheduled child decoder
2926    ///
2927    /// The default implementation does not expect children and returns
2928    /// an error.
2929    fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
2930    /// Creates a task to decode `num_rows` of data into an array
2931    fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
2932    /// The data type of the decoded data
2933    fn data_type(&self) -> &DataType;
2934}
2935
2936#[derive(Debug, Default)]
2937pub struct DecoderPlugins {}
2938
2939/// The top-level column layout used by an in-memory encoded batch.
2940#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2941pub enum EncodedBatchLayout {
2942    /// Array pages include structural columns.
2943    Array,
2944    /// Structural pages include only leaf columns.
2945    Structural,
2946}
2947
2948/// Decodes a batch of data from an in-memory structure created by [`crate::encoder::encode_batch`]
2949pub async fn decode_batch(
2950    batch: &EncodedBatch,
2951    filter: &FilterExpression,
2952    decoder_plugins: Arc<DecoderPlugins>,
2953    should_validate: bool,
2954    layout: EncodedBatchLayout,
2955    cache: Option<Arc<LanceCache>>,
2956) -> Result<RecordBatch> {
2957    // The io is synchronous so it shouldn't be possible for any async stuff to still be in progress
2958    // Still, if we just use now_or_never we hit misfires because some futures (channels) need to be
2959    // polled twice.
2960
2961    let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
2962    let cache = if let Some(cache) = cache {
2963        cache
2964    } else {
2965        Arc::new(lance_core::cache::LanceCache::with_capacity(
2966            128 * 1024 * 1024,
2967        ))
2968    };
2969    let mut decode_scheduler = DecodeBatchScheduler::try_new(
2970        batch.schema.as_ref(),
2971        &batch.top_level_columns,
2972        &batch.page_table,
2973        &vec![],
2974        batch.num_rows,
2975        decoder_plugins,
2976        io_scheduler.clone(),
2977        cache,
2978        filter,
2979        &DecoderConfig::default(),
2980    )
2981    .await?;
2982    let (tx, rx) = unbounded_channel();
2983    decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
2984    let is_structural = layout == EncodedBatchLayout::Structural;
2985    let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2986    let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
2987    let mut decode_stream = create_decode_stream(
2988        &batch.schema,
2989        batch.num_rows,
2990        batch.num_rows as u32,
2991        is_structural,
2992        should_validate,
2993        spawn_structural_batch_decode_tasks,
2994        rx,
2995        None,
2996    )?;
2997    decode_stream.next().await.unwrap().task.await
2998}
2999
3000#[cfg(test)]
3001// test coalesce indices to ranges
3002mod tests {
3003    use super::*;
3004    use std::collections::VecDeque;
3005
3006    #[derive(Debug)]
3007    struct FailingPageDecoder {
3008        page_data_type: DataType,
3009        total_rows: u64,
3010        load_error_message: &'static str,
3011    }
3012
3013    impl FailingPageDecoder {
3014        fn new(
3015            page_data_type: DataType,
3016            total_rows: u64,
3017            load_error_message: &'static str,
3018        ) -> Self {
3019            Self {
3020                page_data_type,
3021                total_rows,
3022                load_error_message,
3023            }
3024        }
3025    }
3026
3027    impl LogicalPageDecoder for FailingPageDecoder {
3028        fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
3029            let load_error_message = self.load_error_message;
3030            async move { Err(Error::io(load_error_message)) }.boxed()
3031        }
3032
3033        fn rows_loaded(&self) -> u64 {
3034            0
3035        }
3036
3037        fn num_rows(&self) -> u64 {
3038            self.total_rows
3039        }
3040
3041        fn rows_drained(&self) -> u64 {
3042            0
3043        }
3044
3045        fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
3046            Err(Error::internal(format!(
3047                "failing page decoder should not be drained after load error \
3048                 (requested_rows={})",
3049                requested_rows
3050            )))
3051        }
3052
3053        fn data_type(&self) -> &DataType {
3054            &self.page_data_type
3055        }
3056    }
3057
3058    struct InvalidInputDecodeTask;
3059
3060    impl DecodeArrayTask for InvalidInputDecodeTask {
3061        fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3062            Err(Error::invalid_input_source("malformed sparse page".into()))
3063        }
3064    }
3065
3066    #[test]
3067    fn next_decode_task_preserves_invalid_input_errors() {
3068        let err = NextDecodeTask {
3069            task: Box::new(InvalidInputDecodeTask),
3070            num_rows: 0,
3071        }
3072        .into_batch(Arc::new(Once::new()))
3073        .unwrap_err();
3074        assert!(matches!(err, Error::InvalidInput { .. }));
3075    }
3076
3077    #[test]
3078    fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
3079        // Simulates reading a column whose stored schema declares a
3080        // zero-dimension FixedSizeList, as old writers (before #5102) could
3081        // persist. The read plan is built by the field-scheduler factories,
3082        // which run the dimension guard before touching any column data, so
3083        // an empty column iterator is sufficient to reach the guard. The read
3084        // must surface a clean error rather than a divide-by-zero panic.
3085        use arrow_schema::Field as ArrowField;
3086
3087        let zero_dim = DataType::FixedSizeList(
3088            Arc::new(ArrowField::new("item", DataType::Float32, true)),
3089            0,
3090        );
3091        let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3092        let strategy = CoreFieldDecoderStrategy::default();
3093
3094        let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3095        let err = strategy
3096            .create_structural_field_scheduler(&field, &mut structural_columns)
3097            .unwrap_err();
3098        assert!(
3099            err.to_string()
3100                .contains("dimension must be a positive integer"),
3101            "unexpected error: {}",
3102            err
3103        );
3104
3105        let mut array_columns = ColumnInfoIter::new(vec![], &[]);
3106        let err = strategy
3107            .create_array_field_scheduler(
3108                &field,
3109                &mut array_columns,
3110                FileBuffers {
3111                    positions_and_sizes: &[],
3112                },
3113            )
3114            .unwrap_err();
3115        assert!(
3116            err.to_string()
3117                .contains("dimension must be a positive integer"),
3118            "unexpected error: {}",
3119            err
3120        );
3121    }
3122
3123    #[test]
3124    fn test_list_page_with_non_list_encoding_returns_error() {
3125        let item = Arc::new(ArrowField::new("item", DataType::Int32, true));
3126        let list = DataType::List(item);
3127        let field = Field::try_from(&ArrowField::new("values", list, true)).unwrap();
3128        let values_encoding = pb::ColumnEncoding {
3129            column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
3130        };
3131        let offsets_column = Arc::new(ColumnInfo::new(
3132            0,
3133            Arc::new([PageInfo {
3134                num_rows: 1,
3135                priority: 0,
3136                encoding: PageEncoding::Legacy(pb::ArrayEncoding {
3137                    array_encoding: Some(pb::array_encoding::ArrayEncoding::Flat(
3138                        pb::Flat::default(),
3139                    )),
3140                }),
3141                buffer_offsets_and_sizes: Arc::new([]),
3142            }]),
3143            vec![],
3144            values_encoding.clone(),
3145        ));
3146        let items_column = Arc::new(ColumnInfo::new(1, Arc::new([]), vec![], values_encoding));
3147        let column_indices = [0, 1];
3148        let mut columns = ColumnInfoIter::new(vec![offsets_column, items_column], &column_indices);
3149
3150        let err = CoreFieldDecoderStrategy::default()
3151            .create_array_field_scheduler(
3152                &field,
3153                &mut columns,
3154                FileBuffers {
3155                    positions_and_sizes: &[],
3156                },
3157            )
3158            .unwrap_err();
3159
3160        assert!(matches!(err, Error::InvalidInput { .. }));
3161        assert!(
3162            err.to_string()
3163                .contains("expected list encoding for field 'values' in column 0, page 0 but got"),
3164            "unexpected error: {err}"
3165        );
3166    }
3167
3168    #[tokio::test]
3169    async fn test_array_stream_stops_on_load_error() {
3170        use arrow_schema::Field as ArrowField;
3171
3172        let rows_per_batch = 1;
3173        let total_rows = 2;
3174        let scheduled_rows = 1;
3175        let page_rows = 1;
3176        let batch_readahead = 2;
3177        let load_error_message = "simulated page load failure";
3178        let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3179        let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3180        let (tx, rx) = unbounded_channel();
3181
3182        tx.send(Ok(DecoderMessage {
3183            scheduled_so_far: scheduled_rows,
3184            decoders: vec![MessageType::DecoderReady(DecoderReady {
3185                decoder: Box::new(FailingPageDecoder::new(
3186                    DataType::Float32,
3187                    page_rows,
3188                    load_error_message,
3189                )),
3190                path: VecDeque::from([0]),
3191            })],
3192        }))
3193        .unwrap();
3194        drop(tx);
3195
3196        let stream =
3197            BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3198        let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3199
3200        let err = batches
3201            .next()
3202            .await
3203            .expect("stream should emit the array page-load error")
3204            .unwrap_err();
3205        assert!(
3206            err.to_string().contains(load_error_message),
3207            "unexpected error: {}",
3208            err
3209        );
3210        assert!(
3211            batches.next().await.is_none(),
3212            "stream should stop after the array page-load error"
3213        );
3214    }
3215
3216    #[tokio::test]
3217    async fn test_structural_stream_stops_on_load_error() {
3218        let rows_per_batch = 1;
3219        let total_rows = 2;
3220        let scheduled_rows = 1;
3221        let batch_readahead = 2;
3222        let load_error_message = "simulated page load failure";
3223        let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3224        let root_decoder = StructuralStructDecoder::new(fields, false, /*is_root=*/ true).unwrap();
3225        let (tx, rx) = unbounded_channel();
3226        let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3227
3228        tx.send(Ok(DecoderMessage {
3229            scheduled_so_far: scheduled_rows,
3230            decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3231        }))
3232        .unwrap();
3233        drop(tx);
3234
3235        let stream = StructuralBatchDecodeStream::new(
3236            rx,
3237            rows_per_batch,
3238            total_rows,
3239            root_decoder,
3240            /*spawn_batch_decode_tasks=*/ true,
3241            None,
3242        )
3243        .into_stream();
3244        let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3245
3246        let err = batches
3247            .next()
3248            .await
3249            .expect("stream should emit the page-load error")
3250            .unwrap_err();
3251        assert!(
3252            err.to_string().contains(load_error_message),
3253            "unexpected error: {}",
3254            err
3255        );
3256        assert!(
3257            batches.next().await.is_none(),
3258            "stream should stop after the page-load error"
3259        );
3260    }
3261
3262    #[test]
3263    fn test_coalesce_indices_to_ranges_with_single_index() {
3264        let indices = vec![1];
3265        let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3266        assert_eq!(ranges, vec![1..2]);
3267    }
3268
3269    #[test]
3270    fn test_coalesce_indices_to_ranges() {
3271        let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3272        let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3273        assert_eq!(ranges, vec![1..10]);
3274    }
3275
3276    #[test]
3277    fn test_coalesce_indices_to_ranges_with_gaps() {
3278        let indices = vec![1, 2, 3, 5, 6, 7, 9];
3279        let ranges = DecodeBatchScheduler::indices_to_ranges(&indices);
3280        assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3281    }
3282
3283    #[test]
3284    fn test_estimate_bytes_per_row() {
3285        assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3286        assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3287        assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3288        assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3289        assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3290        assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3291        // Struct of 4 x Int32 = 16 bytes
3292        let struct_type = DataType::Struct(Fields::from(vec![
3293            ArrowField::new("a", DataType::Int32, false),
3294            ArrowField::new("b", DataType::Int32, false),
3295            ArrowField::new("c", DataType::Int32, false),
3296            ArrowField::new("d", DataType::Int32, false),
3297        ]));
3298        assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3299    }
3300
3301    /// Helper: encode a batch, then decode it as a stream with optional
3302    /// `batch_size_bytes`, collecting all output batches.
3303    async fn decode_batches_with_byte_limit(
3304        batch: &RecordBatch,
3305        batch_size: u32,
3306        batch_size_bytes: Option<u64>,
3307    ) -> Vec<RecordBatch> {
3308        use crate::{
3309            encoder::{EncodingOptions, encode_batch},
3310            testing::{TestEncoding, test_encoding_strategy},
3311        };
3312
3313        let version = TestEncoding::StructuralU16;
3314        let options = EncodingOptions::default();
3315        let strategy = test_encoding_strategy(version);
3316        let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3317        let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3318            .await
3319            .unwrap();
3320
3321        let io_scheduler =
3322            Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
3323        let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
3324            128 * 1024 * 1024,
3325        ));
3326        let decoder_plugins = Arc::new(DecoderPlugins::default());
3327
3328        let mut decode_scheduler = DecodeBatchScheduler::try_new(
3329            encoded.schema.as_ref(),
3330            &encoded.top_level_columns,
3331            &encoded.page_table,
3332            &vec![],
3333            encoded.num_rows,
3334            decoder_plugins,
3335            io_scheduler.clone(),
3336            cache,
3337            &FilterExpression::no_filter(),
3338            &DecoderConfig::default(),
3339        )
3340        .await
3341        .unwrap();
3342
3343        let (tx, rx) = unbounded_channel();
3344        decode_scheduler.schedule_range(
3345            0..encoded.num_rows,
3346            &FilterExpression::no_filter(),
3347            tx,
3348            io_scheduler,
3349        );
3350
3351        let mut decode_stream = create_decode_stream(
3352            &encoded.schema,
3353            encoded.num_rows,
3354            batch_size,
3355            /*is_structural=*/ true,
3356            /*should_validate=*/ true,
3357            /*spawn_structural_batch_decode_tasks=*/ true,
3358            rx,
3359            batch_size_bytes,
3360        )
3361        .unwrap();
3362
3363        let mut batches = Vec::new();
3364        while let Some(task) = decode_stream.next().await {
3365            batches.push(task.task.await.unwrap());
3366        }
3367        batches
3368    }
3369
3370    #[tokio::test]
3371    async fn test_byte_sized_batches_fixed_width() {
3372        use arrow_array::Int32Array;
3373
3374        // 1000 rows x 4 Int32 columns = 16 bytes/row
3375        let num_rows: i32 = 1000;
3376        let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
3377            .map(|col| {
3378                Arc::new(Int32Array::from_iter_values(
3379                    (0..num_rows).map(move |row| row * 10 + col),
3380                )) as _
3381            })
3382            .collect();
3383
3384        let schema = Arc::new(ArrowSchema::new(vec![
3385            ArrowField::new("a", DataType::Int32, false),
3386            ArrowField::new("b", DataType::Int32, false),
3387            ArrowField::new("c", DataType::Int32, false),
3388            ArrowField::new("d", DataType::Int32, false),
3389        ]));
3390        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3391
3392        // 16 bytes/row, batch_size_bytes=1600 => 100 rows/batch
3393        let batches =
3394            decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 1024, Some(1600)).await;
3395
3396        // Should produce 10 batches of 100 rows each
3397        assert_eq!(batches.len(), 10);
3398        for (i, batch) in batches.iter().enumerate() {
3399            assert_eq!(
3400                batch.num_rows(),
3401                100,
3402                "batch {i} should have 100 rows, got {}",
3403                batch.num_rows()
3404            );
3405        }
3406
3407        // Verify roundtrip: concatenate and compare
3408        let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3409        let concatenated =
3410            arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3411                .unwrap();
3412        assert_eq!(concatenated.num_rows(), num_rows as usize);
3413        for col in 0..4 {
3414            assert_eq!(
3415                concatenated.column(col).as_ref(),
3416                input_batch.column(col).as_ref(),
3417                "column {col} roundtrip mismatch"
3418            );
3419        }
3420    }
3421
3422    #[tokio::test]
3423    async fn test_byte_sized_batches_none_unchanged() {
3424        use arrow_array::Int32Array;
3425
3426        // Without batch_size_bytes, rows_per_batch controls batching
3427        let num_rows: i32 = 1000;
3428        let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
3429            .map(|col| {
3430                Arc::new(Int32Array::from_iter_values(
3431                    (0..num_rows).map(move |row| row * 10 + col),
3432                )) as _
3433            })
3434            .collect();
3435
3436        let schema = Arc::new(ArrowSchema::new(vec![
3437            ArrowField::new("x", DataType::Int32, false),
3438            ArrowField::new("y", DataType::Int32, false),
3439        ]));
3440        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3441
3442        // batch_size=250, batch_size_bytes=None => 4 batches of 250 rows
3443        let batches = decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 250, None).await;
3444        assert_eq!(batches.len(), 4);
3445        for (i, batch) in batches.iter().enumerate() {
3446            assert_eq!(
3447                batch.num_rows(),
3448                250,
3449                "batch {i} should have 250 rows, got {}",
3450                batch.num_rows()
3451            );
3452        }
3453    }
3454
3455    #[tokio::test]
3456    async fn test_byte_sized_batches_respect_row_limit() {
3457        use arrow_array::Int32Array;
3458
3459        let num_rows: i32 = 1000;
3460        let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
3461            "x",
3462            DataType::Int32,
3463            false,
3464        )]));
3465        let input_batch = RecordBatch::try_new(
3466            schema,
3467            vec![Arc::new(Int32Array::from_iter_values(0..num_rows))],
3468        )
3469        .unwrap();
3470
3471        // The byte limit can hold every row, so the 100-row limit must win.
3472        let batches =
3473            decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 100, Some(10_000)).await;
3474        assert_eq!(batches.len(), 10);
3475        assert!(batches.iter().all(|batch| batch.num_rows() == 100));
3476    }
3477
3478    #[tokio::test]
3479    async fn test_byte_sized_batches_feedback_convergence() {
3480        use arrow_array::StringArray;
3481
3482        // Each row has a 100-byte string. Schema estimate = 64 bytes (default
3483        // for Utf8), so the first batch will overshoot. The feedback loop
3484        // should correct subsequent batches toward the target.
3485        let num_rows = 500;
3486        let value: String = "x".repeat(100);
3487        let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
3488            (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
3489        ))];
3490        let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
3491            "s",
3492            DataType::Utf8,
3493            false,
3494        )]));
3495        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
3496
3497        // Target 5000 bytes/batch. At 100 bytes/row the ideal is 50 rows/batch.
3498        // Schema estimate is 64 bytes/row → first batch ~78 rows (overshoot).
3499        // After feedback kicks in, batches should converge to ~50 rows.
3500        let target_bytes: u64 = 5000;
3501        let batches = decode_batches_with_byte_limit(
3502            &input_batch,
3503            /*batch_size=*/ 1024,
3504            Some(target_bytes),
3505        )
3506        .await;
3507
3508        // Verify all data round-trips correctly
3509        let all_batches: Vec<&RecordBatch> = batches.iter().collect();
3510        let concatenated =
3511            arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
3512                .unwrap();
3513        assert_eq!(concatenated.num_rows(), num_rows as usize);
3514        assert_eq!(
3515            concatenated.column(0).as_ref(),
3516            input_batch.column(0).as_ref()
3517        );
3518
3519        // After the first batch, subsequent batches should be closer to the
3520        // target. The ideal is 50 rows/batch.
3521        assert!(
3522            batches.len() >= 2,
3523            "need at least 2 batches to test convergence"
3524        );
3525        // The first batch uses the schema estimate (64 bytes/row) →
3526        // ~78 rows. After feedback the rows should settle near 50.
3527        if batches.len() >= 3 {
3528            let second_batch_rows = batches[1].num_rows();
3529            let third_batch_rows = batches[2].num_rows();
3530            // Both should be within 20% of the ideal (50 rows)
3531            assert!(
3532                (40..=60).contains(&second_batch_rows),
3533                "second batch should be near 50 rows, got {second_batch_rows}"
3534            );
3535            assert!(
3536                (40..=60).contains(&third_batch_rows),
3537                "third batch should be near 50 rows, got {third_batch_rows}"
3538            );
3539        }
3540    }
3541}