Coverage Report

Created: 2024-10-13 08:39

/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
}