use datafusion::prelude::{SessionConfig, SessionContext};
use crate::DeltaFunnelError;
use crate::pipeline::{BatchPipelinePhase, validate_nonzero_usize_option};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct QueryOptions {
pub target_partitions: Option<usize>,
pub output_batch_size: Option<usize>,
}
impl QueryOptions {
pub fn validate(&self) -> Result<(), DeltaFunnelError> {
if let Some(target_partitions) = self.target_partitions {
validate_nonzero_usize_option(
BatchPipelinePhase::Configuration,
"target_partitions",
target_partitions,
)?;
}
if let Some(output_batch_size) = self.output_batch_size {
validate_nonzero_usize_option(
BatchPipelinePhase::Configuration,
"output_batch_size",
output_batch_size,
)?;
}
Ok(())
}
pub fn apply_to_session_config(
&self,
mut config: SessionConfig,
) -> Result<SessionConfig, DeltaFunnelError> {
self.validate()?;
if let Some(target_partitions) = self.target_partitions {
config = config.with_target_partitions(target_partitions);
}
if let Some(output_batch_size) = self.output_batch_size {
config = config.with_batch_size(output_batch_size);
}
Ok(config)
}
}
pub fn datafusion_session_config(options: QueryOptions) -> Result<SessionConfig, DeltaFunnelError> {
options.apply_to_session_config(SessionConfig::new())
}
pub fn datafusion_session_context(
options: QueryOptions,
) -> Result<SessionContext, DeltaFunnelError> {
Ok(SessionContext::new_with_config(datafusion_session_config(
options,
)?))
}
#[cfg(test)]
mod tests {
use datafusion::prelude::SessionConfig;
use super::{QueryOptions, datafusion_session_config, datafusion_session_context};
use crate::{BatchPipelinePhase, DeltaFunnelError};
#[test]
fn omitted_query_options_preserve_datafusion_defaults() -> Result<(), Box<dyn std::error::Error>>
{
let default_config = SessionConfig::new();
let configured = datafusion_session_config(QueryOptions::default())?;
assert_eq!(configured.batch_size(), default_config.batch_size());
assert_eq!(
configured.target_partitions(),
default_config.target_partitions()
);
Ok(())
}
#[test]
fn output_batch_size_is_applied_to_session_config() -> Result<(), Box<dyn std::error::Error>> {
let configured = datafusion_session_config(QueryOptions {
target_partitions: None,
output_batch_size: Some(17),
})?;
assert_eq!(configured.batch_size(), 17);
assert_eq!(
configured.target_partitions(),
SessionConfig::new().target_partitions()
);
Ok(())
}
#[test]
fn target_partitions_is_applied_to_session_config() -> Result<(), Box<dyn std::error::Error>> {
let configured = datafusion_session_config(QueryOptions {
target_partitions: Some(3),
output_batch_size: None,
})?;
assert_eq!(configured.target_partitions(), 3);
assert_eq!(configured.batch_size(), SessionConfig::new().batch_size());
Ok(())
}
#[test]
fn target_partitions_and_output_batch_size_are_independent()
-> Result<(), Box<dyn std::error::Error>> {
let configured = datafusion_session_config(QueryOptions {
target_partitions: Some(4),
output_batch_size: Some(11),
})?;
assert_eq!(configured.target_partitions(), 4);
assert_eq!(configured.batch_size(), 11);
Ok(())
}
#[test]
fn session_context_uses_configured_query_options() -> Result<(), Box<dyn std::error::Error>> {
let ctx = datafusion_session_context(QueryOptions {
target_partitions: Some(2),
output_batch_size: Some(9),
})?;
assert_eq!(ctx.state().config().target_partitions(), 2);
assert_eq!(ctx.state().config().batch_size(), 9);
Ok(())
}
#[test]
fn validation_rejects_zero_output_batch_size() {
let error = QueryOptions {
target_partitions: None,
output_batch_size: Some(0),
}
.validate();
assert!(matches!(
error,
Err(DeltaFunnelError::BatchPipeline {
phase: BatchPipelinePhase::Configuration,
option: "output_batch_size",
..
})
));
}
#[test]
fn validation_rejects_zero_target_partitions() {
let error = QueryOptions {
target_partitions: Some(0),
output_batch_size: None,
}
.validate();
assert!(matches!(
error,
Err(DeltaFunnelError::BatchPipeline {
phase: BatchPipelinePhase::Configuration,
option: "target_partitions",
..
})
));
}
}