diff --git a/datafusion/sql/src/expr/unary_op.rs b/datafusion/sql/src/expr/unary_op.rs index a4096ec2355b..a4a973a74aa2 100644 --- a/datafusion/sql/src/expr/unary_op.rs +++ b/datafusion/sql/src/expr/unary_op.rs @@ -16,7 +16,7 @@ // under the License. use crate::planner::{ContextProvider, PlannerContext, SqlToRel}; -use datafusion_common::{not_impl_err, plan_err, DFSchema, Result}; +use datafusion_common::{not_impl_err, plan_err, DFSchema, Diagnostic, Result}; use datafusion_expr::{ type_coercion::{is_interval, is_timestamp}, Expr, ExprSchemable, @@ -45,7 +45,16 @@ impl SqlToRel<'_, S> { { Ok(operand) } else { - plan_err!("Unary operator '+' only supports numeric, interval and timestamp types") + plan_err!("Unary operator '+' only supports numeric, interval and timestamp types").map_err(|e| { + let span = operand.spans().and_then(|s| s.first()); + let mut diagnostic = Diagnostic::new_error( + format!("+ cannot be used with {data_type}"), + span + ); + diagnostic.add_note("+ can only be used with numbers, intervals, and timestamps", None); + diagnostic.add_help(format!("perhaps you need to cast {operand}"), None); + e.with_diagnostic(diagnostic) + }) } } UnaryOperator::Minus => { diff --git a/datafusion/sql/tests/cases/diagnostic.rs b/datafusion/sql/tests/cases/diagnostic.rs index 5481f046e70a..db3aad146711 100644 --- a/datafusion/sql/tests/cases/diagnostic.rs +++ b/datafusion/sql/tests/cases/diagnostic.rs @@ -351,6 +351,44 @@ fn test_in_subquery_multiple_columns() -> Result<(), Box> .collect::>(), vec!["Select only one column in the subquery"] ); + Ok(()) +} +#[test] +fn test_unary_op_plus_with_column() -> Result<()> { + // Test with a direct query that references a column with an incompatible type + let query = "SELECT +/*whole*/first_name/*whole*/ FROM person"; + let spans = get_spans(query); + let diag = do_query(query); + assert_eq!(diag.message, "+ cannot be used with Utf8"); + assert_eq!(diag.span, Some(spans["whole"])); + assert_eq!( + diag.notes[0].message, + "+ can only be used with numbers, intervals, and timestamps" + ); + assert_eq!( + diag.helps[0].message, + "perhaps you need to cast person.first_name" + ); + Ok(()) +} + +#[test] +fn test_unary_op_plus_with_non_column() -> Result<()> { + // create a table with a column of type varchar + let query = "SELECT +'a'"; + let diag = do_query(query); + assert_eq!(diag.message, "+ cannot be used with Utf8"); + assert_eq!( + diag.notes[0].message, + "+ can only be used with numbers, intervals, and timestamps" + ); + assert_eq!(diag.notes[0].span, None); + assert_eq!( + diag.helps[0].message, + "perhaps you need to cast Utf8(\"a\")" + ); + assert_eq!(diag.helps[0].span, None); + assert_eq!(diag.span, None); Ok(()) }