mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-24 07:54:06 +00:00
supporting snowflake extract syntax (#1374)
Co-authored-by: Andrew Lamb <andrew@nerdnetworks.org>
This commit is contained in:
parent
ca5262c13f
commit
f5b818e74b
5 changed files with 78 additions and 3 deletions
|
@ -477,6 +477,22 @@ pub enum CastKind {
|
||||||
DoubleColon,
|
DoubleColon,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `EXTRACT` syntax variants.
|
||||||
|
///
|
||||||
|
/// In Snowflake dialect, the `EXTRACT` expression can support either the `from` syntax
|
||||||
|
/// or the comma syntax.
|
||||||
|
///
|
||||||
|
/// See <https://docs.snowflake.com/en/sql-reference/functions/extract>
|
||||||
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
|
pub enum ExtractSyntax {
|
||||||
|
/// `EXTRACT( <date_or_time_part> FROM <date_or_time_expr> )`
|
||||||
|
From,
|
||||||
|
/// `EXTRACT( <date_or_time_part> , <date_or_timestamp_expr> )`
|
||||||
|
Comma,
|
||||||
|
}
|
||||||
|
|
||||||
/// An SQL expression of any type.
|
/// An SQL expression of any type.
|
||||||
///
|
///
|
||||||
/// The parser does not distinguish between expressions of different types
|
/// The parser does not distinguish between expressions of different types
|
||||||
|
@ -637,13 +653,15 @@ pub enum Expr {
|
||||||
time_zone: Box<Expr>,
|
time_zone: Box<Expr>,
|
||||||
},
|
},
|
||||||
/// Extract a field from a timestamp e.g. `EXTRACT(MONTH FROM foo)`
|
/// Extract a field from a timestamp e.g. `EXTRACT(MONTH FROM foo)`
|
||||||
|
/// Or `EXTRACT(MONTH, foo)`
|
||||||
///
|
///
|
||||||
/// Syntax:
|
/// Syntax:
|
||||||
/// ```sql
|
/// ```sql
|
||||||
/// EXTRACT(DateTimeField FROM <expr>)
|
/// EXTRACT(DateTimeField FROM <expr>) | EXTRACT(DateTimeField, <expr>)
|
||||||
/// ```
|
/// ```
|
||||||
Extract {
|
Extract {
|
||||||
field: DateTimeField,
|
field: DateTimeField,
|
||||||
|
syntax: ExtractSyntax,
|
||||||
expr: Box<Expr>,
|
expr: Box<Expr>,
|
||||||
},
|
},
|
||||||
/// ```sql
|
/// ```sql
|
||||||
|
@ -1197,7 +1215,14 @@ impl fmt::Display for Expr {
|
||||||
write!(f, "{expr}::{data_type}")
|
write!(f, "{expr}::{data_type}")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Expr::Extract { field, expr } => write!(f, "EXTRACT({field} FROM {expr})"),
|
Expr::Extract {
|
||||||
|
field,
|
||||||
|
syntax,
|
||||||
|
expr,
|
||||||
|
} => match syntax {
|
||||||
|
ExtractSyntax::From => write!(f, "EXTRACT({field} FROM {expr})"),
|
||||||
|
ExtractSyntax::Comma => write!(f, "EXTRACT({field}, {expr})"),
|
||||||
|
},
|
||||||
Expr::Ceil { expr, field } => {
|
Expr::Ceil { expr, field } => {
|
||||||
if field == &DateTimeField::NoDateTime {
|
if field == &DateTimeField::NoDateTime {
|
||||||
write!(f, "CEIL({expr})")
|
write!(f, "CEIL({expr})")
|
||||||
|
|
|
@ -1682,12 +1682,25 @@ impl<'a> Parser<'a> {
|
||||||
pub fn parse_extract_expr(&mut self) -> Result<Expr, ParserError> {
|
pub fn parse_extract_expr(&mut self) -> Result<Expr, ParserError> {
|
||||||
self.expect_token(&Token::LParen)?;
|
self.expect_token(&Token::LParen)?;
|
||||||
let field = self.parse_date_time_field()?;
|
let field = self.parse_date_time_field()?;
|
||||||
self.expect_keyword(Keyword::FROM)?;
|
|
||||||
|
let syntax = if self.parse_keyword(Keyword::FROM) {
|
||||||
|
ExtractSyntax::From
|
||||||
|
} else if self.consume_token(&Token::Comma)
|
||||||
|
&& dialect_of!(self is SnowflakeDialect | GenericDialect)
|
||||||
|
{
|
||||||
|
ExtractSyntax::Comma
|
||||||
|
} else {
|
||||||
|
return Err(ParserError::ParserError(
|
||||||
|
"Expected 'FROM' or ','".to_string(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
let expr = self.parse_expr()?;
|
let expr = self.parse_expr()?;
|
||||||
self.expect_token(&Token::RParen)?;
|
self.expect_token(&Token::RParen)?;
|
||||||
Ok(Expr::Extract {
|
Ok(Expr::Extract {
|
||||||
field,
|
field,
|
||||||
expr: Box::new(expr),
|
expr: Box::new(expr),
|
||||||
|
syntax,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1950,6 +1963,12 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
_ => self.expected("date/time field", next_token),
|
_ => self.expected("date/time field", next_token),
|
||||||
},
|
},
|
||||||
|
Token::SingleQuotedString(_) if dialect_of!(self is SnowflakeDialect | GenericDialect) =>
|
||||||
|
{
|
||||||
|
self.prev_token();
|
||||||
|
let custom = self.parse_identifier(false)?;
|
||||||
|
Ok(DateTimeField::Custom(custom))
|
||||||
|
}
|
||||||
_ => self.expected("date/time field", next_token),
|
_ => self.expected("date/time field", next_token),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2136,6 +2136,7 @@ fn parse_extract_weekday() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::Extract {
|
&Expr::Extract {
|
||||||
field: DateTimeField::Week(Some(Ident::new("MONDAY"))),
|
field: DateTimeField::Week(Some(Ident::new("MONDAY"))),
|
||||||
|
syntax: ExtractSyntax::From,
|
||||||
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
|
|
|
@ -2430,6 +2430,7 @@ fn parse_extract() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::Extract {
|
&Expr::Extract {
|
||||||
field: DateTimeField::Year,
|
field: DateTimeField::Year,
|
||||||
|
syntax: ExtractSyntax::From,
|
||||||
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
|
|
|
@ -2019,6 +2019,35 @@ fn parse_extract_custom_part() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
&Expr::Extract {
|
&Expr::Extract {
|
||||||
field: DateTimeField::Custom(Ident::new("eod")),
|
field: DateTimeField::Custom(Ident::new("eod")),
|
||||||
|
syntax: ExtractSyntax::From,
|
||||||
|
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||||
|
},
|
||||||
|
expr_from_projection(only(&select.projection)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_extract_comma() {
|
||||||
|
let sql = "SELECT EXTRACT(HOUR, d)";
|
||||||
|
let select = snowflake_and_generic().verified_only_select(sql);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Extract {
|
||||||
|
field: DateTimeField::Hour,
|
||||||
|
syntax: ExtractSyntax::Comma,
|
||||||
|
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||||
|
},
|
||||||
|
expr_from_projection(only(&select.projection)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_extract_comma_quoted() {
|
||||||
|
let sql = "SELECT EXTRACT('hour', d)";
|
||||||
|
let select = snowflake_and_generic().verified_only_select(sql);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Extract {
|
||||||
|
field: DateTimeField::Custom(Ident::with_quote('\'', "hour")),
|
||||||
|
syntax: ExtractSyntax::Comma,
|
||||||
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
expr: Box::new(Expr::Identifier(Ident::new("d"))),
|
||||||
},
|
},
|
||||||
expr_from_projection(only(&select.projection)),
|
expr_from_projection(only(&select.projection)),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue