Skip to content

Commit 13af5f3

Browse files
chore: Attach Diagnostic to "incompatible type in unary expression" error (#15209)
* chore: attach diagnostic to unary_op PLUS * remove condition and update test case
1 parent a7d8b97 commit 13af5f3

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

datafusion/sql/src/expr/unary_op.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// under the License.
1717

1818
use crate::planner::{ContextProvider, PlannerContext, SqlToRel};
19-
use datafusion_common::{not_impl_err, plan_err, DFSchema, Result};
19+
use datafusion_common::{not_impl_err, plan_err, DFSchema, Diagnostic, Result};
2020
use datafusion_expr::{
2121
type_coercion::{is_interval, is_timestamp},
2222
Expr, ExprSchemable,
@@ -45,7 +45,16 @@ impl<S: ContextProvider> SqlToRel<'_, S> {
4545
{
4646
Ok(operand)
4747
} else {
48-
plan_err!("Unary operator '+' only supports numeric, interval and timestamp types")
48+
plan_err!("Unary operator '+' only supports numeric, interval and timestamp types").map_err(|e| {
49+
let span = operand.spans().and_then(|s| s.first());
50+
let mut diagnostic = Diagnostic::new_error(
51+
format!("+ cannot be used with {data_type}"),
52+
span
53+
);
54+
diagnostic.add_note("+ can only be used with numbers, intervals, and timestamps", None);
55+
diagnostic.add_help(format!("perhaps you need to cast {operand}"), None);
56+
e.with_diagnostic(diagnostic)
57+
})
4958
}
5059
}
5160
UnaryOperator::Minus => {

datafusion/sql/tests/cases/diagnostic.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,44 @@ fn test_in_subquery_multiple_columns() -> Result<(), Box<dyn std::error::Error>>
351351
.collect::<Vec<_>>(),
352352
vec!["Select only one column in the subquery"]
353353
);
354+
Ok(())
355+
}
354356

357+
#[test]
358+
fn test_unary_op_plus_with_column() -> Result<()> {
359+
// Test with a direct query that references a column with an incompatible type
360+
let query = "SELECT +/*whole*/first_name/*whole*/ FROM person";
361+
let spans = get_spans(query);
362+
let diag = do_query(query);
363+
assert_eq!(diag.message, "+ cannot be used with Utf8");
364+
assert_eq!(diag.span, Some(spans["whole"]));
365+
assert_eq!(
366+
diag.notes[0].message,
367+
"+ can only be used with numbers, intervals, and timestamps"
368+
);
369+
assert_eq!(
370+
diag.helps[0].message,
371+
"perhaps you need to cast person.first_name"
372+
);
373+
Ok(())
374+
}
375+
376+
#[test]
377+
fn test_unary_op_plus_with_non_column() -> Result<()> {
378+
// create a table with a column of type varchar
379+
let query = "SELECT +'a'";
380+
let diag = do_query(query);
381+
assert_eq!(diag.message, "+ cannot be used with Utf8");
382+
assert_eq!(
383+
diag.notes[0].message,
384+
"+ can only be used with numbers, intervals, and timestamps"
385+
);
386+
assert_eq!(diag.notes[0].span, None);
387+
assert_eq!(
388+
diag.helps[0].message,
389+
"perhaps you need to cast Utf8(\"a\")"
390+
);
391+
assert_eq!(diag.helps[0].span, None);
392+
assert_eq!(diag.span, None);
355393
Ok(())
356394
}

0 commit comments

Comments
 (0)