MsSQL TRY_CONVERT (#1477)

This commit is contained in:
Yoav Cohen 2024-10-20 22:29:55 +02:00 committed by GitHub
parent 3421e1e4d4
commit 45c5d69b22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 40 additions and 5 deletions

View file

@ -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
@ -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}")

View file

@ -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
}
}

View file

@ -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

View file

@ -53,4 +53,8 @@ impl Dialect for MsSqlDialect {
fn supports_eq_alias_assigment(&self) -> bool {
true
}
fn supports_try_convert(&self) -> bool {
true
}
}

View file

@ -771,6 +771,7 @@ define_keywords!(
TRUE,
TRUNCATE,
TRY_CAST,
TRY_CONVERT,
TUPLE,
TYPE,
UESCAPE,

View file

@ -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<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)?;
@ -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<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()?;
@ -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,

View file

@ -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))");
}

View file

@ -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());