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, Mutex, Once, OnceLock};
218use std::{ops::Range, sync::Arc};
219
220use arrow_array::cast::AsArray;
221use arrow_array::{ArrayRef, RecordBatch, RecordBatchIterator, RecordBatchReader};
222use arrow_schema::{ArrowError, DataType, Field as ArrowField, Fields, Schema as ArrowSchema};
223use bytes::Bytes;
224use futures::channel::oneshot;
225use futures::future::{BoxFuture, MaybeDone, Shared, maybe_done};
226use futures::stream::{self, BoxStream};
227use futures::{FutureExt, StreamExt};
228use lance_arrow::DataTypeExt;
229use lance_core::cache::{Context, DeepSizeOf, LanceCache};
230use lance_core::datatypes::{
231    BLOB_DESC_LANCE_FIELD, Field, Schema, validate_fixed_size_list_dimensions,
232};
233use lance_core::utils::futures::{FinallyStreamExt, StreamOnDropExt};
234use lance_core::utils::parse::parse_env_as_bool;
235use log::{debug, trace, warn};
236use prost::Message;
237use tokio::sync::mpsc::error::SendError;
238use tokio::sync::mpsc::{self, unbounded_channel};
239
240use lance_core::error::{CloneableError, LanceOptionExt};
241use lance_core::{ArrowResult, Error, Result};
242use tracing::instrument;
243
244use crate::array_encoding::logical::list::OffsetPageInfo;
245use crate::array_encoding::logical::r#struct::{SimpleStructDecoder, SimpleStructScheduler};
246use crate::array_encoding::logical::{
247    binary::BinaryFieldScheduler, blob::BlobFieldScheduler, list::ListFieldScheduler,
248    primitive::PrimitiveFieldScheduler,
249};
250use crate::compression::{DecompressionStrategy, DefaultDecompressionStrategy};
251use crate::data::DataBlock;
252use crate::encoder::EncodedBatch;
253use crate::encodings::logical::fixed_size_list::StructuralFixedSizeListScheduler;
254use crate::encodings::logical::list::StructuralListScheduler;
255use crate::encodings::logical::map::StructuralMapScheduler;
256use crate::encodings::logical::primitive::StructuralPrimitiveFieldScheduler;
257use crate::encodings::logical::r#struct::{StructuralStructDecoder, StructuralStructScheduler};
258use crate::format::metadata;
259use crate::format::pb::{self, column_encoding};
260use crate::repdef::{CompositeRepDefUnraveler, RepDefUnraveler};
261use crate::{BufferScheduler, EncodingsIo};
262
263/// Candidate batch sizes evaluated during byte-budget planning.
264/// Powers of 4, covering 1–16Ki rows in 8 probes.
265pub const CANDIDATE_BATCH_SIZES: [u32; 8] = [1, 4, 16, 64, 256, 1024, 4096, 16384];
266
267pub trait SchedulingJob: std::fmt::Debug {
268    fn schedule_next(
269        &mut self,
270        context: &mut SchedulerContext,
271        priority: &dyn PriorityRange,
272    ) -> Result<ScheduledScanLine>;
273
274    fn num_rows(&self) -> u64;
275}
276
277/// Schedules the I/O needed to decode one field.
278pub trait FieldScheduler: Send + Sync + std::fmt::Debug {
279    fn initialize<'a>(
280        &'a self,
281        filter: &'a FilterExpression,
282        context: &'a SchedulerContext,
283    ) -> BoxFuture<'a, Result<()>>;
284
285    fn schedule_ranges<'a>(
286        &'a self,
287        ranges: &[Range<u64>],
288        filter: &FilterExpression,
289    ) -> Result<Box<dyn SchedulingJob + 'a>>;
290
291    fn num_rows(&self) -> u64;
292}
293
294#[derive(Debug)]
295pub struct DecoderReady {
296    pub decoder: Box<dyn LogicalPageDecoder>,
297    pub path: VecDeque<u32>,
298}
299
300/// Stateful decoder for one logical page.
301pub trait LogicalPageDecoder: std::fmt::Debug + Send {
302    fn accept_child(&mut self, _child: DecoderReady) -> Result<()> {
303        Err(Error::internal(format!(
304            "The decoder {:?} does not expect children but received a child",
305            self
306        )))
307    }
308
309    fn wait_for_loaded(&'_ mut self, loaded_need: u64) -> BoxFuture<'_, Result<()>>;
310
311    fn rows_loaded(&self) -> u64;
312
313    fn rows_unloaded(&self) -> u64 {
314        self.num_rows() - self.rows_loaded()
315    }
316
317    fn num_rows(&self) -> u64;
318
319    fn rows_drained(&self) -> u64;
320
321    fn rows_left(&self) -> u64 {
322        self.num_rows() - self.rows_drained()
323    }
324
325    fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
326
327    /// Returns how many of the requested rows can be decoded into one Arrow array
328    /// while keeping every i32 offset buffer within `byte_budget` bytes.
329    ///
330    /// Most page decoders can always drain the full request and consume no budget.
331    /// Decoders that build arrays with i32 offsets (Utf8 / Binary / List) may return
332    /// fewer rows so that the concatenated array's offsets stay representable, and
333    /// report the budget those rows consume so callers can accumulate across pages.
334    fn max_rows_to_drain(&self, num_rows: u64, _byte_budget: u64) -> Result<DrainLimit> {
335        Ok(DrainLimit {
336            rows: num_rows,
337            bytes: 0,
338        })
339    }
340
341    fn data_type(&self) -> &DataType;
342}
343
344/// Result of limiting a drain request to a variable-width byte budget.
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346pub struct DrainLimit {
347    /// How many of the requested rows can be decoded into one Arrow array
348    pub rows: u64,
349    /// The variable-width bytes (at the most constrained i32-offset nesting level)
350    /// those rows consume from the caller's budget
351    pub bytes: u64,
352}
353
354/// The byte budget applied to each output batch of an i32-offset column.
355///
356/// Concatenating decoded pages into one Arrow array fails when a variable-width
357/// buffer's offsets exceed `i32::MAX`, so batches are split before that point.
358pub(crate) const I32_OFFSET_BYTE_BUDGET: u64 = i32::MAX as u64;
359
360/// Clamp a batch's row count to the byte-budgeted limit.
361///
362/// A batch always advances by at least one row: a first row whose (possibly
363/// over-estimated) size exceeds the whole budget is emitted alone.  A genuinely
364/// oversized row then surfaces the same arrow-level offset error it always did,
365/// while an over-estimated one decodes fine.
366fn clamp_rows_to_i32_budget(limit: DrainLimit, to_take: u64) -> u64 {
367    if to_take == 0 {
368        return 0;
369    }
370    limit.rows.clamp(1, to_take)
371}
372
373/// Returns whether concatenating arrays of this type can overflow an i32 offset buffer.
374pub(crate) fn has_i32_offsets(data_type: &DataType) -> bool {
375    match data_type {
376        DataType::Binary | DataType::Utf8 | DataType::List(_) | DataType::ListView(_) => true,
377        DataType::Map(_, _) => true,
378        DataType::LargeList(field)
379        | DataType::LargeListView(field)
380        | DataType::FixedSizeList(field, _) => has_i32_offsets(field.data_type()),
381        DataType::Struct(fields) => fields
382            .iter()
383            .any(|field| has_i32_offsets(field.data_type())),
384        DataType::Dictionary(_, values) => has_i32_offsets(values),
385        DataType::RunEndEncoded(_, values) => has_i32_offsets(values.data_type()),
386        _ => false,
387    }
388}
389
390// If users are getting batches over 10MiB large then it's time to reduce the batch size
391const BATCH_SIZE_BYTES_WARNING: u64 = 10 * 1024 * 1024;
392const ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE: &str =
393    "LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE";
394const ENV_LANCE_READ_CACHE_REPETITION_INDEX: &str = "LANCE_READ_CACHE_REPETITION_INDEX";
395const ENV_LANCE_INLINE_SCHEDULING_THRESHOLD: &str = "LANCE_INLINE_SCHEDULING_THRESHOLD";
396
397// If a request is for at most this many rows we skip the scheduler-task spawn
398// and run scheduling inline as part of the `schedule_and_decode` await.
399const DEFAULT_INLINE_SCHEDULING_THRESHOLD: u64 = 16 * 1024;
400
401fn default_cache_repetition_index() -> bool {
402    static DEFAULT_CACHE_REPETITION_INDEX: OnceLock<bool> = OnceLock::new();
403    *DEFAULT_CACHE_REPETITION_INDEX
404        .get_or_init(|| parse_env_as_bool(ENV_LANCE_READ_CACHE_REPETITION_INDEX, true))
405}
406
407fn inline_scheduling_threshold() -> u64 {
408    static THRESHOLD: OnceLock<u64> = OnceLock::new();
409    *THRESHOLD.get_or_init(|| {
410        std::env::var(ENV_LANCE_INLINE_SCHEDULING_THRESHOLD)
411            .ok()
412            .and_then(|v| v.trim().parse::<u64>().ok())
413            .unwrap_or(DEFAULT_INLINE_SCHEDULING_THRESHOLD)
414    })
415}
416
417/// A page encoding and the metadata used to execute it.
418///
419/// A file uses the metadata grammar associated with its version: array encodings
420/// in 2.0, structural page layouts in 2.1/2.2, and planar page layouts in 2.3.
421#[derive(Debug, Clone)]
422pub enum PageEncoding {
423    /// The frozen 2.0 array grammar.
424    Legacy(pb::ArrayEncoding),
425    /// Execution metadata decoded from or destined for the stable 2.1/2.2 grammar.
426    Structural(metadata::PageLayout),
427    /// The unstable 2.3 independent structure and value planes.
428    Planar(crate::format::pb23::PlanarLayout),
429}
430
431impl From<metadata::PageLayout> for PageEncoding {
432    fn from(layout: metadata::PageLayout) -> Self {
433        Self::Structural(layout)
434    }
435}
436
437impl From<crate::format::pb21::PageLayout> for PageEncoding {
438    fn from(layout: crate::format::pb21::PageLayout) -> Self {
439        Self::Structural(layout.into())
440    }
441}
442
443impl DeepSizeOf for PageEncoding {
444    fn deep_size_of_children(&self, _context: &mut Context) -> usize {
445        match self {
446            Self::Legacy(encoding) => encoding.encoded_len() * 4,
447            Self::Structural(encoding) => encoding.estimated_size(),
448            Self::Planar(encoding) => encoding.encoded_len() * 4,
449        }
450    }
451}
452
453impl PageEncoding {
454    pub fn as_legacy(&self) -> &pb::ArrayEncoding {
455        match self {
456            Self::Legacy(enc) => enc,
457            Self::Structural(_) | Self::Planar(_) => panic!("Expected a legacy encoding"),
458        }
459    }
460
461    pub fn as_structural(&self) -> &metadata::PageLayout {
462        match self {
463            Self::Structural(enc) => enc,
464            Self::Legacy(_) | Self::Planar(_) => panic!("Expected a structural encoding"),
465        }
466    }
467
468    pub fn is_structural(&self) -> bool {
469        matches!(self, Self::Structural(_) | Self::Planar(_))
470    }
471}
472
473/// Metadata describing a page in a file
474///
475/// This is typically created by reading the metadata section of a Lance file
476#[derive(Debug)]
477pub struct PageInfo {
478    /// The number of rows in the page
479    pub num_rows: u64,
480    /// The priority (top level row number) of the page
481    ///
482    /// This is only set in 2.1 files and will be 0 for 2.0 files
483    pub priority: u64,
484    /// The encoding that explains the buffers in the page
485    pub encoding: PageEncoding,
486    /// The offsets and sizes of the buffers in the file
487    pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
488}
489
490impl DeepSizeOf for PageInfo {
491    fn deep_size_of_children(&self, context: &mut Context) -> usize {
492        self.encoding.deep_size_of_children(context)
493            + self.buffer_offsets_and_sizes.deep_size_of_children(context)
494    }
495}
496
497/// Metadata describing a column in a file
498///
499/// This is typically created by reading the metadata section of a Lance file
500#[derive(Debug, Clone)]
501pub struct ColumnInfo {
502    /// The index of the column in the file
503    pub index: u32,
504    /// The metadata for each page in the column
505    pub page_infos: Arc<[PageInfo]>,
506    /// File positions and their sizes of the column-level buffers
507    pub buffer_offsets_and_sizes: Arc<[(u64, u64)]>,
508    pub encoding: pb::ColumnEncoding,
509}
510
511impl DeepSizeOf for ColumnInfo {
512    fn deep_size_of_children(&self, context: &mut Context) -> usize {
513        self.page_infos.deep_size_of_children(context)
514            + self.buffer_offsets_and_sizes.deep_size_of_children(context)
515            + self.encoding.encoded_len() * 4
516    }
517}
518
519impl ColumnInfo {
520    /// Create a new instance
521    pub fn new(
522        index: u32,
523        page_infos: Arc<[PageInfo]>,
524        buffer_offsets_and_sizes: Vec<(u64, u64)>,
525        encoding: pb::ColumnEncoding,
526    ) -> Self {
527        Self {
528            index,
529            page_infos,
530            buffer_offsets_and_sizes: buffer_offsets_and_sizes.into_boxed_slice().into(),
531            encoding,
532        }
533    }
534
535    pub fn is_structural(&self) -> bool {
536        self.page_infos
537            // Can just look at the first since all should be the same
538            .first()
539            .map(|page| page.encoding.is_structural())
540            .unwrap_or(false)
541    }
542}
543
544enum RootScheduler {
545    Structural(Box<dyn StructuralFieldScheduler>),
546    Array(Arc<dyn FieldScheduler>),
547}
548
549impl RootScheduler {
550    fn as_array(&self) -> &Arc<dyn FieldScheduler> {
551        match self {
552            Self::Structural(_) => panic!("Expected an array scheduler"),
553            Self::Array(s) => s,
554        }
555    }
556
557    fn as_structural(&self) -> &dyn StructuralFieldScheduler {
558        match self {
559            Self::Structural(s) => s.as_ref(),
560            Self::Array(_) => panic!("Expected a structural scheduler"),
561        }
562    }
563}
564
565/// The scheduler for decoding batches
566///
567/// Lance decoding is done in two steps, scheduling, and decoding.  The
568/// scheduling tends to be lightweight and should quickly figure what data
569/// is needed from the disk issue the appropriate I/O requests.  A decode task is
570/// created to eventually decode the data (once it is loaded) and scheduling
571/// moves on to scheduling the next page.
572///
573/// Meanwhile, it's expected that a decode stream will be setup to run at the
574/// same time.  Decode tasks take the data that is loaded and turn it into
575/// Arrow arrays.
576///
577/// This approach allows us to keep our I/O parallelism and CPU parallelism
578/// completely separate since those are often two very different values.
579///
580/// Backpressure should be achieved via the I/O service.  Requests that are
581/// issued will pile up if the decode stream is not polling quickly enough.
582/// The [`crate::EncodingsIo::submit_request`] function should return a pending
583/// future once there are too many I/O requests in flight.
584///
585/// TODO: Implement backpressure
586pub struct DecodeBatchScheduler {
587    root_scheduler: RootScheduler,
588    pub root_fields: Fields,
589    cache: Arc<LanceCache>,
590}
591
592pub struct ColumnInfoIter<'a> {
593    column_infos: Vec<Arc<ColumnInfo>>,
594    column_indices: &'a [u32],
595    column_info_pos: usize,
596    column_indices_pos: usize,
597}
598
599impl<'a> ColumnInfoIter<'a> {
600    pub fn new(column_infos: Vec<Arc<ColumnInfo>>, column_indices: &'a [u32]) -> Self {
601        let initial_pos = column_indices.first().copied().unwrap_or(0) as usize;
602        Self {
603            column_infos,
604            column_indices,
605            column_info_pos: initial_pos,
606            column_indices_pos: 0,
607        }
608    }
609
610    pub fn peek(&self) -> &Arc<ColumnInfo> {
611        &self.column_infos[self.column_info_pos]
612    }
613
614    pub fn peek_transform(&mut self, transform: impl FnOnce(Arc<ColumnInfo>) -> Arc<ColumnInfo>) {
615        let column_info = self.column_infos[self.column_info_pos].clone();
616        let transformed = transform(column_info);
617        self.column_infos[self.column_info_pos] = transformed;
618    }
619
620    pub fn expect_next(&mut self) -> Result<&Arc<ColumnInfo>> {
621        self.next().ok_or_else(|| {
622            Error::invalid_input(
623                "there were more fields in the schema than provided column indices / infos",
624            )
625        })
626    }
627
628    fn next(&mut self) -> Option<&Arc<ColumnInfo>> {
629        if self.column_info_pos < self.column_infos.len() {
630            let info = &self.column_infos[self.column_info_pos];
631            self.column_info_pos += 1;
632            Some(info)
633        } else {
634            None
635        }
636    }
637
638    pub(crate) fn next_top_level(&mut self) {
639        self.column_indices_pos += 1;
640        if self.column_indices_pos < self.column_indices.len() {
641            self.column_info_pos = self.column_indices[self.column_indices_pos] as usize;
642        } else {
643            self.column_info_pos = self.column_infos.len();
644        }
645    }
646}
647
648/// These contain the file buffers shared across the entire file
649#[derive(Clone, Copy, Debug)]
650pub struct FileBuffers<'a> {
651    pub positions_and_sizes: &'a [(u64, u64)],
652}
653
654/// These contain the file buffers and also buffers specific to a column
655#[derive(Clone, Copy, Debug)]
656pub struct ColumnBuffers<'a, 'b> {
657    pub file_buffers: FileBuffers<'a>,
658    pub positions_and_sizes: &'b [(u64, u64)],
659}
660
661/// These contain the file & column buffers and also buffers specific to a page
662#[derive(Clone, Copy, Debug)]
663pub struct PageBuffers<'a, 'b, 'c> {
664    pub column_buffers: ColumnBuffers<'a, 'b>,
665    pub positions_and_sizes: &'c [(u64, u64)],
666}
667
668/// The core decoder strategy handles all the various Arrow types
669#[derive(Debug)]
670pub struct CoreFieldDecoderStrategy {
671    pub validate_data: bool,
672    pub decompressor_strategy: Arc<dyn DecompressionStrategy>,
673    pub cache_repetition_index: bool,
674}
675
676impl Default for CoreFieldDecoderStrategy {
677    fn default() -> Self {
678        Self {
679            validate_data: false,
680            decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
681            cache_repetition_index: false,
682        }
683    }
684}
685
686impl CoreFieldDecoderStrategy {
687    /// Create a new strategy with cache_repetition_index enabled
688    pub fn with_cache_repetition_index(mut self, cache_repetition_index: bool) -> Self {
689        self.cache_repetition_index = cache_repetition_index;
690        self
691    }
692
693    /// Create a new strategy from decoder config
694    pub fn from_decoder_config(config: &DecoderConfig) -> Self {
695        Self {
696            validate_data: config.validate_on_decode,
697            decompressor_strategy: Arc::new(DefaultDecompressionStrategy {}),
698            cache_repetition_index: config.cache_repetition_index,
699        }
700    }
701
702    /// This is just a sanity check to ensure there is no "wrapped encodings"
703    /// that haven't been handled.
704    fn ensure_values_encoded(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
705        let column_encoding = column_info
706            .encoding
707            .column_encoding
708            .as_ref()
709            .ok_or_else(|| {
710                Error::invalid_input(format!(
711                    "the column at index {} was missing a ColumnEncoding",
712                    column_info.index
713                ))
714            })?;
715        if matches!(
716            column_encoding,
717            pb::column_encoding::ColumnEncoding::Values(_)
718        ) {
719            Ok(())
720        } else {
721            Err(Error::invalid_input(format!(
722                "the column at index {} mapping to the input field {} has column encoding {:?} and no decoder is registered to handle it",
723                column_info.index, field_name, column_encoding
724            )))
725        }
726    }
727
728    fn is_structural_primitive(data_type: &DataType) -> bool {
729        if data_type.is_primitive() {
730            true
731        } else {
732            match data_type {
733                // DataType::is_primitive doesn't consider these primitive but we do
734                DataType::Dictionary(_, value_type) => Self::is_structural_primitive(value_type),
735                DataType::Boolean
736                | DataType::Null
737                | DataType::FixedSizeBinary(_)
738                | DataType::Binary
739                | DataType::LargeBinary
740                | DataType::Utf8
741                | DataType::LargeUtf8 => true,
742                DataType::FixedSizeList(inner, _) => {
743                    Self::is_structural_primitive(inner.data_type())
744                }
745                _ => false,
746            }
747        }
748    }
749
750    fn is_array_primitive(data_type: &DataType) -> bool {
751        if data_type.is_primitive() {
752            true
753        } else {
754            match data_type {
755                // DataType::is_primitive doesn't consider these primitive but we do
756                DataType::Boolean | DataType::Null | DataType::FixedSizeBinary(_) => true,
757                DataType::FixedSizeList(inner, _) => Self::is_array_primitive(inner.data_type()),
758                _ => false,
759            }
760        }
761    }
762
763    fn create_primitive_scheduler(
764        &self,
765        field: &Field,
766        column: &ColumnInfo,
767        buffers: FileBuffers,
768    ) -> Result<Box<dyn FieldScheduler>> {
769        Self::ensure_values_encoded(column, &field.name)?;
770        // Primitive fields map to a single column
771        let column_buffers = ColumnBuffers {
772            file_buffers: buffers,
773            positions_and_sizes: &column.buffer_offsets_and_sizes,
774        };
775        Ok(Box::new(PrimitiveFieldScheduler::new(
776            column.index,
777            field.data_type(),
778            column.page_infos.clone(),
779            column_buffers,
780            self.validate_data,
781        )))
782    }
783
784    /// Helper method to verify the page encoding of a struct header column
785    fn check_simple_struct(column_info: &ColumnInfo, field_name: &str) -> Result<()> {
786        Self::ensure_values_encoded(column_info, field_name)?;
787        if column_info.page_infos.len() != 1 {
788            return Err(Error::invalid_input_source(format!("Due to schema we expected a struct column but we received a column with {} pages and right now we only support struct columns with 1 page", column_info.page_infos.len()).into()));
789        }
790        let encoding = &column_info.page_infos[0].encoding;
791        match encoding.as_legacy().array_encoding.as_ref().unwrap() {
792            pb::array_encoding::ArrayEncoding::Struct(_) => Ok(()),
793            _ => Err(Error::invalid_input_source(format!("Expected a struct encoding because we have a struct field in the schema but got the encoding {:?}", encoding).into())),
794        }
795    }
796
797    fn check_packed_struct(column_info: &ColumnInfo) -> bool {
798        let encoding = &column_info.page_infos[0].encoding;
799        matches!(
800            encoding.as_legacy().array_encoding.as_ref().unwrap(),
801            pb::array_encoding::ArrayEncoding::PackedStruct(_)
802        )
803    }
804
805    fn create_list_scheduler(
806        &self,
807        list_field: &Field,
808        column_infos: &mut ColumnInfoIter,
809        buffers: FileBuffers,
810        offsets_column: &ColumnInfo,
811    ) -> Result<Box<dyn FieldScheduler>> {
812        Self::ensure_values_encoded(offsets_column, &list_field.name)?;
813        let offsets_column_buffers = ColumnBuffers {
814            file_buffers: buffers,
815            positions_and_sizes: &offsets_column.buffer_offsets_and_sizes,
816        };
817        let items_scheduler =
818            self.create_array_field_scheduler(&list_field.children[0], column_infos, buffers)?;
819
820        let mut inner_infos = Vec::with_capacity(offsets_column.page_infos.len());
821        let mut null_offset_adjustments = Vec::with_capacity(offsets_column.page_infos.len());
822        for (page_index, offsets_page) in offsets_column
823            .page_infos
824            .iter()
825            .enumerate()
826            .filter(|(_, offsets_page)| offsets_page.num_rows > 0)
827        {
828            let PageEncoding::Legacy(pb::ArrayEncoding {
829                array_encoding: Some(pb::array_encoding::ArrayEncoding::List(list_encoding)),
830            }) = &offsets_page.encoding
831            else {
832                return Err(Error::invalid_input(format!(
833                    "expected list encoding for field '{}' in column {}, page {} but got {:?}",
834                    list_field.name, offsets_column.index, page_index, offsets_page.encoding
835                )));
836            };
837            let offsets_encoding = list_encoding.offsets.as_ref().ok_or_else(|| {
838                Error::invalid_input(format!(
839                    "list encoding for field '{}' in column {}, page {} is missing its offsets encoding",
840                    list_field.name, offsets_column.index, page_index
841                ))
842            })?;
843            inner_infos.push(PageInfo {
844                buffer_offsets_and_sizes: offsets_page.buffer_offsets_and_sizes.clone(),
845                encoding: PageEncoding::Legacy(offsets_encoding.as_ref().clone()),
846                num_rows: offsets_page.num_rows,
847                priority: 0,
848            });
849            null_offset_adjustments.push(OffsetPageInfo {
850                offsets_in_page: offsets_page.num_rows,
851                null_offset_adjustment: list_encoding.null_offset_adjustment,
852                num_items_referenced_by_page: list_encoding.num_items,
853            });
854        }
855        let inner = Arc::new(PrimitiveFieldScheduler::new(
856            offsets_column.index,
857            DataType::UInt64,
858            Arc::from(inner_infos.into_boxed_slice()),
859            offsets_column_buffers,
860            self.validate_data,
861        )) as Arc<dyn FieldScheduler>;
862        let items_field = match list_field.data_type() {
863            DataType::List(inner) => inner,
864            DataType::LargeList(inner) => inner,
865            _ => unreachable!(),
866        };
867        let offset_type = if matches!(list_field.data_type(), DataType::List(_)) {
868            DataType::Int32
869        } else {
870            DataType::Int64
871        };
872        Ok(Box::new(ListFieldScheduler::new(
873            inner,
874            items_scheduler.into(),
875            items_field,
876            offset_type,
877            null_offset_adjustments,
878        )))
879    }
880
881    fn unwrap_blob(column_info: &ColumnInfo) -> Option<ColumnInfo> {
882        if let column_encoding::ColumnEncoding::Blob(blob) =
883            column_info.encoding.column_encoding.as_ref().unwrap()
884        {
885            let mut column_info = column_info.clone();
886            column_info.encoding = blob.inner.as_ref().unwrap().as_ref().clone();
887            Some(column_info)
888        } else {
889            None
890        }
891    }
892
893    fn create_structural_field_scheduler(
894        &self,
895        field: &Field,
896        column_infos: &mut ColumnInfoIter,
897    ) -> Result<Box<dyn StructuralFieldScheduler>> {
898        let data_type = field.data_type();
899        validate_fixed_size_list_dimensions(&field.name, &data_type)?;
900        if Self::is_structural_primitive(&data_type) {
901            let column_info = column_infos.expect_next()?;
902            let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
903                column_info.clone(),
904                self.decompressor_strategy.clone(),
905                self.cache_repetition_index,
906                field,
907            )?);
908
909            // advance to the next top level column
910            column_infos.next_top_level();
911
912            return Ok(scheduler);
913        }
914        match &data_type {
915            DataType::Struct(fields) => {
916                if field.is_packed_struct() {
917                    // Packed struct
918                    let column_info = column_infos.expect_next()?;
919                    let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
920                        column_info.clone(),
921                        self.decompressor_strategy.clone(),
922                        self.cache_repetition_index,
923                        field,
924                    )?);
925
926                    // advance to the next top level column
927                    column_infos.next_top_level();
928
929                    return Ok(scheduler);
930                }
931                // Maybe a blob descriptions struct?
932                if field.is_blob() {
933                    let column_info = column_infos.peek();
934                    if column_info.page_infos.iter().any(|page| {
935                        matches!(
936                            page.encoding,
937                            PageEncoding::Structural(metadata::PageLayout {
938                                layout: Some(metadata::page_layout::Layout::BlobLayout(_))
939                            })
940                        )
941                    }) {
942                        let column_info = column_infos.expect_next()?;
943                        let scheduler = Box::new(StructuralPrimitiveFieldScheduler::try_new_lazy(
944                            column_info.clone(),
945                            self.decompressor_strategy.clone(),
946                            self.cache_repetition_index,
947                            field,
948                        )?);
949                        column_infos.next_top_level();
950                        return Ok(scheduler);
951                    }
952                }
953
954                let mut child_schedulers = Vec::with_capacity(field.children.len());
955                for field in field.children.iter() {
956                    let field_scheduler =
957                        self.create_structural_field_scheduler(field, column_infos)?;
958                    child_schedulers.push(field_scheduler);
959                }
960
961                let fields = fields.clone();
962                Ok(
963                    Box::new(StructuralStructScheduler::new(child_schedulers, fields))
964                        as Box<dyn StructuralFieldScheduler>,
965                )
966            }
967            DataType::List(_) | DataType::LargeList(_) => {
968                let child = field.children.first().expect_ok()?;
969                let child_scheduler =
970                    self.create_structural_field_scheduler(child, column_infos)?;
971                Ok(Box::new(StructuralListScheduler::new(child_scheduler))
972                    as Box<dyn StructuralFieldScheduler>)
973            }
974            DataType::FixedSizeList(inner, dimension)
975                if matches!(inner.data_type(), DataType::Struct(_)) =>
976            {
977                let child = field.children.first().expect_ok()?;
978                let child_scheduler =
979                    self.create_structural_field_scheduler(child, column_infos)?;
980                Ok(Box::new(StructuralFixedSizeListScheduler::new(
981                    child_scheduler,
982                    *dimension,
983                )) as Box<dyn StructuralFieldScheduler>)
984            }
985            DataType::Map(_, keys_sorted) => {
986                // TODO: We only support keys_sorted=false for now,
987                //  because converting a rust arrow map field to the python arrow field will
988                //  lose the keys_sorted property.
989                if *keys_sorted {
990                    return Err(Error::not_supported_source(format!("Map data type is not supported with keys_sorted=true now, current value is {}", *keys_sorted).into()));
991                }
992                let entries_child = field.children.first().expect_ok()?;
993                let child_scheduler =
994                    self.create_structural_field_scheduler(entries_child, column_infos)?;
995                Ok(Box::new(StructuralMapScheduler::new(child_scheduler))
996                    as Box<dyn StructuralFieldScheduler>)
997            }
998            _ => todo!("create_structural_field_scheduler for {}", data_type),
999        }
1000    }
1001
1002    fn create_array_field_scheduler(
1003        &self,
1004        field: &Field,
1005        column_infos: &mut ColumnInfoIter,
1006        buffers: FileBuffers,
1007    ) -> Result<Box<dyn FieldScheduler>> {
1008        let data_type = field.data_type();
1009        validate_fixed_size_list_dimensions(&field.name, &data_type)?;
1010        if Self::is_array_primitive(&data_type) {
1011            let column_info = column_infos.expect_next()?;
1012            let scheduler = self.create_primitive_scheduler(field, column_info, buffers)?;
1013            return Ok(scheduler);
1014        } else if data_type.is_binary_like() {
1015            let column_info = column_infos.expect_next()?.clone();
1016            // Column is blob and user is asking for binary data
1017            if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1018                let desc_scheduler =
1019                    self.create_primitive_scheduler(&BLOB_DESC_LANCE_FIELD, &blob_col, buffers)?;
1020                let blob_scheduler = Box::new(BlobFieldScheduler::new(desc_scheduler.into()));
1021                return Ok(blob_scheduler);
1022            }
1023            if let Some(page_info) = column_info.page_infos.first() {
1024                if matches!(
1025                    page_info.encoding.as_legacy(),
1026                    pb::ArrayEncoding {
1027                        array_encoding: Some(pb::array_encoding::ArrayEncoding::List(..))
1028                    }
1029                ) {
1030                    let list_type = if matches!(data_type, DataType::Utf8 | DataType::Binary) {
1031                        DataType::List(Arc::new(ArrowField::new("item", DataType::UInt8, false)))
1032                    } else {
1033                        DataType::LargeList(Arc::new(ArrowField::new(
1034                            "item",
1035                            DataType::UInt8,
1036                            false,
1037                        )))
1038                    };
1039                    let list_field = Field::try_from(ArrowField::new(
1040                        field.name.clone(),
1041                        list_type,
1042                        field.nullable,
1043                    ))
1044                    .unwrap();
1045                    let list_scheduler = self.create_list_scheduler(
1046                        &list_field,
1047                        column_infos,
1048                        buffers,
1049                        &column_info,
1050                    )?;
1051                    let binary_scheduler = Box::new(BinaryFieldScheduler::new(
1052                        list_scheduler.into(),
1053                        field.data_type(),
1054                    ));
1055                    return Ok(binary_scheduler);
1056                } else {
1057                    let scheduler =
1058                        self.create_primitive_scheduler(field, &column_info, buffers)?;
1059                    return Ok(scheduler);
1060                }
1061            } else {
1062                return self.create_primitive_scheduler(field, &column_info, buffers);
1063            }
1064        }
1065        match &data_type {
1066            DataType::FixedSizeList(inner, _dimension) => {
1067                // A fixed size list column could either be a physical or a logical decoder
1068                // depending on the child data type.
1069                if Self::is_array_primitive(inner.data_type()) {
1070                    let primitive_col = column_infos.expect_next()?;
1071                    let scheduler =
1072                        self.create_primitive_scheduler(field, primitive_col, buffers)?;
1073                    Ok(scheduler)
1074                } else {
1075                    todo!()
1076                }
1077            }
1078            DataType::Dictionary(_key_type, value_type) => {
1079                if Self::is_array_primitive(value_type) || value_type.is_binary_like() {
1080                    let primitive_col = column_infos.expect_next()?;
1081                    let scheduler =
1082                        self.create_primitive_scheduler(field, primitive_col, buffers)?;
1083                    Ok(scheduler)
1084                } else {
1085                    Err(Error::not_supported_source(
1086                        format!(
1087                            "No way to decode into a dictionary field of type {}",
1088                            value_type
1089                        )
1090                        .into(),
1091                    ))
1092                }
1093            }
1094            DataType::List(_) | DataType::LargeList(_) => {
1095                let offsets_column = column_infos.expect_next()?.clone();
1096                column_infos.next_top_level();
1097                self.create_list_scheduler(field, column_infos, buffers, &offsets_column)
1098            }
1099            DataType::Struct(fields) => {
1100                let column_info = column_infos.expect_next()?;
1101
1102                // Column is blob and user is asking for descriptions
1103                if let Some(blob_col) = Self::unwrap_blob(column_info.as_ref()) {
1104                    // Can use primitive scheduler here since descriptions are always packed struct
1105                    return self.create_primitive_scheduler(field, &blob_col, buffers);
1106                }
1107
1108                if Self::check_packed_struct(column_info) {
1109                    // use packed struct encoding
1110                    self.create_primitive_scheduler(field, column_info, buffers)
1111                } else {
1112                    // use default struct encoding
1113                    Self::check_simple_struct(column_info, &field.name).unwrap();
1114                    let num_rows = column_info
1115                        .page_infos
1116                        .iter()
1117                        .map(|page| page.num_rows)
1118                        .sum();
1119                    let mut child_schedulers = Vec::with_capacity(field.children.len());
1120                    for field in &field.children {
1121                        column_infos.next_top_level();
1122                        let field_scheduler =
1123                            self.create_array_field_scheduler(field, column_infos, buffers)?;
1124                        child_schedulers.push(Arc::from(field_scheduler));
1125                    }
1126
1127                    let fields = fields.clone();
1128                    Ok(Box::new(SimpleStructScheduler::new(
1129                        child_schedulers,
1130                        fields,
1131                        num_rows,
1132                    )))
1133                }
1134            }
1135            // TODO: Still need support for RLE
1136            _ => todo!(),
1137        }
1138    }
1139}
1140
1141/// Create's a dummy ColumnInfo for the root column
1142fn root_column(num_rows: u64) -> ColumnInfo {
1143    let num_root_pages = num_rows.div_ceil(u32::MAX as u64);
1144    let final_page_num_rows = num_rows % (u32::MAX as u64);
1145    let root_pages = (0..num_root_pages)
1146        .map(|i| PageInfo {
1147            num_rows: if i == num_root_pages - 1 {
1148                final_page_num_rows
1149            } else {
1150                u64::MAX
1151            },
1152            encoding: PageEncoding::Legacy(pb::ArrayEncoding {
1153                array_encoding: Some(pb::array_encoding::ArrayEncoding::Struct(
1154                    pb::SimpleStruct {},
1155                )),
1156            }),
1157            priority: 0, // not used by the array scheduler
1158            buffer_offsets_and_sizes: Arc::new([]),
1159        })
1160        .collect::<Vec<_>>();
1161    ColumnInfo {
1162        buffer_offsets_and_sizes: Arc::new([]),
1163        encoding: pb::ColumnEncoding {
1164            column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
1165        },
1166        index: u32::MAX,
1167        page_infos: Arc::from(root_pages),
1168    }
1169}
1170
1171pub enum RootDecoder {
1172    Structural(StructuralStructDecoder),
1173    Array(SimpleStructDecoder),
1174}
1175
1176impl RootDecoder {
1177    pub fn into_structural(self) -> StructuralStructDecoder {
1178        match self {
1179            Self::Structural(decoder) => decoder,
1180            Self::Array(_) => panic!("Expected a structural decoder"),
1181        }
1182    }
1183
1184    pub fn into_array(self) -> SimpleStructDecoder {
1185        match self {
1186            Self::Array(decoder) => decoder,
1187            Self::Structural(_) => panic!("Expected an array decoder"),
1188        }
1189    }
1190}
1191
1192impl DecodeBatchScheduler {
1193    /// Creates a new decode scheduler with the expected schema and the column
1194    /// metadata of the file.
1195    #[allow(clippy::too_many_arguments)]
1196    pub async fn try_new<'a>(
1197        schema: &'a Schema,
1198        column_indices: &[u32],
1199        column_infos: &[Arc<ColumnInfo>],
1200        file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1201        num_rows: u64,
1202        decoder_plugins: Arc<DecoderPlugins>,
1203        io: Arc<dyn EncodingsIo>,
1204        cache: Arc<LanceCache>,
1205        filter: &FilterExpression,
1206        decoder_config: &DecoderConfig,
1207    ) -> Result<Self> {
1208        Self::try_new_with_ranges(
1209            schema,
1210            column_indices,
1211            column_infos,
1212            file_buffer_positions_and_sizes,
1213            num_rows,
1214            decoder_plugins,
1215            io,
1216            cache,
1217            None,
1218            filter,
1219            decoder_config,
1220        )
1221        .await
1222    }
1223
1224    /// Creates a decode scheduler and initializes only pages overlapping known ranges.
1225    #[allow(clippy::too_many_arguments)]
1226    pub(crate) async fn try_new_with_ranges<'a>(
1227        schema: &'a Schema,
1228        column_indices: &[u32],
1229        column_infos: &[Arc<ColumnInfo>],
1230        file_buffer_positions_and_sizes: &'a Vec<(u64, u64)>,
1231        num_rows: u64,
1232        _decoder_plugins: Arc<DecoderPlugins>,
1233        io: Arc<dyn EncodingsIo>,
1234        cache: Arc<LanceCache>,
1235        // The top-level row ranges that will be scheduled, if known. This lets
1236        // the structural path initialize only the pages those ranges touch.
1237        // `None` preserves `try_new`'s eager initialization behavior.
1238        requested_ranges: Option<&[Range<u64>]>,
1239        filter: &FilterExpression,
1240        decoder_config: &DecoderConfig,
1241    ) -> Result<Self> {
1242        assert!(num_rows > 0);
1243        let buffers = FileBuffers {
1244            positions_and_sizes: file_buffer_positions_and_sizes,
1245        };
1246        let arrow_schema = ArrowSchema::from(schema);
1247        let root_fields = arrow_schema.fields().clone();
1248        let root_type = DataType::Struct(root_fields.clone());
1249        let mut root_field = Field::try_from(&ArrowField::new("root", root_type, false))?;
1250        // root_field.children and schema.fields should be identical at this point but the latter
1251        // has field ids and the former does not.  This line restores that.
1252        // TODO:  Is there another way to create the root field without forcing a trip through arrow?
1253        root_field.children.clone_from(&schema.fields);
1254        root_field
1255            .metadata
1256            .insert("__lance_decoder_root".to_string(), "true".to_string());
1257
1258        if column_infos.is_empty() || column_infos[0].is_structural() {
1259            let mut column_iter = ColumnInfoIter::new(column_infos.to_vec(), column_indices);
1260
1261            let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1262            let mut root_scheduler =
1263                strategy.create_structural_field_scheduler(&root_field, &mut column_iter)?;
1264
1265            let context = SchedulerContext::new(io, cache.clone());
1266            root_scheduler
1267                .initialize(requested_ranges, filter, &context)
1268                .await?;
1269
1270            Ok(Self {
1271                root_scheduler: RootScheduler::Structural(root_scheduler),
1272                root_fields,
1273                cache,
1274            })
1275        } else {
1276            // The old encoding style expected a header column for structs and so we
1277            // need a header column for the top-level struct
1278            let mut columns = Vec::with_capacity(column_infos.len() + 1);
1279            columns.push(Arc::new(root_column(num_rows)));
1280            columns.extend(column_infos.iter().cloned());
1281
1282            let adjusted_column_indices = [0_u32]
1283                .into_iter()
1284                .chain(column_indices.iter().map(|i| i.saturating_add(1)))
1285                .collect::<Vec<_>>();
1286            let mut column_iter = ColumnInfoIter::new(columns, &adjusted_column_indices);
1287            let strategy = CoreFieldDecoderStrategy::from_decoder_config(decoder_config);
1288            let root_scheduler =
1289                strategy.create_array_field_scheduler(&root_field, &mut column_iter, buffers)?;
1290
1291            let context = SchedulerContext::new(io, cache.clone());
1292            root_scheduler.initialize(filter, &context).await?;
1293
1294            Ok(Self {
1295                root_scheduler: RootScheduler::Array(root_scheduler.into()),
1296                root_fields,
1297                cache,
1298            })
1299        }
1300    }
1301
1302    #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
1303    pub fn from_scheduler(
1304        root_scheduler: Arc<dyn FieldScheduler>,
1305        root_fields: Fields,
1306        cache: Arc<LanceCache>,
1307    ) -> Self {
1308        Self {
1309            root_scheduler: RootScheduler::Array(root_scheduler),
1310            root_fields,
1311            cache,
1312        }
1313    }
1314
1315    fn do_schedule_ranges_structural(
1316        &mut self,
1317        ranges: &[Range<u64>],
1318        filter: &FilterExpression,
1319        io: Arc<dyn EncodingsIo>,
1320        mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1321    ) {
1322        let root_scheduler = self.root_scheduler.as_structural();
1323        let mut context = SchedulerContext::new(io, self.cache.clone());
1324        let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1325        if let Err(schedule_ranges_err) = maybe_root_job {
1326            schedule_action(Err(schedule_ranges_err));
1327            return;
1328        }
1329        let mut root_job = maybe_root_job.unwrap();
1330        let mut num_rows_scheduled = 0;
1331        loop {
1332            let maybe_next_scan_lines = root_job.schedule_next(&mut context);
1333            if let Err(err) = maybe_next_scan_lines {
1334                schedule_action(Err(err));
1335                return;
1336            }
1337            let next_scan_lines = maybe_next_scan_lines.unwrap();
1338            context.flush_io();
1339            if next_scan_lines.is_empty() {
1340                return;
1341            }
1342            for next_scan_line in next_scan_lines {
1343                trace!(
1344                    "Scheduled scan line of {} rows and {} decoders",
1345                    next_scan_line.rows_scheduled,
1346                    next_scan_line.decoders.len()
1347                );
1348                num_rows_scheduled += next_scan_line.rows_scheduled;
1349                if !schedule_action(Ok(DecoderMessage {
1350                    scheduled_so_far: num_rows_scheduled,
1351                    decoders: next_scan_line.decoders,
1352                })) {
1353                    // Decoder has disconnected
1354                    return;
1355                }
1356            }
1357        }
1358    }
1359
1360    fn do_schedule_ranges_array(
1361        &mut self,
1362        ranges: &[Range<u64>],
1363        filter: &FilterExpression,
1364        io: Arc<dyn EncodingsIo>,
1365        mut schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1366        // If specified, this will be used as the top_level_row for all scheduling
1367        // tasks.  This is used by list scheduling to ensure all items scheduling
1368        // tasks are scheduled at the same top level row.
1369        priority: Option<Box<dyn PriorityRange>>,
1370    ) {
1371        let root_scheduler = self.root_scheduler.as_array();
1372        let rows_requested = ranges.iter().map(|r| r.end - r.start).sum::<u64>();
1373        trace!(
1374            "Scheduling {} ranges across {}..{} ({} rows){}",
1375            ranges.len(),
1376            ranges.first().unwrap().start,
1377            ranges.last().unwrap().end,
1378            rows_requested,
1379            priority
1380                .as_ref()
1381                .map(|p| format!(" (priority={:?})", p))
1382                .unwrap_or_default()
1383        );
1384
1385        let mut context = SchedulerContext::new(io, self.cache.clone());
1386        let maybe_root_job = root_scheduler.schedule_ranges(ranges, filter);
1387        if let Err(schedule_ranges_err) = maybe_root_job {
1388            schedule_action(Err(schedule_ranges_err));
1389            return;
1390        }
1391        let mut root_job = maybe_root_job.unwrap();
1392        let mut num_rows_scheduled = 0;
1393        let mut rows_to_schedule = root_job.num_rows();
1394        let mut priority = priority.unwrap_or(Box::new(SimplePriorityRange::new(0)));
1395        trace!("Scheduled ranges refined to {} rows", rows_to_schedule);
1396        while rows_to_schedule > 0 {
1397            let maybe_next_scan_line = root_job.schedule_next(&mut context, priority.as_ref());
1398            if let Err(schedule_next_err) = maybe_next_scan_line {
1399                schedule_action(Err(schedule_next_err));
1400                return;
1401            }
1402            let next_scan_line = maybe_next_scan_line.unwrap();
1403            context.flush_io();
1404            priority.advance(next_scan_line.rows_scheduled);
1405            num_rows_scheduled += next_scan_line.rows_scheduled;
1406            rows_to_schedule -= next_scan_line.rows_scheduled;
1407            trace!(
1408                "Scheduled scan line of {} rows and {} decoders",
1409                next_scan_line.rows_scheduled,
1410                next_scan_line.decoders.len()
1411            );
1412            if !schedule_action(Ok(DecoderMessage {
1413                scheduled_so_far: num_rows_scheduled,
1414                decoders: next_scan_line.decoders,
1415            })) {
1416                // Decoder has disconnected
1417                return;
1418            }
1419
1420            trace!("Finished scheduling {} ranges", ranges.len());
1421        }
1422    }
1423
1424    fn do_schedule_ranges(
1425        &mut self,
1426        ranges: &[Range<u64>],
1427        filter: &FilterExpression,
1428        io: Arc<dyn EncodingsIo>,
1429        schedule_action: impl FnMut(Result<DecoderMessage>) -> bool,
1430        // If specified, this will be used as the top_level_row for all scheduling
1431        // tasks.  This is used by list scheduling to ensure all items scheduling
1432        // tasks are scheduled at the same top level row.
1433        priority: Option<Box<dyn PriorityRange>>,
1434    ) {
1435        match &self.root_scheduler {
1436            RootScheduler::Array(_) => {
1437                self.do_schedule_ranges_array(ranges, filter, io, schedule_action, priority)
1438            }
1439            RootScheduler::Structural(_) => {
1440                self.do_schedule_ranges_structural(ranges, filter, io, schedule_action)
1441            }
1442        }
1443    }
1444
1445    // This method is similar to schedule_ranges but instead of
1446    // sending the decoders to a channel it collects them all into a vector
1447    pub fn schedule_ranges_to_vec(
1448        &mut self,
1449        ranges: &[Range<u64>],
1450        filter: &FilterExpression,
1451        io: Arc<dyn EncodingsIo>,
1452        priority: Option<Box<dyn PriorityRange>>,
1453    ) -> Result<Vec<DecoderMessage>> {
1454        let mut decode_messages = Vec::new();
1455        self.do_schedule_ranges(
1456            ranges,
1457            filter,
1458            io,
1459            |msg| {
1460                decode_messages.push(msg);
1461                true
1462            },
1463            priority,
1464        );
1465        decode_messages.into_iter().collect::<Result<Vec<_>>>()
1466    }
1467
1468    /// Schedules the load of multiple ranges of rows
1469    ///
1470    /// Ranges must be non-overlapping and in sorted order
1471    ///
1472    /// # Arguments
1473    ///
1474    /// * `ranges` - The ranges of rows to load
1475    /// * `sink` - A channel to send the decode tasks
1476    /// * `scheduler` An I/O scheduler to issue I/O requests
1477    #[instrument(level = "debug", skip_all)]
1478    pub fn schedule_ranges(
1479        &mut self,
1480        ranges: &[Range<u64>],
1481        filter: &FilterExpression,
1482        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1483        scheduler: Arc<dyn EncodingsIo>,
1484    ) {
1485        self.do_schedule_ranges(
1486            ranges,
1487            filter,
1488            scheduler,
1489            |msg| {
1490                match sink.send(msg) {
1491                    Ok(_) => true,
1492                    Err(SendError { .. }) => {
1493                        // The receiver has gone away.  We can't do anything about it
1494                        // so just ignore the error.
1495                        debug!(
1496                        "schedule_ranges aborting early since decoder appears to have been dropped"
1497                    );
1498                        false
1499                    }
1500                }
1501            },
1502            None,
1503        )
1504    }
1505
1506    /// Schedules the load of a range of rows
1507    ///
1508    /// # Arguments
1509    ///
1510    /// * `range` - The range of rows to load
1511    /// * `sink` - A channel to send the decode tasks
1512    /// * `scheduler` An I/O scheduler to issue I/O requests
1513    #[instrument(level = "debug", skip_all)]
1514    pub fn schedule_range(
1515        &mut self,
1516        range: Range<u64>,
1517        filter: &FilterExpression,
1518        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1519        scheduler: Arc<dyn EncodingsIo>,
1520    ) {
1521        self.schedule_ranges(&[range], filter, sink, scheduler)
1522    }
1523
1524    /// Schedules the load of selected rows
1525    ///
1526    /// # Arguments
1527    ///
1528    /// * `indices` - The row indices to load (these must be in ascending order!)
1529    /// * `sink` - A channel to send the decode tasks
1530    /// * `scheduler` An I/O scheduler to issue I/O requests
1531    pub fn schedule_take(
1532        &mut self,
1533        indices: &[u64],
1534        filter: &FilterExpression,
1535        sink: mpsc::UnboundedSender<Result<DecoderMessage>>,
1536        scheduler: Arc<dyn EncodingsIo>,
1537    ) {
1538        if indices.is_empty() {
1539            return;
1540        }
1541        trace!("Scheduling take of {} rows", indices.len());
1542        let ranges = match RequestedRows::indices_to_ranges(indices) {
1543            Ok(ranges) => ranges,
1544            Err(error) => {
1545                if let Err(SendError { .. }) = sink.send(Err(error)) {
1546                    debug!(
1547                        "schedule_take could not report invalid indices because the decoder was dropped"
1548                    );
1549                }
1550                return;
1551            }
1552        };
1553        self.schedule_ranges(&ranges, filter, sink, scheduler)
1554    }
1555}
1556
1557pub struct ReadBatchTask {
1558    pub task: BoxFuture<'static, Result<RecordBatch>>,
1559    pub num_rows: u32,
1560}
1561
1562/// A stream that takes scheduled jobs and generates decode tasks from them.
1563pub struct BatchDecodeStream {
1564    context: DecoderContext,
1565    root_decoder: SimpleStructDecoder,
1566    rows_remaining: u64,
1567    rows_per_batch: u32,
1568    rows_scheduled: u64,
1569    rows_drained: u64,
1570    scheduler_exhausted: bool,
1571    emitted_batch_size_warning: Arc<Once>,
1572    limit_i32_offset_batch_size: bool,
1573}
1574
1575impl BatchDecodeStream {
1576    /// Create a new instance of a batch decode stream
1577    ///
1578    /// # Arguments
1579    ///
1580    /// * `scheduled` - an incoming stream of decode tasks from a `DecodeBatchScheduler`
1581    /// * `schema` - the schema of the data to create
1582    /// * `rows_per_batch` the number of rows to create before making a batch
1583    /// * `num_rows` the total number of rows scheduled
1584    /// * `num_columns` the total number of columns in the file
1585    pub fn new(
1586        scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1587        rows_per_batch: u32,
1588        num_rows: u64,
1589        root_decoder: SimpleStructDecoder,
1590    ) -> Self {
1591        Self::new_with_i32_offset_limit(scheduled, rows_per_batch, num_rows, root_decoder, true)
1592    }
1593
1594    fn new_with_i32_offset_limit(
1595        scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
1596        rows_per_batch: u32,
1597        num_rows: u64,
1598        root_decoder: SimpleStructDecoder,
1599        limit_i32_offset_batch_size: bool,
1600    ) -> Self {
1601        Self {
1602            context: DecoderContext::new(scheduled),
1603            root_decoder,
1604            rows_remaining: num_rows,
1605            rows_per_batch,
1606            rows_scheduled: 0,
1607            rows_drained: 0,
1608            scheduler_exhausted: false,
1609            emitted_batch_size_warning: Arc::new(Once::new()),
1610            limit_i32_offset_batch_size,
1611        }
1612    }
1613
1614    fn accept_decoder(&mut self, decoder: DecoderReady) -> Result<()> {
1615        if decoder.path.is_empty() {
1616            // The root decoder we can ignore
1617            Ok(())
1618        } else {
1619            self.root_decoder.accept_child(decoder)
1620        }
1621    }
1622
1623    #[instrument(level = "debug", skip_all)]
1624    async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
1625        if self.scheduler_exhausted {
1626            return Ok(self.rows_scheduled);
1627        }
1628        while self.rows_scheduled < scheduled_need {
1629            let next_message = self.context.source.recv().await;
1630            match next_message {
1631                Some(scan_line) => {
1632                    let scan_line = scan_line?;
1633                    self.rows_scheduled = scan_line.scheduled_so_far;
1634                    for message in scan_line.decoders {
1635                        self.accept_decoder(message.into_array())?;
1636                    }
1637                }
1638                None => {
1639                    // Schedule ended before we got all the data we expected.  This probably
1640                    // means some kind of pushdown filter was applied and we didn't load as
1641                    // much data as we thought we would.
1642                    self.scheduler_exhausted = true;
1643                    return Ok(self.rows_scheduled);
1644                }
1645            }
1646        }
1647        Ok(scheduled_need)
1648    }
1649
1650    #[instrument(level = "debug", skip_all)]
1651    async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
1652        trace!(
1653            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1654            self.rows_remaining, self.rows_drained, self.rows_scheduled,
1655        );
1656        if self.rows_remaining == 0 {
1657            return Ok(None);
1658        }
1659
1660        let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1661        self.rows_remaining -= to_take;
1662
1663        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1664        trace!(
1665            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1666            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1667        );
1668        if scheduled_need > 0 {
1669            let desired_scheduled = scheduled_need + self.rows_scheduled;
1670            trace!(
1671                "Draining from scheduler (desire at least {} scheduled rows)",
1672                desired_scheduled
1673            );
1674            let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
1675            if actually_scheduled < desired_scheduled {
1676                let under_scheduled = desired_scheduled - actually_scheduled;
1677                to_take -= under_scheduled;
1678            }
1679        }
1680
1681        if to_take == 0 {
1682            return Ok(None);
1683        }
1684
1685        // wait_for_loaded waits for *>* loaded_need (not >=) so we do a -1 here
1686        let loaded_need = self.rows_drained + to_take - 1;
1687        trace!(
1688            "Waiting for I/O (desire at least {} fully loaded rows)",
1689            loaded_need
1690        );
1691        self.root_decoder.wait_for_loaded(loaded_need).await?;
1692
1693        if self.limit_i32_offset_batch_size && has_i32_offsets(self.root_decoder.data_type()) {
1694            let limit = LogicalPageDecoder::max_rows_to_drain(
1695                &self.root_decoder,
1696                to_take,
1697                I32_OFFSET_BYTE_BUDGET,
1698            )?;
1699            let clamped = clamp_rows_to_i32_budget(limit, to_take);
1700            self.rows_remaining += to_take - clamped;
1701            to_take = clamped;
1702        }
1703
1704        let next_task = self.root_decoder.drain(to_take)?;
1705        self.rows_drained += to_take;
1706        Ok(Some(next_task))
1707    }
1708
1709    pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
1710        let stream = futures::stream::unfold(self, |mut slf| async move {
1711            let next_task = match slf.next_batch_task().await {
1712                Ok(Some(next_task)) => next_task,
1713                Ok(None) => return None,
1714                Err(err) => {
1715                    slf.rows_remaining = 0;
1716                    return Some((
1717                        ReadBatchTask {
1718                            task: async move { Err(err) }.boxed(),
1719                            num_rows: 0,
1720                        },
1721                        slf,
1722                    ));
1723                }
1724            };
1725            let num_rows = next_task.num_rows;
1726            let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
1727            let task = async move {
1728                // Real decode work happens inside into_batch, which can block the current
1729                // thread for a long time. By spawning it as a new task, we allow Tokio's
1730                // worker threads to keep making progress.
1731                let (batch, _data_size) =
1732                    tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
1733                        .await
1734                        .map_err(|err| Error::wrapped(err.into()))??;
1735                Ok(batch)
1736            };
1737            // This should be true since batch size is u32
1738            debug_assert!(num_rows <= u32::MAX as u64);
1739            Some((
1740                ReadBatchTask {
1741                    task: task.boxed(),
1742                    num_rows: num_rows as u32,
1743                },
1744                slf,
1745            ))
1746        });
1747        stream.boxed()
1748    }
1749}
1750
1751// Utility types to smooth out the differences between the 2.0 and 2.1 decoders so that
1752// we can have a single implementation of the batch decode iterator
1753enum RootDecoderMessage {
1754    LoadedPage(LoadedPageShard),
1755    ArrayPage(DecoderReady),
1756}
1757trait RootDecoderType {
1758    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()>;
1759    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask>;
1760    fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()>;
1761    /// See [`LogicalPageDecoder::max_rows_to_drain`].  Decoders without i32-offset
1762    /// accounting place no limit.
1763    fn max_rows_to_drain(&self, num_rows: u64, _byte_budget: u64) -> Result<DrainLimit> {
1764        Ok(DrainLimit {
1765            rows: num_rows,
1766            bytes: 0,
1767        })
1768    }
1769}
1770impl RootDecoderType for StructuralStructDecoder {
1771    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1772        let RootDecoderMessage::LoadedPage(loaded_page) = message else {
1773            unreachable!()
1774        };
1775        self.accept_page(loaded_page)
1776    }
1777    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1778        self.drain_batch_task(num_rows)
1779    }
1780    fn wait(&mut self, _: u64, _: &tokio::runtime::Runtime) -> Result<()> {
1781        // Waiting happens elsewhere (not as part of the decoder)
1782        Ok(())
1783    }
1784}
1785impl RootDecoderType for SimpleStructDecoder {
1786    fn accept_message(&mut self, message: RootDecoderMessage) -> Result<()> {
1787        let RootDecoderMessage::ArrayPage(array_page) = message else {
1788            unreachable!()
1789        };
1790        self.accept_child(array_page)
1791    }
1792    fn drain_batch(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
1793        self.drain(num_rows)
1794    }
1795    fn wait(&mut self, loaded_need: u64, runtime: &tokio::runtime::Runtime) -> Result<()> {
1796        runtime.block_on(self.wait_for_loaded(loaded_need))
1797    }
1798    fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
1799        if !has_i32_offsets(LogicalPageDecoder::data_type(self)) {
1800            return Ok(DrainLimit {
1801                rows: num_rows,
1802                bytes: 0,
1803            });
1804        }
1805        LogicalPageDecoder::max_rows_to_drain(self, num_rows, byte_budget)
1806    }
1807}
1808
1809/// A blocking batch decoder that performs synchronous decoding
1810struct BatchDecodeIterator<T: RootDecoderType> {
1811    messages: VecDeque<Result<DecoderMessage>>,
1812    root_decoder: T,
1813    rows_remaining: u64,
1814    rows_per_batch: u32,
1815    rows_scheduled: u64,
1816    rows_drained: u64,
1817    emitted_batch_size_warning: Arc<Once>,
1818    // Note: this is not the runtime on which I/O happens.
1819    // That's always in the scheduler.  This is just a runtime we use to
1820    // sleep the current thread if I/O is unready
1821    wait_for_io_runtime: tokio::runtime::Runtime,
1822    schema: Arc<ArrowSchema>,
1823}
1824
1825impl<T: RootDecoderType> BatchDecodeIterator<T> {
1826    /// Create a new instance of a batch decode iterator
1827    pub fn new(
1828        messages: VecDeque<Result<DecoderMessage>>,
1829        rows_per_batch: u32,
1830        num_rows: u64,
1831        root_decoder: T,
1832        schema: Arc<ArrowSchema>,
1833    ) -> Self {
1834        Self {
1835            messages,
1836            root_decoder,
1837            rows_remaining: num_rows,
1838            rows_per_batch,
1839            rows_scheduled: 0,
1840            rows_drained: 0,
1841            wait_for_io_runtime: tokio::runtime::Builder::new_current_thread()
1842                .build()
1843                .unwrap(),
1844            emitted_batch_size_warning: Arc::new(Once::new()),
1845            schema,
1846        }
1847    }
1848
1849    /// Wait for a single page of data to finish loading
1850    ///
1851    /// If the data is not available this will perform a *blocking* wait (put
1852    /// the current thread to sleep)
1853    fn wait_for_page(&self, unloaded_page: UnloadedPageShard) -> Result<LoadedPageShard> {
1854        match maybe_done(unloaded_page.0) {
1855            // Fast path, avoid all runtime shenanigans if the data is ready
1856            MaybeDone::Done(loaded_page) => loaded_page,
1857            // Slow path, we need to wait on I/O, enter the runtime
1858            MaybeDone::Future(fut) => self.wait_for_io_runtime.block_on(fut),
1859            MaybeDone::Gone => unreachable!(),
1860        }
1861    }
1862
1863    /// Waits for I/O until `scheduled_need` rows have been loaded
1864    ///
1865    /// Note that `scheduled_need` is cumulative.  E.g. this method
1866    /// should be called with 5, 10, 15 and not 5, 5, 5
1867    #[instrument(level = "debug", skip_all)]
1868    fn wait_for_io(&mut self, scheduled_need: u64, to_take: u64) -> Result<u64> {
1869        while self.rows_scheduled < scheduled_need && !self.messages.is_empty() {
1870            let message = self.messages.pop_front().unwrap()?;
1871            self.rows_scheduled = message.scheduled_so_far;
1872            for decoder_message in message.decoders {
1873                match decoder_message {
1874                    MessageType::UnloadedPage(unloaded_page) => {
1875                        let loaded_page = self.wait_for_page(unloaded_page)?;
1876                        self.root_decoder
1877                            .accept_message(RootDecoderMessage::LoadedPage(loaded_page))?;
1878                    }
1879                    MessageType::DecoderReady(decoder_ready) => {
1880                        // The root decoder we can ignore
1881                        if !decoder_ready.path.is_empty() {
1882                            self.root_decoder
1883                                .accept_message(RootDecoderMessage::ArrayPage(decoder_ready))?;
1884                        }
1885                    }
1886                }
1887            }
1888        }
1889
1890        let loaded_need = self.rows_drained + to_take.min(self.rows_per_batch as u64) - 1;
1891
1892        self.root_decoder
1893            .wait(loaded_need, &self.wait_for_io_runtime)?;
1894        Ok(self.rows_scheduled)
1895    }
1896
1897    #[instrument(level = "debug", skip_all)]
1898    fn next_batch_task(&mut self) -> Result<Option<RecordBatch>> {
1899        trace!(
1900            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
1901            self.rows_remaining, self.rows_drained, self.rows_scheduled,
1902        );
1903        if self.rows_remaining == 0 {
1904            return Ok(None);
1905        }
1906
1907        let mut to_take = self.rows_remaining.min(self.rows_per_batch as u64);
1908        self.rows_remaining -= to_take;
1909
1910        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
1911        trace!(
1912            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
1913            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
1914        );
1915        if scheduled_need > 0 {
1916            let desired_scheduled = scheduled_need + self.rows_scheduled;
1917            trace!(
1918                "Draining from scheduler (desire at least {} scheduled rows)",
1919                desired_scheduled
1920            );
1921            let actually_scheduled = self.wait_for_io(desired_scheduled, to_take)?;
1922            if actually_scheduled < desired_scheduled {
1923                let under_scheduled = desired_scheduled - actually_scheduled;
1924                to_take -= under_scheduled;
1925            }
1926        }
1927
1928        if to_take == 0 {
1929            return Ok(None);
1930        }
1931
1932        let limit = self
1933            .root_decoder
1934            .max_rows_to_drain(to_take, I32_OFFSET_BYTE_BUDGET)?;
1935        let clamped = clamp_rows_to_i32_budget(limit, to_take);
1936        self.rows_remaining += to_take - clamped;
1937        to_take = clamped;
1938
1939        let next_task = self.root_decoder.drain_batch(to_take)?;
1940
1941        self.rows_drained += to_take;
1942
1943        let (batch, _data_size) = next_task.into_batch(self.emitted_batch_size_warning.clone())?;
1944
1945        Ok(Some(batch))
1946    }
1947}
1948
1949impl<T: RootDecoderType> Iterator for BatchDecodeIterator<T> {
1950    type Item = ArrowResult<RecordBatch>;
1951
1952    fn next(&mut self) -> Option<Self::Item> {
1953        let next = self.next_batch_task().transpose();
1954        if let Some(Err(_)) = &next {
1955            // Fuse after an error instead of re-attempting every remaining batch
1956            self.rows_remaining = 0;
1957        }
1958        next.map(|r| r.map_err(ArrowError::from))
1959    }
1960}
1961
1962impl<T: RootDecoderType> RecordBatchReader for BatchDecodeIterator<T> {
1963    fn schema(&self) -> Arc<ArrowSchema> {
1964        self.schema.clone()
1965    }
1966}
1967
1968/// Estimate the number of bytes per row for a given Arrow data type.
1969///
1970/// For fixed-width types this is exact. For variable-width types (strings,
1971/// binary, lists) a rough default is used. The estimate is used as a
1972/// starting point when `batch_size_bytes` is set; a post-decode feedback
1973/// loop corrects it after the first batch.
1974///
1975/// This estimate ignores validity bitmaps at the moment.  We can't infer
1976/// their presence simply from the data_type and their impact is probably
1977/// fairly negligible.
1978/// Returns a schema-based estimate of the decoded bytes per row for `data_type`.
1979///
1980/// Fixed-width types are exact. Variable-width types (strings, lists, etc.) use
1981/// heuristic constants. This estimate is used both in batch-size planning and as
1982/// a fallback for V1 files that lack structural decoders.
1983pub fn estimate_bytes_per_row(data_type: &DataType) -> f64 {
1984    if let Some(w) = data_type.byte_width_opt() {
1985        return w as f64;
1986    }
1987    match data_type {
1988        DataType::Boolean => 1.0 / 8.0,
1989        DataType::Utf8 | DataType::Binary | DataType::LargeUtf8 | DataType::LargeBinary => 64.0,
1990        DataType::Struct(fields) => fields
1991            .iter()
1992            .map(|f| estimate_bytes_per_row(f.data_type()))
1993            .sum(),
1994        DataType::List(child) | DataType::LargeList(child) => {
1995            5.0 * estimate_bytes_per_row(child.data_type())
1996        }
1997        DataType::FixedSizeList(child, dim) => {
1998            *dim as f64 * estimate_bytes_per_row(child.data_type())
1999        }
2000        DataType::Dictionary(_, value_type) => estimate_bytes_per_row(value_type),
2001        DataType::Map(entries, _) => 5.0 * estimate_bytes_per_row(entries.data_type()),
2002        _ => 64.0,
2003    }
2004}
2005
2006/// A stream that takes scheduled jobs and generates decode tasks from them.
2007pub struct StructuralBatchDecodeStream {
2008    context: DecoderContext,
2009    root_decoder: StructuralStructDecoder,
2010    rows_remaining: u64,
2011    rows_per_batch: u32,
2012    rows_scheduled: u64,
2013    rows_drained: u64,
2014    scheduler_exhausted: bool,
2015    emitted_batch_size_warning: Arc<Once>,
2016    // Decode scheduling policy selected at planning time.
2017    //
2018    // Performance tradeoff:
2019    // - true: spawn `into_batch` onto Tokio, which improves scan throughput by allowing
2020    //   more decode parallelism.
2021    // - false: run `into_batch` inline, which avoids Tokio scheduling overhead and is
2022    //   typically better for point lookups / small takes.
2023    spawn_batch_decode_tasks: bool,
2024    /// If set, target this many bytes per batch while retaining `rows_per_batch`
2025    /// as an independent upper bound.
2026    batch_size_bytes: Option<u64>,
2027    /// Schema-based estimate of bytes per row, computed once at construction.
2028    /// Only meaningful when `batch_size_bytes` is `Some`.
2029    schema_bytes_per_row: f64,
2030    /// Post-decode feedback: actual bytes-per-row measured from the most
2031    /// recently decoded batch.  Zero means no feedback yet (use schema estimate).
2032    bytes_per_row_feedback: Arc<AtomicU64>,
2033}
2034
2035impl StructuralBatchDecodeStream {
2036    /// Create a new instance of a batch decode stream
2037    ///
2038    /// # Arguments
2039    ///
2040    /// * `scheduled` - an incoming stream of decode tasks from a `DecodeBatchScheduler`
2041    /// * `schema` - the schema of the data to create
2042    /// * `rows_per_batch` the number of rows to create before making a batch
2043    /// * `num_rows` the total number of rows scheduled
2044    /// * `num_columns` the total number of columns in the file
2045    pub fn new(
2046        scheduled: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2047        rows_per_batch: u32,
2048        num_rows: u64,
2049        root_decoder: StructuralStructDecoder,
2050        spawn_batch_decode_tasks: bool,
2051        batch_size_bytes: Option<u64>,
2052    ) -> Self {
2053        let schema_bytes_per_row = if batch_size_bytes.is_some() {
2054            estimate_bytes_per_row(root_decoder.data_type()).max(1.0)
2055        } else {
2056            0.0
2057        };
2058        Self {
2059            context: DecoderContext::new(scheduled),
2060            root_decoder,
2061            rows_remaining: num_rows,
2062            rows_per_batch,
2063            rows_scheduled: 0,
2064            rows_drained: 0,
2065            scheduler_exhausted: false,
2066            emitted_batch_size_warning: Arc::new(Once::new()),
2067            spawn_batch_decode_tasks,
2068            batch_size_bytes,
2069            schema_bytes_per_row,
2070            bytes_per_row_feedback: Arc::new(AtomicU64::new(0)),
2071        }
2072    }
2073
2074    #[instrument(level = "debug", skip_all)]
2075    async fn wait_for_scheduled(&mut self, scheduled_need: u64) -> Result<u64> {
2076        if self.scheduler_exhausted {
2077            return Ok(self.rows_scheduled);
2078        }
2079        while self.rows_scheduled < scheduled_need {
2080            let next_message = self.context.source.recv().await;
2081            match next_message {
2082                Some(scan_line) => {
2083                    let scan_line = scan_line?;
2084                    self.rows_scheduled = scan_line.scheduled_so_far;
2085                    for message in scan_line.decoders {
2086                        let unloaded_page = message.into_structural();
2087                        let loaded_page = unloaded_page.0.await?;
2088                        self.root_decoder.accept_page(loaded_page)?;
2089                    }
2090                }
2091                None => {
2092                    // Schedule ended before we got all the data we expected.  This probably
2093                    // means some kind of pushdown filter was applied and we didn't load as
2094                    // much data as we thought we would.
2095                    self.scheduler_exhausted = true;
2096                    return Ok(self.rows_scheduled);
2097                }
2098            }
2099        }
2100        Ok(scheduled_need)
2101    }
2102
2103    #[instrument(level = "debug", skip_all)]
2104    async fn next_batch_task(&mut self) -> Result<Option<NextDecodeTask>> {
2105        trace!(
2106            "Draining batch task (rows_remaining={} rows_drained={} rows_scheduled={})",
2107            self.rows_remaining, self.rows_drained, self.rows_scheduled,
2108        );
2109        if self.rows_remaining == 0 {
2110            return Ok(None);
2111        }
2112
2113        let row_limit = self.rows_remaining.min(self.rows_per_batch as u64);
2114        let mut to_take = if let Some(batch_size_bytes) = self.batch_size_bytes {
2115            let feedback = self.bytes_per_row_feedback.load(Ordering::Relaxed);
2116            let bpr = if feedback > 0 {
2117                feedback as f64
2118            } else {
2119                self.schema_bytes_per_row
2120            };
2121            let rows = (batch_size_bytes as f64 / bpr) as u64;
2122            row_limit.min(rows.max(1))
2123        } else {
2124            row_limit
2125        };
2126        self.rows_remaining -= to_take;
2127
2128        let scheduled_need = (self.rows_drained + to_take).saturating_sub(self.rows_scheduled);
2129        trace!(
2130            "scheduled_need = {} because rows_drained = {} and to_take = {} and rows_scheduled = {}",
2131            scheduled_need, self.rows_drained, to_take, self.rows_scheduled
2132        );
2133        if scheduled_need > 0 {
2134            let desired_scheduled = scheduled_need + self.rows_scheduled;
2135            trace!(
2136                "Draining from scheduler (desire at least {} scheduled rows)",
2137                desired_scheduled
2138            );
2139            let actually_scheduled = self.wait_for_scheduled(desired_scheduled).await?;
2140            if actually_scheduled < desired_scheduled {
2141                let under_scheduled = desired_scheduled - actually_scheduled;
2142                to_take -= under_scheduled;
2143            }
2144        }
2145
2146        if to_take == 0 {
2147            return Ok(None);
2148        }
2149
2150        let next_task = self.root_decoder.drain_batch_task(to_take)?;
2151        self.rows_drained += to_take;
2152        Ok(Some(next_task))
2153    }
2154
2155    pub fn into_stream(self) -> BoxStream<'static, ReadBatchTask> {
2156        let stream = futures::stream::unfold(self, |mut slf| async move {
2157            let next_task = match slf.next_batch_task().await {
2158                Ok(Some(next_task)) => next_task,
2159                Ok(None) => return None,
2160                Err(err) => {
2161                    slf.rows_remaining = 0;
2162                    return Some((
2163                        ReadBatchTask {
2164                            task: async move { Err(err) }.boxed(),
2165                            num_rows: 0,
2166                        },
2167                        slf,
2168                    ));
2169                }
2170            };
2171            let num_rows = next_task.num_rows;
2172            let emitted_batch_size_warning = slf.emitted_batch_size_warning.clone();
2173            let bytes_per_row_feedback = slf.bytes_per_row_feedback.clone();
2174            // Capture the per-stream policy once so every emitted batch task follows the
2175            // same throughput-vs-overhead choice made by the scheduler.
2176            let spawn_batch_decode_tasks = slf.spawn_batch_decode_tasks;
2177            let task = async move {
2178                let (batch, data_size) = if spawn_batch_decode_tasks {
2179                    tokio::spawn(async move { next_task.into_batch(emitted_batch_size_warning) })
2180                        .await
2181                        .map_err(|err| Error::wrapped(err.into()))??
2182                } else {
2183                    next_task.into_batch(emitted_batch_size_warning)?
2184                };
2185                let num_rows = batch.num_rows() as u64;
2186                if let Some(bpr) = data_size.checked_div(num_rows) {
2187                    let prev = bytes_per_row_feedback.load(Ordering::Relaxed);
2188                    let next = if prev == 0 || bpr >= prev {
2189                        // First batch or actual size is larger than estimate:
2190                        // adopt immediately to avoid OOM.
2191                        bpr
2192                    } else {
2193                        // Actual size is smaller: degrade gradually toward
2194                        // the true value to avoid over-correcting on a
2195                        // single anomalous batch.
2196                        (prev + bpr) / 2
2197                    };
2198                    bytes_per_row_feedback.store(next.max(1), Ordering::Relaxed);
2199                }
2200                Ok(batch)
2201            };
2202            // This should be true since batch size is u32
2203            debug_assert!(num_rows <= u32::MAX as u64);
2204            Some((
2205                ReadBatchTask {
2206                    task: task.boxed(),
2207                    num_rows: num_rows as u32,
2208                },
2209                slf,
2210            ))
2211        });
2212        stream.boxed()
2213    }
2214}
2215
2216#[derive(Debug)]
2217pub enum RequestedRows {
2218    Ranges(Vec<Range<u64>>),
2219    Indices(Vec<u64>),
2220}
2221
2222impl RequestedRows {
2223    pub fn num_rows(&self) -> u64 {
2224        match self {
2225            Self::Ranges(ranges) => ranges.iter().map(|r| r.end - r.start).sum(),
2226            Self::Indices(indices) => indices.len() as u64,
2227        }
2228    }
2229
2230    pub fn trim_empty_ranges(mut self) -> Self {
2231        if let Self::Ranges(ranges) = &mut self {
2232            ranges.retain(|r| !r.is_empty());
2233        }
2234        self
2235    }
2236
2237    fn into_ranges(self) -> Result<Vec<Range<u64>>> {
2238        match self {
2239            Self::Ranges(ranges) => Ok(ranges),
2240            Self::Indices(indices) => Self::indices_to_ranges(&indices),
2241        }
2242    }
2243
2244    // coalesce continuous indices if possible (the input indices must be sorted)
2245    fn indices_to_ranges(indices: &[u64]) -> Result<Vec<Range<u64>>> {
2246        if indices.is_empty() {
2247            return Ok(Vec::new());
2248        }
2249
2250        let mut ranges = Vec::with_capacity(indices.len());
2251        let mut start = indices[0];
2252
2253        for (index, pair) in indices.windows(2).enumerate() {
2254            if pair[0] >= pair[1] {
2255                return Err(Error::invalid_input(format!(
2256                    "Requested row indices are not strictly increasing at index {index}: {} then {}",
2257                    pair[0], pair[1]
2258                )));
2259            }
2260            let end = pair[0]
2261                .checked_add(1)
2262                .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2263            if pair[1] != end {
2264                ranges.push(start..end);
2265                start = pair[1];
2266            }
2267        }
2268
2269        let end = indices
2270            .last()
2271            .copied()
2272            .and_then(|index| index.checked_add(1))
2273            .ok_or_else(|| Error::invalid_input("Requested row index cannot equal u64::MAX"))?;
2274        ranges.push(start..end);
2275        Ok(ranges)
2276    }
2277}
2278
2279/// Configuration for decoder behavior
2280#[derive(Debug, Clone)]
2281pub struct DecoderConfig {
2282    /// Whether to cache repetition indices for better performance.
2283    ///
2284    /// This defaults to the `LANCE_READ_CACHE_REPETITION_INDEX` environment variable
2285    /// when present and is enabled by default. Set the env var to a non-truthy
2286    /// value (for example `0` or `false`) to disable it. The env var is read
2287    /// once per process.
2288    pub cache_repetition_index: bool,
2289    /// Whether to validate decoded data
2290    pub validate_on_decode: bool,
2291    /// Override the strategy used to dispatch the scheduling work in
2292    /// [`schedule_and_decode`].
2293    ///
2294    /// `schedule_and_decode` always awaits the scheduler's `initialize` (which
2295    /// performs metadata I/O) before returning.  This flag controls what
2296    /// happens with the subsequent (synchronous) work of pushing decoder
2297    /// messages into the channel that feeds the decode stream.
2298    ///
2299    /// * `None` - default behavior: the scheduling work runs inline (as part
2300    ///   of the `schedule_and_decode` await) when the request is small
2301    ///   (controlled by the `LANCE_INLINE_SCHEDULING_THRESHOLD` env var) and
2302    ///   is dispatched onto a spawned task otherwise.
2303    /// * `Some(true)` - always run scheduling inline.  The await of
2304    ///   `schedule_and_decode` does not return until every decoder message
2305    ///   has been queued.
2306    /// * `Some(false)` - always spawn a task for scheduling so that it can
2307    ///   overlap with consumption of the decode stream.
2308    pub inline_scheduling: Option<bool>,
2309}
2310
2311impl Default for DecoderConfig {
2312    fn default() -> Self {
2313        Self {
2314            cache_repetition_index: default_cache_repetition_index(),
2315            validate_on_decode: false,
2316            inline_scheduling: None,
2317        }
2318    }
2319}
2320
2321#[derive(Debug, Clone)]
2322pub struct SchedulerDecoderConfig {
2323    pub decoder_plugins: Arc<DecoderPlugins>,
2324    pub batch_size: u32,
2325    pub io: Arc<dyn EncodingsIo>,
2326    pub cache: Arc<LanceCache>,
2327    /// Decoder configuration
2328    pub decoder_config: DecoderConfig,
2329    /// If set, target this many bytes per batch while retaining `batch_size` as
2330    /// an independent row-count upper bound.
2331    ///
2332    /// Only supported for v2.1+ (structural) files. For v2.0 files this
2333    /// option is ignored and a warning is logged.
2334    pub batch_size_bytes: Option<u64>,
2335}
2336
2337fn check_scheduler_on_drop(
2338    stream: BoxStream<'static, ReadBatchTask>,
2339    scheduler_handle: tokio::task::JoinHandle<()>,
2340) -> BoxStream<'static, ReadBatchTask> {
2341    // This is a bit weird but we create an "empty stream" that unwraps the scheduler handle (which
2342    // will panic if the scheduler panicked).  This let's us check if the scheduler panicked
2343    // when the stream finishes.
2344    let abort_handle = scheduler_handle.abort_handle();
2345    let mut scheduler_handle = Some(scheduler_handle);
2346    let check_scheduler = stream::unfold((), move |_| {
2347        let handle = scheduler_handle.take();
2348        async move {
2349            if let Some(handle) = handle {
2350                handle.await.unwrap();
2351            }
2352            None
2353        }
2354    });
2355    stream
2356        .chain(check_scheduler)
2357        .on_drop(move || {
2358            // Abort the scheduler task on early drop. The scheduler task holds
2359            // a reference to the I/O scheduler (via config.io) which keeps the
2360            // ScanScheduler alive. If the scheduler task is stuck waiting for
2361            // initialization I/O (which is blocked on backpressure that will
2362            // never drain because no one is consuming the stream), we need to
2363            // abort it so it releases its I/O reference and allows the
2364            // ScanScheduler to drop and cancel pending I/O.
2365            abort_handle.abort();
2366        })
2367        .boxed()
2368}
2369
2370#[allow(clippy::too_many_arguments)]
2371pub fn create_decode_stream(
2372    schema: &Schema,
2373    num_rows: u64,
2374    batch_size: u32,
2375    is_structural: bool,
2376    should_validate: bool,
2377    spawn_structural_batch_decode_tasks: bool,
2378    rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2379    batch_size_bytes: Option<u64>,
2380) -> Result<BoxStream<'static, ReadBatchTask>> {
2381    create_decode_stream_with_i32_offset_limit(
2382        schema,
2383        num_rows,
2384        batch_size,
2385        is_structural,
2386        should_validate,
2387        spawn_structural_batch_decode_tasks,
2388        rx,
2389        batch_size_bytes,
2390        true,
2391    )
2392}
2393
2394#[allow(clippy::too_many_arguments)]
2395fn create_decode_stream_with_i32_offset_limit(
2396    schema: &Schema,
2397    num_rows: u64,
2398    batch_size: u32,
2399    is_structural: bool,
2400    should_validate: bool,
2401    spawn_structural_batch_decode_tasks: bool,
2402    rx: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
2403    batch_size_bytes: Option<u64>,
2404    limit_i32_offset_batch_size: bool,
2405) -> Result<BoxStream<'static, ReadBatchTask>> {
2406    if is_structural {
2407        let arrow_schema = ArrowSchema::from(schema);
2408        let structural_decoder = StructuralStructDecoder::new(
2409            arrow_schema.fields,
2410            should_validate,
2411            /*is_root=*/ true,
2412            /*nullable=*/ false,
2413        )?;
2414        Ok(StructuralBatchDecodeStream::new(
2415            rx,
2416            batch_size,
2417            num_rows,
2418            structural_decoder,
2419            spawn_structural_batch_decode_tasks,
2420            batch_size_bytes,
2421        )
2422        .into_stream())
2423    } else {
2424        if batch_size_bytes.is_some() {
2425            warn!("batch_size_bytes is not supported for v2.0 files and will be ignored");
2426        }
2427        let arrow_schema = ArrowSchema::from(schema);
2428        let root_fields = arrow_schema.fields;
2429
2430        let simple_struct_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2431        Ok(BatchDecodeStream::new_with_i32_offset_limit(
2432            rx,
2433            batch_size,
2434            num_rows,
2435            simple_struct_decoder,
2436            limit_i32_offset_batch_size,
2437        )
2438        .into_stream())
2439    }
2440}
2441
2442/// Creates a iterator that decodes a set of messages in a blocking fashion
2443///
2444/// See [`schedule_and_decode_blocking`] for more information.
2445pub fn create_decode_iterator(
2446    schema: &Schema,
2447    num_rows: u64,
2448    batch_size: u32,
2449    should_validate: bool,
2450    is_structural: bool,
2451    messages: VecDeque<Result<DecoderMessage>>,
2452) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2453    let arrow_schema = Arc::new(ArrowSchema::from(schema));
2454    let root_fields = arrow_schema.fields.clone();
2455    if is_structural {
2456        let simple_struct_decoder = StructuralStructDecoder::new(
2457            root_fields,
2458            should_validate,
2459            /*is_root=*/ true,
2460            /*nullable=*/ false,
2461        )?;
2462        Ok(Box::new(BatchDecodeIterator::new(
2463            messages,
2464            batch_size,
2465            num_rows,
2466            simple_struct_decoder,
2467            arrow_schema,
2468        )))
2469    } else {
2470        let root_decoder = SimpleStructDecoder::new(root_fields, num_rows);
2471        Ok(Box::new(BatchDecodeIterator::new(
2472            messages,
2473            batch_size,
2474            num_rows,
2475            root_decoder,
2476            arrow_schema,
2477        )))
2478    }
2479}
2480
2481async fn create_scheduler_decoder(
2482    column_infos: Vec<Arc<ColumnInfo>>,
2483    requested_rows: RequestedRows,
2484    filter: FilterExpression,
2485    column_indices: Vec<u32>,
2486    target_schema: Arc<Schema>,
2487    config: SchedulerDecoderConfig,
2488) -> Result<BoxStream<'static, ReadBatchTask>> {
2489    let num_rows = requested_rows.num_rows();
2490    let is_range_request = matches!(&requested_rows, RequestedRows::Ranges(_));
2491
2492    let is_structural = column_infos[0].is_structural();
2493    let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
2494    let spawn_structural_batch_decode_tasks = match mode.ok().as_deref() {
2495        Some("always") => true,
2496        Some("never") => false,
2497        _ => is_range_request,
2498    };
2499
2500    let (tx, rx) = mpsc::unbounded_channel();
2501
2502    let decode_stream = create_decode_stream(
2503        &target_schema,
2504        num_rows,
2505        config.batch_size,
2506        is_structural,
2507        config.decoder_config.validate_on_decode,
2508        spawn_structural_batch_decode_tasks,
2509        rx,
2510        config.batch_size_bytes,
2511    )?;
2512
2513    // The scheduler's `initialize` may perform I/O to load column metadata
2514    // unless that metadata is already in the cache.  This metadata loading
2515    // happens as part of this call and should be parallelized if reading
2516    // multiple files.  We pass the rows we are about to schedule so that the
2517    // structural path only initializes the pages those rows touch.
2518    let requested_ranges = requested_rows.into_ranges()?;
2519    let mut decode_scheduler = DecodeBatchScheduler::try_new_with_ranges(
2520        target_schema.as_ref(),
2521        &column_indices,
2522        &column_infos,
2523        &vec![],
2524        num_rows,
2525        config.decoder_plugins,
2526        config.io.clone(),
2527        config.cache,
2528        Some(&requested_ranges),
2529        &filter,
2530        &config.decoder_config,
2531    )
2532    .await?;
2533
2534    // For small requests the scheduling cost is dwarfed by the overhead of
2535    // spawning a task, so we run scheduling inline (still as part of this
2536    // await) before returning.  The threshold is configurable via
2537    // `LANCE_INLINE_SCHEDULING_THRESHOLD`, and callers can force either
2538    // strategy via `DecoderConfig::inline_scheduling`.
2539    let inline_scheduling = config
2540        .decoder_config
2541        .inline_scheduling
2542        .unwrap_or_else(|| num_rows <= inline_scheduling_threshold());
2543
2544    if inline_scheduling {
2545        decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2546        Ok(decode_stream)
2547    } else {
2548        // Spawn the (still synchronous) scheduling work so that decoder
2549        // messages can stream into the channel while the consumer is
2550        // already pulling from the decode stream.
2551        let scheduling = async move {
2552            decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io)
2553        };
2554        let scheduler_handle = tokio::task::spawn(scheduling);
2555        Ok(check_scheduler_on_drop(decode_stream, scheduler_handle))
2556    }
2557}
2558
2559/// Initializes the scheduler, schedules the requested rows, and returns a
2560/// stream of decode tasks for the resulting batches.
2561///
2562/// This is a convenience function that creates both the scheduler and the
2563/// decoder, which can be a little tricky to get right.
2564///
2565/// # Why is this async?
2566///
2567/// Constructing the scheduler runs `initialize` which will perform I/O
2568/// unless the data required is already in the file metadata cache.
2569///
2570/// When `DecoderConfig::inline_scheduling` resolves to `true`, the
2571/// subsequent (synchronous) scheduling work also runs before this function
2572/// returns, leaving a fully primed decode stream.
2573pub async fn schedule_and_decode(
2574    column_infos: Vec<Arc<ColumnInfo>>,
2575    requested_rows: RequestedRows,
2576    filter: FilterExpression,
2577    column_indices: Vec<u32>,
2578    target_schema: Arc<Schema>,
2579    config: SchedulerDecoderConfig,
2580) -> Result<BoxStream<'static, ReadBatchTask>> {
2581    if requested_rows.num_rows() == 0 {
2582        return Ok(stream::empty().boxed());
2583    }
2584
2585    // If the user requested any ranges that are empty, ignore them.  They are pointless and
2586    // trying to read them has caused bugs in the past.
2587    let requested_rows = requested_rows.trim_empty_ranges();
2588
2589    let io = config.io.clone();
2590
2591    let stream = create_scheduler_decoder(
2592        column_infos,
2593        requested_rows,
2594        filter,
2595        column_indices,
2596        target_schema,
2597        config,
2598    )
2599    .await?;
2600
2601    // Keep the io alive until the stream is dropped or finishes.  Otherwise the
2602    // I/O drops as soon as the scheduling is finished and the I/O loop terminates.
2603    Ok(stream.finally(move || drop(io)).boxed())
2604}
2605
2606pub static WAITER_RT: LazyLock<tokio::runtime::Runtime> = LazyLock::new(|| {
2607    tokio::runtime::Builder::new_current_thread()
2608        .build()
2609        .unwrap()
2610});
2611
2612/// Schedules and decodes the requested data in a blocking fashion
2613///
2614/// This function is a blocking version of [`schedule_and_decode`]. It schedules the requested data
2615/// and decodes it in the current thread.
2616///
2617/// This can be useful when the disk is fast (or the data is in memory) and the amount
2618/// of data is relatively small.  For example, when doing a take against NVMe or in-memory data.
2619///
2620/// This should NOT be used for full scans.  Even if the data is in memory this function will
2621/// not parallelize the decode and will be slower than the async version.  Full scans typically
2622/// make relatively few IOPs and so the asynchronous overhead is much smaller.
2623///
2624/// This method will first completely run the scheduling process.  Then it will run the
2625/// decode process.
2626pub fn schedule_and_decode_blocking(
2627    column_infos: Vec<Arc<ColumnInfo>>,
2628    requested_rows: RequestedRows,
2629    filter: FilterExpression,
2630    column_indices: Vec<u32>,
2631    target_schema: Arc<Schema>,
2632    config: SchedulerDecoderConfig,
2633) -> Result<Box<dyn RecordBatchReader + Send + 'static>> {
2634    if requested_rows.num_rows() == 0 {
2635        let arrow_schema = Arc::new(ArrowSchema::from(target_schema.as_ref()));
2636        return Ok(Box::new(RecordBatchIterator::new(vec![], arrow_schema)));
2637    }
2638
2639    let requested_rows = requested_rows.trim_empty_ranges();
2640    let num_rows = requested_rows.num_rows();
2641    let is_structural = column_infos[0].is_structural();
2642
2643    let (tx, mut rx) = mpsc::unbounded_channel();
2644
2645    // Initialize the scheduler.  This is still "asynchronous" but we run it with a current-thread
2646    // runtime.  Pass the rows we are about to schedule so the structural path
2647    // only initializes the pages those rows touch.
2648    let requested_ranges = requested_rows.into_ranges()?;
2649    let mut decode_scheduler = WAITER_RT.block_on(DecodeBatchScheduler::try_new_with_ranges(
2650        target_schema.as_ref(),
2651        &column_indices,
2652        &column_infos,
2653        &vec![],
2654        num_rows,
2655        config.decoder_plugins,
2656        config.io.clone(),
2657        config.cache,
2658        Some(&requested_ranges),
2659        &filter,
2660        &config.decoder_config,
2661    ))?;
2662
2663    // Schedule the requested rows
2664    decode_scheduler.schedule_ranges(&requested_ranges, &filter, tx, config.io);
2665
2666    // Drain the scheduler queue into a vec of decode messages
2667    let mut messages = Vec::new();
2668    while rx
2669        .recv_many(&mut messages, usize::MAX)
2670        .now_or_never()
2671        .unwrap()
2672        != 0
2673    {}
2674
2675    // Create a decoder to decode the messages
2676    let decode_iterator = create_decode_iterator(
2677        &target_schema,
2678        num_rows,
2679        config.batch_size,
2680        config.decoder_config.validate_on_decode,
2681        is_structural,
2682        messages.into(),
2683    )?;
2684
2685    Ok(decode_iterator)
2686}
2687
2688/// A decoder for single-column encodings of primitive data (this includes fixed size
2689/// lists of primitive data)
2690///
2691/// Physical decoders are able to decode into existing buffers for zero-copy operation.
2692///
2693/// Instances should be stateless and `Send` / `Sync`.  This is because multiple decode
2694/// tasks could reference the same page.  For example, imagine a page covers rows 0-2000
2695/// and the decoder stream has a batch size of 1024.  The decoder will be needed by both
2696/// the decode task for batch 0 and the decode task for batch 1.
2697///
2698/// See [`crate::decoder`] for more information
2699pub trait PrimitivePageDecoder: Send + Sync {
2700    /// Decode data into buffers
2701    ///
2702    /// This may be a simple zero-copy from a disk buffer or could involve complex decoding
2703    /// such as decompressing from some compressed representation.
2704    ///
2705    /// Capacity is stored as a tuple of (num_bytes: u64, is_needed: bool).  The `is_needed`
2706    /// portion only needs to be updated if the encoding has some concept of an "optional"
2707    /// buffer.
2708    ///
2709    /// Encodings can have any number of input or output buffers.  For example, a dictionary
2710    /// decoding will convert two buffers (indices + dictionary) into a single buffer
2711    ///
2712    /// Binary decodings have two output buffers (one for values, one for offsets)
2713    ///
2714    /// Other decodings could even expand the # of output buffers.  For example, we could decode
2715    /// fixed size strings into variable length strings going from one input buffer to multiple output
2716    /// buffers.
2717    ///
2718    /// Each Arrow data type typically has a fixed structure of buffers and the encoding chain will
2719    /// generally end at one of these structures.  However, intermediate structures may exist which
2720    /// do not correspond to any Arrow type at all.  For example, a bitpacking encoding will deal
2721    /// with buffers that have bits-per-value that is not a multiple of 8.
2722    ///
2723    /// The `primitive_array_from_buffers` method has an expected buffer layout for each arrow
2724    /// type (order matters) and encodings that aim to decode into arrow types should respect
2725    /// this layout.
2726    /// # Arguments
2727    ///
2728    /// * `rows_to_skip` - how many rows to skip (within the page) before decoding
2729    /// * `num_rows` - how many rows to decode
2730    /// * `all_null` - A mutable bool, set to true if a decoder determines all values are null
2731    fn decode(&self, rows_to_skip: u64, num_rows: u64) -> Result<DataBlock>;
2732
2733    /// Returns the variable-width **value** bytes (the bytes an offsets buffer
2734    /// indexes into) that decoding `num_rows` rows starting at `rows_to_skip`
2735    /// will produce, or `None` when the encoding cannot report sizes without
2736    /// decoding.  An upper bound is acceptable; callers use this to keep i32
2737    /// offset values within capacity, so over-estimating only splits batches
2738    /// earlier while under-estimating would let them overflow.
2739    fn variable_width_bytes(&self, _rows_to_skip: u64, _num_rows: u64) -> Result<Option<u64>> {
2740        Ok(None)
2741    }
2742}
2743
2744/// A scheduler for single-column encodings of primitive data
2745///
2746/// The scheduler is responsible for calculating what I/O is needed for the requested rows
2747///
2748/// Instances should be stateless and `Send` and `Sync`.  This is because instances can
2749/// be shared in follow-up I/O tasks.
2750///
2751/// See [`crate::decoder`] for more information
2752pub trait PageScheduler: Send + Sync + std::fmt::Debug {
2753    /// Schedules a batch of I/O to load the data needed for the requested ranges
2754    ///
2755    /// Returns a future that will yield a decoder once the data has been loaded
2756    ///
2757    /// # Arguments
2758    ///
2759    /// * `range` - the range of row offsets (relative to start of page) requested
2760    ///   these must be ordered and must not overlap
2761    /// * `scheduler` - a scheduler to submit the I/O request to
2762    /// * `top_level_row` - the row offset of the top level field currently being
2763    ///   scheduled.  This can be used to assign priority to I/O requests
2764    fn schedule_ranges(
2765        &self,
2766        ranges: &[Range<u64>],
2767        scheduler: &Arc<dyn EncodingsIo>,
2768        top_level_row: u64,
2769    ) -> BoxFuture<'static, Result<Box<dyn PrimitivePageDecoder>>>;
2770}
2771
2772/// A trait to control the priority of I/O
2773pub trait PriorityRange: std::fmt::Debug + Send + Sync {
2774    fn advance(&mut self, num_rows: u64);
2775    fn current_priority(&self) -> u64;
2776    fn box_clone(&self) -> Box<dyn PriorityRange>;
2777}
2778
2779/// A simple priority scheme for top-level fields with no parent
2780/// repetition
2781#[derive(Debug)]
2782pub struct SimplePriorityRange {
2783    priority: u64,
2784}
2785
2786impl SimplePriorityRange {
2787    fn new(priority: u64) -> Self {
2788        Self { priority }
2789    }
2790}
2791
2792impl PriorityRange for SimplePriorityRange {
2793    fn advance(&mut self, num_rows: u64) {
2794        self.priority += num_rows;
2795    }
2796
2797    fn current_priority(&self) -> u64 {
2798        self.priority
2799    }
2800
2801    fn box_clone(&self) -> Box<dyn PriorityRange> {
2802        Box::new(Self {
2803            priority: self.priority,
2804        })
2805    }
2806}
2807
2808/// Determining the priority of a list request is tricky.  We want
2809/// the priority to be the top-level row.  So if we have a
2810/// `list<list<int>>` and each outer list has 10 rows and each inner
2811/// list has 5 rows then the priority of the 100th item is 1 because
2812/// it is the 5th item in the 10th item of the *second* row.
2813///
2814/// This structure allows us to keep track of this complicated priority
2815/// relationship.
2816///
2817/// There's a fair amount of bookkeeping involved here.
2818///
2819/// A better approach (using repetition levels) is coming in the future.
2820pub struct ListPriorityRange {
2821    base: Box<dyn PriorityRange>,
2822    offsets: Arc<[u64]>,
2823    cur_index_into_offsets: usize,
2824    cur_position: u64,
2825}
2826
2827impl ListPriorityRange {
2828    pub(crate) fn new(base: Box<dyn PriorityRange>, offsets: Arc<[u64]>) -> Self {
2829        Self {
2830            base,
2831            offsets,
2832            cur_index_into_offsets: 0,
2833            cur_position: 0,
2834        }
2835    }
2836}
2837
2838impl std::fmt::Debug for ListPriorityRange {
2839    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2840        f.debug_struct("ListPriorityRange")
2841            .field("base", &self.base)
2842            .field("offsets.len()", &self.offsets.len())
2843            .field("cur_index_into_offsets", &self.cur_index_into_offsets)
2844            .field("cur_position", &self.cur_position)
2845            .finish()
2846    }
2847}
2848
2849impl PriorityRange for ListPriorityRange {
2850    fn advance(&mut self, num_rows: u64) {
2851        // We've scheduled X items.  Now walk through the offsets to
2852        // determine how many rows we've scheduled.
2853        self.cur_position += num_rows;
2854        let mut idx_into_offsets = self.cur_index_into_offsets;
2855        while idx_into_offsets + 1 < self.offsets.len()
2856            && self.offsets[idx_into_offsets + 1] <= self.cur_position
2857        {
2858            idx_into_offsets += 1;
2859        }
2860        let base_rows_advanced = idx_into_offsets - self.cur_index_into_offsets;
2861        self.cur_index_into_offsets = idx_into_offsets;
2862        self.base.advance(base_rows_advanced as u64);
2863    }
2864
2865    fn current_priority(&self) -> u64 {
2866        self.base.current_priority()
2867    }
2868
2869    fn box_clone(&self) -> Box<dyn PriorityRange> {
2870        Box::new(Self {
2871            base: self.base.box_clone(),
2872            offsets: self.offsets.clone(),
2873            cur_index_into_offsets: self.cur_index_into_offsets,
2874            cur_position: self.cur_position,
2875        })
2876    }
2877}
2878
2879/// Contains the context for a scheduler
2880/// The writer accumulates about this many bytes of values per column before it
2881/// cuts pages (`EncodingOptions::cache_bytes_per_column`), and the rep/def
2882/// budget of a mini-block chunk then splits that block into many small pages
2883/// for nested columns. Batching reads back up to this size restores one
2884/// I/O request per accumulated block.
2885pub(crate) const IO_REQUEST_BATCH_BYTES: u64 = 8 * 1024 * 1024;
2886
2887type SharedRead = Shared<BoxFuture<'static, std::result::Result<Arc<Vec<Bytes>>, CloneableError>>>;
2888
2889/// What a pending read receives once its batch is submitted.
2890enum SubmittedRead {
2891    /// The read was alone in its batch and owns the request.
2892    Alone(BoxFuture<'static, Result<Vec<Bytes>>>),
2893    /// The batched request and the position of each of the read's ranges in it.
2894    Shared(SharedRead, Vec<usize>),
2895}
2896
2897struct PendingRead {
2898    ranges: Vec<Range<u64>>,
2899    priority: u64,
2900    tx: oneshot::Sender<SubmittedRead>,
2901}
2902
2903/// Collects the reads submitted while one scheduling step runs and sends them
2904/// to the I/O layer as a single request.
2905///
2906/// The I/O scheduler only coalesces the ranges of one request, so the reads a
2907/// step issues for different pages, or for different columns, cost one request
2908/// each even when they sit next to each other in the file. A take of a few rows
2909/// reads a few bytes per column, and a file's small pages are packed together,
2910/// so batching them turns dozens of requests into a handful.
2911///
2912/// A batch is submitted by [`Self::flush`] when the step ends, as soon as the
2913/// queued reads reach [`IO_REQUEST_BATCH_BYTES`] (a page that shards its own
2914/// reads to bound buffering keeps every shard in a bounded read), or when a
2915/// caller awaits one of its reads, so a read queued outside a step (indirect
2916/// I/O issued from a load future) never waits for a flush that does not come.
2917/// That last flush waits for one yield first: sibling futures polled in the
2918/// same pass (a struct's columns initializing their pages, a scan line's list
2919/// columns fetching their items) all queue their reads before the first of
2920/// them resumes and submits the lot.
2921pub(crate) struct RequestBatch {
2922    inner: Arc<dyn EncodingsIo>,
2923    pending: Arc<Mutex<Vec<PendingRead>>>,
2924}
2925
2926impl std::fmt::Debug for RequestBatch {
2927    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2928        f.debug_struct("RequestBatch").finish_non_exhaustive()
2929    }
2930}
2931
2932impl RequestBatch {
2933    pub(crate) fn new(inner: Arc<dyn EncodingsIo>) -> Self {
2934        Self {
2935            inner,
2936            pending: Arc::new(Mutex::new(Vec::new())),
2937        }
2938    }
2939
2940    fn queued_bytes(pending: &[PendingRead]) -> u64 {
2941        pending
2942            .iter()
2943            .flat_map(|read| &read.ranges)
2944            .map(|range| range.end - range.start)
2945            .sum()
2946    }
2947
2948    /// Bytes requested by the reads collected so far.
2949    pub(crate) fn pending_bytes(&self) -> u64 {
2950        Self::queued_bytes(&self.pending.lock().unwrap())
2951    }
2952
2953    /// Submits the collected reads as one request.
2954    pub(crate) fn flush(&self) {
2955        Self::flush_pending(&self.inner, &self.pending);
2956    }
2957
2958    fn flush_pending(inner: &Arc<dyn EncodingsIo>, pending: &Mutex<Vec<PendingRead>>) {
2959        let pending = std::mem::take(&mut *pending.lock().unwrap());
2960        Self::submit_batch(inner, pending);
2961    }
2962
2963    /// Submits `pending` as one request, sorted by file offset, and hands each
2964    /// read its own slice of the result.
2965    fn submit_batch(inner: &Arc<dyn EncodingsIo>, pending: Vec<PendingRead>) {
2966        if pending.is_empty() {
2967            return;
2968        }
2969        if pending.len() == 1 {
2970            let read = pending.into_iter().next().unwrap();
2971            let _ = read.tx.send(SubmittedRead::Alone(
2972                inner.submit_request(read.ranges, read.priority),
2973            ));
2974            return;
2975        }
2976        // The lowest row number any of the batched reads delivers data for.
2977        let priority = pending.iter().map(|read| read.priority).min().unwrap();
2978        let mut ordered = pending
2979            .iter()
2980            .enumerate()
2981            .flat_map(|(read_idx, read)| {
2982                read.ranges
2983                    .iter()
2984                    .enumerate()
2985                    .map(move |(range_idx, range)| (range.clone(), read_idx, range_idx))
2986            })
2987            .collect::<Vec<_>>();
2988        ordered.sort_by_key(|(range, _, _)| (range.start, range.end));
2989        let ranges = ordered
2990            .iter()
2991            .map(|(range, _, _)| range.clone())
2992            .collect::<Vec<_>>();
2993        // For every read, where each of its ranges landed in the sorted request.
2994        let mut positions = pending
2995            .iter()
2996            .map(|read| vec![0; read.ranges.len()])
2997            .collect::<Vec<_>>();
2998        for (position, (_, read_idx, range_idx)) in ordered.iter().enumerate() {
2999            positions[*read_idx][*range_idx] = position;
3000        }
3001        let batched: SharedRead = inner
3002            .submit_request(ranges, priority)
3003            .map(|result| result.map(Arc::new).map_err(CloneableError::new))
3004            .boxed()
3005            .shared();
3006        for (read, read_positions) in pending.into_iter().zip(positions) {
3007            // The receiver is gone only when the read's future was dropped.
3008            let _ = read
3009                .tx
3010                .send(SubmittedRead::Shared(batched.clone(), read_positions));
3011        }
3012    }
3013}
3014
3015impl EncodingsIo for RequestBatch {
3016    fn submit_request(
3017        &self,
3018        ranges: Vec<Range<u64>>,
3019        priority: u64,
3020    ) -> BoxFuture<'static, Result<Vec<Bytes>>> {
3021        let mut pending = self.pending.lock().unwrap();
3022        // Keep every batch within the budget: a page that shards its reads
3023        // must not have all its shards collapse into one unbounded read.
3024        let bytes = ranges
3025            .iter()
3026            .map(|range| range.end - range.start)
3027            .sum::<u64>();
3028        if !pending.is_empty() && Self::queued_bytes(&pending) + bytes > IO_REQUEST_BATCH_BYTES {
3029            Self::submit_batch(&self.inner, std::mem::take(&mut *pending));
3030        }
3031        let (tx, mut rx) = oneshot::channel();
3032        pending.push(PendingRead {
3033            ranges,
3034            priority,
3035            tx,
3036        });
3037        drop(pending);
3038        let inner = self.inner.clone();
3039        let pending = self.pending.clone();
3040        async move {
3041            let submitted = match rx.try_recv() {
3042                // The step that queued this read already flushed it.
3043                Ok(Some(submitted)) => submitted,
3044                _ => {
3045                    // Awaiting a read is the end of whatever step queued it.
3046                    // Yield once so the siblings polled in the same pass get
3047                    // to queue too, then submit them all.
3048                    tokio::task::yield_now().await;
3049                    Self::flush_pending(&inner, &pending);
3050                    rx.await.map_err(|_| {
3051                        Error::internal("a batched read was dropped before the batch was submitted")
3052                    })?
3053                }
3054            };
3055            match submitted {
3056                SubmittedRead::Alone(request) => request.await,
3057                SubmittedRead::Shared(batched, positions) => {
3058                    let bytes = batched.await.map_err(|err| err.into_inner())?;
3059                    Ok(positions
3060                        .into_iter()
3061                        .map(|position| bytes[position].clone())
3062                        .collect())
3063                }
3064            }
3065        }
3066        .boxed()
3067    }
3068
3069    fn with_bypass_backpressure(&self) -> Option<Arc<dyn EncodingsIo>> {
3070        self.inner.with_bypass_backpressure()
3071    }
3072
3073    fn with_io_stats(
3074        &self,
3075        stats: Arc<dyn lance_core::utils::io_stats::IoStatsRecorder>,
3076    ) -> Option<Arc<dyn EncodingsIo>> {
3077        self.inner.with_io_stats(stats)
3078    }
3079}
3080
3081pub struct SchedulerContext {
3082    recv: Option<mpsc::UnboundedReceiver<DecoderMessage>>,
3083    io: Arc<dyn EncodingsIo>,
3084    /// `io`, as the batch that collects one scheduling step's reads.
3085    batch: Arc<RequestBatch>,
3086    cache: Arc<LanceCache>,
3087    name: String,
3088    path: Vec<u32>,
3089    path_names: Vec<String>,
3090}
3091
3092pub struct ScopedSchedulerContext<'a> {
3093    pub context: &'a mut SchedulerContext,
3094}
3095
3096impl<'a> ScopedSchedulerContext<'a> {
3097    pub fn pop(self) -> &'a mut SchedulerContext {
3098        self.context.pop();
3099        self.context
3100    }
3101}
3102
3103impl SchedulerContext {
3104    pub fn new(io: Arc<dyn EncodingsIo>, cache: Arc<LanceCache>) -> Self {
3105        let batch = Arc::new(RequestBatch::new(io));
3106        Self {
3107            io: batch.clone(),
3108            batch,
3109            cache,
3110            recv: None,
3111            name: "".to_string(),
3112            path: Vec::new(),
3113            path_names: Vec::new(),
3114        }
3115    }
3116
3117    pub fn io(&self) -> &Arc<dyn EncodingsIo> {
3118        &self.io
3119    }
3120
3121    /// Submits the reads the schedulers queued since the last step as one request.
3122    pub fn flush_io(&self) {
3123        self.batch.flush();
3124    }
3125
3126    pub fn cache(&self) -> &Arc<LanceCache> {
3127        &self.cache
3128    }
3129
3130    pub fn push(&'_ mut self, name: &str, index: u32) -> ScopedSchedulerContext<'_> {
3131        self.path.push(index);
3132        self.path_names.push(name.to_string());
3133        ScopedSchedulerContext { context: self }
3134    }
3135
3136    pub fn pop(&mut self) {
3137        self.path.pop();
3138        self.path_names.pop();
3139    }
3140
3141    pub fn path_name(&self) -> String {
3142        let path = self.path_names.join("/");
3143        if self.recv.is_some() {
3144            format!("TEMP({}){}", self.name, path)
3145        } else {
3146            format!("ROOT{}", path)
3147        }
3148    }
3149
3150    pub fn current_path(&self) -> VecDeque<u32> {
3151        VecDeque::from_iter(self.path.iter().copied())
3152    }
3153
3154    #[deprecated(since = "0.29.1", note = "This is for v2.0 array-encoding paths")]
3155    pub fn locate_decoder(&mut self, decoder: Box<dyn LogicalPageDecoder>) -> DecoderReady {
3156        trace!(
3157            "Scheduling decoder of type {:?} for {:?}",
3158            decoder.data_type(),
3159            self.path,
3160        );
3161        DecoderReady {
3162            decoder,
3163            path: self.current_path(),
3164        }
3165    }
3166}
3167
3168pub struct UnloadedPageShard(pub BoxFuture<'static, Result<LoadedPageShard>>);
3169
3170impl std::fmt::Debug for UnloadedPageShard {
3171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3172        f.debug_struct("UnloadedPage").finish()
3173    }
3174}
3175
3176#[derive(Debug)]
3177pub struct ScheduledScanLine {
3178    pub rows_scheduled: u64,
3179    pub decoders: Vec<MessageType>,
3180}
3181
3182pub trait StructuralSchedulingJob: std::fmt::Debug {
3183    /// Schedule the next batch of data
3184    ///
3185    /// Normally this equates to scheduling the next page of data into one task.  Very large pages
3186    /// might be split into multiple scan lines.  Each scan line has one or more rows.
3187    ///
3188    /// If a scheduler ends early it may return an empty vector.
3189    fn schedule_next(&mut self, context: &mut SchedulerContext) -> Result<Vec<ScheduledScanLine>>;
3190}
3191
3192/// A filter expression to apply to the data
3193///
3194/// The core decoders do not currently take advantage of filtering in
3195/// any way.  In order to maintain the abstraction we represent filters
3196/// as an arbitrary byte sequence.
3197///
3198/// We recommend that encodings use Substrait for filters.
3199pub struct FilterExpression(pub Bytes);
3200
3201impl FilterExpression {
3202    /// Create a filter expression that does not filter any data
3203    ///
3204    /// This is currently represented by an empty byte array.  Encoders
3205    /// that are "filter aware" should make sure they handle this case.
3206    pub fn no_filter() -> Self {
3207        Self(Bytes::new())
3208    }
3209
3210    /// Returns true if the filter is the same as the [`Self::no_filter`] filter
3211    pub fn is_noop(&self) -> bool {
3212        self.0.is_empty()
3213    }
3214}
3215
3216pub trait StructuralFieldScheduler: Send + std::fmt::Debug {
3217    /// Loads the per-page metadata this column needs before scheduling.
3218    ///
3219    /// When `requested_ranges` is present, only pages overlapping those
3220    /// top-level row ranges are initialized. `None` initializes the complete
3221    /// field.
3222    fn initialize<'a>(
3223        &'a mut self,
3224        requested_ranges: Option<&'a [Range<u64>]>,
3225        filter: &'a FilterExpression,
3226        context: &'a SchedulerContext,
3227    ) -> BoxFuture<'a, Result<()>>;
3228    fn schedule_ranges<'a>(
3229        &'a self,
3230        ranges: &[Range<u64>],
3231        filter: &FilterExpression,
3232    ) -> Result<Box<dyn StructuralSchedulingJob + 'a>>;
3233}
3234
3235/// A trait for tasks that decode data into an Arrow array
3236pub trait DecodeArrayTask: Send {
3237    /// Decodes the data into an Arrow array and its data size in bytes
3238    fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)>;
3239}
3240
3241impl DecodeArrayTask for Box<dyn StructuralDecodeArrayTask> {
3242    fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3243        let decoded_array = StructuralDecodeArrayTask::decode(*self)?;
3244        decoded_array.repdef.ensure_exhausted()?;
3245        Ok((decoded_array.array, decoded_array.data_size))
3246    }
3247}
3248
3249/// A task to decode data into an Arrow record batch
3250///
3251/// It has a child `task` which decodes a struct array with no nulls.
3252/// This is then converted into a record batch.
3253pub struct NextDecodeTask {
3254    /// The decode task itself
3255    pub task: Box<dyn DecodeArrayTask>,
3256    /// The number of rows that will be created
3257    pub num_rows: u64,
3258}
3259
3260impl NextDecodeTask {
3261    // Run the task and produce a record batch
3262    //
3263    // If the batch is very large this function will log a warning message
3264    // suggesting the user try a smaller batch size.
3265    #[instrument(name = "task_to_batch", level = "debug", skip_all)]
3266    fn into_batch(self, emitted_batch_size_warning: Arc<Once>) -> Result<(RecordBatch, u64)> {
3267        let (struct_arr, data_size) = self.task.decode()?;
3268        let batch = RecordBatch::from(struct_arr.as_struct());
3269        if data_size > BATCH_SIZE_BYTES_WARNING {
3270            emitted_batch_size_warning.call_once(|| {
3271                let size_mb = data_size / 1024 / 1024;
3272                debug!("Lance read in a single batch that contained more than {}MiB of data.  You may want to consider reducing the batch size.", size_mb);
3273            });
3274        }
3275        Ok((batch, data_size))
3276    }
3277}
3278
3279// An envelope to wrap both 2.0 style messages and 2.1 style messages so we can
3280// share some code paths between the two.  Decoders can safely unwrap into whatever
3281// style they expect since a file will be either all-2.0 or all-2.1
3282#[derive(Debug)]
3283pub enum MessageType {
3284    // The older v2.0 scheduler/decoder used a scheme where the message was the
3285    // decoder itself.  The messages were not sent in priority order and the decoder
3286    // had to wait for I/O, figuring out the correct priority.  This was a lot of
3287    // complexity.
3288    DecoderReady(DecoderReady),
3289    // Starting in 2.1 we use a simpler scheme where the scheduling happens in priority
3290    // order and the message is an unloaded decoder.  These can be awaited, in order, and
3291    // the decoder does not have to worry about waiting for I/O.
3292    UnloadedPage(UnloadedPageShard),
3293}
3294
3295impl MessageType {
3296    pub fn into_array(self) -> DecoderReady {
3297        match self {
3298            Self::DecoderReady(decoder) => decoder,
3299            Self::UnloadedPage(_) => {
3300                panic!("Expected DecoderReady but got UnloadedPage")
3301            }
3302        }
3303    }
3304
3305    pub fn into_structural(self) -> UnloadedPageShard {
3306        match self {
3307            Self::UnloadedPage(unloaded) => unloaded,
3308            Self::DecoderReady(_) => {
3309                panic!("Expected UnloadedPage but got DecoderReady")
3310            }
3311        }
3312    }
3313}
3314
3315pub struct DecoderMessage {
3316    pub scheduled_so_far: u64,
3317    pub decoders: Vec<MessageType>,
3318}
3319
3320pub struct DecoderContext {
3321    source: mpsc::UnboundedReceiver<Result<DecoderMessage>>,
3322}
3323
3324impl DecoderContext {
3325    pub fn new(source: mpsc::UnboundedReceiver<Result<DecoderMessage>>) -> Self {
3326        Self { source }
3327    }
3328}
3329
3330pub struct DecodedPage {
3331    pub data: DataBlock,
3332    pub repdef: RepDefUnraveler,
3333}
3334
3335pub trait DecodePageTask: Send + std::fmt::Debug {
3336    /// Decodes the data into an Arrow array
3337    fn decode(self: Box<Self>) -> Result<DecodedPage>;
3338}
3339
3340pub trait StructuralPageDecoder: std::fmt::Debug + Send {
3341    fn drain(&mut self, num_rows: u64) -> Result<Box<dyn DecodePageTask>>;
3342    fn num_rows(&self) -> u64;
3343    /// Returns the exact decoded byte count for the next `num_rows` rows
3344    /// from this decoder's current position, without consuming any rows.
3345    fn decoded_bytes(&self, _num_rows: u64) -> Result<u64> {
3346        Err(Error::not_supported(
3347            "decoded_bytes is not implemented for this page decoder".to_string(),
3348        ))
3349    }
3350}
3351
3352#[derive(Debug)]
3353pub struct LoadedPageShard {
3354    // The decoder that is ready to be decoded
3355    pub decoder: Box<dyn StructuralPageDecoder>,
3356    // The path to the decoder, the first value is the column index
3357    // following values, if present, are nested child indices
3358    //
3359    // For example, a path of [1, 1, 0] would mean to grab the second
3360    // column, then the second child, and then the first child.
3361    //
3362    // It could represent x in the following schema:
3363    //
3364    // score: float64
3365    // points: struct
3366    //   color: string
3367    //   location: struct
3368    //     x: float64
3369    //
3370    // Currently, only struct decoders have "children" although other
3371    // decoders may at some point as well.  List children are only
3372    // handled through indirect I/O at the moment and so they don't
3373    // need to be represented (yet)
3374    pub path: VecDeque<u32>,
3375}
3376
3377pub struct DecodedArray {
3378    pub array: ArrayRef,
3379    pub repdef: CompositeRepDefUnraveler,
3380    /// The number of bytes of data in this array (excluding Arrow overhead).
3381    pub data_size: u64,
3382}
3383
3384pub trait StructuralDecodeArrayTask: std::fmt::Debug + Send {
3385    fn decode(self: Box<Self>) -> Result<DecodedArray>;
3386}
3387
3388pub trait StructuralFieldDecoder: std::fmt::Debug + Send {
3389    /// Add a newly scheduled child decoder
3390    ///
3391    /// The default implementation does not expect children and returns
3392    /// an error.
3393    fn accept_page(&mut self, _child: LoadedPageShard) -> Result<()>;
3394    /// Creates a task to decode `num_rows` of data into an array
3395    fn drain(&mut self, num_rows: u64) -> Result<Box<dyn StructuralDecodeArrayTask>>;
3396    /// The data type of the decoded data
3397    fn data_type(&self) -> &DataType;
3398    /// Returns the exact decoded byte count for each of [`CANDIDATE_BATCH_SIZES`]
3399    /// row counts, clamped to `rows_remaining`.
3400    ///
3401    /// Implementations should do their best to estimate the exact size required for
3402    /// the uncompressed data.  In cases where this is not possible they should return
3403    /// a worst-case estimate.
3404    ///
3405    /// The default implementation simply returns a "not supported" error though this
3406    /// will hopefully be removed once implementation is complete.
3407    fn plan_decoded_bytes(&self, _rows_remaining: u64) -> Result<[u64; 8]> {
3408        Err(Error::not_supported(
3409            "decoded_bytes is not implemented for this field decoder".to_string(),
3410        ))
3411    }
3412}
3413
3414#[derive(Debug, Default)]
3415pub struct DecoderPlugins {}
3416
3417/// The top-level column layout used by an in-memory encoded batch.
3418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3419pub enum EncodedBatchLayout {
3420    /// Array pages include structural columns.
3421    Array,
3422    /// Structural pages include only leaf columns.
3423    Structural,
3424}
3425
3426/// Decodes a batch of data from an in-memory structure created by [`crate::encoder::encode_batch`]
3427pub async fn decode_batch(
3428    batch: &EncodedBatch,
3429    filter: &FilterExpression,
3430    decoder_plugins: Arc<DecoderPlugins>,
3431    should_validate: bool,
3432    layout: EncodedBatchLayout,
3433    cache: Option<Arc<LanceCache>>,
3434) -> Result<RecordBatch> {
3435    // The io is synchronous so it shouldn't be possible for any async stuff to still be in progress
3436    // Still, if we just use now_or_never we hit misfires because some futures (channels) need to be
3437    // polled twice.
3438
3439    let io_scheduler = Arc::new(BufferScheduler::new(batch.data.clone())) as Arc<dyn EncodingsIo>;
3440    let cache = if let Some(cache) = cache {
3441        cache
3442    } else {
3443        Arc::new(lance_core::cache::LanceCache::with_capacity(
3444            128 * 1024 * 1024,
3445        ))
3446    };
3447    let mut decode_scheduler = DecodeBatchScheduler::try_new(
3448        batch.schema.as_ref(),
3449        &batch.top_level_columns,
3450        &batch.page_table,
3451        &vec![],
3452        batch.num_rows,
3453        decoder_plugins,
3454        io_scheduler.clone(),
3455        cache,
3456        filter,
3457        &DecoderConfig::default(),
3458    )
3459    .await?;
3460    let (tx, rx) = unbounded_channel();
3461    decode_scheduler.schedule_range(0..batch.num_rows, filter, tx, io_scheduler);
3462    let is_structural = layout == EncodedBatchLayout::Structural;
3463    let mode = std::env::var(ENV_LANCE_STRUCTURAL_BATCH_DECODE_SPAWN_MODE);
3464    let spawn_structural_batch_decode_tasks = !matches!(mode.ok().as_deref(), Some("never"));
3465    // `EncodedBatch` is created from one RecordBatch, so every i32-offset array is
3466    // already known to fit in one Arrow array. Preserve the single-batch contract here.
3467    let mut decode_stream = create_decode_stream_with_i32_offset_limit(
3468        &batch.schema,
3469        batch.num_rows,
3470        batch.num_rows as u32,
3471        is_structural,
3472        should_validate,
3473        spawn_structural_batch_decode_tasks,
3474        rx,
3475        None,
3476        false,
3477    )?;
3478    decode_stream.next().await.unwrap().task.await
3479}
3480
3481#[cfg(test)]
3482// test coalesce indices to ranges
3483mod tests {
3484    use super::*;
3485    use arrow_array::StringArray;
3486    use std::collections::VecDeque;
3487
3488    struct StaticArrayDecodeTask(ArrayRef);
3489
3490    impl DecodeArrayTask for StaticArrayDecodeTask {
3491        fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3492            Ok((self.0, 0))
3493        }
3494    }
3495
3496    #[derive(Debug)]
3497    struct StaticArrayPageDecoder {
3498        array: ArrayRef,
3499        rows_drained: u64,
3500        /// Pretend each row decodes to this many bytes instead of its real size,
3501        /// so budget exhaustion can be tested without gigabyte allocations.
3502        fake_bytes_per_row: Option<u64>,
3503    }
3504
3505    impl StaticArrayPageDecoder {
3506        fn new(values: &[&str]) -> Self {
3507            Self {
3508                array: Arc::new(StringArray::from(values.to_vec())),
3509                rows_drained: 0,
3510                fake_bytes_per_row: None,
3511            }
3512        }
3513
3514        fn with_fake_bytes_per_row(values: &[&str], fake_bytes_per_row: u64) -> Self {
3515            Self {
3516                fake_bytes_per_row: Some(fake_bytes_per_row),
3517                ..Self::new(values)
3518            }
3519        }
3520
3521        fn row_bytes(&self, row: usize) -> u64 {
3522            self.fake_bytes_per_row.unwrap_or_else(|| {
3523                self.array
3524                    .as_any()
3525                    .downcast_ref::<StringArray>()
3526                    .unwrap()
3527                    .value(row)
3528                    .len() as u64
3529            })
3530        }
3531    }
3532
3533    impl LogicalPageDecoder for StaticArrayPageDecoder {
3534        fn wait_for_loaded(&mut self, _loaded_need: u64) -> BoxFuture<'_, Result<()>> {
3535            std::future::ready(Ok(())).boxed()
3536        }
3537
3538        fn rows_loaded(&self) -> u64 {
3539            self.array.len() as u64
3540        }
3541
3542        fn num_rows(&self) -> u64 {
3543            self.array.len() as u64
3544        }
3545
3546        fn rows_drained(&self) -> u64 {
3547            self.rows_drained
3548        }
3549
3550        fn drain(&mut self, num_rows: u64) -> Result<NextDecodeTask> {
3551            let array = self
3552                .array
3553                .slice(self.rows_drained as usize, num_rows as usize);
3554            self.rows_drained += num_rows;
3555            Ok(NextDecodeTask {
3556                task: Box::new(StaticArrayDecodeTask(array)),
3557                num_rows,
3558            })
3559        }
3560
3561        fn max_rows_to_drain(&self, num_rows: u64, byte_budget: u64) -> Result<DrainLimit> {
3562            let start = self.rows_drained as usize;
3563            let mut rows = 0u64;
3564            let mut bytes = 0u64;
3565            for row in start..start + num_rows as usize {
3566                let row_bytes = self.row_bytes(row);
3567                if bytes + row_bytes > byte_budget {
3568                    break;
3569                }
3570                bytes += row_bytes;
3571                rows += 1;
3572            }
3573            Ok(DrainLimit { rows, bytes })
3574        }
3575
3576        fn data_type(&self) -> &DataType {
3577            self.array.data_type()
3578        }
3579    }
3580
3581    async fn array_stream_batch_sizes(
3582        pages: Vec<StaticArrayPageDecoder>,
3583        batch_size: u32,
3584    ) -> Vec<usize> {
3585        let num_rows = pages.iter().map(|page| page.num_rows()).sum::<u64>();
3586        let fields = Fields::from(vec![ArrowField::new("value", DataType::Utf8, false)]);
3587        let root_decoder = SimpleStructDecoder::new(fields, num_rows);
3588        let (tx, rx) = unbounded_channel();
3589        let pages = pages
3590            .into_iter()
3591            .map(|page| {
3592                MessageType::DecoderReady(DecoderReady {
3593                    decoder: Box::new(page),
3594                    path: VecDeque::from([0]),
3595                })
3596            })
3597            .collect();
3598        tx.send(Ok(DecoderMessage {
3599            scheduled_so_far: num_rows,
3600            decoders: pages,
3601        }))
3602        .unwrap();
3603        drop(tx);
3604
3605        let mut stream =
3606            BatchDecodeStream::new(rx, batch_size, num_rows, root_decoder).into_stream();
3607        let mut batch_sizes = Vec::new();
3608        while let Some(task) = stream.next().await {
3609            batch_sizes.push(task.task.await.unwrap().num_rows());
3610        }
3611        batch_sizes
3612    }
3613
3614    #[tokio::test]
3615    async fn test_array_stream_spans_i32_offset_pages_within_budget() {
3616        // Small variable-width pages must NOT split the batch at page boundaries.
3617        let batch_sizes = array_stream_batch_sizes(
3618            vec![
3619                StaticArrayPageDecoder::new(&["a", "b"]),
3620                StaticArrayPageDecoder::new(&["c", "d"]),
3621            ],
3622            4,
3623        )
3624        .await;
3625        assert_eq!(batch_sizes, vec![4]);
3626    }
3627
3628    #[tokio::test]
3629    async fn test_array_stream_splits_when_i32_offset_budget_exhausted() {
3630        // Three single-row pages pretending to hold 800MiB each: two fit the
3631        // i32 offset budget (~2GiB), the third starts a new batch.
3632        const FAKE_ROW_BYTES: u64 = 800 * 1024 * 1024;
3633        let batch_sizes = array_stream_batch_sizes(
3634            vec![
3635                StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3636                StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3637                StaticArrayPageDecoder::with_fake_bytes_per_row(&["c"], FAKE_ROW_BYTES),
3638            ],
3639            4,
3640        )
3641        .await;
3642        assert_eq!(batch_sizes, vec![2, 1]);
3643    }
3644
3645    #[tokio::test]
3646    async fn test_array_stream_emits_over_budget_rows_alone() {
3647        // Rows whose (possibly over-estimated) size exceeds the whole budget are
3648        // emitted as single-row batches rather than failing the scan.
3649        const FAKE_ROW_BYTES: u64 = 3 * 1024 * 1024 * 1024;
3650        let batch_sizes = array_stream_batch_sizes(
3651            vec![
3652                StaticArrayPageDecoder::with_fake_bytes_per_row(&["a"], FAKE_ROW_BYTES),
3653                StaticArrayPageDecoder::with_fake_bytes_per_row(&["b"], FAKE_ROW_BYTES),
3654            ],
3655            4,
3656        )
3657        .await;
3658        assert_eq!(batch_sizes, vec![1, 1]);
3659    }
3660
3661    #[test]
3662    fn requested_row_indices_convert_to_checked_ranges() {
3663        let requested_rows = RequestedRows::Indices(vec![1, 2, 5, 8, 9]);
3664        let ranges = requested_rows.into_ranges().unwrap();
3665        assert_eq!(ranges, [1..3, 5..6, 8..10]);
3666
3667        let error = RequestedRows::Indices(vec![2, 2])
3668            .into_ranges()
3669            .unwrap_err();
3670        assert!(matches!(error, Error::InvalidInput { .. }));
3671        assert!(error.to_string().contains("not strictly increasing"));
3672
3673        let error = RequestedRows::Indices(vec![u64::MAX])
3674            .into_ranges()
3675            .unwrap_err();
3676        assert!(matches!(error, Error::InvalidInput { .. }));
3677        assert!(error.to_string().contains("u64::MAX"));
3678    }
3679
3680    #[derive(Debug)]
3681    struct FailingPageDecoder {
3682        page_data_type: DataType,
3683        total_rows: u64,
3684        load_error_message: &'static str,
3685    }
3686
3687    impl FailingPageDecoder {
3688        fn new(
3689            page_data_type: DataType,
3690            total_rows: u64,
3691            load_error_message: &'static str,
3692        ) -> Self {
3693            Self {
3694                page_data_type,
3695                total_rows,
3696                load_error_message,
3697            }
3698        }
3699    }
3700
3701    impl LogicalPageDecoder for FailingPageDecoder {
3702        fn wait_for_loaded(&'_ mut self, _rows_needed: u64) -> BoxFuture<'_, Result<()>> {
3703            let load_error_message = self.load_error_message;
3704            async move { Err(Error::io(load_error_message)) }.boxed()
3705        }
3706
3707        fn rows_loaded(&self) -> u64 {
3708            0
3709        }
3710
3711        fn num_rows(&self) -> u64 {
3712            self.total_rows
3713        }
3714
3715        fn rows_drained(&self) -> u64 {
3716            0
3717        }
3718
3719        fn drain(&mut self, requested_rows: u64) -> Result<NextDecodeTask> {
3720            Err(Error::internal(format!(
3721                "failing page decoder should not be drained after load error \
3722                 (requested_rows={})",
3723                requested_rows
3724            )))
3725        }
3726
3727        fn data_type(&self) -> &DataType {
3728            &self.page_data_type
3729        }
3730    }
3731
3732    struct InvalidInputDecodeTask;
3733
3734    impl DecodeArrayTask for InvalidInputDecodeTask {
3735        fn decode(self: Box<Self>) -> Result<(ArrayRef, u64)> {
3736            Err(Error::invalid_input_source("malformed sparse page".into()))
3737        }
3738    }
3739
3740    #[test]
3741    fn next_decode_task_preserves_invalid_input_errors() {
3742        let err = NextDecodeTask {
3743            task: Box::new(InvalidInputDecodeTask),
3744            num_rows: 0,
3745        }
3746        .into_batch(Arc::new(Once::new()))
3747        .unwrap_err();
3748        assert!(matches!(err, Error::InvalidInput { .. }));
3749    }
3750
3751    #[test]
3752    fn test_read_zero_dimension_fsl_errors_instead_of_panicking() {
3753        // Simulates reading a column whose stored schema declares a
3754        // zero-dimension FixedSizeList, as old writers (before #5102) could
3755        // persist. The read plan is built by the field-scheduler factories,
3756        // which run the dimension guard before touching any column data, so
3757        // an empty column iterator is sufficient to reach the guard. The read
3758        // must surface a clean error rather than a divide-by-zero panic.
3759        use arrow_schema::Field as ArrowField;
3760
3761        let zero_dim = DataType::FixedSizeList(
3762            Arc::new(ArrowField::new("item", DataType::Float32, true)),
3763            0,
3764        );
3765        let field = Field::try_from(&ArrowField::new("vec", zero_dim, true)).unwrap();
3766        let strategy = CoreFieldDecoderStrategy::default();
3767
3768        let mut structural_columns = ColumnInfoIter::new(vec![], &[]);
3769        let err = strategy
3770            .create_structural_field_scheduler(&field, &mut structural_columns)
3771            .unwrap_err();
3772        assert!(
3773            err.to_string()
3774                .contains("dimension must be a positive integer"),
3775            "unexpected error: {}",
3776            err
3777        );
3778
3779        let mut array_columns = ColumnInfoIter::new(vec![], &[]);
3780        let err = strategy
3781            .create_array_field_scheduler(
3782                &field,
3783                &mut array_columns,
3784                FileBuffers {
3785                    positions_and_sizes: &[],
3786                },
3787            )
3788            .unwrap_err();
3789        assert!(
3790            err.to_string()
3791                .contains("dimension must be a positive integer"),
3792            "unexpected error: {}",
3793            err
3794        );
3795    }
3796
3797    #[test]
3798    fn test_list_page_with_non_list_encoding_returns_error() {
3799        let item = Arc::new(ArrowField::new("item", DataType::Int32, true));
3800        let list = DataType::List(item);
3801        let field = Field::try_from(&ArrowField::new("values", list, true)).unwrap();
3802        let values_encoding = pb::ColumnEncoding {
3803            column_encoding: Some(pb::column_encoding::ColumnEncoding::Values(())),
3804        };
3805        let offsets_column = Arc::new(ColumnInfo::new(
3806            0,
3807            Arc::new([PageInfo {
3808                num_rows: 1,
3809                priority: 0,
3810                encoding: PageEncoding::Legacy(pb::ArrayEncoding {
3811                    array_encoding: Some(pb::array_encoding::ArrayEncoding::Flat(
3812                        pb::Flat::default(),
3813                    )),
3814                }),
3815                buffer_offsets_and_sizes: Arc::new([]),
3816            }]),
3817            vec![],
3818            values_encoding.clone(),
3819        ));
3820        let items_column = Arc::new(ColumnInfo::new(1, Arc::new([]), vec![], values_encoding));
3821        let column_indices = [0, 1];
3822        let mut columns = ColumnInfoIter::new(vec![offsets_column, items_column], &column_indices);
3823
3824        let err = CoreFieldDecoderStrategy::default()
3825            .create_array_field_scheduler(
3826                &field,
3827                &mut columns,
3828                FileBuffers {
3829                    positions_and_sizes: &[],
3830                },
3831            )
3832            .unwrap_err();
3833
3834        assert!(matches!(err, Error::InvalidInput { .. }));
3835        assert!(
3836            err.to_string()
3837                .contains("expected list encoding for field 'values' in column 0, page 0 but got"),
3838            "unexpected error: {err}"
3839        );
3840    }
3841
3842    #[tokio::test]
3843    async fn test_array_stream_stops_on_load_error() {
3844        use arrow_schema::Field as ArrowField;
3845
3846        let rows_per_batch = 1;
3847        let total_rows = 2;
3848        let scheduled_rows = 1;
3849        let page_rows = 1;
3850        let batch_readahead = 2;
3851        let load_error_message = "simulated page load failure";
3852        let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3853        let root_decoder = SimpleStructDecoder::new(fields, total_rows);
3854        let (tx, rx) = unbounded_channel();
3855
3856        tx.send(Ok(DecoderMessage {
3857            scheduled_so_far: scheduled_rows,
3858            decoders: vec![MessageType::DecoderReady(DecoderReady {
3859                decoder: Box::new(FailingPageDecoder::new(
3860                    DataType::Float32,
3861                    page_rows,
3862                    load_error_message,
3863                )),
3864                path: VecDeque::from([0]),
3865            })],
3866        }))
3867        .unwrap();
3868        drop(tx);
3869
3870        let stream =
3871            BatchDecodeStream::new(rx, rows_per_batch, total_rows, root_decoder).into_stream();
3872        let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3873
3874        let err = batches
3875            .next()
3876            .await
3877            .expect("stream should emit the array page-load error")
3878            .unwrap_err();
3879        assert!(
3880            err.to_string().contains(load_error_message),
3881            "unexpected error: {}",
3882            err
3883        );
3884        assert!(
3885            batches.next().await.is_none(),
3886            "stream should stop after the array page-load error"
3887        );
3888    }
3889
3890    #[tokio::test]
3891    async fn test_structural_stream_stops_on_load_error() {
3892        let rows_per_batch = 1;
3893        let total_rows = 2;
3894        let scheduled_rows = 1;
3895        let batch_readahead = 2;
3896        let load_error_message = "simulated page load failure";
3897        let fields = Fields::from(vec![ArrowField::new("vector", DataType::Float32, true)]);
3898        let root_decoder = StructuralStructDecoder::new(
3899            fields, false, /*is_root=*/ true, /*nullable=*/ false,
3900        )
3901        .unwrap();
3902        let (tx, rx) = unbounded_channel();
3903        let failed_page = async move { Err(Error::io(load_error_message)) }.boxed();
3904
3905        tx.send(Ok(DecoderMessage {
3906            scheduled_so_far: scheduled_rows,
3907            decoders: vec![MessageType::UnloadedPage(UnloadedPageShard(failed_page))],
3908        }))
3909        .unwrap();
3910        drop(tx);
3911
3912        let stream = StructuralBatchDecodeStream::new(
3913            rx,
3914            rows_per_batch,
3915            total_rows,
3916            root_decoder,
3917            /*spawn_batch_decode_tasks=*/ true,
3918            None,
3919        )
3920        .into_stream();
3921        let mut batches = stream.map(|task| task.task).buffered(batch_readahead);
3922
3923        let err = batches
3924            .next()
3925            .await
3926            .expect("stream should emit the page-load error")
3927            .unwrap_err();
3928        assert!(
3929            err.to_string().contains(load_error_message),
3930            "unexpected error: {}",
3931            err
3932        );
3933        assert!(
3934            batches.next().await.is_none(),
3935            "stream should stop after the page-load error"
3936        );
3937    }
3938
3939    #[test]
3940    fn test_coalesce_indices_to_ranges_with_single_index() {
3941        let indices = vec![1];
3942        let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3943        assert_eq!(ranges, vec![1..2]);
3944    }
3945
3946    #[test]
3947    fn test_coalesce_indices_to_ranges() {
3948        let indices = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
3949        let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3950        assert_eq!(ranges, vec![1..10]);
3951    }
3952
3953    #[test]
3954    fn test_coalesce_indices_to_ranges_with_gaps() {
3955        let indices = vec![1, 2, 3, 5, 6, 7, 9];
3956        let ranges = RequestedRows::indices_to_ranges(&indices).unwrap();
3957        assert_eq!(ranges, vec![1..4, 5..8, 9..10]);
3958    }
3959
3960    #[test]
3961    fn test_estimate_bytes_per_row() {
3962        assert_eq!(estimate_bytes_per_row(&DataType::Int32), 4.0);
3963        assert_eq!(estimate_bytes_per_row(&DataType::Int64), 8.0);
3964        assert_eq!(estimate_bytes_per_row(&DataType::Float32), 4.0);
3965        assert_eq!(estimate_bytes_per_row(&DataType::Boolean), 1.0 / 8.0);
3966        assert_eq!(estimate_bytes_per_row(&DataType::Utf8), 64.0);
3967        assert_eq!(estimate_bytes_per_row(&DataType::Binary), 64.0);
3968        // Struct of 4 x Int32 = 16 bytes
3969        let struct_type = DataType::Struct(Fields::from(vec![
3970            ArrowField::new("a", DataType::Int32, false),
3971            ArrowField::new("b", DataType::Int32, false),
3972            ArrowField::new("c", DataType::Int32, false),
3973            ArrowField::new("d", DataType::Int32, false),
3974        ]));
3975        assert_eq!(estimate_bytes_per_row(&struct_type), 16.0);
3976    }
3977
3978    /// Helper: encode a batch, then decode it as a stream with optional
3979    /// `batch_size_bytes`, collecting all output batches.
3980    async fn decode_batches_with_byte_limit(
3981        batch: &RecordBatch,
3982        batch_size: u32,
3983        batch_size_bytes: Option<u64>,
3984    ) -> Vec<RecordBatch> {
3985        use crate::{
3986            encoder::{EncodingOptions, encode_batch},
3987            testing::{TestEncoding, test_encoding_strategy},
3988        };
3989
3990        let version = TestEncoding::StructuralU16;
3991        let options = EncodingOptions::default();
3992        let strategy = test_encoding_strategy(version);
3993        let schema = Schema::try_from(batch.schema().as_ref()).unwrap();
3994        let encoded = encode_batch(batch, Arc::new(schema.clone()), strategy.as_ref(), &options)
3995            .await
3996            .unwrap();
3997
3998        let io_scheduler =
3999            Arc::new(BufferScheduler::new(encoded.data.clone())) as Arc<dyn EncodingsIo>;
4000        let cache = Arc::new(lance_core::cache::LanceCache::with_capacity(
4001            128 * 1024 * 1024,
4002        ));
4003        let decoder_plugins = Arc::new(DecoderPlugins::default());
4004
4005        let mut decode_scheduler = DecodeBatchScheduler::try_new(
4006            encoded.schema.as_ref(),
4007            &encoded.top_level_columns,
4008            &encoded.page_table,
4009            &vec![],
4010            encoded.num_rows,
4011            decoder_plugins,
4012            io_scheduler.clone(),
4013            cache,
4014            &FilterExpression::no_filter(),
4015            &DecoderConfig::default(),
4016        )
4017        .await
4018        .unwrap();
4019
4020        let (tx, rx) = unbounded_channel();
4021        decode_scheduler.schedule_range(
4022            0..encoded.num_rows,
4023            &FilterExpression::no_filter(),
4024            tx,
4025            io_scheduler,
4026        );
4027
4028        let mut decode_stream = create_decode_stream(
4029            &encoded.schema,
4030            encoded.num_rows,
4031            batch_size,
4032            /*is_structural=*/ true,
4033            /*should_validate=*/ true,
4034            /*spawn_structural_batch_decode_tasks=*/ true,
4035            rx,
4036            batch_size_bytes,
4037        )
4038        .unwrap();
4039
4040        let mut batches = Vec::new();
4041        while let Some(task) = decode_stream.next().await {
4042            batches.push(task.task.await.unwrap());
4043        }
4044        batches
4045    }
4046
4047    #[tokio::test]
4048    async fn test_byte_sized_batches_fixed_width() {
4049        use arrow_array::Int32Array;
4050
4051        // 1000 rows x 4 Int32 columns = 16 bytes/row
4052        let num_rows: i32 = 1000;
4053        let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..4)
4054            .map(|col| {
4055                Arc::new(Int32Array::from_iter_values(
4056                    (0..num_rows).map(move |row| row * 10 + col),
4057                )) as _
4058            })
4059            .collect();
4060
4061        let schema = Arc::new(ArrowSchema::new(vec![
4062            ArrowField::new("a", DataType::Int32, false),
4063            ArrowField::new("b", DataType::Int32, false),
4064            ArrowField::new("c", DataType::Int32, false),
4065            ArrowField::new("d", DataType::Int32, false),
4066        ]));
4067        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4068
4069        // 16 bytes/row, batch_size_bytes=1600 => 100 rows/batch
4070        let batches =
4071            decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 1024, Some(1600)).await;
4072
4073        // Should produce 10 batches of 100 rows each
4074        assert_eq!(batches.len(), 10);
4075        for (i, batch) in batches.iter().enumerate() {
4076            assert_eq!(
4077                batch.num_rows(),
4078                100,
4079                "batch {i} should have 100 rows, got {}",
4080                batch.num_rows()
4081            );
4082        }
4083
4084        // Verify roundtrip: concatenate and compare
4085        let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4086        let concatenated =
4087            arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4088                .unwrap();
4089        assert_eq!(concatenated.num_rows(), num_rows as usize);
4090        for col in 0..4 {
4091            assert_eq!(
4092                concatenated.column(col).as_ref(),
4093                input_batch.column(col).as_ref(),
4094                "column {col} roundtrip mismatch"
4095            );
4096        }
4097    }
4098
4099    #[tokio::test]
4100    async fn test_byte_sized_batches_none_unchanged() {
4101        use arrow_array::Int32Array;
4102
4103        // Without batch_size_bytes, rows_per_batch controls batching
4104        let num_rows: i32 = 1000;
4105        let arrays: Vec<Arc<dyn arrow_array::Array>> = (0..2)
4106            .map(|col| {
4107                Arc::new(Int32Array::from_iter_values(
4108                    (0..num_rows).map(move |row| row * 10 + col),
4109                )) as _
4110            })
4111            .collect();
4112
4113        let schema = Arc::new(ArrowSchema::new(vec![
4114            ArrowField::new("x", DataType::Int32, false),
4115            ArrowField::new("y", DataType::Int32, false),
4116        ]));
4117        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4118
4119        // batch_size=250, batch_size_bytes=None => 4 batches of 250 rows
4120        let batches = decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 250, None).await;
4121        assert_eq!(batches.len(), 4);
4122        for (i, batch) in batches.iter().enumerate() {
4123            assert_eq!(
4124                batch.num_rows(),
4125                250,
4126                "batch {i} should have 250 rows, got {}",
4127                batch.num_rows()
4128            );
4129        }
4130    }
4131
4132    #[tokio::test]
4133    async fn test_byte_sized_batches_respect_row_limit() {
4134        use arrow_array::Int32Array;
4135
4136        let num_rows: i32 = 1000;
4137        let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4138            "x",
4139            DataType::Int32,
4140            false,
4141        )]));
4142        let input_batch = RecordBatch::try_new(
4143            schema,
4144            vec![Arc::new(Int32Array::from_iter_values(0..num_rows))],
4145        )
4146        .unwrap();
4147
4148        // The byte limit can hold every row, so the 100-row limit must win.
4149        let batches =
4150            decode_batches_with_byte_limit(&input_batch, /*batch_size=*/ 100, Some(10_000)).await;
4151        assert_eq!(batches.len(), 10);
4152        assert!(batches.iter().all(|batch| batch.num_rows() == 100));
4153    }
4154
4155    #[tokio::test]
4156    async fn test_byte_sized_batches_feedback_convergence() {
4157        use arrow_array::StringArray;
4158
4159        // Each row has a 100-byte string. Schema estimate = 64 bytes (default
4160        // for Utf8), so the first batch will overshoot. The feedback loop
4161        // should correct subsequent batches toward the target.
4162        let num_rows = 500;
4163        let value: String = "x".repeat(100);
4164        let arrays: Vec<Arc<dyn arrow_array::Array>> = vec![Arc::new(StringArray::from(
4165            (0..num_rows).map(|_| value.as_str()).collect::<Vec<_>>(),
4166        ))];
4167        let schema = Arc::new(ArrowSchema::new(vec![ArrowField::new(
4168            "s",
4169            DataType::Utf8,
4170            false,
4171        )]));
4172        let input_batch = RecordBatch::try_new(schema, arrays).unwrap();
4173
4174        // Target 5000 bytes/batch. At 100 bytes/row the ideal is 50 rows/batch.
4175        // Schema estimate is 64 bytes/row → first batch ~78 rows (overshoot).
4176        // After feedback kicks in, batches should converge to ~50 rows.
4177        let target_bytes: u64 = 5000;
4178        let batches = decode_batches_with_byte_limit(
4179            &input_batch,
4180            /*batch_size=*/ 1024,
4181            Some(target_bytes),
4182        )
4183        .await;
4184
4185        // Verify all data round-trips correctly
4186        let all_batches: Vec<&RecordBatch> = batches.iter().collect();
4187        let concatenated =
4188            arrow_select::concat::concat_batches(&batches[0].schema(), all_batches.iter().copied())
4189                .unwrap();
4190        assert_eq!(concatenated.num_rows(), num_rows as usize);
4191        assert_eq!(
4192            concatenated.column(0).as_ref(),
4193            input_batch.column(0).as_ref()
4194        );
4195
4196        // After the first batch, subsequent batches should be closer to the
4197        // target. The ideal is 50 rows/batch.
4198        assert!(
4199            batches.len() >= 2,
4200            "need at least 2 batches to test convergence"
4201        );
4202        // The first batch uses the schema estimate (64 bytes/row) →
4203        // ~78 rows. After feedback the rows should settle near 50.
4204        if batches.len() >= 3 {
4205            let second_batch_rows = batches[1].num_rows();
4206            let third_batch_rows = batches[2].num_rows();
4207            // Both should be within 20% of the ideal (50 rows)
4208            assert!(
4209                (40..=60).contains(&second_batch_rows),
4210                "second batch should be near 50 rows, got {second_batch_rows}"
4211            );
4212            assert!(
4213                (40..=60).contains(&third_batch_rows),
4214                "third batch should be near 50 rows, got {third_batch_rows}"
4215            );
4216        }
4217    }
4218}