Skip to content

Commit e6e37b4

Browse files
authored
Implement TRY_CAST (#299)
Adds support for `TRY_CAST` and fixes a clippy error
1 parent 43fef23 commit e6e37b4

File tree

5 files changed

+59
-7
lines changed

5 files changed

+59
-7
lines changed

src/ast/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ pub enum Expr {
201201
expr: Box<Expr>,
202202
data_type: DataType,
203203
},
204+
/// TRY_CAST an expression to a different data type e.g. `TRY_CAST(foo AS VARCHAR(123))`
205+
// this differs from CAST in the choice of how to implement invalid conversions
206+
TryCast {
207+
expr: Box<Expr>,
208+
data_type: DataType,
209+
},
204210
/// EXTRACT(DateTimeField FROM <expr>)
205211
Extract {
206212
field: DateTimeField,
@@ -309,6 +315,7 @@ impl fmt::Display for Expr {
309315
}
310316
}
311317
Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
318+
Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({} AS {})", expr, data_type),
312319
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
313320
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
314321
Expr::Nested(ast) => write!(f, "({})", ast),

src/dialect/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ define_keywords!(
456456
TRIM_ARRAY,
457457
TRUE,
458458
TRUNCATE,
459+
TRY_CAST,
459460
UESCAPE,
460461
UNBOUNDED,
461462
UNCOMMITTED,

src/parser.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ impl<'a> Parser<'a> {
352352
}
353353
Keyword::CASE => self.parse_case_expr(),
354354
Keyword::CAST => self.parse_cast_expr(),
355+
Keyword::TRY_CAST => self.parse_try_cast_expr(),
355356
Keyword::EXISTS => self.parse_exists_expr(),
356357
Keyword::EXTRACT => self.parse_extract_expr(),
357358
Keyword::SUBSTRING => self.parse_substring_expr(),
@@ -591,6 +592,19 @@ impl<'a> Parser<'a> {
591592
})
592593
}
593594

595+
/// Parse a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)`
596+
pub fn parse_try_cast_expr(&mut self) -> Result<Expr, ParserError> {
597+
self.expect_token(&Token::LParen)?;
598+
let expr = self.parse_expr()?;
599+
self.expect_keyword(Keyword::AS)?;
600+
let data_type = self.parse_data_type()?;
601+
self.expect_token(&Token::RParen)?;
602+
Ok(Expr::TryCast {
603+
expr: Box::new(expr),
604+
data_type,
605+
})
606+
}
607+
594608
/// Parse a SQL EXISTS expression e.g. `WHERE EXISTS(SELECT ...)`.
595609
pub fn parse_exists_expr(&mut self) -> Result<Expr, ParserError> {
596610
self.expect_token(&Token::LParen)?;
@@ -1806,7 +1820,7 @@ impl<'a> Parser<'a> {
18061820
let columns = self.parse_parenthesized_column_list(Optional)?;
18071821
self.expect_keywords(&[Keyword::FROM, Keyword::STDIN])?;
18081822
self.expect_token(&Token::SemiColon)?;
1809-
let values = self.parse_tsv()?;
1823+
let values = self.parse_tsv();
18101824
Ok(Statement::Copy {
18111825
table_name,
18121826
columns,
@@ -1816,12 +1830,11 @@ impl<'a> Parser<'a> {
18161830

18171831
/// Parse a tab separated values in
18181832
/// COPY payload
1819-
fn parse_tsv(&mut self) -> Result<Vec<Option<String>>, ParserError> {
1820-
let values = self.parse_tab_value()?;
1821-
Ok(values)
1833+
fn parse_tsv(&mut self) -> Vec<Option<String>> {
1834+
self.parse_tab_value()
18221835
}
18231836

1824-
fn parse_tab_value(&mut self) -> Result<Vec<Option<String>>, ParserError> {
1837+
fn parse_tab_value(&mut self) -> Vec<Option<String>> {
18251838
let mut values = vec![];
18261839
let mut content = String::from("");
18271840
while let Some(t) = self.next_token_no_skip() {
@@ -1836,7 +1849,7 @@ impl<'a> Parser<'a> {
18361849
}
18371850
Token::Backslash => {
18381851
if self.consume_token(&Token::Period) {
1839-
return Ok(values);
1852+
return values;
18401853
}
18411854
if let Token::Word(w) = self.next_token() {
18421855
if w.value == "N" {
@@ -1849,7 +1862,7 @@ impl<'a> Parser<'a> {
18491862
}
18501863
}
18511864
}
1852-
Ok(values)
1865+
values
18531866
}
18541867

18551868
/// Parse a literal value (numbers, strings, date/time, booleans)

src/tokenizer.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ impl<'a> Tokenizer<'a> {
626626
}
627627
}
628628

629+
#[allow(clippy::unnecessary_wraps)]
629630
fn consume_and_return(
630631
&self,
631632
chars: &mut Peekable<Chars<'_>>,

tests/sqlparser_common.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,35 @@ fn parse_cast() {
981981
);
982982
}
983983

984+
#[test]
985+
fn parse_try_cast() {
986+
let sql = "SELECT TRY_CAST(id AS BIGINT) FROM customer";
987+
let select = verified_only_select(sql);
988+
assert_eq!(
989+
&Expr::TryCast {
990+
expr: Box::new(Expr::Identifier(Ident::new("id"))),
991+
data_type: DataType::BigInt
992+
},
993+
expr_from_projection(only(&select.projection))
994+
);
995+
one_statement_parses_to(
996+
"SELECT TRY_CAST(id AS BIGINT) FROM customer",
997+
"SELECT TRY_CAST(id AS BIGINT) FROM customer",
998+
);
999+
1000+
verified_stmt("SELECT TRY_CAST(id AS NUMERIC) FROM customer");
1001+
1002+
one_statement_parses_to(
1003+
"SELECT TRY_CAST(id AS DEC) FROM customer",
1004+
"SELECT TRY_CAST(id AS NUMERIC) FROM customer",
1005+
);
1006+
1007+
one_statement_parses_to(
1008+
"SELECT TRY_CAST(id AS DECIMAL) FROM customer",
1009+
"SELECT TRY_CAST(id AS NUMERIC) FROM customer",
1010+
);
1011+
}
1012+
9841013
#[test]
9851014
fn parse_extract() {
9861015
let sql = "SELECT EXTRACT(YEAR FROM d)";
@@ -1224,6 +1253,7 @@ fn parse_assert() {
12241253
}
12251254

12261255
#[test]
1256+
#[allow(clippy::collapsible_match)]
12271257
fn parse_assert_message() {
12281258
let sql = "ASSERT (SELECT COUNT(*) FROM my_table) > 0 AS 'No rows in my_table'";
12291259
let ast = one_statement_parses_to(

0 commit comments

Comments
 (0)