fix(s3select): replace deprecated parquet reader (#5811)

Implement a local AsyncFileReader over DataFusion's object store re-export so Parquet metadata loading no longer uses the deprecated ParquetObjectReader adapter.

Co-authored-by: heihutu <heihutu@gmail.com>
Co-authored-by: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
houseme
2026-08-08 00:00:58 +08:00
committed by GitHub
parent 9d996b82a8
commit 5187f91997
3 changed files with 55 additions and 5 deletions
Generated
+1
View File
@@ -10111,6 +10111,7 @@ version = "1.0.0-beta.12"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"async-trait", "async-trait",
"bytes",
"datafusion", "datafusion",
"derive_builder", "derive_builder",
"futures", "futures",
+1
View File
@@ -45,6 +45,7 @@ hotpath.workspace = true
rustfs-s3select-api = { workspace = true } rustfs-s3select-api = { workspace = true }
async-recursion = { workspace = true } async-recursion = { workspace = true }
async-trait.workspace = true async-trait.workspace = true
bytes.workspace = true
datafusion = { workspace = true, default-features = false, features = ["parquet", "recursive_protection", "sql"] } datafusion = { workspace = true, default-features = false, features = ["parquet", "recursive_protection", "sql"] }
derive_builder = { workspace = true } derive_builder = { workspace = true }
futures = { workspace = true } futures = { workspace = true }
@@ -12,9 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::{fmt, sync::Arc}; use std::{fmt, ops::Range, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use bytes::Bytes;
use datafusion::{ use datafusion::{
arrow::datatypes::SchemaRef, arrow::datatypes::SchemaRef,
catalog::Session, catalog::Session,
@@ -27,13 +28,15 @@ use datafusion::{
}, },
execution::object_store::ObjectStoreUrl, execution::object_store::ObjectStoreUrl,
logical_expr::{Expr, TableProviderFilterPushDown, TableType}, logical_expr::{Expr, TableProviderFilterPushDown, TableType},
object_store::{ObjectStoreExt, path::Path}, object_store::{Error as ObjectStoreError, ObjectStore, ObjectStoreExt, path::Path},
parquet::{ parquet::{
arrow::{ParquetRecordBatchStreamBuilder, async_reader::ParquetObjectReader}, arrow::{ParquetRecordBatchStreamBuilder, arrow_reader::ArrowReaderOptions, async_reader::AsyncFileReader},
file::metadata::{ParquetMetaData, RowGroupMetaData}, errors::{ParquetError, Result as ParquetResult},
file::metadata::{ParquetMetaData, ParquetMetaDataReader, RowGroupMetaData},
}, },
physical_plan::ExecutionPlan, physical_plan::ExecutionPlan,
}; };
use futures::{FutureExt, TryFutureExt, future::BoxFuture};
use rustfs_s3select_api::{ use rustfs_s3select_api::{
QueryError, QueryResult, QueryError, QueryResult,
object_store::{SelectScanRange, scan_range_from_bounds}, object_store::{SelectScanRange, scan_range_from_bounds},
@@ -60,6 +63,43 @@ impl fmt::Debug for ParquetSelectTable {
} }
} }
struct ObjectStoreParquetReader {
store: Arc<dyn ObjectStore>,
path: Path,
file_size: u64,
}
impl AsyncFileReader for ObjectStoreParquetReader {
fn get_bytes(&mut self, range: Range<u64>) -> BoxFuture<'_, ParquetResult<Bytes>> {
self.store.get_range(&self.path, range).map_err(parquet_store_error).boxed()
}
fn get_byte_ranges(&mut self, ranges: Vec<Range<u64>>) -> BoxFuture<'_, ParquetResult<Vec<Bytes>>> {
async move { self.store.get_ranges(&self.path, &ranges).await.map_err(parquet_store_error) }.boxed()
}
fn get_metadata<'a>(
&'a mut self,
options: Option<&'a ArrowReaderOptions>,
) -> BoxFuture<'a, ParquetResult<Arc<ParquetMetaData>>> {
async move {
let metadata_options = options.map(|options| options.metadata_options().clone());
let mut metadata_reader = ParquetMetaDataReader::new().with_metadata_options(metadata_options);
if let Some(options) = options {
metadata_reader = metadata_reader
.with_column_index_policy(options.column_index_policy())
.with_offset_index_policy(options.offset_index_policy());
}
let file_size = self.file_size;
let metadata = metadata_reader.load_and_finish(self, file_size).await?;
Ok(Arc::new(metadata))
}
.boxed()
}
}
impl ParquetSelectTable { impl ParquetSelectTable {
pub async fn try_new(state: &dyn Session, input: &SelectObjectContentInput) -> QueryResult<Arc<dyn TableProvider>> { pub async fn try_new(state: &dyn Session, input: &SelectObjectContentInput) -> QueryResult<Arc<dyn TableProvider>> {
let table_path = ListingTableUrl::parse(format!("s3://{}/{}", input.bucket, input.key))?; let table_path = ListingTableUrl::parse(format!("s3://{}/{}", input.bucket, input.key))?;
@@ -68,7 +108,11 @@ impl ParquetSelectTable {
let store = state.runtime_env().object_store(&object_store_url)?; let store = state.runtime_env().object_store(&object_store_url)?;
let object_meta = store.head(&object_location).await.map_err(query_store_error)?; let object_meta = store.head(&object_location).await.map_err(query_store_error)?;
let reader = ParquetObjectReader::new(Arc::clone(&store), object_location).with_file_size(object_meta.size); let reader = ObjectStoreParquetReader {
store: Arc::clone(&store),
path: object_location,
file_size: object_meta.size,
};
let builder = ParquetRecordBatchStreamBuilder::new(reader) let builder = ParquetRecordBatchStreamBuilder::new(reader)
.await .await
.map_err(query_store_error)?; .map_err(query_store_error)?;
@@ -166,6 +210,10 @@ fn non_negative_offset(offset: i64) -> Option<u64> {
u64::try_from(offset).ok() u64::try_from(offset).ok()
} }
fn parquet_store_error(err: ObjectStoreError) -> ParquetError {
ParquetError::External(Box::new(err))
}
fn query_store_error(err: impl fmt::Display) -> QueryError { fn query_store_error(err: impl fmt::Display) -> QueryError {
QueryError::StoreError { e: err.to_string() } QueryError::StoreError { e: err.to_string() }
} }