Implement TRY_CAST (#299)

Adds support for `TRY_CAST` and fixes a clippy error
This commit is contained in:
Mike Seddon 2021-03-22 09:26:16 +11:00 committed by GitHub
parent 43fef23bc8
commit e6e37b47db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 7 deletions

View file

@ -201,6 +201,12 @@ pub enum Expr {
expr: Box<Expr>, expr: Box<Expr>,
data_type: DataType, data_type: DataType,
}, },
/// TRY_CAST an expression to a different data type e.g. `TRY_CAST(foo AS VARCHAR(123))`
// this differs from CAST in the choice of how to implement invalid conversions
TryCast {
expr: Box<Expr>,
data_type: DataType,
},
/// EXTRACT(DateTimeField FROM <expr>) /// EXTRACT(DateTimeField FROM <expr>)
Extract { Extract {
field: DateTimeField, field: DateTimeField,
@ -309,6 +315,7 @@ impl fmt::Display for Expr {
} }
} }
Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type), Expr::Cast { expr, data_type } => write!(f, "CAST({} AS {})", expr, data_type),
Expr::TryCast { expr, data_type } => write!(f, "TRY_CAST({} AS {})", expr, data_type),
Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr), Expr::Extract { field, expr } => write!(f, "EXTRACT({} FROM {})", field, expr),
Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation), Expr::Collate { expr, collation } => write!(f, "{} COLLATE {}", expr, collation),
Expr::Nested(ast) => write!(f, "({})", ast), Expr::Nested(ast) => write!(f, "({})", ast),

View file

@ -456,6 +456,7 @@ define_keywords!(
TRIM_ARRAY, TRIM_ARRAY,
TRUE, TRUE,
TRUNCATE, TRUNCATE,
TRY_CAST,
UESCAPE, UESCAPE,
UNBOUNDED, UNBOUNDED,
UNCOMMITTED, UNCOMMITTED,

View file

@ -352,6 +352,7 @@ impl<'a> Parser<'a> {
} }
Keyword::CASE => self.parse_case_expr(), Keyword::CASE => self.parse_case_expr(),
Keyword::CAST => self.parse_cast_expr(), Keyword::CAST => self.parse_cast_expr(),
Keyword::TRY_CAST => self.parse_try_cast_expr(),
Keyword::EXISTS => self.parse_exists_expr(), Keyword::EXISTS => self.parse_exists_expr(),
Keyword::EXTRACT => self.parse_extract_expr(), Keyword::EXTRACT => self.parse_extract_expr(),
Keyword::SUBSTRING => self.parse_substring_expr(), Keyword::SUBSTRING => self.parse_substring_expr(),
@ -591,6 +592,19 @@ impl<'a> Parser<'a> {
}) })
} }
/// Parse a SQL TRY_CAST function e.g. `TRY_CAST(expr AS FLOAT)`
pub fn parse_try_cast_expr(&mut self) -> Result<Expr, ParserError> {
self.expect_token(&Token::LParen)?;
let expr = self.parse_expr()?;
self.expect_keyword(Keyword::AS)?;
let data_type = self.parse_data_type()?;
self.expect_token(&Token::RParen)?;
Ok(Expr::TryCast {
expr: Box::new(expr),
data_type,
})
}
/// Parse a SQL EXISTS expression e.g. `WHERE EXISTS(SELECT ...)`. /// Parse a SQL EXISTS expression e.g. `WHERE EXISTS(SELECT ...)`.
pub fn parse_exists_expr(&mut self) -> Result<Expr, ParserError> { pub fn parse_exists_expr(&mut self) -> Result<Expr, ParserError> {
self.expect_token(&Token::LParen)?; self.expect_token(&Token::LParen)?;
@ -1806,7 +1820,7 @@ impl<'a> Parser<'a> {
let columns = self.parse_parenthesized_column_list(Optional)?; let columns = self.parse_parenthesized_column_list(Optional)?;
self.expect_keywords(&[Keyword::FROM, Keyword::STDIN])?; self.expect_keywords(&[Keyword::FROM, Keyword::STDIN])?;
self.expect_token(&Token::SemiColon)?; self.expect_token(&Token::SemiColon)?;
let values = self.parse_tsv()?; let values = self.parse_tsv();
Ok(Statement::Copy { Ok(Statement::Copy {
table_name, table_name,
columns, columns,
@ -1816,12 +1830,11 @@ impl<'a> Parser<'a> {
/// Parse a tab separated values in /// Parse a tab separated values in
/// COPY payload /// COPY payload
fn parse_tsv(&mut self) -> Result<Vec<Option<String>>, ParserError> { fn parse_tsv(&mut self) -> Vec<Option<String>> {
let values = self.parse_tab_value()?; self.parse_tab_value()
Ok(values)
} }
fn parse_tab_value(&mut self) -> Result<Vec<Option<String>>, ParserError> { fn parse_tab_value(&mut self) -> Vec<Option<String>> {
let mut values = vec![]; let mut values = vec![];
let mut content = String::from(""); let mut content = String::from("");
while let Some(t) = self.next_token_no_skip() { while let Some(t) = self.next_token_no_skip() {
@ -1836,7 +1849,7 @@ impl<'a> Parser<'a> {
} }
Token::Backslash => { Token::Backslash => {
if self.consume_token(&Token::Period) { if self.consume_token(&Token::Period) {
return Ok(values); return values;
} }
if let Token::Word(w) = self.next_token() { if let Token::Word(w) = self.next_token() {
if w.value == "N" { if w.value == "N" {
@ -1849,7 +1862,7 @@ impl<'a> Parser<'a> {
} }
} }
} }
Ok(values) values
} }
/// Parse a literal value (numbers, strings, date/time, booleans) /// Parse a literal value (numbers, strings, date/time, booleans)

View file

@ -626,6 +626,7 @@ impl<'a> Tokenizer<'a> {
} }
} }
#[allow(clippy::unnecessary_wraps)]
fn consume_and_return( fn consume_and_return(
&self, &self,
chars: &mut Peekable<Chars<'_>>, chars: &mut Peekable<Chars<'_>>,

View file

@ -981,6 +981,35 @@ fn parse_cast() {
); );
} }
#[test]
fn parse_try_cast() {
let sql = "SELECT TRY_CAST(id AS BIGINT) FROM customer";
let select = verified_only_select(sql);
assert_eq!(
&Expr::TryCast {
expr: Box::new(Expr::Identifier(Ident::new("id"))),
data_type: DataType::BigInt
},
expr_from_projection(only(&select.projection))
);
one_statement_parses_to(
"SELECT TRY_CAST(id AS BIGINT) FROM customer",
"SELECT TRY_CAST(id AS BIGINT) FROM customer",
);
verified_stmt("SELECT TRY_CAST(id AS NUMERIC) FROM customer");
one_statement_parses_to(
"SELECT TRY_CAST(id AS DEC) FROM customer",
"SELECT TRY_CAST(id AS NUMERIC) FROM customer",
);
one_statement_parses_to(
"SELECT TRY_CAST(id AS DECIMAL) FROM customer",
"SELECT TRY_CAST(id AS NUMERIC) FROM customer",
);
}
#[test] #[test]
fn parse_extract() { fn parse_extract() {
let sql = "SELECT EXTRACT(YEAR FROM d)"; let sql = "SELECT EXTRACT(YEAR FROM d)";
@ -1224,6 +1253,7 @@ fn parse_assert() {
} }
#[test] #[test]
#[allow(clippy::collapsible_match)]
fn parse_assert_message() { fn parse_assert_message() {
let sql = "ASSERT (SELECT COUNT(*) FROM my_table) > 0 AS 'No rows in my_table'"; let sql = "ASSERT (SELECT COUNT(*) FROM my_table) > 0 AS 'No rows in my_table'";
let ast = one_statement_parses_to( let ast = one_statement_parses_to(