alamb commented on code in PR #7997:
URL: https://github.com/apache/arrow-rs/pull/7997#discussion_r2429147932
##########
parquet/src/arrow/async_reader/mod.rs:
##########
@@ -967,245 +963,32 @@ where
}
}
-/// An in-memory collection of column chunks
Review Comment:
This structure is now used by the push decoder and the async reader, so I
refactored it so the code could be shared
##########
parquet/src/arrow/async_reader/mod.rs:
##########
@@ -967,245 +963,32 @@ where
}
}
-/// An in-memory collection of column chunks
-struct InMemoryRowGroup<'a> {
- offset_index: Option<&'a [OffsetIndexMetaData]>,
- /// Column chunks for this row group
- column_chunks: Vec<Option<Arc<ColumnChunkData>>>,
- row_count: usize,
- row_group_idx: usize,
- metadata: &'a ParquetMetaData,
-}
-
impl InMemoryRowGroup<'_> {
Review Comment:
This should probably be moved into in_memory_row_group.rs ...
##########
parquet/src/arrow/push_decoder/reader_builder/filter.rs:
##########
@@ -0,0 +1,136 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! [`FilterInfo`] state machine for evaluating row filters
+
+use crate::arrow::ProjectionMask;
+use crate::arrow::array_reader::{CacheOptionsBuilder, RowGroupCache};
+use crate::arrow::arrow_reader::{ArrowPredicate, RowFilter};
+use std::num::NonZeroUsize;
+use std::sync::{Arc, Mutex};
+
+/// State machine for evaluating a sequence of predicates
Review Comment:
This is the third state machine (for evaluating filters). This is fairly
complicated because the predicate is mutable (and thus the onwnership must be
carefully transfered)
##########
parquet/src/arrow/push_decoder/reader_builder/mod.rs:
##########
@@ -0,0 +1,654 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+mod data;
+mod filter;
+
+use crate::DecodeResult;
+use crate::arrow::ProjectionMask;
+use crate::arrow::array_reader::{ArrayReaderBuilder, RowGroupCache};
+use crate::arrow::arrow_reader::metrics::ArrowReaderMetrics;
+use crate::arrow::arrow_reader::{
+ ParquetRecordBatchReader, ReadPlanBuilder, RowFilter, RowSelection,
+};
+use crate::arrow::in_memory_row_group::ColumnChunkData;
+use crate::arrow::push_decoder::reader_builder::data::DataRequestBuilder;
+use crate::arrow::push_decoder::reader_builder::filter::CacheInfo;
+use crate::arrow::schema::ParquetField;
+use crate::errors::ParquetError;
+use crate::file::metadata::ParquetMetaData;
+use crate::util::push_buffers::PushBuffers;
+use bytes::Bytes;
+use data::DataRequest;
+use filter::AdvanceResult;
+use filter::FilterInfo;
+use std::ops::Range;
+use std::sync::{Arc, Mutex};
+
+/// The current row group being read and the read plan
+#[derive(Debug)]
+struct RowGroupInfo {
+ row_group_idx: usize,
+ row_count: usize,
+ plan_builder: ReadPlanBuilder,
+}
+
+/// This is the inner state machine for reading a single row group.
Review Comment:
Here is the second state machine
##########
parquet/src/arrow/in_memory_row_group.rs:
##########
@@ -0,0 +1,296 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+use crate::arrow::ProjectionMask;
+use crate::arrow::array_reader::RowGroups;
+use crate::arrow::arrow_reader::RowSelection;
+use crate::column::page::{PageIterator, PageReader};
+use crate::errors::ParquetError;
+use crate::file::metadata::ParquetMetaData;
+use crate::file::page_index::offset_index::OffsetIndexMetaData;
+use crate::file::reader::{ChunkReader, Length, SerializedPageReader};
+use bytes::{Buf, Bytes};
+use std::ops::Range;
+use std::sync::Arc;
+
+/// An in-memory collection of column chunks
+#[derive(Debug)]
+pub(crate) struct InMemoryRowGroup<'a> {
+ pub(crate) offset_index: Option<&'a [OffsetIndexMetaData]>,
+ /// Column chunks for this row group
+ pub(crate) column_chunks: Vec<Option<Arc<ColumnChunkData>>>,
+ pub(crate) row_count: usize,
+ pub(crate) row_group_idx: usize,
+ pub(crate) metadata: &'a ParquetMetaData,
+}
+
+/// What ranges to fetch for the columns in this row group
+#[derive(Debug)]
+pub(crate) struct FetchRanges {
+ /// The byte ranges to fetch
+ pub(crate) ranges: Vec<Range<u64>>,
+ /// If `Some`, the start offsets of each page for each column chunk
+ pub(crate) page_start_offsets: Option<Vec<Vec<u64>>>,
+}
+
+impl InMemoryRowGroup<'_> {
+ /// Returns the byte ranges to fetch for the columns specified in
+ /// `projection` and `selection`.
+ pub(crate) fn fetch_ranges(
Review Comment:
this logic is intended to be the same, but instead of being embedded in the
reader it is broken into its own structure so it can be shared between push
decoder and the async reader
##########
parquet/src/arrow/arrow_reader/read_plan.rs:
##########
@@ -51,7 +51,6 @@ impl ReadPlanBuilder {
}
/// Returns the current selection, if any
- #[cfg(feature = "async")]
Review Comment:
these functions are now used by the push decoder, which is not behind the
async flag
##########
parquet/src/arrow/push_decoder/mod.rs:
##########
@@ -0,0 +1,1150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! [`ParquetPushDecoder`]: decodes Parquet data with data provided by the
+//! caller (rather than from an underlying reader).
+
+mod reader_builder;
+mod remaining;
+
+use crate::DecodeResult;
+use crate::arrow::arrow_reader::{
+ ArrowReaderBuilder, ArrowReaderMetadata, ArrowReaderOptions,
ParquetRecordBatchReader,
+};
+use crate::errors::ParquetError;
+use crate::file::metadata::ParquetMetaData;
+use crate::util::push_buffers::PushBuffers;
+use arrow_array::RecordBatch;
+use bytes::Bytes;
+use reader_builder::RowGroupReaderBuilder;
+use remaining::RemainingRowGroups;
+use std::ops::Range;
+use std::sync::Arc;
+
+/// A builder for [`ParquetPushDecoder`].
+///
+/// To create a new decoder, use
[`ParquetPushDecoderBuilder::try_new_decoder`] and pass
+/// the file length and metadata of the Parquet file to decode.
+///
+/// You can decode the metadata from a Parquet file using either
+/// [`ParquetMetadataReader`] or [`ParquetMetaDataPushDecoder`].
+///
+/// [`ParquetMetadataReader`]: crate::file::metadata::ParquetMetaDataReader
+/// [`ParquetMetaDataPushDecoder`]:
crate::file::metadata::ParquetMetaDataPushDecoder
+///
+/// Note the "input" type is `u64` which represents the length of the Parquet
file
+/// being decoded. This is needed to initialize the internal buffers that track
+/// what data has been provided to the decoder.
+///
+/// # Example
+/// ```
+/// # use std::ops::Range;
+/// # use std::sync::Arc;
+/// # use bytes::Bytes;
+/// # use arrow_array::record_batch;
+/// # use parquet::DecodeResult;
+/// # use parquet::arrow::push_decoder::ParquetPushDecoderBuilder;
+/// # use parquet::arrow::ArrowWriter;
+/// # use parquet::file::metadata::ParquetMetaDataPushDecoder;
+/// # let file_bytes = {
+/// # let mut buffer = vec![];
+/// # let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
+/// # let mut writer = ArrowWriter::try_new(&mut buffer, batch.schema(),
None).unwrap();
+/// # writer.write(&batch).unwrap();
+/// # writer.close().unwrap();
+/// # Bytes::from(buffer)
+/// # };
+/// # // mimic IO by returning a function that returns the bytes for a given
range
+/// # let get_range = |range: &Range<u64>| -> Bytes {
+/// # let start = range.start as usize;
+/// # let end = range.end as usize;
+/// # file_bytes.slice(start..end)
+/// # };
+/// # let file_length = file_bytes.len() as u64;
+/// # let mut metadata_decoder =
ParquetMetaDataPushDecoder::try_new(file_length).unwrap();
+/// # metadata_decoder.push_ranges(vec![0..file_length],
vec![file_bytes.clone()]).unwrap();
+/// # let DecodeResult::Data(parquet_metadata) =
metadata_decoder.try_decode().unwrap() else { panic!("failed to decode
metadata") };
+/// # let parquet_metadata = Arc::new(parquet_metadata);
+/// // The file length and metadata are required to create the decoder
+/// let mut decoder =
+/// ParquetPushDecoderBuilder::try_new_decoder(file_length,
parquet_metadata)
+/// .unwrap()
+/// // Optionally configure the decoder, e.g. batch size
+/// .with_batch_size(1024)
+/// // Build the decoder
+/// .build()
+/// .unwrap();
+///
+/// // In a loop, ask the decoder what it needs next, and provide it with
the required data
+/// loop {
Review Comment:
I am quite pleased with this example -- as it shows how to use the push
decoder.
I could also crank out an example of using an arbitrary async source too to
help make that more concrete 🤔
##########
parquet/src/arrow/push_decoder/mod.rs:
##########
@@ -0,0 +1,1150 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! [`ParquetPushDecoder`]: decodes Parquet data with data provided by the
+//! caller (rather than from an underlying reader).
+
+mod reader_builder;
+mod remaining;
+
+use crate::DecodeResult;
+use crate::arrow::arrow_reader::{
+ ArrowReaderBuilder, ArrowReaderMetadata, ArrowReaderOptions,
ParquetRecordBatchReader,
+};
+use crate::errors::ParquetError;
+use crate::file::metadata::ParquetMetaData;
+use crate::util::push_buffers::PushBuffers;
+use arrow_array::RecordBatch;
+use bytes::Bytes;
+use reader_builder::RowGroupReaderBuilder;
+use remaining::RemainingRowGroups;
+use std::ops::Range;
+use std::sync::Arc;
+
+/// A builder for [`ParquetPushDecoder`].
+///
+/// To create a new decoder, use
[`ParquetPushDecoderBuilder::try_new_decoder`] and pass
+/// the file length and metadata of the Parquet file to decode.
+///
+/// You can decode the metadata from a Parquet file using either
+/// [`ParquetMetadataReader`] or [`ParquetMetaDataPushDecoder`].
+///
+/// [`ParquetMetadataReader`]: crate::file::metadata::ParquetMetaDataReader
+/// [`ParquetMetaDataPushDecoder`]:
crate::file::metadata::ParquetMetaDataPushDecoder
+///
+/// Note the "input" type is `u64` which represents the length of the Parquet
file
+/// being decoded. This is needed to initialize the internal buffers that track
+/// what data has been provided to the decoder.
+///
+/// # Example
+/// ```
+/// # use std::ops::Range;
+/// # use std::sync::Arc;
+/// # use bytes::Bytes;
+/// # use arrow_array::record_batch;
+/// # use parquet::DecodeResult;
+/// # use parquet::arrow::push_decoder::ParquetPushDecoderBuilder;
+/// # use parquet::arrow::ArrowWriter;
+/// # use parquet::file::metadata::ParquetMetaDataPushDecoder;
+/// # let file_bytes = {
+/// # let mut buffer = vec![];
+/// # let batch = record_batch!(("a", Int32, [1, 2, 3])).unwrap();
+/// # let mut writer = ArrowWriter::try_new(&mut buffer, batch.schema(),
None).unwrap();
+/// # writer.write(&batch).unwrap();
+/// # writer.close().unwrap();
+/// # Bytes::from(buffer)
+/// # };
+/// # // mimic IO by returning a function that returns the bytes for a given
range
+/// # let get_range = |range: &Range<u64>| -> Bytes {
+/// # let start = range.start as usize;
+/// # let end = range.end as usize;
+/// # file_bytes.slice(start..end)
+/// # };
+/// # let file_length = file_bytes.len() as u64;
+/// # let mut metadata_decoder =
ParquetMetaDataPushDecoder::try_new(file_length).unwrap();
+/// # metadata_decoder.push_ranges(vec![0..file_length],
vec![file_bytes.clone()]).unwrap();
+/// # let DecodeResult::Data(parquet_metadata) =
metadata_decoder.try_decode().unwrap() else { panic!("failed to decode
metadata") };
+/// # let parquet_metadata = Arc::new(parquet_metadata);
+/// // The file length and metadata are required to create the decoder
+/// let mut decoder =
+/// ParquetPushDecoderBuilder::try_new_decoder(file_length,
parquet_metadata)
+/// .unwrap()
+/// // Optionally configure the decoder, e.g. batch size
+/// .with_batch_size(1024)
+/// // Build the decoder
+/// .build()
+/// .unwrap();
+///
+/// // In a loop, ask the decoder what it needs next, and provide it with
the required data
+/// loop {
+/// match decoder.try_decode().unwrap() {
+/// DecodeResult::NeedsData(ranges) => {
+/// // The decoder needs more data. Fetch the data for the
given ranges
+/// let data = ranges.iter().map(|r|
get_range(r)).collect::<Vec<_>>();
+/// // Push the data to the decoder
+/// decoder.push_ranges(ranges, data).unwrap();
+/// // After pushing the data, we can try to decode again on
the next iteration
+/// }
+/// DecodeResult::Data(batch) => {
+/// // Successfully decoded a batch of data
+/// assert!(batch.num_rows() > 0);
+/// }
+/// DecodeResult::Finished => {
+/// // The decoder has finished decoding exit the loop
+/// break;
+/// }
+/// }
+/// }
+/// ```
+pub type ParquetPushDecoderBuilder = ArrowReaderBuilder<u64>;
+
+/// Methods for building a ParquetDecoder. See the base [`ArrowReaderBuilder`]
for
+/// more options that can be configured.
+impl ParquetPushDecoderBuilder {
+ /// Create a new `ParquetDecoderBuilder` for configuring a Parquet decoder
for the given file.
+ ///
+ /// See [`ParquetMetadataDecoder`] for a builder that can read the
metadata from a Parquet file.
+ ///
+ /// [`ParquetMetadataDecoder`]:
crate::file::metadata::ParquetMetaDataPushDecoder
+ ///
+ /// See example on [`ParquetPushDecoderBuilder`]
+ pub fn try_new_decoder(
+ file_len: u64,
+ parquet_metadata: Arc<ParquetMetaData>,
+ ) -> Result<Self, ParquetError> {
+ Self::try_new_decoder_with_options(
+ file_len,
+ parquet_metadata,
+ ArrowReaderOptions::default(),
+ )
+ }
+
+ /// Create a new `ParquetDecoderBuilder` for configuring a Parquet decoder
for the given file
+ /// with the given reader options.
+ ///
+ /// This is similar to [`Self::try_new_decoder`] but allows configuring
+ /// options such as Arrow schema
+ pub fn try_new_decoder_with_options(
+ file_len: u64,
+ parquet_metadata: Arc<ParquetMetaData>,
+ arrow_reader_options: ArrowReaderOptions,
+ ) -> Result<Self, ParquetError> {
+ let arrow_reader_metadata =
+ ArrowReaderMetadata::try_new(parquet_metadata,
arrow_reader_options)?;
+ Ok(Self::new_with_metadata(file_len, arrow_reader_metadata))
+ }
+
+ /// Create a new `ParquetDecoderBuilder` given [`ArrowReaderMetadata`].
+ ///
+ /// See [`ArrowReaderMetadata::try_new`] for how to create the metadata
from
+ /// the Parquet metadata and reader options.
+ pub fn new_with_metadata(file_len: u64, arrow_reader_metadata:
ArrowReaderMetadata) -> Self {
+ Self::new_builder(file_len, arrow_reader_metadata)
+ }
+
+ /// Create a [`ParquetPushDecoder`] with the configured options
+ pub fn build(self) -> Result<ParquetPushDecoder, ParquetError> {
+ let Self {
+ input: file_len,
+ metadata: parquet_metadata,
+ schema: _,
+ fields,
+ batch_size,
+ row_groups,
+ projection,
+ filter,
+ selection,
+ limit,
+ offset,
+ metrics,
+ max_predicate_cache_size,
+ } = self;
+
+ // If no row groups were specified, read all of them
+ let row_groups =
+ row_groups.unwrap_or_else(||
(0..parquet_metadata.num_row_groups()).collect());
+
+ // Prepare to build RowGroup readers
+ let buffers = PushBuffers::new(file_len);
+ let row_group_reader_builder = RowGroupReaderBuilder::new(
+ batch_size,
+ projection,
+ Arc::clone(&parquet_metadata),
+ fields,
+ filter,
+ limit,
+ offset,
+ metrics,
+ max_predicate_cache_size,
+ buffers,
+ );
+
+ // Initialize the decoder with the configured options
+ let remaining_row_groups = RemainingRowGroups::new(
+ parquet_metadata,
+ row_groups,
+ selection,
+ row_group_reader_builder,
+ );
+
+ Ok(ParquetPushDecoder {
+ state: ParquetDecoderState::ReadingRowGroup {
+ remaining_row_groups: Box::new(remaining_row_groups),
+ },
+ })
+ }
+}
+
+/// A push based Parquet Decoder
+///
+/// See [`ParquetPushDecoderBuilder`] for an example of how to build and use
the decoder.
+///
+/// [`ParquetPushDecoder`] is a low level API for decoding Parquet data
without an
+/// underlying reader for performing IO, and thus offers fine grained control
+/// over how data is fetched and decoded.
+///
+/// When more data is needed to make progress, instead of reading data directly
+/// from a reader, the decoder returns [`DecodeResult`] indicating what ranges
+/// are needed. Once the caller provides the requested ranges via
+/// [`Self::push_ranges`], they try to decode again by calling
+/// [`Self::try_decode`].
+///
+/// The decoder's internal state tracks what has been already decoded and what
+/// is needed next.
+#[derive(Debug)]
+pub struct ParquetPushDecoder {
+ /// The inner state.
+ ///
+ /// This state is consumed on every transition and a new state is produced
+ /// so the Rust compiler can ensure that the state is always valid and
+ /// transitions are not missed.
+ state: ParquetDecoderState,
+}
+
+impl ParquetPushDecoder {
+ /// Attempt to decode the next batch of data, or return what data is needed
+ ///
+ /// The the decoder communicates the next state with a [`DecodeResult`]
+ ///
+ /// See full example in [`ParquetPushDecoderBuilder`]
+ ///
+ /// ```no_run
+ /// # use parquet::arrow::push_decoder::ParquetPushDecoder;
+ /// use parquet::DecodeResult;
+ /// # fn get_decoder() -> ParquetPushDecoder { unimplemented!() }
+ /// # fn push_data(decoder: &mut ParquetPushDecoder, ranges:
Vec<std::ops::Range<u64>>) { unimplemented!() }
+ /// let mut decoder = get_decoder();
+ /// loop {
+ /// match decoder.try_decode().unwrap() {
+ /// DecodeResult::NeedsData(ranges) => {
+ /// // The decoder needs more data. Fetch the data for the given
ranges
+ /// // call decoder.push_ranges(ranges, data) and call again
+ /// push_data(&mut decoder, ranges);
+ /// }
+ /// DecodeResult::Data(batch) => {
+ /// // Successfully decoded the next batch of data
+ /// println!("Got batch with {} rows", batch.num_rows());
+ /// }
+ /// DecodeResult::Finished => {
+ /// // The decoder has finished decoding all data
+ /// break;
+ /// }
+ /// }
+ /// }
+ ///```
+ pub fn try_decode(&mut self) -> Result<DecodeResult<RecordBatch>,
ParquetError> {
+ let current_state = std::mem::replace(&mut self.state,
ParquetDecoderState::Finished);
+ let (new_state, decode_result) = current_state.try_transition()?;
+ self.state = new_state;
+ Ok(decode_result)
+ }
+
+ /// Push data into the decoder for processing
+ ///
+ /// This is a convenience wrapper around [`Self::push_ranges`] for pushing
a
+ /// single range of data.
+ ///
+ /// Note this can be the entire file or just a part of it. If it is part
of the file,
+ /// the ranges should correspond to the data ranges requested by the
decoder.
+ ///
+ /// See example in [`ParquetPushDecoderBuilder`]
+ pub fn push_range(&mut self, range: Range<u64>, data: Bytes) -> Result<(),
ParquetError> {
+ self.push_ranges(vec![range], vec![data])
+ }
+
+ /// Push data into the decoder for processing
+ ///
+ /// This should correspond to the data ranges requested by the decoder
+ pub fn push_ranges(
+ &mut self,
+ ranges: Vec<Range<u64>>,
+ data: Vec<Bytes>,
+ ) -> Result<(), ParquetError> {
+ let current_state = std::mem::replace(&mut self.state,
ParquetDecoderState::Finished);
+ self.state = current_state.push_data(ranges, data)?;
+ Ok(())
+ }
+
+ /// Returns the total number of buffered bytes in the decoder
+ ///
+ /// This is the sum of the size of all [`Bytes`] that has been pushed to
the
+ /// decoder but not yet consumed.
+ ///
+ /// Note that this does not include any overhead of the internal data
+ /// structures and that since [`Bytes`] are ref counted memory, this may
not
+ /// reflect additional memory usage.
+ ///
+ /// This can be used to monitor memory usage of the decoder.
+ pub fn buffered_bytes(&self) -> u64 {
+ self.state.buffered_bytes()
+ }
+}
+
+/// Internal state machine for the [`ParquetPushDecoder`]
Review Comment:
this is one (of three) state machines for decoding parquet -- but i think it
is pretty clear
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]