Skip to content

Commit 45c5d69

Browse files
authored
MsSQL TRY_CONVERT (#1477)
1 parent 3421e1e commit 45c5d69

File tree

8 files changed

+40
-5
lines changed

8 files changed

+40
-5
lines changed

src/ast/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,9 @@ pub enum Expr {
669669
},
670670
/// CONVERT a value to a different data type or character encoding. e.g. `CONVERT(foo USING utf8mb4)`
671671
Convert {
672+
/// CONVERT (false) or TRY_CONVERT (true)
673+
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/try-convert-transact-sql?view=sql-server-ver16>
674+
is_try: bool,
672675
/// The expression to convert
673676
expr: Box<Expr>,
674677
/// The target data type
@@ -1371,13 +1374,14 @@ impl fmt::Display for Expr {
13711374
}
13721375
}
13731376
Expr::Convert {
1377+
is_try,
13741378
expr,
13751379
target_before_value,
13761380
data_type,
13771381
charset,
13781382
styles,
13791383
} => {
1380-
write!(f, "CONVERT(")?;
1384+
write!(f, "{}CONVERT(", if *is_try { "TRY_" } else { "" })?;
13811385
if let Some(data_type) = data_type {
13821386
if let Some(charset) = charset {
13831387
write!(f, "{expr}, {data_type} CHARACTER SET {charset}")

src/dialect/generic.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,8 @@ impl Dialect for GenericDialect {
107107
fn supports_asc_desc_in_column_definition(&self) -> bool {
108108
true
109109
}
110+
111+
fn supports_try_convert(&self) -> bool {
112+
true
113+
}
110114
}

src/dialect/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@ pub trait Dialect: Debug + Any {
585585
fn supports_eq_alias_assigment(&self) -> bool {
586586
false
587587
}
588+
589+
/// Returns true if this dialect supports the `TRY_CONVERT` function
590+
fn supports_try_convert(&self) -> bool {
591+
false
592+
}
588593
}
589594

590595
/// This represents the operators for which precedence must be defined

src/dialect/mssql.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ impl Dialect for MsSqlDialect {
5353
fn supports_eq_alias_assigment(&self) -> bool {
5454
true
5555
}
56+
57+
fn supports_try_convert(&self) -> bool {
58+
true
59+
}
5660
}

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ define_keywords!(
771771
TRUE,
772772
TRUNCATE,
773773
TRY_CAST,
774+
TRY_CONVERT,
774775
TUPLE,
775776
TYPE,
776777
UESCAPE,

src/parser/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,8 @@ impl<'a> Parser<'a> {
10231023
self.parse_time_functions(ObjectName(vec![w.to_ident()]))
10241024
}
10251025
Keyword::CASE => self.parse_case_expr(),
1026-
Keyword::CONVERT => self.parse_convert_expr(),
1026+
Keyword::CONVERT => self.parse_convert_expr(false),
1027+
Keyword::TRY_CONVERT if self.dialect.supports_try_convert() => self.parse_convert_expr(true),
10271028
Keyword::CAST => self.parse_cast_expr(CastKind::Cast),
10281029
Keyword::TRY_CAST => self.parse_cast_expr(CastKind::TryCast),
10291030
Keyword::SAFE_CAST => self.parse_cast_expr(CastKind::SafeCast),
@@ -1614,7 +1615,7 @@ impl<'a> Parser<'a> {
16141615
}
16151616

16161617
/// mssql-like convert function
1617-
fn parse_mssql_convert(&mut self) -> Result<Expr, ParserError> {
1618+
fn parse_mssql_convert(&mut self, is_try: bool) -> Result<Expr, ParserError> {
16181619
self.expect_token(&Token::LParen)?;
16191620
let data_type = self.parse_data_type()?;
16201621
self.expect_token(&Token::Comma)?;
@@ -1626,6 +1627,7 @@ impl<'a> Parser<'a> {
16261627
};
16271628
self.expect_token(&Token::RParen)?;
16281629
Ok(Expr::Convert {
1630+
is_try,
16291631
expr: Box::new(expr),
16301632
data_type: Some(data_type),
16311633
charset: None,
@@ -1638,16 +1640,17 @@ impl<'a> Parser<'a> {
16381640
/// - `CONVERT('héhé' USING utf8mb4)` (MySQL)
16391641
/// - `CONVERT('héhé', CHAR CHARACTER SET utf8mb4)` (MySQL)
16401642
/// - `CONVERT(DECIMAL(10, 5), 42)` (MSSQL) - the type comes first
1641-
pub fn parse_convert_expr(&mut self) -> Result<Expr, ParserError> {
1643+
pub fn parse_convert_expr(&mut self, is_try: bool) -> Result<Expr, ParserError> {
16421644
if self.dialect.convert_type_before_value() {
1643-
return self.parse_mssql_convert();
1645+
return self.parse_mssql_convert(is_try);
16441646
}
16451647
self.expect_token(&Token::LParen)?;
16461648
let expr = self.parse_expr()?;
16471649
if self.parse_keyword(Keyword::USING) {
16481650
let charset = self.parse_object_name(false)?;
16491651
self.expect_token(&Token::RParen)?;
16501652
return Ok(Expr::Convert {
1653+
is_try,
16511654
expr: Box::new(expr),
16521655
data_type: None,
16531656
charset: Some(charset),
@@ -1664,6 +1667,7 @@ impl<'a> Parser<'a> {
16641667
};
16651668
self.expect_token(&Token::RParen)?;
16661669
Ok(Expr::Convert {
1670+
is_try,
16671671
expr: Box::new(expr),
16681672
data_type: Some(data_type),
16691673
charset,

tests/sqlparser_common.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11449,3 +11449,14 @@ fn test_alias_equal_expr() {
1144911449
let expected = r#"SELECT x = (a * b) FROM some_table"#;
1145011450
let _ = dialects.one_statement_parses_to(sql, expected);
1145111451
}
11452+
11453+
#[test]
11454+
fn test_try_convert() {
11455+
let dialects =
11456+
all_dialects_where(|d| d.supports_try_convert() && d.convert_type_before_value());
11457+
dialects.verified_expr("TRY_CONVERT(VARCHAR(MAX), 'foo')");
11458+
11459+
let dialects =
11460+
all_dialects_where(|d| d.supports_try_convert() && !d.convert_type_before_value());
11461+
dialects.verified_expr("TRY_CONVERT('foo', VARCHAR(MAX))");
11462+
}

tests/sqlparser_mssql.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ fn parse_cast_varchar_max() {
464464
fn parse_convert() {
465465
let sql = "CONVERT(INT, 1, 2, 3, NULL)";
466466
let Expr::Convert {
467+
is_try,
467468
expr,
468469
data_type,
469470
charset,
@@ -473,6 +474,7 @@ fn parse_convert() {
473474
else {
474475
unreachable!()
475476
};
477+
assert!(!is_try);
476478
assert_eq!(Expr::Value(number("1")), *expr);
477479
assert_eq!(Some(DataType::Int(None)), data_type);
478480
assert!(charset.is_none());

0 commit comments

Comments
 (0)