Skip to content

Commit

Permalink
MsSQL TRY_CONVERT (#1477)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoavcloud authored Oct 20, 2024
1 parent 3421e1e commit 45c5d69
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 5 deletions.
6 changes: 5 additions & 1 deletion src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/try-convert-transact-sql?view=sql-server-ver16>
is_try: bool,
/// The expression to convert
expr: Box<Expr>,
/// The target data type
Expand Down Expand Up @@ -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}")
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
5 changes: 5 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ impl Dialect for MsSqlDialect {
fn supports_eq_alias_assigment(&self) -> bool {
true
}

fn supports_try_convert(&self) -> bool {
true
}
}
1 change: 1 addition & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,7 @@ define_keywords!(
TRUE,
TRUNCATE,
TRY_CAST,
TRY_CONVERT,
TUPLE,
TYPE,
UESCAPE,
Expand Down
12 changes: 8 additions & 4 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -1614,7 +1615,7 @@ impl<'a> Parser<'a> {
}

/// mssql-like convert function
fn parse_mssql_convert(&mut self) -> Result<Expr, ParserError> {
fn parse_mssql_convert(&mut self, is_try: bool) -> Result<Expr, ParserError> {
self.expect_token(&Token::LParen)?;
let data_type = self.parse_data_type()?;
self.expect_token(&Token::Comma)?;
Expand All @@ -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,
Expand All @@ -1638,16 +1640,17 @@ 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<Expr, ParserError> {
pub fn parse_convert_expr(&mut self, is_try: bool) -> Result<Expr, ParserError> {
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()?;
if self.parse_keyword(Keyword::USING) {
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),
Expand All @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))");
}
2 changes: 2 additions & 0 deletions tests/sqlparser_mssql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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());
Expand Down

0 comments on commit 45c5d69

Please sign in to comment.