diff --git a/src/parser.rs b/src/parser.rs index 04bb450be..4fecece46 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -814,7 +814,9 @@ impl<'a> Parser<'a> { pub fn parse_extract_expr(&mut self) -> Result { self.expect_token(&Token::LParen)?; - let field = self.parse_date_time_field()?; + let field_parsable_as_string = + 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)?; @@ -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 { - 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 { + 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)?, } } @@ -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, }; @@ -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 { diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 8ab8d69d9..cd5e55492 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -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() + ); +}