Skip to content

Commit ef389d8

Browse files
committed
feat: Allow EXTRACT field to be parsed as a string
1 parent 13aff89 commit ef389d8

File tree

2 files changed

+68
-30
lines changed

2 files changed

+68
-30
lines changed

src/parser.rs

Lines changed: 43 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -814,7 +814,9 @@ impl<'a> Parser<'a> {
814814

815815
pub fn parse_extract_expr(&mut self) -> Result<Expr, ParserError> {
816816
self.expect_token(&Token::LParen)?;
817-
let field = self.parse_date_time_field()?;
817+
let field_parsable_as_string =
818+
dialect_of!(self is GenericDialect | PostgreSqlDialect | RedshiftSqlDialect);
819+
let field = self.parse_date_time_field(field_parsable_as_string)?;
818820
self.expect_keyword(Keyword::FROM)?;
819821
let expr = self.parse_expr()?;
820822
self.expect_token(&Token::RParen)?;
@@ -975,34 +977,45 @@ impl<'a> Parser<'a> {
975977
// operator and interval qualifiers. EXTRACT supports a wider set of
976978
// date/time fields than interval qualifiers, so this function may need to
977979
// be split in two.
978-
pub fn parse_date_time_field(&mut self) -> Result<DateTimeField, ParserError> {
979-
match self.next_token() {
980-
Token::Word(w) => match w.keyword {
981-
Keyword::YEAR => Ok(DateTimeField::Year),
982-
Keyword::MONTH => Ok(DateTimeField::Month),
983-
Keyword::WEEK => Ok(DateTimeField::Week),
984-
Keyword::DAY => Ok(DateTimeField::Day),
985-
Keyword::HOUR => Ok(DateTimeField::Hour),
986-
Keyword::MINUTE => Ok(DateTimeField::Minute),
987-
Keyword::SECOND => Ok(DateTimeField::Second),
988-
Keyword::CENTURY => Ok(DateTimeField::Century),
989-
Keyword::DECADE => Ok(DateTimeField::Decade),
990-
Keyword::DOY => Ok(DateTimeField::Doy),
991-
Keyword::DOW => Ok(DateTimeField::Dow),
992-
Keyword::EPOCH => Ok(DateTimeField::Epoch),
993-
Keyword::ISODOW => Ok(DateTimeField::Isodow),
994-
Keyword::ISOYEAR => Ok(DateTimeField::Isoyear),
995-
Keyword::JULIAN => Ok(DateTimeField::Julian),
996-
Keyword::MICROSECONDS => Ok(DateTimeField::Microseconds),
997-
Keyword::MILLENIUM => Ok(DateTimeField::Millenium),
998-
Keyword::MILLISECONDS => Ok(DateTimeField::Milliseconds),
999-
Keyword::QUARTER => Ok(DateTimeField::Quarter),
1000-
Keyword::TIMEZONE => Ok(DateTimeField::Timezone),
1001-
Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour),
1002-
Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute),
1003-
_ => self.expected("date/time field", Token::Word(w))?,
980+
pub fn parse_date_time_field(
981+
&mut self,
982+
parsable_as_str: bool,
983+
) -> Result<DateTimeField, ParserError> {
984+
let token = self.next_token();
985+
986+
let date_time_field = match &token {
987+
Token::Word(w) => w.keyword,
988+
Token::SingleQuotedString(s) if parsable_as_str => match Token::make_keyword(s) {
989+
Token::Word(w) => w.keyword,
990+
_ => self.expected("date/time field", token.clone())?,
1004991
},
1005-
unexpected => self.expected("date/time field", unexpected),
992+
_ => self.expected("date/time field", token.clone())?,
993+
};
994+
995+
match date_time_field {
996+
Keyword::YEAR => Ok(DateTimeField::Year),
997+
Keyword::MONTH => Ok(DateTimeField::Month),
998+
Keyword::WEEK => Ok(DateTimeField::Week),
999+
Keyword::DAY => Ok(DateTimeField::Day),
1000+
Keyword::HOUR => Ok(DateTimeField::Hour),
1001+
Keyword::MINUTE => Ok(DateTimeField::Minute),
1002+
Keyword::SECOND => Ok(DateTimeField::Second),
1003+
Keyword::CENTURY => Ok(DateTimeField::Century),
1004+
Keyword::DECADE => Ok(DateTimeField::Decade),
1005+
Keyword::DOY => Ok(DateTimeField::Doy),
1006+
Keyword::DOW => Ok(DateTimeField::Dow),
1007+
Keyword::EPOCH => Ok(DateTimeField::Epoch),
1008+
Keyword::ISODOW => Ok(DateTimeField::Isodow),
1009+
Keyword::ISOYEAR => Ok(DateTimeField::Isoyear),
1010+
Keyword::JULIAN => Ok(DateTimeField::Julian),
1011+
Keyword::MICROSECONDS => Ok(DateTimeField::Microseconds),
1012+
Keyword::MILLENIUM => Ok(DateTimeField::Millenium),
1013+
Keyword::MILLISECONDS => Ok(DateTimeField::Milliseconds),
1014+
Keyword::QUARTER => Ok(DateTimeField::Quarter),
1015+
Keyword::TIMEZONE => Ok(DateTimeField::Timezone),
1016+
Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour),
1017+
Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute),
1018+
_ => self.expected("date/time field", token)?,
10061019
}
10071020
}
10081021

@@ -1067,7 +1080,7 @@ impl<'a> Parser<'a> {
10671080
.iter()
10681081
.any(|d| kw.keyword == *d) =>
10691082
{
1070-
Some(self.parse_date_time_field()?)
1083+
Some(self.parse_date_time_field(false)?)
10711084
}
10721085
_ => None,
10731086
};
@@ -1085,7 +1098,7 @@ impl<'a> Parser<'a> {
10851098
} else {
10861099
let leading_precision = self.parse_optional_precision()?;
10871100
if self.parse_keyword(Keyword::TO) {
1088-
let last_field = Some(self.parse_date_time_field()?);
1101+
let last_field = Some(self.parse_date_time_field(false)?);
10891102
let fsec_precision = if last_field == Some(DateTimeField::Second) {
10901103
self.parse_optional_precision()?
10911104
} else {

tests/sqlparser_postgres.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,3 +1546,28 @@ fn parse_interval_math() {
15461546
expr_from_projection(only(&select.projection)),
15471547
);
15481548
}
1549+
1550+
#[test]
1551+
fn parse_pg_extract() {
1552+
// FIXME: It's a good idea to add a few more tests, including expr assertion,
1553+
// but `verified_only_select` validates serialization of the query, leading to
1554+
// quote-escaped variant being unquoted.
1555+
//
1556+
// It could be fixed with an addition of `quote_style` field in Expr::Extract
1557+
// but would introduce enough changes to dependents to consider this out-of-scope
1558+
// for the time being as this only benefits tests.
1559+
pg_and_generic().one_statement_parses_to(
1560+
"SELECT EXTRACT('YEAR' from d)",
1561+
"SELECT EXTRACT(YEAR FROM d)",
1562+
);
1563+
pg_and_generic().one_statement_parses_to(
1564+
"SELECT EXTRACT('month' from d)",
1565+
"SELECT EXTRACT(MONTH FROM d)",
1566+
);
1567+
1568+
let res = pg_and_generic().parse_sql_statements("SELECT EXTRACT('MILLISECOND' FROM d)");
1569+
assert_eq!(
1570+
ParserError::ParserError("Expected date/time field, found: 'MILLISECOND'".to_string()),
1571+
res.unwrap_err()
1572+
);
1573+
}

0 commit comments

Comments
 (0)