use std::collections::BTreeMap;
use crate::{DeltaReaderError, error::InvalidConfigurationSnafu};
const DEFAULT_MAX_CONCURRENT_FILE_READS_PER_PARTITION: usize = 3;
const DEFAULT_OUTPUT_BUFFER_CAPACITY_PER_PARTITION: usize = 1;
const DEFAULT_NATIVE_ASYNC_PREFETCH_FILE_COUNT_PER_PARTITION: usize = 2;
const DEFAULT_PARQUET_METADATA_SIZE_HINT: usize = 64 * 1024;
pub type DeltaStorageOptions = BTreeMap<String, String>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DeltaSnapshotSelection {
#[default]
Latest,
Version(u64),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum DeltaReaderBackend {
OfficialKernel,
#[default]
NativeAsync,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DeltaReaderExecutionOptions {
reader_backend: DeltaReaderBackend,
max_concurrent_file_reads_per_scan: Option<usize>,
max_concurrent_file_reads_per_partition: usize,
output_buffer_capacity_per_partition: usize,
native_async_prefetch_file_count_per_partition: usize,
parquet_metadata_size_hint: Option<usize>,
parquet_full_file_read_threshold: Option<usize>,
}
impl DeltaReaderExecutionOptions {
pub const fn new() -> Self {
Self {
reader_backend: DeltaReaderBackend::NativeAsync,
max_concurrent_file_reads_per_scan: None,
max_concurrent_file_reads_per_partition:
DEFAULT_MAX_CONCURRENT_FILE_READS_PER_PARTITION,
output_buffer_capacity_per_partition: DEFAULT_OUTPUT_BUFFER_CAPACITY_PER_PARTITION,
native_async_prefetch_file_count_per_partition:
DEFAULT_NATIVE_ASYNC_PREFETCH_FILE_COUNT_PER_PARTITION,
parquet_metadata_size_hint: Some(DEFAULT_PARQUET_METADATA_SIZE_HINT),
parquet_full_file_read_threshold: None,
}
}
pub const fn reader_backend(&self) -> DeltaReaderBackend {
self.reader_backend
}
pub const fn max_concurrent_file_reads_per_scan(&self) -> Option<usize> {
self.max_concurrent_file_reads_per_scan
}
pub const fn max_concurrent_file_reads_per_partition(&self) -> usize {
self.max_concurrent_file_reads_per_partition
}
pub const fn output_buffer_capacity_per_partition(&self) -> usize {
self.output_buffer_capacity_per_partition
}
pub const fn native_async_prefetch_file_count_per_partition(&self) -> usize {
self.native_async_prefetch_file_count_per_partition
}
pub const fn parquet_metadata_size_hint(&self) -> Option<usize> {
self.parquet_metadata_size_hint
}
pub const fn parquet_full_file_read_threshold(&self) -> Option<usize> {
self.parquet_full_file_read_threshold
}
pub fn with_reader_backend(
mut self,
value: DeltaReaderBackend,
) -> Result<Self, DeltaReaderError> {
self.reader_backend = value;
self.validate()?;
Ok(self)
}
pub fn with_max_concurrent_file_reads_per_scan(
mut self,
value: Option<usize>,
) -> Result<Self, DeltaReaderError> {
self.max_concurrent_file_reads_per_scan = value;
self.validate()?;
Ok(self)
}
pub fn with_max_concurrent_file_reads_per_partition(
mut self,
value: usize,
) -> Result<Self, DeltaReaderError> {
self.max_concurrent_file_reads_per_partition = value;
self.validate()?;
Ok(self)
}
pub fn with_output_buffer_capacity_per_partition(
mut self,
value: usize,
) -> Result<Self, DeltaReaderError> {
self.output_buffer_capacity_per_partition = value;
self.validate()?;
Ok(self)
}
pub fn with_native_async_prefetch_file_count_per_partition(
mut self,
value: usize,
) -> Result<Self, DeltaReaderError> {
self.native_async_prefetch_file_count_per_partition = value;
self.validate()?;
Ok(self)
}
pub fn with_parquet_metadata_size_hint(
mut self,
value: Option<usize>,
) -> Result<Self, DeltaReaderError> {
self.parquet_metadata_size_hint = value;
self.validate()?;
Ok(self)
}
pub fn with_parquet_full_file_read_threshold(
mut self,
value: Option<usize>,
) -> Result<Self, DeltaReaderError> {
self.parquet_full_file_read_threshold = value;
self.validate()?;
Ok(self)
}
pub fn validate(&self) -> Result<(), DeltaReaderError> {
validate_optional_positive(
self.max_concurrent_file_reads_per_scan,
"max_concurrent_file_reads_per_scan_must_be_positive",
)?;
validate_positive(
self.max_concurrent_file_reads_per_partition,
"max_concurrent_file_reads_per_partition_must_be_positive",
)?;
validate_positive(
self.output_buffer_capacity_per_partition,
"output_buffer_capacity_per_partition_must_be_positive",
)?;
validate_optional_positive(
self.parquet_metadata_size_hint,
"parquet_metadata_size_hint_must_be_positive",
)?;
validate_optional_positive(
self.parquet_full_file_read_threshold,
"parquet_full_file_read_threshold_must_be_positive",
)?;
Ok(())
}
pub(crate) fn resolved_max_concurrent_file_reads_per_scan(
&self,
target_partitions: usize,
) -> usize {
self.max_concurrent_file_reads_per_scan.unwrap_or_else(|| {
target_partitions
.saturating_mul(self.max_concurrent_file_reads_per_partition)
.max(1)
})
}
}
impl Default for DeltaReaderExecutionOptions {
fn default() -> Self {
Self::new()
}
}
fn validate_positive(value: usize, reason: &'static str) -> Result<(), DeltaReaderError> {
if value == 0 {
return InvalidConfigurationSnafu { reason }.fail();
}
Ok(())
}
fn validate_optional_positive(
value: Option<usize>,
reason: &'static str,
) -> Result<(), DeltaReaderError> {
if value == Some(0) {
return InvalidConfigurationSnafu { reason }.fail();
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::DeltaReaderPhase;
use super::{
DeltaReaderBackend, DeltaReaderExecutionOptions, DeltaSnapshotSelection,
DeltaStorageOptions,
};
#[test]
fn public_defaults_match_the_frozen_baseline() {
let options = DeltaReaderExecutionOptions::new();
assert_eq!(
DeltaSnapshotSelection::default(),
DeltaSnapshotSelection::Latest
);
assert_eq!(
DeltaReaderBackend::default(),
DeltaReaderBackend::NativeAsync
);
assert_eq!(DeltaReaderExecutionOptions::default(), options);
assert_eq!(options.reader_backend(), DeltaReaderBackend::NativeAsync);
assert_eq!(options.max_concurrent_file_reads_per_scan(), None);
assert_eq!(options.max_concurrent_file_reads_per_partition(), 3);
assert_eq!(options.output_buffer_capacity_per_partition(), 1);
assert_eq!(options.native_async_prefetch_file_count_per_partition(), 2);
assert_eq!(options.parquet_metadata_size_hint(), Some(65_536));
assert_eq!(options.parquet_full_file_read_threshold(), None);
assert_eq!(DeltaStorageOptions::default(), DeltaStorageOptions::new());
assert_eq!(
DeltaSnapshotSelection::Version(7),
DeltaSnapshotSelection::Version(7)
);
}
#[test]
fn builders_set_every_public_option() -> Result<(), Box<dyn std::error::Error>> {
let options = DeltaReaderExecutionOptions::new()
.with_reader_backend(DeltaReaderBackend::OfficialKernel)?
.with_max_concurrent_file_reads_per_scan(Some(8))?
.with_max_concurrent_file_reads_per_partition(4)?
.with_output_buffer_capacity_per_partition(2)?
.with_native_async_prefetch_file_count_per_partition(0)?
.with_parquet_metadata_size_hint(None)?
.with_parquet_full_file_read_threshold(Some(1024))?;
assert_eq!(options.reader_backend(), DeltaReaderBackend::OfficialKernel);
assert_eq!(options.max_concurrent_file_reads_per_scan(), Some(8));
assert_eq!(options.max_concurrent_file_reads_per_partition(), 4);
assert_eq!(options.output_buffer_capacity_per_partition(), 2);
assert_eq!(options.native_async_prefetch_file_count_per_partition(), 0);
assert_eq!(options.parquet_metadata_size_hint(), None);
assert_eq!(options.parquet_full_file_read_threshold(), Some(1024));
Ok(())
}
#[test]
fn invalid_bounds_return_redacted_configuration_errors() {
let invalid = [
DeltaReaderExecutionOptions::new().with_max_concurrent_file_reads_per_scan(Some(0)),
DeltaReaderExecutionOptions::new().with_max_concurrent_file_reads_per_partition(0),
DeltaReaderExecutionOptions::new().with_output_buffer_capacity_per_partition(0),
DeltaReaderExecutionOptions::new().with_parquet_metadata_size_hint(Some(0)),
DeltaReaderExecutionOptions::new().with_parquet_full_file_read_threshold(Some(0)),
];
for result in invalid {
let error = result.expect_err("invalid execution options must fail");
assert_eq!(error.phase(), DeltaReaderPhase::Configuration);
assert_eq!(error.as_str(), "invalid_configuration");
}
}
#[test]
fn independent_bounds_preserve_the_frozen_reader_behavior()
-> Result<(), Box<dyn std::error::Error>> {
let options = DeltaReaderExecutionOptions::new()
.with_max_concurrent_file_reads_per_scan(Some(2))?
.with_native_async_prefetch_file_count_per_partition(4)?;
assert_eq!(options.max_concurrent_file_reads_per_scan(), Some(2));
assert_eq!(options.max_concurrent_file_reads_per_partition(), 3);
assert_eq!(options.native_async_prefetch_file_count_per_partition(), 4);
Ok(())
}
#[test]
fn scan_capacity_resolves_once_from_the_fixed_partition_target()
-> Result<(), Box<dyn std::error::Error>> {
let defaults = DeltaReaderExecutionOptions::new();
assert_eq!(defaults.resolved_max_concurrent_file_reads_per_scan(4), 12);
assert_eq!(
defaults.resolved_max_concurrent_file_reads_per_scan(usize::MAX),
usize::MAX
);
let explicit = defaults.with_max_concurrent_file_reads_per_scan(Some(7))?;
assert_eq!(explicit.resolved_max_concurrent_file_reads_per_scan(4), 7);
Ok(())
}
}