diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2e46722fa..4900307e5 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -669,6 +669,9 @@ pub enum Expr { }, /// CONVERT a value to a different data type or character encoding. e.g. `CONVERT(foo USING utf8mb4)` Convert { + /// CONVERT (false) or TRY_CONVERT (true) + /// + is_try: bool, /// The expression to convert expr: Box, /// The target data type @@ -1371,13 +1374,14 @@ impl fmt::Display for Expr { } } Expr::Convert { + is_try, expr, target_before_value, data_type, charset, styles, } => { - write!(f, "CONVERT(")?; + write!(f, "{}CONVERT(", if *is_try { "TRY_" } else { "" })?; if let Some(data_type) = data_type { if let Some(charset) = charset { write!(f, "{expr}, {data_type} CHARACTER SET {charset}") diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 92d720a0b..0a5464c9c 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -107,4 +107,8 @@ impl Dialect for GenericDialect { fn supports_asc_desc_in_column_definition(&self) -> bool { true } + + fn supports_try_convert(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index b34e22081..be97f929b 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -585,6 +585,11 @@ pub trait Dialect: Debug + Any { fn supports_eq_alias_assigment(&self) -> bool { false } + + /// Returns true if this dialect supports the `TRY_CONVERT` function + fn supports_try_convert(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index cace372c0..78ec621ed 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -53,4 +53,8 @@ impl Dialect for MsSqlDialect { fn supports_eq_alias_assigment(&self) -> bool { true } + + fn supports_try_convert(&self) -> bool { + true + } } diff --git a/src/keywords.rs b/src/keywords.rs index 001de7484..6182ae176 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -771,6 +771,7 @@ define_keywords!( TRUE, TRUNCATE, TRY_CAST, + TRY_CONVERT, TUPLE, TYPE, UESCAPE, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5bd64392a..a1079f6f7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1023,7 +1023,8 @@ impl<'a> Parser<'a> { self.parse_time_functions(ObjectName(vec![w.to_ident()])) } Keyword::CASE => self.parse_case_expr(), - Keyword::CONVERT => self.parse_convert_expr(), + Keyword::CONVERT => self.parse_convert_expr(false), + Keyword::TRY_CONVERT if self.dialect.supports_try_convert() => self.parse_convert_expr(true), Keyword::CAST => self.parse_cast_expr(CastKind::Cast), Keyword::TRY_CAST => self.parse_cast_expr(CastKind::TryCast), Keyword::SAFE_CAST => self.parse_cast_expr(CastKind::SafeCast), @@ -1614,7 +1615,7 @@ impl<'a> Parser<'a> { } /// mssql-like convert function - fn parse_mssql_convert(&mut self) -> Result { + fn parse_mssql_convert(&mut self, is_try: bool) -> Result { self.expect_token(&Token::LParen)?; let data_type = self.parse_data_type()?; self.expect_token(&Token::Comma)?; @@ -1626,6 +1627,7 @@ impl<'a> Parser<'a> { }; self.expect_token(&Token::RParen)?; Ok(Expr::Convert { + is_try, expr: Box::new(expr), data_type: Some(data_type), charset: None, @@ -1638,9 +1640,9 @@ impl<'a> Parser<'a> { /// - `CONVERT('héhé' USING utf8mb4)` (MySQL) /// - `CONVERT('héhé', CHAR CHARACTER SET utf8mb4)` (MySQL) /// - `CONVERT(DECIMAL(10, 5), 42)` (MSSQL) - the type comes first - pub fn parse_convert_expr(&mut self) -> Result { + pub fn parse_convert_expr(&mut self, is_try: bool) -> Result { if self.dialect.convert_type_before_value() { - return self.parse_mssql_convert(); + return self.parse_mssql_convert(is_try); } self.expect_token(&Token::LParen)?; let expr = self.parse_expr()?; @@ -1648,6 +1650,7 @@ impl<'a> Parser<'a> { let charset = self.parse_object_name(false)?; self.expect_token(&Token::RParen)?; return Ok(Expr::Convert { + is_try, expr: Box::new(expr), data_type: None, charset: Some(charset), @@ -1664,6 +1667,7 @@ impl<'a> Parser<'a> { }; self.expect_token(&Token::RParen)?; Ok(Expr::Convert { + is_try, expr: Box::new(expr), data_type: Some(data_type), charset, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 55725eeca..5683bcf91 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -11449,3 +11449,14 @@ fn test_alias_equal_expr() { let expected = r#"SELECT x = (a * b) FROM some_table"#; let _ = dialects.one_statement_parses_to(sql, expected); } + +#[test] +fn test_try_convert() { + let dialects = + all_dialects_where(|d| d.supports_try_convert() && d.convert_type_before_value()); + dialects.verified_expr("TRY_CONVERT(VARCHAR(MAX), 'foo')"); + + let dialects = + all_dialects_where(|d| d.supports_try_convert() && !d.convert_type_before_value()); + dialects.verified_expr("TRY_CONVERT('foo', VARCHAR(MAX))"); +} diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index ef89a4768..58765f6c0 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -464,6 +464,7 @@ fn parse_cast_varchar_max() { fn parse_convert() { let sql = "CONVERT(INT, 1, 2, 3, NULL)"; let Expr::Convert { + is_try, expr, data_type, charset, @@ -473,6 +474,7 @@ fn parse_convert() { else { unreachable!() }; + assert!(!is_try); assert_eq!(Expr::Value(number("1")), *expr); assert_eq!(Some(DataType::Int(None)), data_type); assert!(charset.is_none());