use std::fmt;
use crate::{
DeltaFunnelError, DeltaProviderScanExecutionOptions, MssqlConnectionConfig,
MssqlSchemaPlanOptions, MssqlWorkflowWriteOptions, MssqlWriteBackend, QueryOptions,
ValidationOptions, default_mssql_write_backend,
};
#[derive(Clone)]
pub struct SessionOptions {
query_options: QueryOptions,
provider_scan_options: DeltaProviderScanExecutionOptions,
mssql_schema_options: MssqlSchemaPlanOptions,
mssql_write_backend: MssqlWriteBackend,
mssql_workflow_options: MssqlWorkflowWriteOptions,
validation_options: ValidationOptions,
default_mssql_connection: Option<MssqlConnectionConfig>,
}
impl Default for SessionOptions {
fn default() -> Self {
Self {
query_options: QueryOptions::default(),
provider_scan_options: DeltaProviderScanExecutionOptions::default(),
mssql_schema_options: MssqlSchemaPlanOptions::default(),
mssql_write_backend: default_mssql_write_backend(),
mssql_workflow_options: MssqlWorkflowWriteOptions::default(),
validation_options: ValidationOptions::default(),
default_mssql_connection: None,
}
}
}
impl SessionOptions {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub const fn with_query_options(mut self, query_options: QueryOptions) -> Self {
self.query_options = query_options;
self
}
#[must_use]
pub const fn with_provider_scan_options(
mut self,
provider_scan_options: DeltaProviderScanExecutionOptions,
) -> Self {
self.provider_scan_options = provider_scan_options;
self
}
#[must_use]
pub const fn with_mssql_schema_options(
mut self,
mssql_schema_options: MssqlSchemaPlanOptions,
) -> Self {
self.mssql_schema_options = mssql_schema_options;
self
}
#[must_use]
pub const fn with_mssql_write_backend(
mut self,
mssql_write_backend: MssqlWriteBackend,
) -> Self {
self.mssql_write_backend = mssql_write_backend;
self
}
#[must_use]
pub const fn with_mssql_workflow_options(
mut self,
mssql_workflow_options: MssqlWorkflowWriteOptions,
) -> Self {
self.mssql_workflow_options = mssql_workflow_options;
self
}
#[must_use]
pub const fn with_validation_options(mut self, validation_options: ValidationOptions) -> Self {
self.validation_options = validation_options;
self
}
#[must_use]
pub fn with_default_mssql_connection(
mut self,
default_mssql_connection: MssqlConnectionConfig,
) -> Self {
self.default_mssql_connection = Some(default_mssql_connection);
self
}
#[must_use]
pub const fn query_options(&self) -> QueryOptions {
self.query_options
}
#[must_use]
pub const fn provider_scan_options(&self) -> DeltaProviderScanExecutionOptions {
self.provider_scan_options
}
#[must_use]
pub const fn mssql_schema_options(&self) -> MssqlSchemaPlanOptions {
self.mssql_schema_options
}
#[must_use]
pub const fn mssql_write_backend(&self) -> MssqlWriteBackend {
self.mssql_write_backend
}
#[must_use]
pub const fn mssql_workflow_options(&self) -> MssqlWorkflowWriteOptions {
self.mssql_workflow_options
}
#[must_use]
pub const fn validation_options(&self) -> ValidationOptions {
self.validation_options
}
#[must_use]
pub fn default_mssql_connection(&self) -> Option<&MssqlConnectionConfig> {
self.default_mssql_connection.as_ref()
}
pub fn validate(&self) -> Result<(), DeltaFunnelError> {
self.query_options.validate()?;
self.provider_scan_options.validate()?;
self.mssql_workflow_options.validate()?;
self.validation_options.validate()?;
Ok(())
}
}
impl fmt::Debug for SessionOptions {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SessionOptions")
.field("query_options", &self.query_options)
.field("provider_scan_options", &self.provider_scan_options)
.field("mssql_schema_options", &self.mssql_schema_options)
.field("mssql_write_backend", &self.mssql_write_backend)
.field("mssql_workflow_options", &self.mssql_workflow_options)
.field("validation_options", &self.validation_options)
.field(
"default_mssql_connection",
&self
.default_mssql_connection
.as_ref()
.map(MssqlConnectionConfig::summary),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use crate::{
BatchPipelinePhase, DeltaFunnelError, DeltaProviderReaderBackend,
DeltaProviderScanExecutionOptions, MssqlConnectionConfig, MssqlWorkflowWriteOptions,
QueryOptions,
};
use super::SessionOptions;
use crate::orchestrator::DeltaFunnelSession;
#[test]
fn default_session_constructs_datafusion_context() -> Result<(), DeltaFunnelError> {
let session = DeltaFunnelSession::new(SessionOptions::default())?;
assert_eq!(session.options().query_options(), QueryOptions::default());
assert!(
session
.options()
.validation_options()
.require_successful_planning()
);
assert_eq!(
session.context().state().config().target_partitions(),
datafusion::prelude::SessionConfig::new().target_partitions()
);
assert_eq!(session.next_table_id(), 0);
Ok(())
}
#[test]
fn session_applies_query_options_to_datafusion_context() -> Result<(), DeltaFunnelError> {
let session =
DeltaFunnelSession::new(SessionOptions::new().with_query_options(QueryOptions {
target_partitions: Some(3),
output_batch_size: Some(11),
}))?;
assert_eq!(session.context().state().config().target_partitions(), 3);
assert_eq!(session.context().state().config().batch_size(), 11);
Ok(())
}
#[test]
fn query_option_validation_failure_reaches_session_construction() {
let error =
DeltaFunnelSession::new(SessionOptions::new().with_query_options(QueryOptions {
target_partitions: Some(0),
output_batch_size: None,
}));
assert!(matches!(
error,
Err(DeltaFunnelError::BatchPipeline {
phase: BatchPipelinePhase::Configuration,
option: "target_partitions",
..
})
));
}
#[test]
fn provider_option_validation_failure_reaches_session_construction() {
let error = DeltaFunnelSession::new(SessionOptions::new().with_provider_scan_options(
DeltaProviderScanExecutionOptions {
reader_backend: DeltaProviderReaderBackend::OfficialKernel,
max_concurrent_file_reads_per_scan: Some(1),
max_concurrent_file_reads_per_partition: 1,
output_buffer_capacity_per_partition: 0,
native_async_prefetch_file_count_per_partition: 0,
},
));
assert!(matches!(error, Err(DeltaFunnelError::Config { .. })));
}
#[test]
fn workflow_parallelism_validation_failure_reaches_session_construction() {
let error = DeltaFunnelSession::new(SessionOptions::new().with_mssql_workflow_options(
MssqlWorkflowWriteOptions::new().with_max_parallel_outputs(2),
));
assert!(matches!(
error,
Err(DeltaFunnelError::MssqlWorkflowPlanning { message })
if message.contains("parallel MSSQL output writers are not supported")
));
}
#[test]
fn workflow_zero_parallelism_validation_failure_reaches_session_construction() {
let error = DeltaFunnelSession::new(SessionOptions::new().with_mssql_workflow_options(
MssqlWorkflowWriteOptions::new().with_max_parallel_outputs(0),
));
assert!(matches!(
error,
Err(DeltaFunnelError::MssqlWorkflowPlanning { message })
if message.contains("max_parallel_outputs must be at least 1")
));
}
#[test]
fn session_debug_redacts_default_mssql_connection() -> Result<(), DeltaFunnelError> {
let connection = MssqlConnectionConfig::new(
"server=tcp:sql.example.com;database=warehouse;user=admin;password=secret-token",
)?
.with_display_label("warehouse-primary");
let session = DeltaFunnelSession::new(
SessionOptions::new().with_default_mssql_connection(connection),
)?;
let debug = format!("{session:?}");
assert!(debug.contains("warehouse-primary"));
assert!(!debug.contains("secret-token"));
assert!(!debug.contains("password"));
assert!(!debug.contains("server=tcp"));
Ok(())
}
}