diff --git a/Cargo.toml b/Cargo.toml index 77e3c6038ea7..c09d41d607e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,6 +57,7 @@ arrow-schema = { version = "48.0.0", default-features = false } parquet = { version = "48.0.0", features = ["arrow", "async", "object_store"] } sqlparser = { version = "0.38.0", features = ["visitor"] } chrono = { version = "0.4.31", default-features = false } +thiserror = { version = "1.0.44" } [profile.release] codegen-units = 1 diff --git a/datafusion-cli/Cargo.lock b/datafusion-cli/Cargo.lock index b83088f94c57..23aaa1c0a507 100644 --- a/datafusion-cli/Cargo.lock +++ b/datafusion-cli/Cargo.lock @@ -1185,6 +1185,7 @@ dependencies = [ "object_store", "parquet", "sqlparser", + "thiserror", ] [[package]] diff --git a/datafusion/common/Cargo.toml b/datafusion/common/Cargo.toml index 87087c50a2d2..912bcdb0f500 100644 --- a/datafusion/common/Cargo.toml +++ b/datafusion/common/Cargo.toml @@ -51,6 +51,7 @@ object_store = { version = "0.7.0", default-features = false, optional = true } parquet = { workspace = true, optional = true } pyo3 = { version = "0.20.0", optional = true } sqlparser = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] rand = "0.8.4" diff --git a/datafusion/common/src/error.rs b/datafusion/common/src/error.rs index adf58e282ed9..c8a74fae21c6 100644 --- a/datafusion/common/src/error.rs +++ b/datafusion/common/src/error.rs @@ -44,56 +44,90 @@ pub type SharedResult = result::Result>; pub type GenericError = Box; /// DataFusion error -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum DataFusionError { /// Error returned by arrow. - ArrowError(ArrowError), + #[error("Arrow error: {0}")] + ArrowError(#[from] ArrowError), + /// Wraps an error from the Parquet crate #[cfg(feature = "parquet")] - ParquetError(ParquetError), + #[error("Parquet error: {0}")] + ParquetError(#[from] ParquetError), + /// Wraps an error from the Avro crate #[cfg(feature = "avro")] - AvroError(AvroError), + #[error("Avro error: {0}")] + AvroError(#[from] AvroError), + /// Wraps an error from the object_store crate #[cfg(feature = "object_store")] + #[error("Object Store error: {0}")] ObjectStore(object_store::Error), + /// Error associated to I/O operations and associated traits. - IoError(io::Error), + #[error("IO error: {0}")] + IoError(#[from] io::Error), + /// Error returned when SQL is syntactically incorrect. - SQL(ParserError), + #[error("SQL error: {0:?}")] + SQL(#[from] ParserError), + /// Error returned on a branch that we know it is possible /// but to which we still have no implementation for. /// Often, these errors are tracked in our issue tracker. + #[error("This feature is not implemented: {0}")] NotImplemented(String), + /// Error returned as a consequence of an error in DataFusion. /// This error should not happen in normal usage of DataFusion. /// /// DataFusions has internal invariants that the compiler is not /// always able to check. This error is raised when one of those /// invariants is not verified during execution. + #[error( + "Internal error: {0}.\nThis was likely caused by a bug in DataFusion's \ + code and we would welcome that you file an bug report in our issue tracker" + )] Internal(String), + /// This error happens whenever a plan is not valid. Examples include /// impossible casts. + #[error("Error during planning: {0}")] Plan(String), + /// This error happens when an invalid or unsupported option is passed /// in a SQL statement + #[error("Invalid or Unsupported Configuration: {0}")] Configuration(String), + /// This error happens with schema-related errors, such as schema inference not possible /// and non-unique column names. - SchemaError(SchemaError), + #[error("Schema error: {0}")] + SchemaError(#[from] SchemaError), + /// Error returned during execution of the query. /// Examples include files not found, errors in parsing certain types. + #[error("Execution error: {0}")] Execution(String), + /// This error is thrown when a consumer cannot acquire memory from the Memory Manager /// we can just cancel the execution of the partition. + #[error("Resources exhausted: {0}")] ResourcesExhausted(String), + /// Errors originating from outside DataFusion's core codebase. /// For example, a custom S3Error from the crate datafusion-objectstore-s3 - External(GenericError), + #[error("External error: {0}")] + External(#[from] GenericError), + /// Error with additional context - Context(String, Box), + #[error("{0}\ncaused by\n{1}")] + Context(String, #[source] Box), + /// Errors originating from either mapping LogicalPlans to/from Substrait plans /// or serializing/deserializing protobytes to Substrait plans + #[error("Substrait error: {0}")] Substrait(String), } @@ -215,18 +249,6 @@ impl From for DataFusionError { } } -impl From for DataFusionError { - fn from(e: io::Error) -> Self { - DataFusionError::IoError(e) - } -} - -impl From for DataFusionError { - fn from(e: ArrowError) -> Self { - DataFusionError::ArrowError(e) - } -} - impl From for ArrowError { fn from(e: DataFusionError) -> Self { match e { @@ -237,20 +259,6 @@ impl From for ArrowError { } } -#[cfg(feature = "parquet")] -impl From for DataFusionError { - fn from(e: ParquetError) -> Self { - DataFusionError::ParquetError(e) - } -} - -#[cfg(feature = "avro")] -impl From for DataFusionError { - fn from(e: AvroError) -> Self { - DataFusionError::AvroError(e) - } -} - #[cfg(feature = "object_store")] impl From for DataFusionError { fn from(e: object_store::Error) -> Self { @@ -265,103 +273,6 @@ impl From for DataFusionError { } } -impl From for DataFusionError { - fn from(e: ParserError) -> Self { - DataFusionError::SQL(e) - } -} - -impl From for DataFusionError { - fn from(err: GenericError) -> Self { - DataFusionError::External(err) - } -} - -impl Display for DataFusionError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match *self { - DataFusionError::ArrowError(ref desc) => { - write!(f, "Arrow error: {desc}") - } - #[cfg(feature = "parquet")] - DataFusionError::ParquetError(ref desc) => { - write!(f, "Parquet error: {desc}") - } - #[cfg(feature = "avro")] - DataFusionError::AvroError(ref desc) => { - write!(f, "Avro error: {desc}") - } - DataFusionError::IoError(ref desc) => { - write!(f, "IO error: {desc}") - } - DataFusionError::SQL(ref desc) => { - write!(f, "SQL error: {desc:?}") - } - DataFusionError::Configuration(ref desc) => { - write!(f, "Invalid or Unsupported Configuration: {desc}") - } - DataFusionError::NotImplemented(ref desc) => { - write!(f, "This feature is not implemented: {desc}") - } - DataFusionError::Internal(ref desc) => { - write!(f, "Internal error: {desc}.\nThis was likely caused by a bug in DataFusion's \ - code and we would welcome that you file an bug report in our issue tracker") - } - DataFusionError::Plan(ref desc) => { - write!(f, "Error during planning: {desc}") - } - DataFusionError::SchemaError(ref desc) => { - write!(f, "Schema error: {desc}") - } - DataFusionError::Execution(ref desc) => { - write!(f, "Execution error: {desc}") - } - DataFusionError::ResourcesExhausted(ref desc) => { - write!(f, "Resources exhausted: {desc}") - } - DataFusionError::External(ref desc) => { - write!(f, "External error: {desc}") - } - #[cfg(feature = "object_store")] - DataFusionError::ObjectStore(ref desc) => { - write!(f, "Object Store error: {desc}") - } - DataFusionError::Context(ref desc, ref err) => { - write!(f, "{}\ncaused by\n{}", desc, *err) - } - DataFusionError::Substrait(ref desc) => { - write!(f, "Substrait error: {desc}") - } - } - } -} - -impl Error for DataFusionError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - DataFusionError::ArrowError(e) => Some(e), - #[cfg(feature = "parquet")] - DataFusionError::ParquetError(e) => Some(e), - #[cfg(feature = "avro")] - DataFusionError::AvroError(e) => Some(e), - #[cfg(feature = "object_store")] - DataFusionError::ObjectStore(e) => Some(e), - DataFusionError::IoError(e) => Some(e), - DataFusionError::SQL(e) => Some(e), - DataFusionError::NotImplemented(_) => None, - DataFusionError::Internal(_) => None, - DataFusionError::Configuration(_) => None, - DataFusionError::Plan(_) => None, - DataFusionError::SchemaError(e) => Some(e), - DataFusionError::Execution(_) => None, - DataFusionError::ResourcesExhausted(_) => None, - DataFusionError::External(e) => Some(e.as_ref()), - DataFusionError::Context(_, e) => Some(e.as_ref()), - DataFusionError::Substrait(_) => None, - } - } -} - impl From for io::Error { fn from(e: DataFusionError) -> Self { io::Error::new(io::ErrorKind::Other, e) @@ -398,6 +309,8 @@ impl DataFusionError { // remember the lowest datafusion error so far if let Some(e) = root_error.downcast_ref::() { last_datafusion_error = e; + } else if let Some(e) = root_error.downcast_ref::>() { + last_datafusion_error = e; } else if let Some(e) = root_error.downcast_ref::>() { // As `Arc::source()` calls through to `T::source()` we need to // explicitly match `Arc` to capture it