Skip to main content

parquet/arrow/async_reader/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! `async` API for reading Parquet files as [`RecordBatch`]es
19//!
20//! See the [crate-level documentation](crate) for more details.
21//!
22//! See example on [`ParquetRecordBatchStreamBuilder::new`]
23
24use std::fmt::Formatter;
25use std::io::SeekFrom;
26use std::ops::Range;
27use std::pin::Pin;
28use std::sync::Arc;
29use std::task::{Context, Poll};
30
31use bytes::Bytes;
32use futures::future::{BoxFuture, FutureExt};
33use futures::stream::Stream;
34use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt};
35
36use arrow_array::{ArrayRef, RecordBatch};
37use arrow_schema::{Schema, SchemaRef};
38
39use crate::arrow::arrow_reader::{
40    ArrowReaderBuilder, ArrowReaderMetadata, ArrowReaderOptions, ParquetRecordBatchReader,
41};
42
43use crate::basic::{BloomFilterAlgorithm, BloomFilterCompression, BloomFilterHash};
44use crate::bloom_filter::{
45    SBBF_HEADER_SIZE_ESTIMATE, Sbbf, chunk_read_bloom_filter_header_and_offset,
46};
47use crate::errors::{ParquetError, Result};
48use crate::file::metadata::{ParquetMetaData, ParquetMetaDataReader};
49
50mod metadata;
51pub use metadata::*;
52
53mod spawn;
54pub use spawn::SpawnedReader;
55
56/// Re-exported so [`ParquetRecordBatchStreamBuilder::with_row_group_selections`]
57/// can be used without importing from another module.
58pub use crate::arrow::arrow_reader::RowGroupSelection;
59
60#[cfg(feature = "object_store")]
61mod store;
62
63use crate::DecodeResult;
64use crate::arrow::push_decoder::{ParquetPushDecoder, ParquetPushDecoderBuilder, PushDecoderInput};
65#[cfg(feature = "object_store")]
66pub use store::*;
67
68/// The asynchronous interface used by [`ParquetRecordBatchStream`] to read parquet files
69///
70/// Notes:
71///
72/// 1. There is a default implementation for types that implement [`AsyncRead`]
73///    and [`AsyncSeek`], for example [`tokio::fs::File`].
74///
75/// 2. Implementations for remote storage, such as the `object_store` crate,
76///    can implement this interface directly, typically by pairing a store
77///    handle with an object path and delegating [`Self::get_bytes`] and
78///    [`Self::get_byte_ranges`] to ranged reads. [`SpawnedReader`] can wrap
79///    such a reader to perform its I/O on a dedicated runtime, and
80///    [`ParquetMetaDataReader::with_arrow_reader_options`] simplifies
81///    implementing [`Self::get_metadata`].
82///
83/// # Example: implementing `AsyncFileReader` for the `object_store` crate
84///
85/// ```no_run
86/// # use std::ops::Range;
87/// # use std::sync::Arc;
88/// use bytes::Bytes;
89/// use futures::future::BoxFuture;
90/// use futures::{FutureExt, TryFutureExt};
91/// use object_store::path::Path;
92/// use object_store::{GetOptions, GetRange, ObjectStore, ObjectStoreExt};
93/// use parquet::arrow::arrow_reader::ArrowReaderOptions;
94/// use parquet::arrow::async_reader::{AsyncFileReader, MetadataSuffixFetch};
95/// use parquet::errors::{ParquetError, Result};
96/// use parquet::file::metadata::{ParquetMetaData, ParquetMetaDataReader};
97///
98/// fn to_parquet_err(e: object_store::Error) -> ParquetError {
99///     ParquetError::External(Box::new(e))
100/// }
101///
102/// #[derive(Clone)]
103/// struct ObjectStoreReader {
104///     store: Arc<dyn ObjectStore>,
105///     path: Path,
106/// }
107///
108/// impl AsyncFileReader for ObjectStoreReader {
109///     fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
110///         self.store
111///             .get_range(&self.path, range)
112///             .map_err(to_parquet_err)
113///             .boxed()
114///     }
115///
116///     fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
117///         async move {
118///             self.store
119///                 .get_ranges(&self.path, &ranges)
120///                 .await
121///                 .map_err(to_parquet_err)
122///         }
123///         .boxed()
124///     }
125///
126///     fn get_metadata<'a>(
127///         &'a mut self,
128///         options: Option<&'a ArrowReaderOptions>,
129///     ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
130///         async move {
131///             let metadata = ParquetMetaDataReader::new()
132///                 .with_arrow_reader_options(options)
133///                 .load_via_suffix_and_finish(self)
134///                 .await?;
135///             Ok(Arc::new(metadata))
136///         }
137///         .boxed()
138///     }
139/// }
140///
141/// /// Supports fetching the parquet footer without knowing the file size,
142/// /// via suffix range requests
143/// impl MetadataSuffixFetch for &mut ObjectStoreReader {
144///     fn fetch_suffix(&mut self, suffix: usize) -> BoxFuture<'_, Result<Bytes>> {
145///         let options = GetOptions {
146///             range: Some(GetRange::Suffix(suffix as u64)),
147///             ..Default::default()
148///         };
149///         async move {
150///             let resp = self
151///                 .store
152///                 .get_opts(&self.path, options)
153///                 .await
154///                 .map_err(to_parquet_err)?;
155///             resp.bytes().await.map_err(to_parquet_err)
156///         }
157///         .boxed()
158///     }
159/// }
160/// ```
161///
162/// [`ParquetMetaDataReader::with_arrow_reader_options`]: crate::file::metadata::ParquetMetaDataReader::with_arrow_reader_options
163///
164/// [`tokio::fs::File`]: https://docs.rs/tokio/latest/tokio/fs/struct.File.html
165pub trait AsyncFileReader: Send {
166    /// Retrieve the bytes in `range`
167    fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>>;
168
169    /// Retrieve multiple byte ranges. The default implementation will call `get_bytes` sequentially
170    fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
171        async move {
172            let mut result = Vec::with_capacity(ranges.len());
173
174            for range in ranges {
175                let data = self.get_bytes(range).await?;
176                result.push(data);
177            }
178
179            Ok(result)
180        }
181        .boxed()
182    }
183
184    /// Return a future which results in the [`ParquetMetaData`] for this Parquet file.
185    ///
186    /// This is an asynchronous operation as it may involve reading the file
187    /// footer and potentially other metadata from disk or a remote source.
188    ///
189    /// Reading data from Parquet requires the metadata to understand the
190    /// schema, row groups, and location of pages within the file. This metadata
191    /// is stored primarily in the footer of the Parquet file, and can be read using
192    /// [`ParquetMetaDataReader`].
193    ///
194    /// However, implementations can significantly speed up reading Parquet by
195    /// supplying cached metadata or pre-fetched metadata via this API.
196    ///
197    /// # Parameters
198    /// * `options`: Optional [`ArrowReaderOptions`] that may contain decryption
199    ///   and other options that affect how the metadata is read.
200    fn get_metadata<'a>(
201        &'a mut self,
202        options: Option<&'a ArrowReaderOptions>,
203    ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>>;
204}
205
206/// This allows Box<dyn AsyncFileReader + '_> to be used as an AsyncFileReader,
207impl AsyncFileReader for Box<dyn AsyncFileReader + '_> {
208    fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
209        self.as_mut().get_bytes(range)
210    }
211
212    fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, Result<Vec<Bytes>>> {
213        self.as_mut().get_byte_ranges(ranges)
214    }
215
216    fn get_metadata<'a>(
217        &'a mut self,
218        options: Option<&'a ArrowReaderOptions>,
219    ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
220        self.as_mut().get_metadata(options)
221    }
222}
223
224impl<T: AsyncFileReader + MetadataFetch + AsyncRead + AsyncSeek + Unpin> MetadataSuffixFetch for T {
225    fn fetch_suffix(&mut self, suffix: usize) -> BoxFuture<'_, Result<Bytes>> {
226        async move {
227            self.seek(SeekFrom::End(-(suffix as i64))).await?;
228            let mut buf = Vec::with_capacity(suffix);
229            self.take(suffix as _).read_to_end(&mut buf).await?;
230            Ok(buf.into())
231        }
232        .boxed()
233    }
234}
235
236impl<T: AsyncRead + AsyncSeek + Unpin + Send> AsyncFileReader for T {
237    fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
238        async move {
239            self.seek(SeekFrom::Start(range.start)).await?;
240
241            let to_read = range.end - range.start;
242            let mut buffer = Vec::with_capacity(to_read.try_into()?);
243            let read = self.take(to_read).read_to_end(&mut buffer).await?;
244            if read as u64 != to_read {
245                return Err(eof_err!("expected to read {} bytes, got {}", to_read, read));
246            }
247
248            Ok(buffer.into())
249        }
250        .boxed()
251    }
252
253    fn get_metadata<'a>(
254        &'a mut self,
255        options: Option<&'a ArrowReaderOptions>,
256    ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
257        async move {
258            let metadata_reader = ParquetMetaDataReader::new().with_arrow_reader_options(options);
259            let parquet_metadata = metadata_reader.load_via_suffix_and_finish(self).await?;
260            Ok(Arc::new(parquet_metadata))
261        }
262        .boxed()
263    }
264}
265
266impl ArrowReaderMetadata {
267    /// Returns a new [`ArrowReaderMetadata`] for this builder
268    ///
269    /// See [`ParquetRecordBatchStreamBuilder::new_with_metadata`] for how this can be used
270    pub async fn load_async<T: AsyncFileReader>(
271        input: &mut T,
272        options: ArrowReaderOptions,
273    ) -> Result<Self> {
274        let metadata = input.get_metadata(Some(&options)).await?;
275        Self::try_new(metadata, options)
276    }
277}
278
279#[doc(hidden)]
280/// Newtype (wrapper) used within [`ArrowReaderBuilder`] to distinguish sync readers from async
281///
282/// Allows sharing the same builder for different readers while keeping the same
283/// ParquetRecordBatchStreamBuilder API
284pub struct AsyncReader<T>(T);
285
286/// A builder for reading parquet files from an `async` source as  [`ParquetRecordBatchStream`]
287///
288/// This can be used to decode a Parquet file in streaming fashion (without
289/// downloading the whole file at once) from a remote source, such as an object store.
290///
291/// This builder handles reading the parquet file metadata, allowing consumers
292/// to use this information to select what specific columns, row groups, etc.
293/// they wish to be read by the resulting stream.
294///
295/// See examples on [`ParquetRecordBatchStreamBuilder::new`], including how to
296/// issue multiple I/O requests in parallel using multiple streams.
297///
298/// # See also:
299/// * [`ParquetPushDecoderBuilder`] for lower level control over buffering and
300///   decoding.
301/// * [`ParquetRecordBatchStream::next_row_group`] for I/O prefetching
302///
303///
304/// See [`ArrowReaderBuilder`] for additional member functions
305pub type ParquetRecordBatchStreamBuilder<T> = ArrowReaderBuilder<AsyncReader<T>>;
306
307impl<T: AsyncFileReader + Send + 'static> ParquetRecordBatchStreamBuilder<T> {
308    /// Create a new [`ParquetRecordBatchStreamBuilder`] for reading from the
309    /// specified source.
310    ///
311    /// # Examples:
312    /// * [Basic example reading from an async source](#example)
313    /// * [Configuring options and reading metadata](#example-configuring-options-and-reading-metadata)
314    /// * [Reading Row Groups in Parallel](#example-reading-row-groups-in-parallel)
315    ///
316    /// # Example
317    /// ```
318    /// # #[tokio::main(flavor="current_thread")]
319    /// # async fn main() {
320    /// #
321    /// # use arrow_array::RecordBatch;
322    /// # use arrow::util::pretty::pretty_format_batches;
323    /// # use futures::TryStreamExt;
324    /// #
325    /// # use parquet::arrow::{ParquetRecordBatchStreamBuilder, ProjectionMask};
326    /// #
327    /// # fn assert_batches_eq(batches: &[RecordBatch], expected_lines: &[&str]) {
328    /// #     let formatted = pretty_format_batches(batches).unwrap().to_string();
329    /// #     let actual_lines: Vec<_> = formatted.trim().lines().collect();
330    /// #     assert_eq!(
331    /// #          &actual_lines, expected_lines,
332    /// #          "\n\nexpected:\n\n{:#?}\nactual:\n\n{:#?}\n\n",
333    /// #          expected_lines, actual_lines
334    /// #      );
335    /// #  }
336    /// #
337    /// # let testdata = arrow::util::test_util::parquet_test_data();
338    /// # let path = format!("{}/alltypes_plain.parquet", testdata);
339    /// // Use tokio::fs::File to read data using an async I/O. This can be replaced with
340    /// // another async I/O reader such as a reader from an object store.
341    /// let file = tokio::fs::File::open(path).await.unwrap();
342    ///
343    /// // Configure options for reading from the async source
344    /// let builder = ParquetRecordBatchStreamBuilder::new(file)
345    ///     .await
346    ///     .unwrap();
347    /// // Building the stream opens the parquet file (reads metadata, etc) and returns
348    /// // a stream that can be used to incrementally read the data in batches
349    /// let stream = builder.build().unwrap();
350    /// // In this example, we collect the stream into a Vec<RecordBatch>
351    /// // but real applications would likely process the batches as they are read
352    /// let results = stream.try_collect::<Vec<_>>().await.unwrap();
353    /// // Demonstrate the results are as expected
354    /// assert_batches_eq(
355    ///     &results,
356    ///     &[
357    ///       "+----+----------+-------------+--------------+---------+------------+-----------+------------+------------------+------------+---------------------+",
358    ///       "| id | bool_col | tinyint_col | smallint_col | int_col | bigint_col | float_col | double_col | date_string_col  | string_col | timestamp_col       |",
359    ///       "+----+----------+-------------+--------------+---------+------------+-----------+------------+------------------+------------+---------------------+",
360    ///       "| 4  | true     | 0           | 0            | 0       | 0          | 0.0       | 0.0        | 30332f30312f3039 | 30         | 2009-03-01T00:00:00 |",
361    ///       "| 5  | false    | 1           | 1            | 1       | 10         | 1.1       | 10.1       | 30332f30312f3039 | 31         | 2009-03-01T00:01:00 |",
362    ///       "| 6  | true     | 0           | 0            | 0       | 0          | 0.0       | 0.0        | 30342f30312f3039 | 30         | 2009-04-01T00:00:00 |",
363    ///       "| 7  | false    | 1           | 1            | 1       | 10         | 1.1       | 10.1       | 30342f30312f3039 | 31         | 2009-04-01T00:01:00 |",
364    ///       "| 2  | true     | 0           | 0            | 0       | 0          | 0.0       | 0.0        | 30322f30312f3039 | 30         | 2009-02-01T00:00:00 |",
365    ///       "| 3  | false    | 1           | 1            | 1       | 10         | 1.1       | 10.1       | 30322f30312f3039 | 31         | 2009-02-01T00:01:00 |",
366    ///       "| 0  | true     | 0           | 0            | 0       | 0          | 0.0       | 0.0        | 30312f30312f3039 | 30         | 2009-01-01T00:00:00 |",
367    ///       "| 1  | false    | 1           | 1            | 1       | 10         | 1.1       | 10.1       | 30312f30312f3039 | 31         | 2009-01-01T00:01:00 |",
368    ///       "+----+----------+-------------+--------------+---------+------------+-----------+------------+------------------+------------+---------------------+",
369    ///      ],
370    ///  );
371    /// # }
372    /// ```
373    ///
374    /// # Example Configuring Options and Reading Metadata
375    ///
376    /// There are many options that control the behavior of the reader, such as
377    /// `with_batch_size`, `with_projection`, `with_filter`, etc...
378    ///
379    /// ```
380    /// # #[tokio::main(flavor="current_thread")]
381    /// # async fn main() {
382    /// #
383    /// # use arrow_array::RecordBatch;
384    /// # use arrow::util::pretty::pretty_format_batches;
385    /// # use futures::TryStreamExt;
386    /// #
387    /// # use parquet::arrow::{ParquetRecordBatchStreamBuilder, ProjectionMask};
388    /// #
389    /// # fn assert_batches_eq(batches: &[RecordBatch], expected_lines: &[&str]) {
390    /// #     let formatted = pretty_format_batches(batches).unwrap().to_string();
391    /// #     let actual_lines: Vec<_> = formatted.trim().lines().collect();
392    /// #     assert_eq!(
393    /// #          &actual_lines, expected_lines,
394    /// #          "\n\nexpected:\n\n{:#?}\nactual:\n\n{:#?}\n\n",
395    /// #          expected_lines, actual_lines
396    /// #      );
397    /// #  }
398    /// #
399    /// # let testdata = arrow::util::test_util::parquet_test_data();
400    /// # let path = format!("{}/alltypes_plain.parquet", testdata);
401    /// // As before, use tokio::fs::File to read data using an async I/O.
402    /// let file = tokio::fs::File::open(path).await.unwrap();
403    ///
404    /// // Configure options for reading from the async source, in this case we set the batch size
405    /// // to 3 which produces 3 rows at a time.
406    /// let builder = ParquetRecordBatchStreamBuilder::new(file)
407    ///     .await
408    ///     .unwrap()
409    ///     .with_batch_size(3);
410    ///
411    /// // We can also read the metadata to inspect the schema and other metadata
412    /// // before actually reading the data
413    /// let file_metadata = builder.metadata().file_metadata();
414    /// // Specify that we only want to read the 1st, 2nd, and 6th columns
415    /// let mask = ProjectionMask::roots(file_metadata.schema_descr(), [1, 2, 6]);
416    ///
417    /// let stream = builder.with_projection(mask).build().unwrap();
418    /// let results = stream.try_collect::<Vec<_>>().await.unwrap();
419    /// // Print out the results
420    /// assert_batches_eq(
421    ///     &results,
422    ///     &[
423    ///         "+----------+-------------+-----------+",
424    ///         "| bool_col | tinyint_col | float_col |",
425    ///         "+----------+-------------+-----------+",
426    ///         "| true     | 0           | 0.0       |",
427    ///         "| false    | 1           | 1.1       |",
428    ///         "| true     | 0           | 0.0       |",
429    ///         "| false    | 1           | 1.1       |",
430    ///         "| true     | 0           | 0.0       |",
431    ///         "| false    | 1           | 1.1       |",
432    ///         "| true     | 0           | 0.0       |",
433    ///         "| false    | 1           | 1.1       |",
434    ///         "+----------+-------------+-----------+",
435    ///      ],
436    ///  );
437    ///
438    /// // The results has 8 rows, so since we set the batch size to 3, we expect
439    /// // 3 batches, two with 3 rows each and the last batch with 2 rows.
440    /// assert_eq!(results.len(), 3);
441    /// # }
442    /// ```
443    ///
444    /// # Example reading Row Groups in Parallel
445    ///
446    /// Each [`ParquetRecordBatchStream`] is independent and can be used to read
447    /// from the same underlying source in parallel. Use
448    /// [`ParquetRecordBatchStream::next_row_group`] with a single stream to
449    /// begin prefetching the next Row Group. To read a file in parallel, create
450    /// a stream for each subset of the file. For example, you can read each
451    /// row group in parallel by creating a stream for each row group using the
452    /// [`ParquetRecordBatchStreamBuilder::with_row_groups`] API as shown below
453    ///
454    /// ```
455    /// # use std::sync::Arc;
456    /// # use arrow_array::{ArrayRef, Int32Array, RecordBatch};
457    /// # use arrow::util::pretty::pretty_format_batches;
458    /// # use futures::{StreamExt, TryStreamExt};
459    /// # use tempfile::NamedTempFile;
460    /// # use parquet::arrow::{ArrowWriter, ParquetRecordBatchStreamBuilder, ProjectionMask};
461    /// # use parquet::arrow::arrow_reader::{ArrowReaderMetadata, ArrowReaderOptions};
462    /// # use parquet::file::metadata::ParquetMetaDataReader;
463    /// # use parquet::file::properties::{WriterProperties};
464    /// # // write to a temporary file with 10 RowGroups and read back with async API
465    /// # fn write_file() -> parquet::errors::Result<NamedTempFile> {
466    /// #   let mut file = NamedTempFile::new().unwrap();
467    /// #   let small_batch = RecordBatch::try_from_iter([
468    /// #      ("id", Arc::new(Int32Array::from(vec![0, 1, 2, 3, 4])) as ArrayRef),
469    /// #   ]).unwrap();
470    /// #   let props = WriterProperties::builder()
471    /// #     .set_max_row_group_row_count(Some(5))
472    /// #     .set_write_batch_size(5)
473    /// #     .build();
474    /// #   let mut writer = ArrowWriter::try_new(&mut file, small_batch.schema(), Some(props))?;
475    /// #   for i in 0..10 {
476    /// #     writer.write(&small_batch)?
477    /// #   };
478    /// #   writer.close()?;
479    /// #   Ok(file)
480    /// # }
481    /// # #[tokio::main(flavor="current_thread")]
482    /// # async fn main() -> parquet::errors::Result<()> {
483    /// # let t = write_file()?;
484    /// # let path = t.path();
485    /// // This example uses a tokio::fs::File as the async source, but it
486    /// // could be any async source such as an object store reader)
487    /// let mut file = tokio::fs::File::open(path).await?;
488    /// // To read Row Groups in parallel, create a separate stream builder for each Row Group.
489    /// // First get the metadata to find the row group information
490    /// let file_size = file.metadata().await?.len();
491    /// let metadata = ParquetMetaDataReader::new().load_and_finish(&mut file, file_size).await?;
492    /// assert_eq!(metadata.num_row_groups(), 10); // file has 10 row groups with 5 rows each
493    /// // Create a stream reader for each row group
494    /// let reader_metadata = ArrowReaderMetadata::try_new(
495    ///   Arc::new(metadata),
496    ///   ArrowReaderOptions::new()
497    /// )?;
498    /// let mut streams = vec![];
499    ///  for row_group_index in 0..10 {
500    ///   // Each stream needs its own source instance to issue
501    ///   // parallel IO requests, so clone the file for each stream
502    ///   let this_file = file.try_clone().await?;
503    ///   let stream = ParquetRecordBatchStreamBuilder::new_with_metadata(
504    ///        this_file,
505    ///        reader_metadata.clone()
506    ///      )
507    ///      .with_row_groups(vec![row_group_index]) // read only this row group
508    ///      .build()?;
509    ///     streams.push(stream);
510    /// }
511    /// // Each reader can now be polled independently and in parallel, for
512    /// // example using StreamExt::buffered to read from 3 at a time
513    /// let results = futures::stream::iter(streams)
514    ///  .map(|stream| async move { stream })
515    ///  .buffered(3)
516    ///  .flatten()
517    ///  .try_collect::<Vec<_>>().await?;
518    /// // read all 50 rows (10 row groups x 5 rows per group)
519    /// assert_eq!(50, results.iter().map(|s| s.num_rows()).sum::<usize>());
520    /// # Ok(())
521    /// # }
522    /// ```
523    pub async fn new(input: T) -> Result<Self> {
524        Self::new_with_options(input, Default::default()).await
525    }
526
527    /// Create a new [`ParquetRecordBatchStreamBuilder`] with the provided async source
528    /// and [`ArrowReaderOptions`].
529    pub async fn new_with_options(mut input: T, options: ArrowReaderOptions) -> Result<Self> {
530        let metadata = ArrowReaderMetadata::load_async(&mut input, options).await?;
531        Ok(Self::new_with_metadata(input, metadata))
532    }
533
534    /// Create a [`ParquetRecordBatchStreamBuilder`] from the provided [`ArrowReaderMetadata`]
535    ///
536    /// This allows loading metadata once and using it to create multiple builders with
537    /// potentially different settings, that can be read in parallel.
538    ///
539    /// # Example of reading from multiple streams in parallel
540    ///
541    /// ```
542    /// # use std::fs::metadata;
543    /// # use std::sync::Arc;
544    /// # use bytes::Bytes;
545    /// # use arrow_array::{Int32Array, RecordBatch};
546    /// # use arrow_schema::{DataType, Field, Schema};
547    /// # use parquet::arrow::arrow_reader::ArrowReaderMetadata;
548    /// # use parquet::arrow::{ArrowWriter, ParquetRecordBatchStreamBuilder};
549    /// # use tempfile::tempfile;
550    /// # use futures::StreamExt;
551    /// # #[tokio::main(flavor="current_thread")]
552    /// # async fn main() {
553    /// #
554    /// # let mut file = tempfile().unwrap();
555    /// # let schema = Arc::new(Schema::new(vec![Field::new("i32", DataType::Int32, false)]));
556    /// # let mut writer = ArrowWriter::try_new(&mut file, schema.clone(), None).unwrap();
557    /// # let batch = RecordBatch::try_new(schema, vec![Arc::new(Int32Array::from(vec![1, 2, 3]))]).unwrap();
558    /// # writer.write(&batch).unwrap();
559    /// # writer.close().unwrap();
560    /// // open file with parquet data
561    /// let mut file = tokio::fs::File::from_std(file);
562    /// // load metadata once
563    /// let meta = ArrowReaderMetadata::load_async(&mut file, Default::default()).await.unwrap();
564    /// // create two readers, a and b, from the same underlying file
565    /// // without reading the metadata again
566    /// let mut a = ParquetRecordBatchStreamBuilder::new_with_metadata(
567    ///     file.try_clone().await.unwrap(),
568    ///     meta.clone()
569    /// ).build().unwrap();
570    /// let mut b = ParquetRecordBatchStreamBuilder::new_with_metadata(file, meta).build().unwrap();
571    ///
572    /// // Can read batches from both readers in parallel
573    /// assert_eq!(
574    ///   a.next().await.unwrap().unwrap(),
575    ///   b.next().await.unwrap().unwrap(),
576    /// );
577    /// # }
578    /// ```
579    pub fn new_with_metadata(input: T, metadata: ArrowReaderMetadata) -> Self {
580        Self::new_builder(AsyncReader(input), metadata)
581    }
582
583    /// Read bloom filter for a column in a row group
584    ///
585    /// Returns `None` if the column does not have a bloom filter
586    ///
587    /// We should call this function after other forms pruning, such as projection and predicate pushdown.
588    pub async fn get_row_group_column_bloom_filter(
589        &mut self,
590        row_group_idx: usize,
591        column_idx: usize,
592    ) -> Result<Option<Sbbf>> {
593        let metadata = self.metadata.row_group(row_group_idx);
594        let column_metadata = metadata.column(column_idx);
595
596        let offset: u64 = if let Some(offset) = column_metadata.bloom_filter_offset() {
597            offset
598                .try_into()
599                .map_err(|_| ParquetError::General("Bloom filter offset is invalid".to_string()))?
600        } else {
601            return Ok(None);
602        };
603
604        let buffer = match column_metadata.bloom_filter_length() {
605            Some(length) => self.input.0.get_bytes(offset..offset + length as u64),
606            None => self
607                .input
608                .0
609                .get_bytes(offset..offset + SBBF_HEADER_SIZE_ESTIMATE as u64),
610        }
611        .await?;
612
613        let (header, bitset_offset) =
614            chunk_read_bloom_filter_header_and_offset(offset, buffer.clone())?;
615
616        match header.algorithm {
617            BloomFilterAlgorithm::BLOCK => {
618                // this match exists to future proof the singleton algorithm enum
619            }
620        }
621        match header.compression {
622            BloomFilterCompression::UNCOMPRESSED => {
623                // this match exists to future proof the singleton compression enum
624            }
625        }
626        match header.hash {
627            BloomFilterHash::XXHASH => {
628                // this match exists to future proof the singleton hash enum
629            }
630        }
631
632        let bitset = match column_metadata.bloom_filter_length() {
633            Some(_) => {
634                let bitset_start = bitset_offset
635                    .checked_sub(offset)
636                    .and_then(|start| usize::try_from(start).ok())
637                    .ok_or_else(|| {
638                        ParquetError::General("Bloom filter offset is invalid".to_string())
639                    })?;
640                buffer.slice(bitset_start..)
641            }
642            None => {
643                let bitset_length: u64 = header.num_bytes.try_into().map_err(|_| {
644                    ParquetError::General("Bloom filter length is invalid".to_string())
645                })?;
646                self.input
647                    .0
648                    .get_bytes(bitset_offset..bitset_offset + bitset_length)
649                    .await?
650            }
651        };
652        Ok(Some(Sbbf::new(&bitset)))
653    }
654
655    /// Select row groups and rows using row-group-local coordinates.
656    ///
657    /// Entries are decoded in the supplied order, omitted row groups are
658    /// skipped, and a `None` selection reads the whole row group. This is
659    /// mutually exclusive with [`ArrowReaderBuilder::with_row_groups`] and
660    /// [`ArrowReaderBuilder::with_row_selection`]; combining them returns an
661    /// error from [`Self::build`].
662    ///
663    /// See [`ParquetPushDecoderBuilder::with_row_group_selections`] for the
664    /// full semantics and a worked example. This builder supports the same API
665    /// because the async stream is implemented using the push decoder; the
666    /// synchronous reader does not support row-group-local selections.
667    ///
668    /// [`ParquetPushDecoderBuilder::with_row_group_selections`]: crate::arrow::push_decoder::ParquetPushDecoderBuilder::with_row_group_selections
669    pub fn with_row_group_selections(
670        mut self,
671        row_group_selections: Vec<RowGroupSelection>,
672    ) -> Self {
673        self.row_group_plan
674            .set_row_group_selections(row_group_selections);
675        self
676    }
677
678    /// Read and decode the dictionary page for a column in a row group, if any.
679    ///
680    /// Returns `Ok(None)` if the column chunk has no dictionary page, or if
681    /// its physical type is not `BYTE_ARRAY` (the only physical type
682    /// currently supported).
683    ///
684    /// The returned array contains raw `Binary` values, even for columns
685    /// annotated as strings. Callers can compare byte slices directly or
686    /// convert values to UTF-8 explicitly.
687    ///
688    /// This can be used to inspect dictionary values when selecting or pruning
689    /// row groups before passing the selected indices to
690    /// [`ParquetRecordBatchStreamBuilder::with_row_groups`].
691    ///
692    /// Note this does not verify that the *entire* column chunk is
693    /// dictionary-encoded -- callers that need that guarantee (e.g. to treat
694    /// the dictionary as an exhaustive set of the column's values) should
695    /// check
696    /// [`crate::file::metadata::ColumnChunkMetaData::page_encoding_stats_mask`].
697    pub async fn get_column_chunk_dictionary(
698        &mut self,
699        row_group_idx: usize,
700        column_idx: usize,
701    ) -> Result<Option<ArrayRef>> {
702        ParquetMetaDataReader::read_column_dictionary_async(
703            &mut self.input.0,
704            &self.metadata,
705            row_group_idx,
706            column_idx,
707        )
708        .await
709    }
710
711    /// Build a new [`ParquetRecordBatchStream`]
712    ///
713    /// See examples on [`ParquetRecordBatchStreamBuilder::new`]
714    pub fn build(self) -> Result<ParquetRecordBatchStream<T>> {
715        let Self {
716            input,
717            metadata,
718            schema,
719            fields,
720            batch_size,
721            row_group_plan,
722            projection,
723            filter,
724            row_selection_policy: selection_strategy,
725            limit,
726            offset,
727            metrics,
728            max_predicate_cache_size,
729        } = self;
730
731        // Ensure schema of ParquetRecordBatchStream respects projection, and does
732        // not store metadata (same as for ParquetRecordBatchReader and emitted RecordBatches)
733        let projection_len = projection.mask.as_ref().map_or(usize::MAX, |m| m.len());
734        let projected_fields = schema
735            .fields
736            .filter_leaves(|idx, _| idx < projection_len && projection.leaf_included(idx));
737        let projected_schema = Arc::new(Schema::new(projected_fields));
738
739        let decoder = ParquetPushDecoderBuilder {
740            input: PushDecoderInput::default(),
741            metadata,
742            schema,
743            fields,
744            projection,
745            filter,
746            row_group_plan,
747            row_selection_policy: selection_strategy,
748            batch_size,
749            limit,
750            offset,
751            metrics,
752            max_predicate_cache_size,
753        }
754        .build()?;
755
756        let request_state = RequestState::None { input: input.0 };
757
758        Ok(ParquetRecordBatchStream {
759            schema: projected_schema,
760            decoder,
761            request_state,
762        })
763    }
764}
765
766/// State machine that tracks outstanding requests to fetch data
767///
768/// The parameter `T` is the input, typically an `AsyncFileReader`
769enum RequestState<T> {
770    /// No outstanding requests
771    None {
772        input: T,
773    },
774    /// There is an outstanding request for data
775    Outstanding {
776        /// Ranges that have been requested
777        ranges: Vec<Range<u64>>,
778        /// Future that will resolve (input, requested_ranges)
779        ///
780        /// Note the future owns the reader while the request is outstanding
781        /// and returns it upon completion
782        future: BoxFuture<'static, Result<(T, Vec<Bytes>)>>,
783    },
784    Done,
785}
786
787impl<T> RequestState<T>
788where
789    T: AsyncFileReader + Unpin + Send + 'static,
790{
791    /// Issue a request to fetch `ranges`, returning the Outstanding state
792    fn begin_request(mut input: T, ranges: Vec<Range<u64>>) -> Self {
793        let ranges_captured = ranges.clone();
794
795        // Note this must move the input *into* the future
796        // because the get_byte_ranges future has a lifetime
797        // (aka can have references internally) and thus must
798        // own the input while the request is outstanding.
799        let future = async move {
800            let data = input.get_byte_ranges(ranges_captured).await?;
801            Ok((input, data))
802        }
803        .boxed();
804        RequestState::Outstanding { ranges, future }
805    }
806}
807
808impl<T> std::fmt::Debug for RequestState<T> {
809    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
810        match self {
811            RequestState::None { input: _ } => f
812                .debug_struct("RequestState::None")
813                .field("input", &"...")
814                .finish(),
815            RequestState::Outstanding { ranges, .. } => f
816                .debug_struct("RequestState::Outstanding")
817                .field("ranges", &ranges)
818                .finish(),
819            RequestState::Done => {
820                write!(f, "RequestState::Done")
821            }
822        }
823    }
824}
825
826/// An asynchronous [`Stream`]of [`RecordBatch`] constructed using [`ParquetRecordBatchStreamBuilder`] to read parquet files.
827///
828/// `ParquetRecordBatchStream` also provides [`ParquetRecordBatchStream::next_row_group`] for fetching row groups,
829/// allowing users to decode record batches separately from I/O.
830///
831/// # I/O Buffering
832///
833/// `ParquetRecordBatchStream` buffers *all* data pages selected after predicates
834/// (projection + filtering, etc) and decodes the rows from those buffered pages.
835///
836/// For example, if all rows and columns are selected, the entire row group is
837/// buffered in memory during decode. This minimizes the number of IO operations
838/// required, which is especially important for object stores, where IO operations
839/// have latencies in the hundreds of milliseconds
840///
841/// See [`ParquetPushDecoderBuilder`] for an API with lower level control over
842/// buffering.
843///
844/// [`Stream`]: https://docs.rs/futures/latest/futures/stream/trait.Stream.html
845pub struct ParquetRecordBatchStream<T> {
846    /// Output schema of the stream
847    schema: SchemaRef,
848    /// Input and Outstanding IO request, if any
849    request_state: RequestState<T>,
850    /// Decoding state machine (no IO)
851    decoder: ParquetPushDecoder,
852}
853
854impl<T> std::fmt::Debug for ParquetRecordBatchStream<T> {
855    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
856        f.debug_struct("ParquetRecordBatchStream")
857            .field("request_state", &self.request_state)
858            .finish()
859    }
860}
861
862impl<T> ParquetRecordBatchStream<T> {
863    /// Returns the projected [`SchemaRef`] for reading the parquet file.
864    ///
865    /// Note that the schema metadata will be stripped here. See
866    /// [`ParquetRecordBatchStreamBuilder::schema`] if the metadata is desired.
867    pub fn schema(&self) -> &SchemaRef {
868        &self.schema
869    }
870}
871
872impl<T> ParquetRecordBatchStream<T>
873where
874    T: AsyncFileReader + Unpin + Send + 'static,
875{
876    /// Fetches the next row group from the stream.
877    ///
878    /// Users can continue to call this function to get row groups and decode them concurrently.
879    ///
880    /// ## Notes
881    ///
882    /// ParquetRecordBatchStream should be used either as a `Stream` or with `next_row_group`; they should not be used simultaneously.
883    ///
884    /// ## Returns
885    ///
886    /// - `Ok(None)` if the stream has ended.
887    /// - `Err(error)` if the stream has errored. All subsequent calls will return `Ok(None)`.
888    /// - `Ok(Some(reader))` which holds all the data for the row group.
889    pub async fn next_row_group(&mut self) -> Result<Option<ParquetRecordBatchReader>> {
890        loop {
891            // Take ownership of request state to process, leaving self in a
892            // valid state
893            let request_state = std::mem::replace(&mut self.request_state, RequestState::Done);
894            match request_state {
895                // No outstanding requests, proceed to setup next row group
896                RequestState::None { input } => {
897                    match self.decoder.try_next_reader()? {
898                        DecodeResult::NeedsData(ranges) => {
899                            self.request_state = RequestState::begin_request(input, ranges);
900                            // Will loop again: the input might be ready immediately.
901                        }
902                        DecodeResult::Data(reader) => {
903                            self.request_state = RequestState::None { input };
904                            return Ok(Some(reader));
905                        }
906                        DecodeResult::Finished => return Ok(None),
907                    }
908                }
909                RequestState::Outstanding { ranges, future } => {
910                    let (input, data) = future.await?;
911                    // Push the requested data to the decoder and try again
912                    self.decoder.push_ranges(ranges, data)?;
913                    self.request_state = RequestState::None { input };
914                    // Will try and decode on the next iteration.
915                }
916                RequestState::Done => {
917                    self.request_state = RequestState::Done;
918                    return Ok(None);
919                }
920            }
921        }
922    }
923}
924
925impl<T> Stream for ParquetRecordBatchStream<T>
926where
927    T: AsyncFileReader + Unpin + Send + 'static,
928{
929    type Item = Result<RecordBatch>;
930    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
931        match self.poll_next_inner(cx) {
932            Ok(res) => {
933                // Successfully decoded a batch, or reached end of stream.
934                // convert Option<RecordBatch> to Option<Result<RecordBatch>>
935                res.map(|res| Ok(res).transpose())
936            }
937            Err(e) => {
938                self.request_state = RequestState::Done;
939                Poll::Ready(Some(Err(e)))
940            }
941        }
942    }
943}
944
945impl<T> ParquetRecordBatchStream<T>
946where
947    T: AsyncFileReader + Unpin + Send + 'static,
948{
949    /// Inner state machine
950    ///
951    /// Note this is separate from poll_next so we can use ? operator to check for errors
952    /// as it returns `Result<Poll<Option<RecordBatch>>>`
953    fn poll_next_inner(&mut self, cx: &mut Context<'_>) -> Result<Poll<Option<RecordBatch>>> {
954        loop {
955            let request_state = std::mem::replace(&mut self.request_state, RequestState::Done);
956            match request_state {
957                RequestState::None { input } => {
958                    // No outstanding requests, proceed to decode the next batch
959                    match self.decoder.try_decode()? {
960                        DecodeResult::NeedsData(ranges) => {
961                            self.request_state = RequestState::begin_request(input, ranges);
962                            // Will loop again: the input might be ready immediately.
963                        }
964                        DecodeResult::Data(batch) => {
965                            self.request_state = RequestState::None { input };
966                            return Ok(Poll::Ready(Some(batch)));
967                        }
968                        DecodeResult::Finished => {
969                            self.request_state = RequestState::Done;
970                            return Ok(Poll::Ready(None));
971                        }
972                    }
973                }
974                RequestState::Outstanding { ranges, mut future } => match future.poll_unpin(cx) {
975                    // Data was ready, push it to the decoder and continue
976                    Poll::Ready(result) => {
977                        let (input, data) = result?;
978                        // Push the requested data to the decoder
979                        self.decoder.push_ranges(ranges, data)?;
980                        self.request_state = RequestState::None { input };
981                        // The next iteration will try to decode the next batch.
982                    }
983                    Poll::Pending => {
984                        self.request_state = RequestState::Outstanding { ranges, future };
985                        return Ok(Poll::Pending);
986                    }
987                },
988                RequestState::Done => {
989                    // Stream is done (error or end), return None
990                    self.request_state = RequestState::Done;
991                    return Ok(Poll::Ready(None));
992                }
993            }
994        }
995    }
996}
997
998#[cfg(test)]
999mod tests {
1000    use super::*;
1001    use crate::arrow::ArrowWriter;
1002    use crate::arrow::ProjectionMask;
1003    use crate::arrow::arrow_reader::tests::test_row_numbers_with_multiple_row_groups_helper;
1004    use crate::arrow::arrow_reader::{ArrowReaderMetadata, ArrowReaderOptions};
1005    use crate::arrow::schema::virtual_type::RowNumber;
1006    use crate::basic::Encoding;
1007    use crate::file::metadata::PageIndexPolicy;
1008    use crate::file::metadata::ParquetMetaDataReader;
1009    use crate::file::metadata::page_index::PageIndex;
1010    use crate::file::properties::WriterProperties;
1011    use arrow_array::cast::AsArray;
1012    use arrow_array::{Array, ArrayRef, BinaryArray, StringArray};
1013    use arrow_schema::{DataType, Field, Schema};
1014    use futures::TryStreamExt;
1015    use std::sync::{Arc, Mutex};
1016
1017    #[derive(Clone)]
1018    struct TestReader {
1019        data: Bytes,
1020        metadata: Option<Arc<ParquetMetaData>>,
1021        requests: Arc<Mutex<Vec<Range<usize>>>>,
1022    }
1023
1024    impl TestReader {
1025        fn new(data: Bytes) -> Self {
1026            Self {
1027                data,
1028                metadata: Default::default(),
1029                requests: Default::default(),
1030            }
1031        }
1032    }
1033
1034    impl AsyncFileReader for TestReader {
1035        fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, Result<Bytes>> {
1036            let range = range.clone();
1037            self.requests
1038                .lock()
1039                .unwrap()
1040                .push(range.start as usize..range.end as usize);
1041            futures::future::ready(Ok(self
1042                .data
1043                .slice(range.start as usize..range.end as usize)))
1044            .boxed()
1045        }
1046
1047        fn get_metadata<'a>(
1048            &'a mut self,
1049            options: Option<&'a ArrowReaderOptions>,
1050        ) -> BoxFuture<'a, Result<Arc<ParquetMetaData>>> {
1051            let metadata_reader = ParquetMetaDataReader::new().with_arrow_reader_options(options);
1052            self.metadata = Some(Arc::new(
1053                metadata_reader.parse_and_finish(&self.data).unwrap(),
1054            ));
1055            futures::future::ready(Ok(self.metadata.clone().unwrap().clone())).boxed()
1056        }
1057    }
1058
1059    #[tokio::test]
1060    async fn test_get_column_chunk_dictionary() {
1061        let schema = Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, false)]));
1062        let values: Vec<&str> = ["alpha", "beta", "gamma"]
1063            .iter()
1064            .copied()
1065            .cycle()
1066            .take(30)
1067            .collect();
1068        let array: ArrayRef = Arc::new(StringArray::from(values));
1069        let batch = RecordBatch::try_new(schema.clone(), vec![array]).unwrap();
1070
1071        let props = WriterProperties::builder()
1072            .set_dictionary_enabled(true)
1073            .build();
1074        let mut buf = Vec::new();
1075        {
1076            let mut writer = ArrowWriter::try_new(&mut buf, schema, Some(props)).unwrap();
1077            writer.write(&batch).unwrap();
1078            writer.close().unwrap();
1079        }
1080        let data = Bytes::from(buf);
1081
1082        let direct_metadata = ParquetMetaDataReader::new()
1083            .parse_and_finish(&data)
1084            .unwrap();
1085        let mut direct_reader = TestReader::new(data.clone());
1086        let direct = ParquetMetaDataReader::read_column_dictionary_async(
1087            &mut direct_reader,
1088            &direct_metadata,
1089            0,
1090            0,
1091        )
1092        .await
1093        .unwrap()
1094        .unwrap();
1095        let direct = direct.as_any().downcast_ref::<BinaryArray>().unwrap();
1096        assert_eq!(direct.value(0), b"alpha");
1097
1098        let async_reader = TestReader::new(data);
1099        let mut builder = ParquetRecordBatchStreamBuilder::new(async_reader)
1100            .await
1101            .unwrap();
1102
1103        let dictionary = builder
1104            .get_column_chunk_dictionary(0, 0)
1105            .await
1106            .unwrap()
1107            .unwrap();
1108        let dictionary = dictionary.as_any().downcast_ref::<BinaryArray>().unwrap();
1109        let dictionary_values: Vec<&[u8]> = dictionary.iter().map(|v| v.unwrap()).collect();
1110        assert_eq!(
1111            dictionary_values,
1112            vec![b"alpha".as_slice(), b"beta", b"gamma"]
1113        );
1114    }
1115
1116    // This test demonstrates row group pruning using dictionary pages,
1117    // verifying that data pages of skipped row groups are not read
1118    #[tokio::test]
1119    async fn test_dictionary_selects_row_groups_without_reading_skipped_data() {
1120        // Write two row groups, each dictionary-encoded and containing a single
1121        // distinct string repeated 30 times: row group 0 is all "skip", row
1122        // group 1 is all "target".
1123        let schema = Arc::new(Schema::new(vec![Field::new("s", DataType::Utf8, false)]));
1124        let row_group_values = ["skip", "target"];
1125        let props = WriterProperties::builder()
1126            .set_dictionary_enabled(true)
1127            .build();
1128        let mut buf = Vec::new();
1129        {
1130            let mut writer = ArrowWriter::try_new(&mut buf, schema.clone(), Some(props)).unwrap();
1131            for value in row_group_values {
1132                let array: ArrayRef = Arc::new(StringArray::from(vec![value; 30]));
1133                let batch = RecordBatch::try_new(schema.clone(), vec![array]).unwrap();
1134                writer.write(&batch).unwrap();
1135                writer.flush().unwrap();
1136            }
1137            writer.close().unwrap();
1138        }
1139
1140        let async_reader = TestReader::new(Bytes::from(buf));
1141        // `requests` records the byte ranges fetched from the underlying reader,
1142        // which we later use to check that we read only the needed page
1143        let requests = async_reader.requests.clone();
1144        let mut builder = ParquetRecordBatchStreamBuilder::new(async_reader)
1145            .await
1146            .unwrap();
1147        let metadata = builder.metadata().clone();
1148        assert_eq!(metadata.num_row_groups(), 2);
1149
1150        // For each row group, fetch and decode just the dictionary page
1151        // to decide whether to read the whole row group
1152        let mut selected_row_groups = Vec::new();
1153        let mut dictionary_ranges = Vec::new();
1154        for row_group_idx in 0..metadata.num_row_groups() {
1155            let column = metadata.row_group(row_group_idx).column(0);
1156            let encoding_mask = column.page_encoding_stats_mask().unwrap();
1157            assert!(
1158                encoding_mask.is_only(Encoding::PLAIN_DICTIONARY)
1159                    || encoding_mask.is_only(Encoding::RLE_DICTIONARY)
1160            );
1161
1162            let dictionary_start = column.dictionary_page_offset().unwrap() as usize;
1163            let data_start = column.data_page_offset() as usize;
1164            dictionary_ranges.push(dictionary_start..data_start);
1165
1166            let dictionary = builder
1167                .get_column_chunk_dictionary(row_group_idx, 0)
1168                .await
1169                .unwrap()
1170                .unwrap();
1171            let dictionary = dictionary.as_binary::<i32>();
1172            if dictionary
1173                .iter()
1174                .any(|value| value == Some(b"target".as_slice()))
1175            {
1176                selected_row_groups.push(row_group_idx);
1177            }
1178        }
1179        assert_eq!(selected_row_groups, vec![1]);
1180
1181        // Read the filtered row group and verify the values
1182        let batches: Vec<_> = builder
1183            .with_row_groups(selected_row_groups)
1184            .build()
1185            .unwrap()
1186            .try_collect()
1187            .await
1188            .unwrap();
1189        let values: Vec<_> = batches
1190            .iter()
1191            .flat_map(|batch| batch.column(0).as_string::<i32>().iter())
1192            .collect();
1193        assert_eq!(values, vec![Some("target"); 30]);
1194
1195        // Finally, verify none of the requests overlapped the data pages
1196        // of the skipped row group
1197        let skipped_column = metadata.row_group(0).column(0);
1198        let (skipped_start, skipped_len) = skipped_column.byte_range();
1199        let skipped_data_range =
1200            skipped_column.data_page_offset() as usize..(skipped_start + skipped_len) as usize;
1201        let requests = requests.lock().unwrap();
1202        for dictionary_range in dictionary_ranges {
1203            assert!(requests.contains(&dictionary_range));
1204        }
1205        assert!(requests.iter().all(|request| {
1206            request.end <= skipped_data_range.start || request.start >= skipped_data_range.end
1207        }));
1208    }
1209
1210    #[tokio::test]
1211    async fn test_batch_size_overallocate() {
1212        let testdata = arrow::util::test_util::parquet_test_data();
1213        // `alltypes_plain.parquet` only have 8 rows
1214        let path = format!("{testdata}/alltypes_plain.parquet");
1215        let data = Bytes::from(std::fs::read(path).unwrap());
1216
1217        let async_reader = TestReader::new(data.clone());
1218
1219        let builder = ParquetRecordBatchStreamBuilder::new(async_reader)
1220            .await
1221            .unwrap();
1222
1223        let file_rows = builder.metadata().file_metadata().num_rows() as usize;
1224
1225        let builder = builder
1226            .with_projection(ProjectionMask::all())
1227            .with_batch_size(1024);
1228
1229        // even though the batch size is set to 1024, it should adjust to the max
1230        // number of rows in the file (8)
1231        assert_ne!(1024, file_rows);
1232        assert_eq!(builder.batch_size, file_rows);
1233
1234        let _stream = builder.build().unwrap();
1235    }
1236
1237    #[tokio::test]
1238    async fn empty_offset_index_doesnt_panic_in_read_row_group() {
1239        use tokio::fs::File;
1240        let testdata = arrow::util::test_util::parquet_test_data();
1241        let path = format!("{testdata}/alltypes_plain.parquet");
1242        let mut file = File::open(&path).await.unwrap();
1243        let file_size = file.metadata().await.unwrap().len();
1244        let mut metadata = ParquetMetaDataReader::new()
1245            .with_page_index_policy(PageIndexPolicy::Required)
1246            .load_and_finish(&mut file, file_size)
1247            .await
1248            .unwrap();
1249
1250        let page_index = PageIndex::new(None, Some(vec![]));
1251        metadata.set_page_index(Some(Arc::new(page_index)));
1252        let options = ArrowReaderOptions::new().with_page_index_policy(PageIndexPolicy::Required);
1253        let arrow_reader_metadata = ArrowReaderMetadata::try_new(metadata.into(), options).unwrap();
1254        let reader =
1255            ParquetRecordBatchStreamBuilder::new_with_metadata(file, arrow_reader_metadata)
1256                .build()
1257                .unwrap();
1258
1259        let result = reader.try_collect::<Vec<_>>().await.unwrap();
1260        assert_eq!(result.len(), 1);
1261    }
1262
1263    #[test]
1264    fn test_row_numbers_with_multiple_row_groups() {
1265        test_row_numbers_with_multiple_row_groups_helper(
1266            false,
1267            |path, selection, _row_filter, batch_size| {
1268                let runtime = tokio::runtime::Builder::new_current_thread()
1269                    .enable_all()
1270                    .build()
1271                    .expect("Could not create runtime");
1272                runtime.block_on(async move {
1273                    let file = tokio::fs::File::open(path).await.unwrap();
1274                    let row_number_field = Arc::new(
1275                        Field::new("row_number", DataType::Int64, false)
1276                            .with_extension_type(RowNumber),
1277                    );
1278                    let options = ArrowReaderOptions::new()
1279                        .with_virtual_columns(vec![row_number_field])
1280                        .unwrap();
1281                    let reader = ParquetRecordBatchStreamBuilder::new_with_options(file, options)
1282                        .await
1283                        .unwrap()
1284                        .with_row_selection(selection)
1285                        .with_batch_size(batch_size)
1286                        .build()
1287                        .expect("Could not create reader");
1288                    reader.try_collect::<Vec<_>>().await.unwrap()
1289                })
1290            },
1291        );
1292    }
1293
1294    #[test]
1295    fn test_row_numbers_with_multiple_row_groups_and_filter() {
1296        test_row_numbers_with_multiple_row_groups_helper(
1297            true,
1298            |path, selection, row_filter, batch_size| {
1299                let runtime = tokio::runtime::Builder::new_current_thread()
1300                    .enable_all()
1301                    .build()
1302                    .expect("Could not create runtime");
1303                runtime.block_on(async move {
1304                    let file = tokio::fs::File::open(path).await.unwrap();
1305                    let row_number_field = Arc::new(
1306                        Field::new("row_number", DataType::Int64, false)
1307                            .with_extension_type(RowNumber),
1308                    );
1309                    let options = ArrowReaderOptions::new()
1310                        .with_virtual_columns(vec![row_number_field])
1311                        .unwrap();
1312                    let reader = ParquetRecordBatchStreamBuilder::new_with_options(file, options)
1313                        .await
1314                        .unwrap()
1315                        .with_row_selection(selection)
1316                        .with_row_filter(row_filter.expect("No row filter"))
1317                        .with_batch_size(batch_size)
1318                        .build()
1319                        .expect("Could not create reader");
1320                    reader.try_collect::<Vec<_>>().await.unwrap()
1321                })
1322            },
1323        );
1324    }
1325}