use datafusion::arrow::error::ArrowError;
use datafusion::error::DataFusionError;
use crate::BackendKind;
pub type Result<T, E = EngineError> = core::result::Result<T, E>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum EngineError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("allocation of {bytes} bytes (align {align}) failed: {detail}")]
Allocation {
bytes: usize,
align: usize,
detail: String,
},
#[error("{backend} device error: {detail}")]
Device {
backend: BackendKind,
detail: String,
},
#[error("execution error: {0}")]
Execution(String),
#[error("plan error: {0}")]
Plan(String),
#[error("format error: {0}")]
Format(String),
#[error("unsupported: {feature} ({detail})")]
Unsupported {
feature: &'static str,
detail: String,
},
#[error(transparent)]
Arrow(#[from] ArrowError),
#[error(transparent)]
DataFusion(#[from] DataFusionError),
}
impl EngineError {
pub fn unsupported(feature: &'static str, detail: impl Into<String>) -> Self {
Self::Unsupported {
feature,
detail: detail.into(),
}
}
pub fn execution(detail: impl Into<String>) -> Self {
Self::Execution(detail.into())
}
pub fn plan(detail: impl Into<String>) -> Self {
Self::Plan(detail.into())
}
pub fn format(detail: impl Into<String>) -> Self {
Self::Format(detail.into())
}
pub fn device(backend: BackendKind, detail: impl Into<String>) -> Self {
Self::Device {
backend,
detail: detail.into(),
}
}
pub fn allocation(bytes: usize, align: usize, detail: impl Into<String>) -> Self {
Self::Allocation {
bytes,
align,
detail: detail.into(),
}
}
pub fn is_unsupported(&self) -> bool {
matches!(self, Self::Unsupported { .. })
}
}
impl From<EngineError> for DataFusionError {
fn from(err: EngineError) -> Self {
match err {
EngineError::DataFusion(inner) => inner,
other => DataFusionError::External(Box::new(other)),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn unsupported_is_detectable() {
let err = EngineError::unsupported("metal.hash_join", "not in v1");
assert!(err.is_unsupported());
assert_eq!(err.to_string(), "unsupported: metal.hash_join (not in v1)");
}
#[test]
fn datafusion_round_trip_unwraps_inner_error() {
let inner = DataFusionError::Plan("boom".to_owned());
let wrapped = EngineError::from(inner);
let back: DataFusionError = wrapped.into();
assert!(matches!(back, DataFusionError::Plan(ref m) if m == "boom"));
}
#[test]
fn other_errors_become_external() {
let back: DataFusionError = EngineError::format("truncated footer").into();
assert!(matches!(back, DataFusionError::External(_)));
assert!(back.to_string().contains("truncated footer"));
}
#[test]
fn io_errors_convert() {
let io = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
let err: EngineError = io.into();
assert!(matches!(err, EngineError::Io(_)));
}
}