mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-21 14:40:18 +00:00
Implement TRY_CAST (#299)
Adds support for `TRY_CAST` and fixes a clippy error
This commit is contained in:
parent
43fef23bc8
commit
e6e37b47db
5 changed files with 59 additions and 7 deletions
|
@ -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),
|
||||||
|
|
|
@ -456,6 +456,7 @@ define_keywords!(
|
||||||
TRIM_ARRAY,
|
TRIM_ARRAY,
|
||||||
TRUE,
|
TRUE,
|
||||||
TRUNCATE,
|
TRUNCATE,
|
||||||
|
TRY_CAST,
|
||||||
UESCAPE,
|
UESCAPE,
|
||||||
UNBOUNDED,
|
UNBOUNDED,
|
||||||
UNCOMMITTED,
|
UNCOMMITTED,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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<'_>>,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue