/Users/andrewlamb/Software/datafusion/datafusion/common/src/error.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | //! DataFusion error types |
19 | | #[cfg(feature = "backtrace")] |
20 | | use std::backtrace::{Backtrace, BacktraceStatus}; |
21 | | |
22 | | use std::borrow::Cow; |
23 | | use std::error::Error; |
24 | | use std::fmt::{Display, Formatter}; |
25 | | use std::io; |
26 | | use std::result; |
27 | | use std::sync::Arc; |
28 | | |
29 | | use crate::utils::quote_identifier; |
30 | | use crate::{Column, DFSchema, TableReference}; |
31 | | #[cfg(feature = "avro")] |
32 | | use apache_avro::Error as AvroError; |
33 | | use arrow::error::ArrowError; |
34 | | #[cfg(feature = "parquet")] |
35 | | use parquet::errors::ParquetError; |
36 | | use sqlparser::parser::ParserError; |
37 | | use tokio::task::JoinError; |
38 | | |
39 | | /// Result type for operations that could result in an [DataFusionError] |
40 | | pub type Result<T, E = DataFusionError> = result::Result<T, E>; |
41 | | |
42 | | /// Result type for operations that could result in an [DataFusionError] and needs to be shared (wrapped into `Arc`). |
43 | | pub type SharedResult<T> = result::Result<T, Arc<DataFusionError>>; |
44 | | |
45 | | /// Error type for generic operations that could result in DataFusionError::External |
46 | | pub type GenericError = Box<dyn Error + Send + Sync>; |
47 | | |
48 | | /// DataFusion error |
49 | | #[derive(Debug)] |
50 | | pub enum DataFusionError { |
51 | | /// Error returned by arrow. |
52 | | /// |
53 | | /// 2nd argument is for optional backtrace |
54 | | ArrowError(ArrowError, Option<String>), |
55 | | /// Error when reading / writing Parquet data. |
56 | | #[cfg(feature = "parquet")] |
57 | | ParquetError(ParquetError), |
58 | | /// Error when reading Avro data. |
59 | | #[cfg(feature = "avro")] |
60 | | AvroError(AvroError), |
61 | | /// Error when reading / writing to / from an object_store (e.g. S3 or LocalFile) |
62 | | #[cfg(feature = "object_store")] |
63 | | ObjectStore(object_store::Error), |
64 | | /// Error when an I/O operation fails |
65 | | IoError(io::Error), |
66 | | /// Error when SQL is syntactically incorrect. |
67 | | /// |
68 | | /// 2nd argument is for optional backtrace |
69 | | SQL(ParserError, Option<String>), |
70 | | /// Error when a feature is not yet implemented. |
71 | | /// |
72 | | /// These errors are sometimes returned for features that are still in |
73 | | /// development and are not entirely complete. Often, these errors are |
74 | | /// tracked in our issue tracker. |
75 | | NotImplemented(String), |
76 | | /// Error due to bugs in DataFusion |
77 | | /// |
78 | | /// This error should not happen in normal usage of DataFusion. It results |
79 | | /// from something that wasn't expected/anticipated by the implementation |
80 | | /// and that is most likely a bug (the error message even encourages users |
81 | | /// to open a bug report). A user should not be able to trigger internal |
82 | | /// errors under normal circumstances by feeding in malformed queries, bad |
83 | | /// data, etc. |
84 | | /// |
85 | | /// Note that I/O errors (or any error that happens due to external systems) |
86 | | /// do NOT fall under this category. See other variants such as |
87 | | /// [`Self::IoError`] and [`Self::External`]. |
88 | | /// |
89 | | /// DataFusions has internal invariants that the compiler is not always able |
90 | | /// to check. This error is raised when one of those invariants does not |
91 | | /// hold for some reason. |
92 | | Internal(String), |
93 | | /// Error during planning of the query. |
94 | | /// |
95 | | /// This error happens when the user provides a bad query or plan, for |
96 | | /// example the user attempts to call a function that doesn't exist, or if |
97 | | /// the types of a function call are not supported. |
98 | | Plan(String), |
99 | | /// Error for invalid or unsupported configuration options. |
100 | | Configuration(String), |
101 | | /// Error when there is a problem with the query related to schema. |
102 | | /// |
103 | | /// This error can be returned in cases such as when schema inference is not |
104 | | /// possible and when column names are not unique. |
105 | | /// |
106 | | /// 2nd argument is for optional backtrace |
107 | | /// Boxing the optional backtrace to prevent <https://rust-lang.github.io/rust-clippy/master/index.html#/result_large_err> |
108 | | SchemaError(SchemaError, Box<Option<String>>), |
109 | | /// Error during execution of the query. |
110 | | /// |
111 | | /// This error is returned when an error happens during execution due to a |
112 | | /// malformed input. For example, the user passed malformed arguments to a |
113 | | /// SQL method, opened a CSV file that is broken, or tried to divide an |
114 | | /// integer by zero. |
115 | | Execution(String), |
116 | | /// [`JoinError`] during execution of the query. |
117 | | /// |
118 | | /// This error can unoccur for unjoined tasks, such as execution shutdown. |
119 | | ExecutionJoin(JoinError), |
120 | | /// Error when resources (such as memory of scratch disk space) are exhausted. |
121 | | /// |
122 | | /// This error is thrown when a consumer cannot acquire additional memory |
123 | | /// or other resources needed to execute the query from the Memory Manager. |
124 | | ResourcesExhausted(String), |
125 | | /// Errors originating from outside DataFusion's core codebase. |
126 | | /// |
127 | | /// For example, a custom S3Error from the crate datafusion-objectstore-s3 |
128 | | External(GenericError), |
129 | | /// Error with additional context |
130 | | Context(String, Box<DataFusionError>), |
131 | | /// Errors from either mapping LogicalPlans to/from Substrait plans |
132 | | /// or serializing/deserializing protobytes to Substrait plans |
133 | | Substrait(String), |
134 | | } |
135 | | |
136 | | #[macro_export] |
137 | | macro_rules! context { |
138 | | ($desc:expr, $err:expr) => { |
139 | | $err.context(format!("{} at {}:{}", $desc, file!(), line!())) |
140 | | }; |
141 | | } |
142 | | |
143 | | /// Schema-related errors |
144 | | #[derive(Debug)] |
145 | | pub enum SchemaError { |
146 | | /// Schema contains a (possibly) qualified and unqualified field with same unqualified name |
147 | | AmbiguousReference { field: Column }, |
148 | | /// Schema contains duplicate qualified field name |
149 | | DuplicateQualifiedField { |
150 | | qualifier: Box<TableReference>, |
151 | | name: String, |
152 | | }, |
153 | | /// Schema contains duplicate unqualified field name |
154 | | DuplicateUnqualifiedField { name: String }, |
155 | | /// No field with this name |
156 | | FieldNotFound { |
157 | | field: Box<Column>, |
158 | | valid_fields: Vec<Column>, |
159 | | }, |
160 | | } |
161 | | |
162 | | impl Display for SchemaError { |
163 | 0 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { |
164 | 0 | match self { |
165 | | Self::FieldNotFound { |
166 | 0 | field, |
167 | 0 | valid_fields, |
168 | 0 | } => { |
169 | 0 | write!(f, "No field named {}", field.quoted_flat_name())?; |
170 | 0 | if !valid_fields.is_empty() { |
171 | 0 | write!( |
172 | 0 | f, |
173 | 0 | ". Valid fields are {}", |
174 | 0 | valid_fields |
175 | 0 | .iter() |
176 | 0 | .map(|field| field.quoted_flat_name()) |
177 | 0 | .collect::<Vec<String>>() |
178 | 0 | .join(", ") |
179 | 0 | )?; |
180 | 0 | } |
181 | 0 | write!(f, ".") |
182 | | } |
183 | 0 | Self::DuplicateQualifiedField { qualifier, name } => { |
184 | 0 | write!( |
185 | 0 | f, |
186 | 0 | "Schema contains duplicate qualified field name {}.{}", |
187 | 0 | qualifier.to_quoted_string(), |
188 | 0 | quote_identifier(name) |
189 | 0 | ) |
190 | | } |
191 | 0 | Self::DuplicateUnqualifiedField { name } => { |
192 | 0 | write!( |
193 | 0 | f, |
194 | 0 | "Schema contains duplicate unqualified field name {}", |
195 | 0 | quote_identifier(name) |
196 | 0 | ) |
197 | | } |
198 | 0 | Self::AmbiguousReference { field } => { |
199 | 0 | if field.relation.is_some() { |
200 | 0 | write!( |
201 | 0 | f, |
202 | 0 | "Schema contains qualified field name {} and unqualified field name {} which would be ambiguous", |
203 | 0 | field.quoted_flat_name(), |
204 | 0 | quote_identifier(&field.name) |
205 | 0 | ) |
206 | | } else { |
207 | 0 | write!( |
208 | 0 | f, |
209 | 0 | "Ambiguous reference to unqualified field {}", |
210 | 0 | field.quoted_flat_name() |
211 | 0 | ) |
212 | | } |
213 | | } |
214 | | } |
215 | 0 | } |
216 | | } |
217 | | |
218 | | impl Error for SchemaError {} |
219 | | |
220 | | impl From<std::fmt::Error> for DataFusionError { |
221 | 0 | fn from(_e: std::fmt::Error) -> Self { |
222 | 0 | DataFusionError::Execution("Fail to format".to_string()) |
223 | 0 | } |
224 | | } |
225 | | |
226 | | impl From<io::Error> for DataFusionError { |
227 | 0 | fn from(e: io::Error) -> Self { |
228 | 0 | DataFusionError::IoError(e) |
229 | 0 | } |
230 | | } |
231 | | |
232 | | impl From<ArrowError> for DataFusionError { |
233 | 2 | fn from(e: ArrowError) -> Self { |
234 | 2 | DataFusionError::ArrowError(e, None) |
235 | 2 | } |
236 | | } |
237 | | |
238 | | impl From<DataFusionError> for ArrowError { |
239 | 5 | fn from(e: DataFusionError) -> Self { |
240 | 5 | match e { |
241 | 0 | DataFusionError::ArrowError(e, _) => e, |
242 | 5 | DataFusionError::External(e) => ArrowError::ExternalError(e), |
243 | 0 | other => ArrowError::ExternalError(Box::new(other)), |
244 | | } |
245 | 5 | } |
246 | | } |
247 | | |
248 | | #[cfg(feature = "parquet")] |
249 | | impl From<ParquetError> for DataFusionError { |
250 | | fn from(e: ParquetError) -> Self { |
251 | | DataFusionError::ParquetError(e) |
252 | | } |
253 | | } |
254 | | |
255 | | #[cfg(feature = "avro")] |
256 | | impl From<AvroError> for DataFusionError { |
257 | | fn from(e: AvroError) -> Self { |
258 | | DataFusionError::AvroError(e) |
259 | | } |
260 | | } |
261 | | |
262 | | #[cfg(feature = "object_store")] |
263 | | impl From<object_store::Error> for DataFusionError { |
264 | | fn from(e: object_store::Error) -> Self { |
265 | | DataFusionError::ObjectStore(e) |
266 | | } |
267 | | } |
268 | | |
269 | | #[cfg(feature = "object_store")] |
270 | | impl From<object_store::path::Error> for DataFusionError { |
271 | | fn from(e: object_store::path::Error) -> Self { |
272 | | DataFusionError::ObjectStore(e.into()) |
273 | | } |
274 | | } |
275 | | |
276 | | impl From<ParserError> for DataFusionError { |
277 | 0 | fn from(e: ParserError) -> Self { |
278 | 0 | DataFusionError::SQL(e, None) |
279 | 0 | } |
280 | | } |
281 | | |
282 | | impl From<GenericError> for DataFusionError { |
283 | 0 | fn from(err: GenericError) -> Self { |
284 | 0 | DataFusionError::External(err) |
285 | 0 | } |
286 | | } |
287 | | |
288 | | impl Display for DataFusionError { |
289 | 102 | fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { |
290 | 102 | let error_prefix = self.error_prefix(); |
291 | 102 | let message = self.message(); |
292 | 102 | write!(f, "{error_prefix}{message}") |
293 | 102 | } |
294 | | } |
295 | | |
296 | | impl Error for DataFusionError { |
297 | 12 | fn source(&self) -> Option<&(dyn Error + 'static)> { |
298 | 12 | match self { |
299 | 6 | DataFusionError::ArrowError(e, _) => Some(e), |
300 | | #[cfg(feature = "parquet")] |
301 | | DataFusionError::ParquetError(e) => Some(e), |
302 | | #[cfg(feature = "avro")] |
303 | | DataFusionError::AvroError(e) => Some(e), |
304 | | #[cfg(feature = "object_store")] |
305 | | DataFusionError::ObjectStore(e) => Some(e), |
306 | 0 | DataFusionError::IoError(e) => Some(e), |
307 | 0 | DataFusionError::SQL(e, _) => Some(e), |
308 | 0 | DataFusionError::NotImplemented(_) => None, |
309 | 0 | DataFusionError::Internal(_) => None, |
310 | 0 | DataFusionError::Configuration(_) => None, |
311 | 0 | DataFusionError::Plan(_) => None, |
312 | 0 | DataFusionError::SchemaError(e, _) => Some(e), |
313 | 0 | DataFusionError::Execution(_) => None, |
314 | 0 | DataFusionError::ExecutionJoin(e) => Some(e), |
315 | 6 | DataFusionError::ResourcesExhausted(_) => None, |
316 | 0 | DataFusionError::External(e) => Some(e.as_ref()), |
317 | 0 | DataFusionError::Context(_, e) => Some(e.as_ref()), |
318 | 0 | DataFusionError::Substrait(_) => None, |
319 | | } |
320 | 12 | } |
321 | | } |
322 | | |
323 | | impl From<DataFusionError> for io::Error { |
324 | | fn from(e: DataFusionError) -> Self { |
325 | | io::Error::new(io::ErrorKind::Other, e) |
326 | | } |
327 | | } |
328 | | |
329 | | impl DataFusionError { |
330 | | /// The separator between the error message and the backtrace |
331 | | pub const BACK_TRACE_SEP: &'static str = "\n\nbacktrace: "; |
332 | | |
333 | | /// Get deepest underlying [`DataFusionError`] |
334 | | /// |
335 | | /// [`DataFusionError`]s sometimes form a chain, such as `DataFusionError::ArrowError()` in order to conform |
336 | | /// to the correct error signature. Thus sometimes there is a chain several layers deep that can obscure the |
337 | | /// original error. This function finds the lowest level DataFusionError possible. |
338 | | /// |
339 | | /// For example, `find_root` will return`DataFusionError::ResourceExhausted` given the input |
340 | | /// ```text |
341 | | /// DataFusionError::ArrowError |
342 | | /// ArrowError::External |
343 | | /// Box(DataFusionError::Context) |
344 | | /// DataFusionError::ResourceExhausted |
345 | | /// ``` |
346 | | /// |
347 | | /// This may be the same as `self`. |
348 | 7 | pub fn find_root(&self) -> &Self { |
349 | 7 | // Note: This is a non-recursive algorithm so we do not run |
350 | 7 | // out of stack space, even for long error chains. |
351 | 7 | |
352 | 7 | let mut last_datafusion_error = self; |
353 | 7 | let mut root_error: &dyn Error = self; |
354 | 18 | while let Some(source11 ) = root_error.source() { |
355 | | // walk the next level |
356 | 11 | root_error = source; |
357 | | // remember the lowest datafusion error so far |
358 | 11 | if let Some(e0 ) = root_error.downcast_ref::<DataFusionError>() { |
359 | 0 | last_datafusion_error = e; |
360 | 11 | } else if let Some(e5 ) = root_error.downcast_ref::<Arc<DataFusionError>>() { |
361 | 5 | // As `Arc<T>::source()` calls through to `T::source()` we need to |
362 | 5 | // explicitly match `Arc<DataFusionError>` to capture it |
363 | 5 | last_datafusion_error = e.as_ref(); |
364 | 6 | } |
365 | | } |
366 | | // return last checkpoint (which may be the original error) |
367 | 7 | last_datafusion_error |
368 | 7 | } |
369 | | |
370 | | /// wraps self in Self::Context with a description |
371 | 0 | pub fn context(self, description: impl Into<String>) -> Self { |
372 | 0 | Self::Context(description.into(), Box::new(self)) |
373 | 0 | } |
374 | | |
375 | | /// Strips backtrace out of the error message |
376 | | /// If backtrace enabled then error has a format "message" [`Self::BACK_TRACE_SEP`] "backtrace" |
377 | | /// The method strips the backtrace and outputs "message" |
378 | 1 | pub fn strip_backtrace(&self) -> String { |
379 | 1 | self.to_string() |
380 | 1 | .split(Self::BACK_TRACE_SEP) |
381 | 1 | .collect::<Vec<&str>>() |
382 | 1 | .first() |
383 | 1 | .unwrap_or(&"") |
384 | 1 | .to_string() |
385 | 1 | } |
386 | | |
387 | | /// To enable optional rust backtrace in DataFusion: |
388 | | /// - [`Setup Env Variables`]<https://doc.rust-lang.org/std/backtrace/index.html#environment-variables> |
389 | | /// - Enable `backtrace` cargo feature |
390 | | /// |
391 | | /// Example: |
392 | | /// cargo build --features 'backtrace' |
393 | | /// RUST_BACKTRACE=1 ./app |
394 | | #[inline(always)] |
395 | 235 | pub fn get_back_trace() -> String { |
396 | 235 | #[cfg(feature = "backtrace")] |
397 | 235 | { |
398 | 235 | let back_trace = Backtrace::capture(); |
399 | 235 | if back_trace.status() == BacktraceStatus::Captured { |
400 | 235 | return format!("{}{}", Self::BACK_TRACE_SEP, back_trace); |
401 | 235 | } |
402 | 235 | |
403 | 235 | "".to_owned() |
404 | 235 | } |
405 | 235 | |
406 | 235 | #[cfg(not(feature = "backtrace"))] |
407 | 235 | "".to_owned() |
408 | 235 | } |
409 | | |
410 | 102 | fn error_prefix(&self) -> &'static str { |
411 | 102 | match self { |
412 | 0 | DataFusionError::ArrowError(_, _) => "Arrow error: ", |
413 | | #[cfg(feature = "parquet")] |
414 | | DataFusionError::ParquetError(_) => "Parquet error: ", |
415 | | #[cfg(feature = "avro")] |
416 | | DataFusionError::AvroError(_) => "Avro error: ", |
417 | | #[cfg(feature = "object_store")] |
418 | | DataFusionError::ObjectStore(_) => "Object Store error: ", |
419 | 0 | DataFusionError::IoError(_) => "IO error: ", |
420 | 0 | DataFusionError::SQL(_, _) => "SQL error: ", |
421 | 1 | DataFusionError::NotImplemented(_) => "This feature is not implemented: ", |
422 | 2 | DataFusionError::Internal(_) => "Internal error: ", |
423 | 0 | DataFusionError::Plan(_) => "Error during planning: ", |
424 | 0 | DataFusionError::Configuration(_) => "Invalid or Unsupported Configuration: ", |
425 | 0 | DataFusionError::SchemaError(_, _) => "Schema error: ", |
426 | 46 | DataFusionError::Execution(_) => "Execution error: ", |
427 | 0 | DataFusionError::ExecutionJoin(_) => "ExecutionJoin error: ", |
428 | 25 | DataFusionError::ResourcesExhausted(_) => "Resources exhausted: ", |
429 | 28 | DataFusionError::External(_) => "External error: ", |
430 | 0 | DataFusionError::Context(_, _) => "", |
431 | 0 | DataFusionError::Substrait(_) => "Substrait error: ", |
432 | | } |
433 | 102 | } |
434 | | |
435 | 114 | pub fn message(&self) -> Cow<str> { |
436 | 114 | match *self { |
437 | 0 | DataFusionError::ArrowError(ref desc, ref backtrace) => { |
438 | 0 | let backtrace = backtrace.clone().unwrap_or("".to_owned()); |
439 | 0 | Cow::Owned(format!("{desc}{backtrace}")) |
440 | | } |
441 | | #[cfg(feature = "parquet")] |
442 | | DataFusionError::ParquetError(ref desc) => Cow::Owned(desc.to_string()), |
443 | | #[cfg(feature = "avro")] |
444 | | DataFusionError::AvroError(ref desc) => Cow::Owned(desc.to_string()), |
445 | 0 | DataFusionError::IoError(ref desc) => Cow::Owned(desc.to_string()), |
446 | 0 | DataFusionError::SQL(ref desc, ref backtrace) => { |
447 | 0 | let backtrace: String = backtrace.clone().unwrap_or("".to_owned()); |
448 | 0 | Cow::Owned(format!("{desc:?}{backtrace}")) |
449 | | } |
450 | 0 | DataFusionError::Configuration(ref desc) => Cow::Owned(desc.to_string()), |
451 | 1 | DataFusionError::NotImplemented(ref desc) => Cow::Owned(desc.to_string()), |
452 | 2 | DataFusionError::Internal(ref desc) => Cow::Owned(format!( |
453 | 2 | "{desc}.\nThis was likely caused by a bug in DataFusion's \ |
454 | 2 | code and we would welcome that you file an bug report in our issue tracker" |
455 | 2 | )), |
456 | 0 | DataFusionError::Plan(ref desc) => Cow::Owned(desc.to_string()), |
457 | 0 | DataFusionError::SchemaError(ref desc, ref backtrace) => { |
458 | 0 | let backtrace: &str = |
459 | 0 | &backtrace.as_ref().clone().unwrap_or("".to_owned()); |
460 | 0 | Cow::Owned(format!("{desc}{backtrace}")) |
461 | | } |
462 | 46 | DataFusionError::Execution(ref desc) => Cow::Owned(desc.to_string()), |
463 | 0 | DataFusionError::ExecutionJoin(ref desc) => Cow::Owned(desc.to_string()), |
464 | 37 | DataFusionError::ResourcesExhausted(ref desc) => Cow::Owned(desc.to_string()), |
465 | 28 | DataFusionError::External(ref desc) => Cow::Owned(desc.to_string()), |
466 | | #[cfg(feature = "object_store")] |
467 | | DataFusionError::ObjectStore(ref desc) => Cow::Owned(desc.to_string()), |
468 | 0 | DataFusionError::Context(ref desc, ref err) => { |
469 | 0 | Cow::Owned(format!("{desc}\ncaused by\n{}", *err)) |
470 | | } |
471 | 0 | DataFusionError::Substrait(ref desc) => Cow::Owned(desc.to_string()), |
472 | | } |
473 | 114 | } |
474 | | } |
475 | | |
476 | | /// Unwrap an `Option` if possible. Otherwise return an `DataFusionError::Internal`. |
477 | | /// In normal usage of DataFusion the unwrap should always succeed. |
478 | | /// |
479 | | /// Example: `let values = unwrap_or_internal_err!(values)` |
480 | | #[macro_export] |
481 | | macro_rules! unwrap_or_internal_err { |
482 | | ($Value: ident) => { |
483 | 0 | $Value.ok_or_else(|| { |
484 | 0 | DataFusionError::Internal(format!( |
485 | 0 | "{} should not be None", |
486 | 0 | stringify!($Value) |
487 | 0 | )) |
488 | 0 | })? |
489 | | }; |
490 | | } |
491 | | |
492 | | /// Add a macros for concise DataFusionError::* errors declaration |
493 | | /// supports placeholders the same way as `format!` |
494 | | /// Examples: |
495 | | /// plan_err!("Error") |
496 | | /// plan_err!("Error {}", val) |
497 | | /// plan_err!("Error {:?}", val) |
498 | | /// plan_err!("Error {val}") |
499 | | /// plan_err!("Error {val:?}") |
500 | | /// |
501 | | /// `NAME_ERR` - macro name for wrapping Err(DataFusionError::*) |
502 | | /// `NAME_DF_ERR` - macro name for wrapping DataFusionError::*. Needed to keep backtrace opportunity |
503 | | /// in construction where DataFusionError::* used directly, like `map_err`, `ok_or_else`, etc |
504 | | macro_rules! make_error { |
505 | | ($NAME_ERR:ident, $NAME_DF_ERR: ident, $ERR:ident) => { make_error!(@inner ($), $NAME_ERR, $NAME_DF_ERR, $ERR); }; |
506 | | (@inner ($d:tt), $NAME_ERR:ident, $NAME_DF_ERR:ident, $ERR:ident) => { |
507 | | ::paste::paste!{ |
508 | | /// Macro wraps `$ERR` to add backtrace feature |
509 | | #[macro_export] |
510 | | macro_rules! $NAME_DF_ERR { |
511 | | ($d($d args:expr),*) => { |
512 | | $crate::DataFusionError::$ERR( |
513 | | ::std::format!( |
514 | | "{}{}", |
515 | | ::std::format!($d($d args),*), |
516 | | $crate::DataFusionError::get_back_trace(), |
517 | | ).into() |
518 | | ) |
519 | | } |
520 | | } |
521 | | |
522 | | /// Macro wraps Err(`$ERR`) to add backtrace feature |
523 | | #[macro_export] |
524 | | macro_rules! $NAME_ERR { |
525 | | ($d($d args:expr),*) => { |
526 | | Err($crate::[<_ $NAME_DF_ERR>]!($d($d args),*)) |
527 | | } |
528 | | } |
529 | | |
530 | | |
531 | | // Note: Certain macros are used in this crate, but not all. |
532 | | // This macro generates a use or all of them in case they are needed |
533 | | // so we allow unused code to avoid warnings when they are not used |
534 | | #[doc(hidden)] |
535 | | #[allow(unused)] |
536 | | pub use $NAME_ERR as [<_ $NAME_ERR>]; |
537 | | #[doc(hidden)] |
538 | | #[allow(unused)] |
539 | | pub use $NAME_DF_ERR as [<_ $NAME_DF_ERR>]; |
540 | | } |
541 | | }; |
542 | | } |
543 | | |
544 | | // Exposes a macro to create `DataFusionError::Plan` with optional backtrace |
545 | | make_error!(plan_err, plan_datafusion_err, Plan); |
546 | | |
547 | | // Exposes a macro to create `DataFusionError::Internal` with optional backtrace |
548 | | make_error!(internal_err, internal_datafusion_err, Internal); |
549 | | |
550 | | // Exposes a macro to create `DataFusionError::NotImplemented` with optional backtrace |
551 | | make_error!(not_impl_err, not_impl_datafusion_err, NotImplemented); |
552 | | |
553 | | // Exposes a macro to create `DataFusionError::Execution` with optional backtrace |
554 | | make_error!(exec_err, exec_datafusion_err, Execution); |
555 | | |
556 | | // Exposes a macro to create `DataFusionError::Configuration` with optional backtrace |
557 | | make_error!(config_err, config_datafusion_err, Configuration); |
558 | | |
559 | | // Exposes a macro to create `DataFusionError::Substrait` with optional backtrace |
560 | | make_error!(substrait_err, substrait_datafusion_err, Substrait); |
561 | | |
562 | | // Exposes a macro to create `DataFusionError::ResourcesExhausted` with optional backtrace |
563 | | make_error!(resources_err, resources_datafusion_err, ResourcesExhausted); |
564 | | |
565 | | // Exposes a macro to create `DataFusionError::SQL` with optional backtrace |
566 | | #[macro_export] |
567 | | macro_rules! sql_datafusion_err { |
568 | | ($ERR:expr) => { |
569 | | DataFusionError::SQL($ERR, Some(DataFusionError::get_back_trace())) |
570 | | }; |
571 | | } |
572 | | |
573 | | // Exposes a macro to create `Err(DataFusionError::SQL)` with optional backtrace |
574 | | #[macro_export] |
575 | | macro_rules! sql_err { |
576 | | ($ERR:expr) => { |
577 | | Err(datafusion_common::sql_datafusion_err!($ERR)) |
578 | | }; |
579 | | } |
580 | | |
581 | | // Exposes a macro to create `DataFusionError::ArrowError` with optional backtrace |
582 | | #[macro_export] |
583 | | macro_rules! arrow_datafusion_err { |
584 | | ($ERR:expr) => { |
585 | | DataFusionError::ArrowError($ERR, Some(DataFusionError::get_back_trace())) |
586 | | }; |
587 | | } |
588 | | |
589 | | // Exposes a macro to create `Err(DataFusionError::ArrowError)` with optional backtrace |
590 | | #[macro_export] |
591 | | macro_rules! arrow_err { |
592 | | ($ERR:expr) => { |
593 | | Err(datafusion_common::arrow_datafusion_err!($ERR)) |
594 | | }; |
595 | | } |
596 | | |
597 | | // Exposes a macro to create `DataFusionError::SchemaError` with optional backtrace |
598 | | #[macro_export] |
599 | | macro_rules! schema_datafusion_err { |
600 | | ($ERR:expr) => { |
601 | | DataFusionError::SchemaError( |
602 | | $ERR, |
603 | | Box::new(Some(DataFusionError::get_back_trace())), |
604 | | ) |
605 | | }; |
606 | | } |
607 | | |
608 | | // Exposes a macro to create `Err(DataFusionError::SchemaError)` with optional backtrace |
609 | | #[macro_export] |
610 | | macro_rules! schema_err { |
611 | | ($ERR:expr) => { |
612 | | Err(DataFusionError::SchemaError( |
613 | | $ERR, |
614 | | Box::new(Some(DataFusionError::get_back_trace())), |
615 | | )) |
616 | | }; |
617 | | } |
618 | | |
619 | | // To avoid compiler error when using macro in the same crate: |
620 | | // macros from the current crate cannot be referred to by absolute paths |
621 | | pub use schema_err as _schema_err; |
622 | | |
623 | | /// Create a "field not found" DataFusion::SchemaError |
624 | 0 | pub fn field_not_found<R: Into<TableReference>>( |
625 | 0 | qualifier: Option<R>, |
626 | 0 | name: &str, |
627 | 0 | schema: &DFSchema, |
628 | 0 | ) -> DataFusionError { |
629 | 0 | schema_datafusion_err!(SchemaError::FieldNotFound { |
630 | 0 | field: Box::new(Column::new(qualifier, name)), |
631 | 0 | valid_fields: schema.columns().to_vec(), |
632 | 0 | }) |
633 | 0 | } |
634 | | |
635 | | /// Convenience wrapper over [`field_not_found`] for when there is no qualifier |
636 | 0 | pub fn unqualified_field_not_found(name: &str, schema: &DFSchema) -> DataFusionError { |
637 | 0 | schema_datafusion_err!(SchemaError::FieldNotFound { |
638 | 0 | field: Box::new(Column::new_unqualified(name)), |
639 | 0 | valid_fields: schema.columns().to_vec(), |
640 | 0 | }) |
641 | 0 | } |
642 | | |
643 | | #[cfg(test)] |
644 | | mod test { |
645 | | use std::sync::Arc; |
646 | | |
647 | | use crate::error::DataFusionError; |
648 | | use arrow::error::ArrowError; |
649 | | |
650 | | #[test] |
651 | | fn datafusion_error_to_arrow() { |
652 | | let res = return_arrow_error().unwrap_err(); |
653 | | assert!(res |
654 | | .to_string() |
655 | | .starts_with("External error: Error during planning: foo")); |
656 | | } |
657 | | |
658 | | #[test] |
659 | | fn arrow_error_to_datafusion() { |
660 | | let res = return_datafusion_error().unwrap_err(); |
661 | | assert_eq!(res.strip_backtrace(), "Arrow error: Schema error: bar"); |
662 | | } |
663 | | |
664 | | // To pass the test the environment variable RUST_BACKTRACE should be set to 1 to enforce backtrace |
665 | | #[cfg(feature = "backtrace")] |
666 | | #[test] |
667 | | #[allow(clippy::unnecessary_literal_unwrap)] |
668 | | fn test_enabled_backtrace() { |
669 | | match std::env::var("RUST_BACKTRACE") { |
670 | | Ok(val) if val == "1" => {} |
671 | | _ => panic!("Environment variable RUST_BACKTRACE must be set to 1"), |
672 | | }; |
673 | | |
674 | | let res: Result<(), DataFusionError> = plan_err!("Err"); |
675 | | let err = res.unwrap_err().to_string(); |
676 | | assert!(err.contains(DataFusionError::BACK_TRACE_SEP)); |
677 | | assert_eq!( |
678 | | err.split(DataFusionError::BACK_TRACE_SEP) |
679 | | .collect::<Vec<&str>>() |
680 | | .first() |
681 | | .unwrap(), |
682 | | &"Error during planning: Err" |
683 | | ); |
684 | | assert!(!err |
685 | | .split(DataFusionError::BACK_TRACE_SEP) |
686 | | .collect::<Vec<&str>>() |
687 | | .get(1) |
688 | | .unwrap() |
689 | | .is_empty()); |
690 | | } |
691 | | |
692 | | #[cfg(not(feature = "backtrace"))] |
693 | | #[test] |
694 | | #[allow(clippy::unnecessary_literal_unwrap)] |
695 | | fn test_disabled_backtrace() { |
696 | | let res: Result<(), DataFusionError> = plan_err!("Err"); |
697 | | let res = res.unwrap_err().to_string(); |
698 | | assert!(!res.contains(DataFusionError::BACK_TRACE_SEP)); |
699 | | assert_eq!(res, "Error during planning: Err"); |
700 | | } |
701 | | |
702 | | #[test] |
703 | | fn test_find_root_error() { |
704 | | do_root_test( |
705 | | DataFusionError::Context( |
706 | | "it happened!".to_string(), |
707 | | Box::new(DataFusionError::ResourcesExhausted("foo".to_string())), |
708 | | ), |
709 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
710 | | ); |
711 | | |
712 | | do_root_test( |
713 | | DataFusionError::ArrowError( |
714 | | ArrowError::ExternalError(Box::new(DataFusionError::ResourcesExhausted( |
715 | | "foo".to_string(), |
716 | | ))), |
717 | | None, |
718 | | ), |
719 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
720 | | ); |
721 | | |
722 | | do_root_test( |
723 | | DataFusionError::External(Box::new(DataFusionError::ResourcesExhausted( |
724 | | "foo".to_string(), |
725 | | ))), |
726 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
727 | | ); |
728 | | |
729 | | do_root_test( |
730 | | DataFusionError::External(Box::new(ArrowError::ExternalError(Box::new( |
731 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
732 | | )))), |
733 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
734 | | ); |
735 | | |
736 | | do_root_test( |
737 | | DataFusionError::ArrowError( |
738 | | ArrowError::ExternalError(Box::new(ArrowError::ExternalError(Box::new( |
739 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
740 | | )))), |
741 | | None, |
742 | | ), |
743 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
744 | | ); |
745 | | |
746 | | do_root_test( |
747 | | DataFusionError::External(Box::new(Arc::new( |
748 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
749 | | ))), |
750 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
751 | | ); |
752 | | |
753 | | do_root_test( |
754 | | DataFusionError::External(Box::new(Arc::new(ArrowError::ExternalError( |
755 | | Box::new(DataFusionError::ResourcesExhausted("foo".to_string())), |
756 | | )))), |
757 | | DataFusionError::ResourcesExhausted("foo".to_string()), |
758 | | ); |
759 | | } |
760 | | |
761 | | #[test] |
762 | | #[allow(clippy::unnecessary_literal_unwrap)] |
763 | | fn test_make_error_parse_input() { |
764 | | let res: Result<(), DataFusionError> = plan_err!("Err"); |
765 | | let res = res.unwrap_err(); |
766 | | assert_eq!(res.strip_backtrace(), "Error during planning: Err"); |
767 | | |
768 | | let extra1 = "extra1"; |
769 | | let extra2 = "extra2"; |
770 | | |
771 | | let res: Result<(), DataFusionError> = plan_err!("Err {} {}", extra1, extra2); |
772 | | let res = res.unwrap_err(); |
773 | | assert_eq!( |
774 | | res.strip_backtrace(), |
775 | | "Error during planning: Err extra1 extra2" |
776 | | ); |
777 | | |
778 | | let res: Result<(), DataFusionError> = |
779 | | plan_err!("Err {:?} {:#?}", extra1, extra2); |
780 | | let res = res.unwrap_err(); |
781 | | assert_eq!( |
782 | | res.strip_backtrace(), |
783 | | "Error during planning: Err \"extra1\" \"extra2\"" |
784 | | ); |
785 | | |
786 | | let res: Result<(), DataFusionError> = plan_err!("Err {extra1} {extra2}"); |
787 | | let res = res.unwrap_err(); |
788 | | assert_eq!( |
789 | | res.strip_backtrace(), |
790 | | "Error during planning: Err extra1 extra2" |
791 | | ); |
792 | | |
793 | | let res: Result<(), DataFusionError> = plan_err!("Err {extra1:?} {extra2:#?}"); |
794 | | let res = res.unwrap_err(); |
795 | | assert_eq!( |
796 | | res.strip_backtrace(), |
797 | | "Error during planning: Err \"extra1\" \"extra2\"" |
798 | | ); |
799 | | } |
800 | | |
801 | | /// Model what happens when implementing SendableRecordBatchStream: |
802 | | /// DataFusion code needs to return an ArrowError |
803 | | fn return_arrow_error() -> arrow::error::Result<()> { |
804 | | // Expect the '?' to work |
805 | | Err(DataFusionError::Plan("foo".to_string()).into()) |
806 | | } |
807 | | |
808 | | /// Model what happens when using arrow kernels in DataFusion |
809 | | /// code: need to turn an ArrowError into a DataFusionError |
810 | | fn return_datafusion_error() -> crate::error::Result<()> { |
811 | | // Expect the '?' to work |
812 | | Err(ArrowError::SchemaError("bar".to_string()).into()) |
813 | | } |
814 | | |
815 | | fn do_root_test(e: DataFusionError, exp: DataFusionError) { |
816 | | let e = e.find_root(); |
817 | | |
818 | | // DataFusionError does not implement Eq, so we use a string comparison + some cheap "same variant" test instead |
819 | | assert_eq!(e.strip_backtrace(), exp.strip_backtrace()); |
820 | | assert_eq!(std::mem::discriminant(e), std::mem::discriminant(&exp),) |
821 | | } |
822 | | } |