Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 43 additions & 30 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,9 @@ impl<'a> Parser<'a> {

pub fn parse_extract_expr(&mut self) -> Result<Expr, ParserError> {
self.expect_token(&Token::LParen)?;
let field = self.parse_date_time_field()?;
let field_parsable_as_string =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why you cannot move this check into parse_date_time_field method?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parse_date_time_field is also used with intervals, for instance, where such string behavior would be explicitly not allowed in Postgres. INTERVAL '1' MONTH is ok, but INTERVAL '1' 'MONTH' would be an error. If I were to move the check, such incorrect syntax would be parsed as valid.

dialect_of!(self is GenericDialect | PostgreSqlDialect | RedshiftSqlDialect);
let field = self.parse_date_time_field(field_parsable_as_string)?;
self.expect_keyword(Keyword::FROM)?;
let expr = self.parse_expr()?;
self.expect_token(&Token::RParen)?;
Expand Down Expand Up @@ -975,34 +977,45 @@ impl<'a> Parser<'a> {
// operator and interval qualifiers. EXTRACT supports a wider set of
// date/time fields than interval qualifiers, so this function may need to
// be split in two.
pub fn parse_date_time_field(&mut self) -> Result<DateTimeField, ParserError> {
match self.next_token() {
Token::Word(w) => match w.keyword {
Keyword::YEAR => Ok(DateTimeField::Year),
Keyword::MONTH => Ok(DateTimeField::Month),
Keyword::WEEK => Ok(DateTimeField::Week),
Keyword::DAY => Ok(DateTimeField::Day),
Keyword::HOUR => Ok(DateTimeField::Hour),
Keyword::MINUTE => Ok(DateTimeField::Minute),
Keyword::SECOND => Ok(DateTimeField::Second),
Keyword::CENTURY => Ok(DateTimeField::Century),
Keyword::DECADE => Ok(DateTimeField::Decade),
Keyword::DOY => Ok(DateTimeField::Doy),
Keyword::DOW => Ok(DateTimeField::Dow),
Keyword::EPOCH => Ok(DateTimeField::Epoch),
Keyword::ISODOW => Ok(DateTimeField::Isodow),
Keyword::ISOYEAR => Ok(DateTimeField::Isoyear),
Keyword::JULIAN => Ok(DateTimeField::Julian),
Keyword::MICROSECONDS => Ok(DateTimeField::Microseconds),
Keyword::MILLENIUM => Ok(DateTimeField::Millenium),
Keyword::MILLISECONDS => Ok(DateTimeField::Milliseconds),
Keyword::QUARTER => Ok(DateTimeField::Quarter),
Keyword::TIMEZONE => Ok(DateTimeField::Timezone),
Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour),
Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute),
_ => self.expected("date/time field", Token::Word(w))?,
pub fn parse_date_time_field(
&mut self,
parsable_as_str: bool,
) -> Result<DateTimeField, ParserError> {
let token = self.next_token();

let date_time_field = match &token {
Token::Word(w) => w.keyword,
Token::SingleQuotedString(s) if parsable_as_str => match Token::make_keyword(s) {
Token::Word(w) => w.keyword,
_ => self.expected("date/time field", token.clone())?,
},
unexpected => self.expected("date/time field", unexpected),
_ => self.expected("date/time field", token.clone())?,
};

match date_time_field {
Keyword::YEAR => Ok(DateTimeField::Year),
Keyword::MONTH => Ok(DateTimeField::Month),
Keyword::WEEK => Ok(DateTimeField::Week),
Keyword::DAY => Ok(DateTimeField::Day),
Keyword::HOUR => Ok(DateTimeField::Hour),
Keyword::MINUTE => Ok(DateTimeField::Minute),
Keyword::SECOND => Ok(DateTimeField::Second),
Keyword::CENTURY => Ok(DateTimeField::Century),
Keyword::DECADE => Ok(DateTimeField::Decade),
Keyword::DOY => Ok(DateTimeField::Doy),
Keyword::DOW => Ok(DateTimeField::Dow),
Keyword::EPOCH => Ok(DateTimeField::Epoch),
Keyword::ISODOW => Ok(DateTimeField::Isodow),
Keyword::ISOYEAR => Ok(DateTimeField::Isoyear),
Keyword::JULIAN => Ok(DateTimeField::Julian),
Keyword::MICROSECONDS => Ok(DateTimeField::Microseconds),
Keyword::MILLENIUM => Ok(DateTimeField::Millenium),
Keyword::MILLISECONDS => Ok(DateTimeField::Milliseconds),
Keyword::QUARTER => Ok(DateTimeField::Quarter),
Keyword::TIMEZONE => Ok(DateTimeField::Timezone),
Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour),
Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute),
_ => self.expected("date/time field", token)?,
}
}

Expand Down Expand Up @@ -1067,7 +1080,7 @@ impl<'a> Parser<'a> {
.iter()
.any(|d| kw.keyword == *d) =>
{
Some(self.parse_date_time_field()?)
Some(self.parse_date_time_field(false)?)
}
_ => None,
};
Expand All @@ -1085,7 +1098,7 @@ impl<'a> Parser<'a> {
} else {
let leading_precision = self.parse_optional_precision()?;
if self.parse_keyword(Keyword::TO) {
let last_field = Some(self.parse_date_time_field()?);
let last_field = Some(self.parse_date_time_field(false)?);
let fsec_precision = if last_field == Some(DateTimeField::Second) {
self.parse_optional_precision()?
} else {
Expand Down
25 changes: 25 additions & 0 deletions tests/sqlparser_postgres.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1546,3 +1546,28 @@ fn parse_interval_math() {
expr_from_projection(only(&select.projection)),
);
}

#[test]
fn parse_pg_extract() {
// FIXME: It's a good idea to add a few more tests, including expr assertion,
// but `verified_only_select` validates serialization of the query, leading to
// quote-escaped variant being unquoted.
//
// It could be fixed with an addition of `quote_style` field in Expr::Extract
// but would introduce enough changes to dependents to consider this out-of-scope
// for the time being as this only benefits tests.
pg_and_generic().one_statement_parses_to(
"SELECT EXTRACT('YEAR' from d)",
"SELECT EXTRACT(YEAR FROM d)",
);
pg_and_generic().one_statement_parses_to(
"SELECT EXTRACT('month' from d)",
"SELECT EXTRACT(MONTH FROM d)",
);

let res = pg_and_generic().parse_sql_statements("SELECT EXTRACT('MILLISECOND' FROM d)");
assert_eq!(
ParserError::ParserError("Expected date/time field, found: 'MILLISECOND'".to_string()),
res.unwrap_err()
);
}