Skip to content

Commit 2ffda2a

Browse files
authored
Add backtrace to error messages (#7434)
* Adding back trace * fmt * fmt * tests * tests * tests * clippy * strip_backtrace * tests * moved backtrace * tests * tests
1 parent 0b3a054 commit 2ffda2a

File tree

41 files changed

+311
-271
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+311
-271
lines changed

datafusion-cli/src/helper.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! Helper that helps with interactive editing, including multi-line parsing and validation,
1919
//! and auto-completion for file name during creating external table.
2020
21+
use datafusion::common::sql_err;
2122
use datafusion::error::DataFusionError;
2223
use datafusion::sql::parser::{DFParser, Statement};
2324
use datafusion::sql::sqlparser::dialect::dialect_from_str;
@@ -162,9 +163,10 @@ pub fn unescape_input(input: &str) -> datafusion::error::Result<String> {
162163
't' => '\t',
163164
'\\' => '\\',
164165
_ => {
165-
return Err(DataFusionError::SQL(ParserError::TokenizerError(
166-
format!("unsupported escape char: '\\{}'", next_char),
167-
)))
166+
return sql_err!(ParserError::TokenizerError(format!(
167+
"unsupported escape char: '\\{}'",
168+
next_char
169+
),))
168170
}
169171
});
170172
}

datafusion/common/src/column.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -428,7 +428,7 @@ mod tests {
428428
)
429429
.expect_err("should've failed to find field");
430430
let expected = r#"Schema error: No field named z. Valid fields are t1.a, t1.b, t2.c, t2.d, t3.a, t3.b, t3.c, t3.d, t3.e."#;
431-
assert_eq!(err.to_string(), expected);
431+
assert_eq!(err.strip_backtrace(), expected);
432432

433433
// ambiguous column reference
434434
let col = Column::from_name("a");
@@ -439,7 +439,7 @@ mod tests {
439439
)
440440
.expect_err("should've found ambiguous field");
441441
let expected = "Schema error: Ambiguous reference to unqualified field a";
442-
assert_eq!(err.to_string(), expected);
442+
assert_eq!(err.strip_backtrace(), expected);
443443

444444
Ok(())
445445
}

datafusion/common/src/dfschema.rs

+8-8
Original file line numberDiff line numberDiff line change
@@ -812,8 +812,8 @@ mod tests {
812812
// lookup with unqualified name "t1.c0"
813813
let err = schema.index_of_column(&col).unwrap_err();
814814
assert_eq!(
815-
err.to_string(),
816-
"Schema error: No field named \"t1.c0\". Valid fields are t1.c0, t1.c1.",
815+
err.strip_backtrace(),
816+
"Schema error: No field named \"t1.c0\". Valid fields are t1.c0, t1.c1."
817817
);
818818
Ok(())
819819
}
@@ -832,8 +832,8 @@ mod tests {
832832
// lookup with unqualified name "t1.c0"
833833
let err = schema.index_of_column(&col).unwrap_err();
834834
assert_eq!(
835-
err.to_string(),
836-
"Schema error: No field named \"t1.c0\". Valid fields are t1.\"CapitalColumn\", t1.\"field.with.period\".",
835+
err.strip_backtrace(),
836+
"Schema error: No field named \"t1.c0\". Valid fields are t1.\"CapitalColumn\", t1.\"field.with.period\"."
837837
);
838838
Ok(())
839839
}
@@ -916,8 +916,8 @@ mod tests {
916916
let right = DFSchema::try_from(test_schema_1())?;
917917
let join = left.join(&right);
918918
assert_eq!(
919-
join.unwrap_err().to_string(),
920-
"Schema error: Schema contains duplicate unqualified field name c0",
919+
join.unwrap_err().strip_backtrace(),
920+
"Schema error: Schema contains duplicate unqualified field name c0"
921921
);
922922
Ok(())
923923
}
@@ -993,12 +993,12 @@ mod tests {
993993

994994
let col = Column::from_qualified_name("t1.c0");
995995
let err = schema.index_of_column(&col).unwrap_err();
996-
assert_eq!(err.to_string(), "Schema error: No field named t1.c0.");
996+
assert_eq!(err.strip_backtrace(), "Schema error: No field named t1.c0.");
997997

998998
// the same check without qualifier
999999
let col = Column::from_name("c0");
10001000
let err = schema.index_of_column(&col).err().unwrap();
1001-
assert_eq!("Schema error: No field named c0.", err.to_string());
1001+
assert_eq!(err.strip_backtrace(), "Schema error: No field named c0.");
10021002
}
10031003

10041004
#[test]

datafusion/common/src/error.rs

+53-17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
//! DataFusion error types
1919
20+
use std::backtrace::{Backtrace, BacktraceStatus};
2021
use std::error::Error;
2122
use std::fmt::{Display, Formatter};
2223
use std::io;
@@ -278,7 +279,9 @@ impl From<GenericError> for DataFusionError {
278279
impl Display for DataFusionError {
279280
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
280281
match *self {
281-
DataFusionError::ArrowError(ref desc) => write!(f, "Arrow error: {desc}"),
282+
DataFusionError::ArrowError(ref desc) => {
283+
write!(f, "Arrow error: {desc}")
284+
}
282285
#[cfg(feature = "parquet")]
283286
DataFusionError::ParquetError(ref desc) => {
284287
write!(f, "Parquet error: {desc}")
@@ -287,7 +290,9 @@ impl Display for DataFusionError {
287290
DataFusionError::AvroError(ref desc) => {
288291
write!(f, "Avro error: {desc}")
289292
}
290-
DataFusionError::IoError(ref desc) => write!(f, "IO error: {desc}"),
293+
DataFusionError::IoError(ref desc) => {
294+
write!(f, "IO error: {desc}")
295+
}
291296
DataFusionError::SQL(ref desc) => {
292297
write!(f, "SQL error: {desc:?}")
293298
}
@@ -298,7 +303,7 @@ impl Display for DataFusionError {
298303
write!(f, "This feature is not implemented: {desc}")
299304
}
300305
DataFusionError::Internal(ref desc) => {
301-
write!(f, "Internal error: {desc}. This was likely caused by a bug in DataFusion's \
306+
write!(f, "Internal error: {desc}.\nThis was likely caused by a bug in DataFusion's \
302307
code and we would welcome that you file an bug report in our issue tracker")
303308
}
304309
DataFusionError::Plan(ref desc) => {
@@ -404,6 +409,24 @@ impl DataFusionError {
404409
pub fn context(self, description: impl Into<String>) -> Self {
405410
Self::Context(description.into(), Box::new(self))
406411
}
412+
413+
pub fn strip_backtrace(&self) -> String {
414+
self.to_string()
415+
.split("\n\nbacktrace: ")
416+
.collect::<Vec<&str>>()
417+
.first()
418+
.unwrap_or(&"")
419+
.to_string()
420+
}
421+
422+
pub fn get_back_trace() -> String {
423+
let back_trace = Backtrace::capture();
424+
if back_trace.status() == BacktraceStatus::Captured {
425+
return format!("\n\nbacktrace: {}", back_trace);
426+
}
427+
428+
"".to_string()
429+
}
407430
}
408431

409432
/// Unwrap an `Option` if possible. Otherwise return an `DataFusionError::Internal`.
@@ -444,7 +467,7 @@ macro_rules! make_error {
444467
#[macro_export]
445468
macro_rules! $NAME {
446469
($d($d args:expr),*) => {
447-
Err(DataFusionError::$ERR(format!($d($d args),*).into()))
470+
Err(DataFusionError::$ERR(format!("{}{}", format!($d($d args),*), DataFusionError::get_back_trace()).into()))
448471
}
449472
}
450473
}
@@ -464,6 +487,14 @@ make_error!(not_impl_err, NotImplemented);
464487
// Exposes a macro to create `DataFusionError::Execution`
465488
make_error!(exec_err, Execution);
466489

490+
// Exposes a macro to create `DataFusionError::SQL`
491+
#[macro_export]
492+
macro_rules! sql_err {
493+
($ERR:expr) => {
494+
Err(DataFusionError::SQL($ERR))
495+
};
496+
}
497+
467498
// To avoid compiler error when using macro in the same crate:
468499
// macros from the current crate cannot be referred to by absolute paths
469500
pub use exec_err as _exec_err;
@@ -478,18 +509,17 @@ mod test {
478509
use arrow::error::ArrowError;
479510

480511
#[test]
481-
fn arrow_error_to_datafusion() {
512+
fn datafusion_error_to_arrow() {
482513
let res = return_arrow_error().unwrap_err();
483-
assert_eq!(
484-
res.to_string(),
485-
"External error: Error during planning: foo"
486-
);
514+
assert!(res
515+
.to_string()
516+
.starts_with("External error: Error during planning: foo"));
487517
}
488518

489519
#[test]
490-
fn datafusion_error_to_arrow() {
520+
fn arrow_error_to_datafusion() {
491521
let res = return_datafusion_error().unwrap_err();
492-
assert_eq!(res.to_string(), "Arrow error: Schema error: bar");
522+
assert_eq!(res.strip_backtrace(), "Arrow error: Schema error: bar");
493523
}
494524

495525
#[test]
@@ -552,31 +582,37 @@ mod test {
552582
fn test_make_error_parse_input() {
553583
let res: Result<(), DataFusionError> = plan_err!("Err");
554584
let res = res.unwrap_err();
555-
assert_eq!(res.to_string(), "Error during planning: Err");
585+
assert_eq!(res.strip_backtrace(), "Error during planning: Err");
556586

557587
let extra1 = "extra1";
558588
let extra2 = "extra2";
559589

560590
let res: Result<(), DataFusionError> = plan_err!("Err {} {}", extra1, extra2);
561591
let res = res.unwrap_err();
562-
assert_eq!(res.to_string(), "Error during planning: Err extra1 extra2");
592+
assert_eq!(
593+
res.strip_backtrace(),
594+
"Error during planning: Err extra1 extra2"
595+
);
563596

564597
let res: Result<(), DataFusionError> =
565598
plan_err!("Err {:?} {:#?}", extra1, extra2);
566599
let res = res.unwrap_err();
567600
assert_eq!(
568-
res.to_string(),
601+
res.strip_backtrace(),
569602
"Error during planning: Err \"extra1\" \"extra2\""
570603
);
571604

572605
let res: Result<(), DataFusionError> = plan_err!("Err {extra1} {extra2}");
573606
let res = res.unwrap_err();
574-
assert_eq!(res.to_string(), "Error during planning: Err extra1 extra2");
607+
assert_eq!(
608+
res.strip_backtrace(),
609+
"Error during planning: Err extra1 extra2"
610+
);
575611

576612
let res: Result<(), DataFusionError> = plan_err!("Err {extra1:?} {extra2:#?}");
577613
let res = res.unwrap_err();
578614
assert_eq!(
579-
res.to_string(),
615+
res.strip_backtrace(),
580616
"Error during planning: Err \"extra1\" \"extra2\""
581617
);
582618
}
@@ -599,7 +635,7 @@ mod test {
599635
let e = e.find_root();
600636

601637
// DataFusionError does not implement Eq, so we use a string comparison + some cheap "same variant" test instead
602-
assert_eq!(e.to_string(), exp.to_string(),);
638+
assert_eq!(e.strip_backtrace(), exp.strip_backtrace());
603639
assert_eq!(std::mem::discriminant(e), std::mem::discriminant(&exp),)
604640
}
605641
}

datafusion/common/src/scalar.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -3265,7 +3265,10 @@ mod tests {
32653265
fn scalar_sub_trait_int32_overflow_test() {
32663266
let int_value = ScalarValue::Int32(Some(i32::MAX));
32673267
let int_value_2 = ScalarValue::Int32(Some(i32::MIN));
3268-
let err = int_value.sub_checked(&int_value_2).unwrap_err().to_string();
3268+
let err = int_value
3269+
.sub_checked(&int_value_2)
3270+
.unwrap_err()
3271+
.strip_backtrace();
32693272
assert_eq!(
32703273
err,
32713274
"Arrow error: Compute error: Overflow happened on: 2147483647 - -2147483648"
@@ -3285,7 +3288,10 @@ mod tests {
32853288
fn scalar_sub_trait_int64_overflow_test() {
32863289
let int_value = ScalarValue::Int64(Some(i64::MAX));
32873290
let int_value_2 = ScalarValue::Int64(Some(i64::MIN));
3288-
let err = int_value.sub_checked(&int_value_2).unwrap_err().to_string();
3291+
let err = int_value
3292+
.sub_checked(&int_value_2)
3293+
.unwrap_err()
3294+
.strip_backtrace();
32893295
assert_eq!(err, "Arrow error: Compute error: Overflow happened on: 9223372036854775807 - -9223372036854775808")
32903296
}
32913297

datafusion/core/src/catalog/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ mod tests {
237237

238238
match catalog.register_schema("foo", schema) {
239239
Ok(_) => panic!("unexpected OK"),
240-
Err(e) => assert_eq!(e.to_string(), "This feature is not implemented: Registering new schemas is not supported"),
240+
Err(e) => assert_eq!(e.strip_backtrace(), "This feature is not implemented: Registering new schemas is not supported"),
241241
};
242242
}
243243

datafusion/core/src/dataframe.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ mod tests {
15021502
// try to sort on some value not present in input to distinct
15031503
.sort(vec![col("c2").sort(true, true)])
15041504
.unwrap_err();
1505-
assert_eq!(err.to_string(), "Error during planning: For SELECT DISTINCT, ORDER BY expressions c2 must appear in select list");
1505+
assert_eq!(err.strip_backtrace(), "Error during planning: For SELECT DISTINCT, ORDER BY expressions c2 must appear in select list");
15061506

15071507
Ok(())
15081508
}
@@ -1560,7 +1560,7 @@ mod tests {
15601560
.join_on(right, JoinType::Inner, [col("c1").eq(col("c1"))])
15611561
.expect_err("join didn't fail check");
15621562
let expected = "Schema error: Ambiguous reference to unqualified field c1";
1563-
assert_eq!(join.to_string(), expected);
1563+
assert_eq!(join.strip_backtrace(), expected);
15641564

15651565
Ok(())
15661566
}
@@ -1917,7 +1917,7 @@ mod tests {
19171917
.with_column_renamed("c2", "AAA")
19181918
.unwrap_err();
19191919
let expected_err = "Schema error: Ambiguous reference to unqualified field c2";
1920-
assert_eq!(actual_err.to_string(), expected_err);
1920+
assert_eq!(actual_err.strip_backtrace(), expected_err);
19211921

19221922
Ok(())
19231923
}

datafusion/core/src/datasource/listing/table.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1862,7 +1862,8 @@ mod tests {
18621862
)
18631863
.await
18641864
.expect_err("Example should fail!");
1865-
assert_eq!("Invalid or Unsupported Configuration: zstd compression requires specifying a level such as zstd(4)", format!("{e}"));
1865+
assert_eq!(e.strip_backtrace(), "Invalid or Unsupported Configuration: zstd compression requires specifying a level such as zstd(4)");
1866+
18661867
Ok(())
18671868
}
18681869

datafusion/core/src/datasource/memory.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,11 @@ mod tests {
435435
],
436436
)?;
437437

438-
match MemTable::try_new(schema2, vec![vec![batch]]) {
439-
Err(DataFusionError::Plan(e)) => {
440-
assert_eq!("\"Mismatch between schema and batches\"", format!("{e:?}"))
441-
}
442-
_ => panic!("MemTable::new should have failed due to schema mismatch"),
443-
}
438+
let e = MemTable::try_new(schema2, vec![vec![batch]]).unwrap_err();
439+
assert_eq!(
440+
"Error during planning: Mismatch between schema and batches",
441+
e.strip_backtrace()
442+
);
444443

445444
Ok(())
446445
}
@@ -466,12 +465,11 @@ mod tests {
466465
],
467466
)?;
468467

469-
match MemTable::try_new(schema2, vec![vec![batch]]) {
470-
Err(DataFusionError::Plan(e)) => {
471-
assert_eq!("\"Mismatch between schema and batches\"", format!("{e:?}"))
472-
}
473-
_ => panic!("MemTable::new should have failed due to schema mismatch"),
474-
}
468+
let e = MemTable::try_new(schema2, vec![vec![batch]]).unwrap_err();
469+
assert_eq!(
470+
"Error during planning: Mismatch between schema and batches",
471+
e.strip_backtrace()
472+
);
475473

476474
Ok(())
477475
}

datafusion/core/src/datasource/physical_plan/csv.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,7 @@ mod tests {
835835

836836
// errors due to https://github.com/apache/arrow-datafusion/issues/4918
837837
let mut it = csv.execute(0, task_ctx)?;
838-
let err = it.next().await.unwrap().unwrap_err().to_string();
838+
let err = it.next().await.unwrap().unwrap_err().strip_backtrace();
839839
assert_eq!(
840840
err,
841841
"Arrow error: Csv error: incorrect number of fields for line 1, expected 14 got 13"
@@ -1075,7 +1075,7 @@ mod tests {
10751075
.write_csv(out_dir_url, DataFrameWriteOptions::new(), None)
10761076
.await
10771077
.expect_err("should fail because input file does not match inferred schema");
1078-
assert_eq!("Arrow error: Parser error: Error while parsing value d for column 0 at line 4", format!("{e}"));
1078+
assert_eq!(e.strip_backtrace(), "Arrow error: Parser error: Error while parsing value d for column 0 at line 4");
10791079
Ok(())
10801080
}
10811081

datafusion/core/src/datasource/physical_plan/json.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ mod tests {
794794
.write_json(out_dir_url, DataFrameWriteOptions::new())
795795
.await
796796
.expect_err("should fail because input file does not match inferred schema");
797-
assert_eq!("Arrow error: Parser error: Error while parsing value d for column 0 at line 4", format!("{e}"));
797+
assert_eq!(e.strip_backtrace(), "Arrow error: Parser error: Error while parsing value d for column 0 at line 4");
798798
Ok(())
799799
}
800800

datafusion/core/src/datasource/physical_plan/parquet.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ mod tests {
936936
.write_parquet(out_dir_url, DataFrameWriteOptions::new(), None)
937937
.await
938938
.expect_err("should fail because input file does not match inferred schema");
939-
assert_eq!("Arrow error: Parser error: Error while parsing value d for column 0 at line 4", format!("{e}"));
939+
assert_eq!(e.strip_backtrace(), "Arrow error: Parser error: Error while parsing value d for column 0 at line 4");
940940
Ok(())
941941
}
942942

0 commit comments

Comments
 (0)