Support pluralized time units (#1630)

This commit is contained in:
wugeer 2025-01-09 01:28:20 +08:00 committed by GitHub
parent 0cd49fb699
commit 62bcaa1c98
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 122 additions and 18 deletions

View file

@ -155,7 +155,9 @@ impl fmt::Display for DollarQuotedString {
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum DateTimeField { pub enum DateTimeField {
Year, Year,
Years,
Month, Month,
Months,
/// Week optionally followed by a WEEKDAY. /// Week optionally followed by a WEEKDAY.
/// ///
/// ```sql /// ```sql
@ -164,14 +166,19 @@ pub enum DateTimeField {
/// ///
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#extract) /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#extract)
Week(Option<Ident>), Week(Option<Ident>),
Weeks,
Day, Day,
DayOfWeek, DayOfWeek,
DayOfYear, DayOfYear,
Days,
Date, Date,
Datetime, Datetime,
Hour, Hour,
Hours,
Minute, Minute,
Minutes,
Second, Second,
Seconds,
Century, Century,
Decade, Decade,
Dow, Dow,
@ -210,7 +217,9 @@ impl fmt::Display for DateTimeField {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
DateTimeField::Year => write!(f, "YEAR"), DateTimeField::Year => write!(f, "YEAR"),
DateTimeField::Years => write!(f, "YEARS"),
DateTimeField::Month => write!(f, "MONTH"), DateTimeField::Month => write!(f, "MONTH"),
DateTimeField::Months => write!(f, "MONTHS"),
DateTimeField::Week(week_day) => { DateTimeField::Week(week_day) => {
write!(f, "WEEK")?; write!(f, "WEEK")?;
if let Some(week_day) = week_day { if let Some(week_day) = week_day {
@ -218,14 +227,19 @@ impl fmt::Display for DateTimeField {
} }
Ok(()) Ok(())
} }
DateTimeField::Weeks => write!(f, "WEEKS"),
DateTimeField::Day => write!(f, "DAY"), DateTimeField::Day => write!(f, "DAY"),
DateTimeField::DayOfWeek => write!(f, "DAYOFWEEK"), DateTimeField::DayOfWeek => write!(f, "DAYOFWEEK"),
DateTimeField::DayOfYear => write!(f, "DAYOFYEAR"), DateTimeField::DayOfYear => write!(f, "DAYOFYEAR"),
DateTimeField::Days => write!(f, "DAYS"),
DateTimeField::Date => write!(f, "DATE"), DateTimeField::Date => write!(f, "DATE"),
DateTimeField::Datetime => write!(f, "DATETIME"), DateTimeField::Datetime => write!(f, "DATETIME"),
DateTimeField::Hour => write!(f, "HOUR"), DateTimeField::Hour => write!(f, "HOUR"),
DateTimeField::Hours => write!(f, "HOURS"),
DateTimeField::Minute => write!(f, "MINUTE"), DateTimeField::Minute => write!(f, "MINUTE"),
DateTimeField::Minutes => write!(f, "MINUTES"),
DateTimeField::Second => write!(f, "SECOND"), DateTimeField::Second => write!(f, "SECOND"),
DateTimeField::Seconds => write!(f, "SECONDS"),
DateTimeField::Century => write!(f, "CENTURY"), DateTimeField::Century => write!(f, "CENTURY"),
DateTimeField::Decade => write!(f, "DECADE"), DateTimeField::Decade => write!(f, "DECADE"),
DateTimeField::Dow => write!(f, "DOW"), DateTimeField::Dow => write!(f, "DOW"),

View file

@ -234,6 +234,7 @@ define_keywords!(
DAY, DAY,
DAYOFWEEK, DAYOFWEEK,
DAYOFYEAR, DAYOFYEAR,
DAYS,
DEALLOCATE, DEALLOCATE,
DEC, DEC,
DECADE, DECADE,
@ -499,6 +500,7 @@ define_keywords!(
MILLISECONDS, MILLISECONDS,
MIN, MIN,
MINUTE, MINUTE,
MINUTES,
MINVALUE, MINVALUE,
MOD, MOD,
MODE, MODE,
@ -506,6 +508,7 @@ define_keywords!(
MODIFY, MODIFY,
MODULE, MODULE,
MONTH, MONTH,
MONTHS,
MSCK, MSCK,
MULTISET, MULTISET,
MUTATION, MUTATION,
@ -698,6 +701,7 @@ define_keywords!(
SEARCH, SEARCH,
SECOND, SECOND,
SECONDARY, SECONDARY,
SECONDS,
SECRET, SECRET,
SECURITY, SECURITY,
SEED, SEED,
@ -866,6 +870,7 @@ define_keywords!(
VOLATILE, VOLATILE,
WAREHOUSE, WAREHOUSE,
WEEK, WEEK,
WEEKS,
WHEN, WHEN,
WHENEVER, WHENEVER,
WHERE, WHERE,
@ -880,6 +885,7 @@ define_keywords!(
XML, XML,
XOR, XOR,
YEAR, YEAR,
YEARS,
ZONE, ZONE,
ZORDER ZORDER
); );

View file

@ -2358,7 +2358,9 @@ impl<'a> Parser<'a> {
match &next_token.token { match &next_token.token {
Token::Word(w) => match w.keyword { Token::Word(w) => match w.keyword {
Keyword::YEAR => Ok(DateTimeField::Year), Keyword::YEAR => Ok(DateTimeField::Year),
Keyword::YEARS => Ok(DateTimeField::Years),
Keyword::MONTH => Ok(DateTimeField::Month), Keyword::MONTH => Ok(DateTimeField::Month),
Keyword::MONTHS => Ok(DateTimeField::Months),
Keyword::WEEK => { Keyword::WEEK => {
let week_day = if dialect_of!(self is BigQueryDialect | GenericDialect) let week_day = if dialect_of!(self is BigQueryDialect | GenericDialect)
&& self.consume_token(&Token::LParen) && self.consume_token(&Token::LParen)
@ -2371,14 +2373,19 @@ impl<'a> Parser<'a> {
}; };
Ok(DateTimeField::Week(week_day)) Ok(DateTimeField::Week(week_day))
} }
Keyword::WEEKS => Ok(DateTimeField::Weeks),
Keyword::DAY => Ok(DateTimeField::Day), Keyword::DAY => Ok(DateTimeField::Day),
Keyword::DAYOFWEEK => Ok(DateTimeField::DayOfWeek), Keyword::DAYOFWEEK => Ok(DateTimeField::DayOfWeek),
Keyword::DAYOFYEAR => Ok(DateTimeField::DayOfYear), Keyword::DAYOFYEAR => Ok(DateTimeField::DayOfYear),
Keyword::DAYS => Ok(DateTimeField::Days),
Keyword::DATE => Ok(DateTimeField::Date), Keyword::DATE => Ok(DateTimeField::Date),
Keyword::DATETIME => Ok(DateTimeField::Datetime), Keyword::DATETIME => Ok(DateTimeField::Datetime),
Keyword::HOUR => Ok(DateTimeField::Hour), Keyword::HOUR => Ok(DateTimeField::Hour),
Keyword::HOURS => Ok(DateTimeField::Hours),
Keyword::MINUTE => Ok(DateTimeField::Minute), Keyword::MINUTE => Ok(DateTimeField::Minute),
Keyword::MINUTES => Ok(DateTimeField::Minutes),
Keyword::SECOND => Ok(DateTimeField::Second), Keyword::SECOND => Ok(DateTimeField::Second),
Keyword::SECONDS => Ok(DateTimeField::Seconds),
Keyword::CENTURY => Ok(DateTimeField::Century), Keyword::CENTURY => Ok(DateTimeField::Century),
Keyword::DECADE => Ok(DateTimeField::Decade), Keyword::DECADE => Ok(DateTimeField::Decade),
Keyword::DOY => Ok(DateTimeField::Doy), Keyword::DOY => Ok(DateTimeField::Doy),
@ -2605,12 +2612,19 @@ impl<'a> Parser<'a> {
matches!( matches!(
word.keyword, word.keyword,
Keyword::YEAR Keyword::YEAR
| Keyword::YEARS
| Keyword::MONTH | Keyword::MONTH
| Keyword::MONTHS
| Keyword::WEEK | Keyword::WEEK
| Keyword::WEEKS
| Keyword::DAY | Keyword::DAY
| Keyword::DAYS
| Keyword::HOUR | Keyword::HOUR
| Keyword::HOURS
| Keyword::MINUTE | Keyword::MINUTE
| Keyword::MINUTES
| Keyword::SECOND | Keyword::SECOND
| Keyword::SECONDS
| Keyword::CENTURY | Keyword::CENTURY
| Keyword::DECADE | Keyword::DECADE
| Keyword::DOW | Keyword::DOW

View file

@ -50,6 +50,7 @@ mod test_utils;
#[cfg(test)] #[cfg(test)]
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use sqlparser::ast::ColumnOption::Comment; use sqlparser::ast::ColumnOption::Comment;
use sqlparser::ast::DateTimeField::Seconds;
use sqlparser::ast::Expr::{Identifier, UnaryOp}; use sqlparser::ast::Expr::{Identifier, UnaryOp};
use sqlparser::ast::Value::Number; use sqlparser::ast::Value::Number;
use sqlparser::test_utils::all_dialects_except; use sqlparser::test_utils::all_dialects_except;
@ -5399,6 +5400,19 @@ fn parse_interval_all() {
expr_from_projection(only(&select.projection)), expr_from_projection(only(&select.projection)),
); );
let sql = "SELECT INTERVAL 5 DAYS";
let select = verified_only_select(sql);
assert_eq!(
&Expr::Interval(Interval {
value: Box::new(Expr::Value(number("5"))),
leading_field: Some(DateTimeField::Days),
leading_precision: None,
last_field: None,
fractional_seconds_precision: None,
}),
expr_from_projection(only(&select.projection)),
);
let sql = "SELECT INTERVAL '10' HOUR (1)"; let sql = "SELECT INTERVAL '10' HOUR (1)";
let select = verified_only_select(sql); let select = verified_only_select(sql);
assert_eq!( assert_eq!(
@ -5426,10 +5440,18 @@ fn parse_interval_all() {
verified_only_select("SELECT INTERVAL '1' YEAR"); verified_only_select("SELECT INTERVAL '1' YEAR");
verified_only_select("SELECT INTERVAL '1' MONTH"); verified_only_select("SELECT INTERVAL '1' MONTH");
verified_only_select("SELECT INTERVAL '1' WEEK");
verified_only_select("SELECT INTERVAL '1' DAY"); verified_only_select("SELECT INTERVAL '1' DAY");
verified_only_select("SELECT INTERVAL '1' HOUR"); verified_only_select("SELECT INTERVAL '1' HOUR");
verified_only_select("SELECT INTERVAL '1' MINUTE"); verified_only_select("SELECT INTERVAL '1' MINUTE");
verified_only_select("SELECT INTERVAL '1' SECOND"); verified_only_select("SELECT INTERVAL '1' SECOND");
verified_only_select("SELECT INTERVAL '1' YEARS");
verified_only_select("SELECT INTERVAL '1' MONTHS");
verified_only_select("SELECT INTERVAL '1' WEEKS");
verified_only_select("SELECT INTERVAL '1' DAYS");
verified_only_select("SELECT INTERVAL '1' HOURS");
verified_only_select("SELECT INTERVAL '1' MINUTES");
verified_only_select("SELECT INTERVAL '1' SECONDS");
verified_only_select("SELECT INTERVAL '1' YEAR TO MONTH"); verified_only_select("SELECT INTERVAL '1' YEAR TO MONTH");
verified_only_select("SELECT INTERVAL '1' DAY TO HOUR"); verified_only_select("SELECT INTERVAL '1' DAY TO HOUR");
verified_only_select("SELECT INTERVAL '1' DAY TO MINUTE"); verified_only_select("SELECT INTERVAL '1' DAY TO MINUTE");
@ -5439,10 +5461,21 @@ fn parse_interval_all() {
verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND"); verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND");
verified_only_select("SELECT INTERVAL 1 YEAR"); verified_only_select("SELECT INTERVAL 1 YEAR");
verified_only_select("SELECT INTERVAL 1 MONTH"); verified_only_select("SELECT INTERVAL 1 MONTH");
verified_only_select("SELECT INTERVAL 1 WEEK");
verified_only_select("SELECT INTERVAL 1 DAY"); verified_only_select("SELECT INTERVAL 1 DAY");
verified_only_select("SELECT INTERVAL 1 HOUR"); verified_only_select("SELECT INTERVAL 1 HOUR");
verified_only_select("SELECT INTERVAL 1 MINUTE"); verified_only_select("SELECT INTERVAL 1 MINUTE");
verified_only_select("SELECT INTERVAL 1 SECOND"); verified_only_select("SELECT INTERVAL 1 SECOND");
verified_only_select("SELECT INTERVAL 1 YEARS");
verified_only_select("SELECT INTERVAL 1 MONTHS");
verified_only_select("SELECT INTERVAL 1 WEEKS");
verified_only_select("SELECT INTERVAL 1 DAYS");
verified_only_select("SELECT INTERVAL 1 HOURS");
verified_only_select("SELECT INTERVAL 1 MINUTES");
verified_only_select("SELECT INTERVAL 1 SECONDS");
verified_only_select(
"SELECT '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::INTERVAL",
);
} }
#[test] #[test]
@ -11356,16 +11389,12 @@ fn test_group_by_nothing() {
#[test] #[test]
fn test_extract_seconds_ok() { fn test_extract_seconds_ok() {
let dialects = all_dialects_where(|d| d.allow_extract_custom()); let dialects = all_dialects_where(|d| d.allow_extract_custom());
let stmt = dialects.verified_expr("EXTRACT(seconds FROM '2 seconds'::INTERVAL)"); let stmt = dialects.verified_expr("EXTRACT(SECONDS FROM '2 seconds'::INTERVAL)");
assert_eq!( assert_eq!(
stmt, stmt,
Expr::Extract { Expr::Extract {
field: DateTimeField::Custom(Ident { field: Seconds,
value: "seconds".to_string(),
quote_style: None,
span: Span::empty(),
}),
syntax: ExtractSyntax::From, syntax: ExtractSyntax::From,
expr: Box::new(Expr::Cast { expr: Box::new(Expr::Cast {
kind: CastKind::DoubleColon, kind: CastKind::DoubleColon,
@ -11376,7 +11405,59 @@ fn test_extract_seconds_ok() {
format: None, format: None,
}), }),
} }
) );
let actual_ast = dialects
.parse_sql_statements("SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)")
.unwrap();
let expected_ast = vec![Statement::Query(Box::new(Query {
with: None,
body: Box::new(SetExpr::Select(Box::new(Select {
select_token: AttachedToken::empty(),
distinct: None,
top: None,
top_before_distinct: false,
projection: vec![UnnamedExpr(Expr::Extract {
field: Seconds,
syntax: ExtractSyntax::From,
expr: Box::new(Expr::Cast {
kind: CastKind::DoubleColon,
expr: Box::new(Expr::Value(Value::SingleQuotedString(
"2 seconds".to_string(),
))),
data_type: DataType::Interval,
format: None,
}),
})],
into: None,
from: vec![],
lateral_views: vec![],
prewhere: None,
selection: None,
group_by: GroupByExpr::Expressions(vec![], vec![]),
cluster_by: vec![],
distribute_by: vec![],
sort_by: vec![],
having: None,
named_window: vec![],
qualify: None,
window_before_qualify: false,
value_table_mode: None,
connect_by: None,
}))),
order_by: None,
limit: None,
limit_by: vec![],
offset: None,
fetch: None,
locks: vec![],
for_clause: None,
settings: None,
format_clause: None,
}))];
assert_eq!(actual_ast, expected_ast);
} }
#[test] #[test]
@ -11405,17 +11486,6 @@ fn test_extract_seconds_single_quote_ok() {
) )
} }
#[test]
fn test_extract_seconds_err() {
let sql = "SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)";
let dialects = all_dialects_except(|d| d.allow_extract_custom());
let err = dialects.parse_sql_statements(sql).unwrap_err();
assert_eq!(
err.to_string(),
"sql parser error: Expected: date/time field, found: seconds"
);
}
#[test] #[test]
fn test_extract_seconds_single_quote_err() { fn test_extract_seconds_single_quote_err() {
let sql = r#"SELECT EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#; let sql = r#"SELECT EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#;