mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-27 16:09:09 +00:00
Support CONVERT
expressions (#1048)
This commit is contained in:
parent
c0c2d58910
commit
c905ee0cb8
7 changed files with 124 additions and 0 deletions
|
@ -473,6 +473,17 @@ pub enum Expr {
|
||||||
},
|
},
|
||||||
/// Unary operation e.g. `NOT foo`
|
/// Unary operation e.g. `NOT foo`
|
||||||
UnaryOp { op: UnaryOperator, expr: Box<Expr> },
|
UnaryOp { op: UnaryOperator, expr: Box<Expr> },
|
||||||
|
/// CONVERT a value to a different data type or character encoding `CONVERT(foo USING utf8mb4)`
|
||||||
|
Convert {
|
||||||
|
/// The expression to convert
|
||||||
|
expr: Box<Expr>,
|
||||||
|
/// The target data type
|
||||||
|
data_type: Option<DataType>,
|
||||||
|
/// The target character encoding
|
||||||
|
charset: Option<ObjectName>,
|
||||||
|
/// whether the target comes before the expr (MSSQL syntax)
|
||||||
|
target_before_value: bool,
|
||||||
|
},
|
||||||
/// CAST an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
|
/// CAST an expression to a different data type e.g. `CAST(foo AS VARCHAR(123))`
|
||||||
Cast {
|
Cast {
|
||||||
expr: Box<Expr>,
|
expr: Box<Expr>,
|
||||||
|
@ -844,6 +855,28 @@ impl fmt::Display for Expr {
|
||||||
write!(f, "{op}{expr}")
|
write!(f, "{op}{expr}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Expr::Convert {
|
||||||
|
expr,
|
||||||
|
target_before_value,
|
||||||
|
data_type,
|
||||||
|
charset,
|
||||||
|
} => {
|
||||||
|
write!(f, "CONVERT(")?;
|
||||||
|
if let Some(data_type) = data_type {
|
||||||
|
if let Some(charset) = charset {
|
||||||
|
write!(f, "{expr}, {data_type} CHARACTER SET {charset}")
|
||||||
|
} else if *target_before_value {
|
||||||
|
write!(f, "{data_type}, {expr}")
|
||||||
|
} else {
|
||||||
|
write!(f, "{expr}, {data_type}")
|
||||||
|
}
|
||||||
|
} else if let Some(charset) = charset {
|
||||||
|
write!(f, "{expr} USING {charset}")
|
||||||
|
} else {
|
||||||
|
write!(f, "{expr}") // This should never happen
|
||||||
|
}?;
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
Expr::Cast {
|
Expr::Cast {
|
||||||
expr,
|
expr,
|
||||||
data_type,
|
data_type,
|
||||||
|
|
|
@ -128,6 +128,11 @@ pub trait Dialect: Debug + Any {
|
||||||
fn supports_in_empty_list(&self) -> bool {
|
fn supports_in_empty_list(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
/// Returns true if the dialect has a CONVERT function which accepts a type first
|
||||||
|
/// and an expression second, e.g. `CONVERT(varchar, 1)`
|
||||||
|
fn convert_type_before_value(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
/// Dialect-specific prefix parser override
|
/// Dialect-specific prefix parser override
|
||||||
fn parse_prefix(&self, _parser: &mut Parser) -> Option<Result<Expr, ParserError>> {
|
fn parse_prefix(&self, _parser: &mut Parser) -> Option<Result<Expr, ParserError>> {
|
||||||
// return None to fall back to the default behavior
|
// return None to fall back to the default behavior
|
||||||
|
|
|
@ -35,6 +35,12 @@ impl Dialect for MsSqlDialect {
|
||||||
|| ch == '_'
|
|| ch == '_'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
||||||
|
/// <https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16>
|
||||||
|
fn convert_type_before_value(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn supports_substring_from_for_expr(&self) -> bool {
|
fn supports_substring_from_for_expr(&self) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,4 +53,10 @@ impl Dialect for RedshiftSqlDialect {
|
||||||
// Extends Postgres dialect with sharp
|
// Extends Postgres dialect with sharp
|
||||||
PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#'
|
PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)`
|
||||||
|
/// <https://docs.aws.amazon.com/redshift/latest/dg/r_CONVERT_function.html>
|
||||||
|
fn convert_type_before_value(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -821,6 +821,7 @@ impl<'a> Parser<'a> {
|
||||||
self.parse_time_functions(ObjectName(vec![w.to_ident()]))
|
self.parse_time_functions(ObjectName(vec![w.to_ident()]))
|
||||||
}
|
}
|
||||||
Keyword::CASE => self.parse_case_expr(),
|
Keyword::CASE => self.parse_case_expr(),
|
||||||
|
Keyword::CONVERT => self.parse_convert_expr(),
|
||||||
Keyword::CAST => self.parse_cast_expr(),
|
Keyword::CAST => self.parse_cast_expr(),
|
||||||
Keyword::TRY_CAST => self.parse_try_cast_expr(),
|
Keyword::TRY_CAST => self.parse_try_cast_expr(),
|
||||||
Keyword::SAFE_CAST => self.parse_safe_cast_expr(),
|
Keyword::SAFE_CAST => self.parse_safe_cast_expr(),
|
||||||
|
@ -1227,6 +1228,57 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// mssql-like convert function
|
||||||
|
fn parse_mssql_convert(&mut self) -> Result<Expr, ParserError> {
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let data_type = self.parse_data_type()?;
|
||||||
|
self.expect_token(&Token::Comma)?;
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(Expr::Convert {
|
||||||
|
expr: Box::new(expr),
|
||||||
|
data_type: Some(data_type),
|
||||||
|
charset: None,
|
||||||
|
target_before_value: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse a SQL CONVERT function:
|
||||||
|
/// - `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> {
|
||||||
|
if self.dialect.convert_type_before_value() {
|
||||||
|
return self.parse_mssql_convert();
|
||||||
|
}
|
||||||
|
self.expect_token(&Token::LParen)?;
|
||||||
|
let expr = self.parse_expr()?;
|
||||||
|
if self.parse_keyword(Keyword::USING) {
|
||||||
|
let charset = self.parse_object_name()?;
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
return Ok(Expr::Convert {
|
||||||
|
expr: Box::new(expr),
|
||||||
|
data_type: None,
|
||||||
|
charset: Some(charset),
|
||||||
|
target_before_value: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
self.expect_token(&Token::Comma)?;
|
||||||
|
let data_type = self.parse_data_type()?;
|
||||||
|
let charset = if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
|
||||||
|
Some(self.parse_object_name()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.expect_token(&Token::RParen)?;
|
||||||
|
Ok(Expr::Convert {
|
||||||
|
expr: Box::new(expr),
|
||||||
|
data_type: Some(data_type),
|
||||||
|
charset,
|
||||||
|
target_before_value: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)`
|
/// Parse a SQL CAST function e.g. `CAST(expr AS FLOAT)`
|
||||||
pub fn parse_cast_expr(&mut self) -> Result<Expr, ParserError> {
|
pub fn parse_cast_expr(&mut self) -> Result<Expr, ParserError> {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
|
|
|
@ -475,6 +475,13 @@ fn parse_cast_varchar_max() {
|
||||||
ms_and_generic().verified_expr("CAST('foo' AS VARCHAR(MAX))");
|
ms_and_generic().verified_expr("CAST('foo' AS VARCHAR(MAX))");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_convert() {
|
||||||
|
ms().verified_expr("CONVERT(VARCHAR(MAX), 'foo')");
|
||||||
|
ms().verified_expr("CONVERT(VARCHAR(10), 'foo')");
|
||||||
|
ms().verified_expr("CONVERT(DECIMAL(10,5), 12.55)");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_similar_to() {
|
fn parse_similar_to() {
|
||||||
fn chk(negated: bool) {
|
fn chk(negated: bool) {
|
||||||
|
|
|
@ -1843,3 +1843,18 @@ fn parse_drop_temporary_table() {
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_convert_using() {
|
||||||
|
// https://dev.mysql.com/doc/refman/8.0/en/cast-functions.html#function_convert
|
||||||
|
|
||||||
|
// CONVERT(expr USING transcoding_name)
|
||||||
|
mysql().verified_only_select("SELECT CONVERT('x' USING latin1)");
|
||||||
|
mysql().verified_only_select("SELECT CONVERT(my_column USING utf8mb4) FROM my_table");
|
||||||
|
|
||||||
|
// CONVERT(expr, type)
|
||||||
|
mysql().verified_only_select("SELECT CONVERT('abc', CHAR(60))");
|
||||||
|
mysql().verified_only_select("SELECT CONVERT(123.456, DECIMAL(5,2))");
|
||||||
|
// with a type + a charset
|
||||||
|
mysql().verified_only_select("SELECT CONVERT('test', CHAR CHARACTER SET utf8mb4)");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue