From 45c5d69b2298bf54db29810149587a28691063ed Mon Sep 17 00:00:00 2001 From: Yoav Cohen <59807311+yoavcloud@users.noreply.github.com> Date: Sun, 20 Oct 2024 22:29:55 +0200 Subject: [PATCH] MsSQL TRY_CONVERT (#1477) --- src/ast/mod.rs | 6 +++++- src/dialect/generic.rs | 4 ++++ src/dialect/mod.rs | 5 +++++ src/dialect/mssql.rs | 4 ++++ src/keywords.rs | 1 + src/parser/mod.rs | 12 ++++++++---- tests/sqlparser_common.rs | 11 +++++++++++ tests/sqlparser_mssql.rs | 2 ++ 8 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2e46722f..4900307e 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 92d720a0..0a5464c9 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 b34e2208..be97f929 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 cace372c..78ec621e 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 001de748..6182ae17 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 5bd64392..a1079f6f 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 55725eec..5683bcf9 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 ef89a476..58765f6c 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());