Skip to content

Commit 62bcaa1

Browse files
authored
Support pluralized time units (#1630)
1 parent 0cd49fb commit 62bcaa1

File tree

4 files changed

+122
-18
lines changed

4 files changed

+122
-18
lines changed

src/ast/value.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ impl fmt::Display for DollarQuotedString {
155155
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
156156
pub enum DateTimeField {
157157
Year,
158+
Years,
158159
Month,
160+
Months,
159161
/// Week optionally followed by a WEEKDAY.
160162
///
161163
/// ```sql
@@ -164,14 +166,19 @@ pub enum DateTimeField {
164166
///
165167
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#extract)
166168
Week(Option<Ident>),
169+
Weeks,
167170
Day,
168171
DayOfWeek,
169172
DayOfYear,
173+
Days,
170174
Date,
171175
Datetime,
172176
Hour,
177+
Hours,
173178
Minute,
179+
Minutes,
174180
Second,
181+
Seconds,
175182
Century,
176183
Decade,
177184
Dow,
@@ -210,22 +217,29 @@ impl fmt::Display for DateTimeField {
210217
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211218
match self {
212219
DateTimeField::Year => write!(f, "YEAR"),
220+
DateTimeField::Years => write!(f, "YEARS"),
213221
DateTimeField::Month => write!(f, "MONTH"),
222+
DateTimeField::Months => write!(f, "MONTHS"),
214223
DateTimeField::Week(week_day) => {
215224
write!(f, "WEEK")?;
216225
if let Some(week_day) = week_day {
217226
write!(f, "({week_day})")?
218227
}
219228
Ok(())
220229
}
230+
DateTimeField::Weeks => write!(f, "WEEKS"),
221231
DateTimeField::Day => write!(f, "DAY"),
222232
DateTimeField::DayOfWeek => write!(f, "DAYOFWEEK"),
223233
DateTimeField::DayOfYear => write!(f, "DAYOFYEAR"),
234+
DateTimeField::Days => write!(f, "DAYS"),
224235
DateTimeField::Date => write!(f, "DATE"),
225236
DateTimeField::Datetime => write!(f, "DATETIME"),
226237
DateTimeField::Hour => write!(f, "HOUR"),
238+
DateTimeField::Hours => write!(f, "HOURS"),
227239
DateTimeField::Minute => write!(f, "MINUTE"),
240+
DateTimeField::Minutes => write!(f, "MINUTES"),
228241
DateTimeField::Second => write!(f, "SECOND"),
242+
DateTimeField::Seconds => write!(f, "SECONDS"),
229243
DateTimeField::Century => write!(f, "CENTURY"),
230244
DateTimeField::Decade => write!(f, "DECADE"),
231245
DateTimeField::Dow => write!(f, "DOW"),

src/keywords.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ define_keywords!(
234234
DAY,
235235
DAYOFWEEK,
236236
DAYOFYEAR,
237+
DAYS,
237238
DEALLOCATE,
238239
DEC,
239240
DECADE,
@@ -499,13 +500,15 @@ define_keywords!(
499500
MILLISECONDS,
500501
MIN,
501502
MINUTE,
503+
MINUTES,
502504
MINVALUE,
503505
MOD,
504506
MODE,
505507
MODIFIES,
506508
MODIFY,
507509
MODULE,
508510
MONTH,
511+
MONTHS,
509512
MSCK,
510513
MULTISET,
511514
MUTATION,
@@ -698,6 +701,7 @@ define_keywords!(
698701
SEARCH,
699702
SECOND,
700703
SECONDARY,
704+
SECONDS,
701705
SECRET,
702706
SECURITY,
703707
SEED,
@@ -866,6 +870,7 @@ define_keywords!(
866870
VOLATILE,
867871
WAREHOUSE,
868872
WEEK,
873+
WEEKS,
869874
WHEN,
870875
WHENEVER,
871876
WHERE,
@@ -880,6 +885,7 @@ define_keywords!(
880885
XML,
881886
XOR,
882887
YEAR,
888+
YEARS,
883889
ZONE,
884890
ZORDER
885891
);

src/parser/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,9 @@ impl<'a> Parser<'a> {
23582358
match &next_token.token {
23592359
Token::Word(w) => match w.keyword {
23602360
Keyword::YEAR => Ok(DateTimeField::Year),
2361+
Keyword::YEARS => Ok(DateTimeField::Years),
23612362
Keyword::MONTH => Ok(DateTimeField::Month),
2363+
Keyword::MONTHS => Ok(DateTimeField::Months),
23622364
Keyword::WEEK => {
23632365
let week_day = if dialect_of!(self is BigQueryDialect | GenericDialect)
23642366
&& self.consume_token(&Token::LParen)
@@ -2371,14 +2373,19 @@ impl<'a> Parser<'a> {
23712373
};
23722374
Ok(DateTimeField::Week(week_day))
23732375
}
2376+
Keyword::WEEKS => Ok(DateTimeField::Weeks),
23742377
Keyword::DAY => Ok(DateTimeField::Day),
23752378
Keyword::DAYOFWEEK => Ok(DateTimeField::DayOfWeek),
23762379
Keyword::DAYOFYEAR => Ok(DateTimeField::DayOfYear),
2380+
Keyword::DAYS => Ok(DateTimeField::Days),
23772381
Keyword::DATE => Ok(DateTimeField::Date),
23782382
Keyword::DATETIME => Ok(DateTimeField::Datetime),
23792383
Keyword::HOUR => Ok(DateTimeField::Hour),
2384+
Keyword::HOURS => Ok(DateTimeField::Hours),
23802385
Keyword::MINUTE => Ok(DateTimeField::Minute),
2386+
Keyword::MINUTES => Ok(DateTimeField::Minutes),
23812387
Keyword::SECOND => Ok(DateTimeField::Second),
2388+
Keyword::SECONDS => Ok(DateTimeField::Seconds),
23822389
Keyword::CENTURY => Ok(DateTimeField::Century),
23832390
Keyword::DECADE => Ok(DateTimeField::Decade),
23842391
Keyword::DOY => Ok(DateTimeField::Doy),
@@ -2605,12 +2612,19 @@ impl<'a> Parser<'a> {
26052612
matches!(
26062613
word.keyword,
26072614
Keyword::YEAR
2615+
| Keyword::YEARS
26082616
| Keyword::MONTH
2617+
| Keyword::MONTHS
26092618
| Keyword::WEEK
2619+
| Keyword::WEEKS
26102620
| Keyword::DAY
2621+
| Keyword::DAYS
26112622
| Keyword::HOUR
2623+
| Keyword::HOURS
26122624
| Keyword::MINUTE
2625+
| Keyword::MINUTES
26132626
| Keyword::SECOND
2627+
| Keyword::SECONDS
26142628
| Keyword::CENTURY
26152629
| Keyword::DECADE
26162630
| Keyword::DOW

tests/sqlparser_common.rs

Lines changed: 88 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ mod test_utils;
5050
#[cfg(test)]
5151
use pretty_assertions::assert_eq;
5252
use sqlparser::ast::ColumnOption::Comment;
53+
use sqlparser::ast::DateTimeField::Seconds;
5354
use sqlparser::ast::Expr::{Identifier, UnaryOp};
5455
use sqlparser::ast::Value::Number;
5556
use sqlparser::test_utils::all_dialects_except;
@@ -5399,6 +5400,19 @@ fn parse_interval_all() {
53995400
expr_from_projection(only(&select.projection)),
54005401
);
54015402

5403+
let sql = "SELECT INTERVAL 5 DAYS";
5404+
let select = verified_only_select(sql);
5405+
assert_eq!(
5406+
&Expr::Interval(Interval {
5407+
value: Box::new(Expr::Value(number("5"))),
5408+
leading_field: Some(DateTimeField::Days),
5409+
leading_precision: None,
5410+
last_field: None,
5411+
fractional_seconds_precision: None,
5412+
}),
5413+
expr_from_projection(only(&select.projection)),
5414+
);
5415+
54025416
let sql = "SELECT INTERVAL '10' HOUR (1)";
54035417
let select = verified_only_select(sql);
54045418
assert_eq!(
@@ -5426,10 +5440,18 @@ fn parse_interval_all() {
54265440

54275441
verified_only_select("SELECT INTERVAL '1' YEAR");
54285442
verified_only_select("SELECT INTERVAL '1' MONTH");
5443+
verified_only_select("SELECT INTERVAL '1' WEEK");
54295444
verified_only_select("SELECT INTERVAL '1' DAY");
54305445
verified_only_select("SELECT INTERVAL '1' HOUR");
54315446
verified_only_select("SELECT INTERVAL '1' MINUTE");
54325447
verified_only_select("SELECT INTERVAL '1' SECOND");
5448+
verified_only_select("SELECT INTERVAL '1' YEARS");
5449+
verified_only_select("SELECT INTERVAL '1' MONTHS");
5450+
verified_only_select("SELECT INTERVAL '1' WEEKS");
5451+
verified_only_select("SELECT INTERVAL '1' DAYS");
5452+
verified_only_select("SELECT INTERVAL '1' HOURS");
5453+
verified_only_select("SELECT INTERVAL '1' MINUTES");
5454+
verified_only_select("SELECT INTERVAL '1' SECONDS");
54335455
verified_only_select("SELECT INTERVAL '1' YEAR TO MONTH");
54345456
verified_only_select("SELECT INTERVAL '1' DAY TO HOUR");
54355457
verified_only_select("SELECT INTERVAL '1' DAY TO MINUTE");
@@ -5439,10 +5461,21 @@ fn parse_interval_all() {
54395461
verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND");
54405462
verified_only_select("SELECT INTERVAL 1 YEAR");
54415463
verified_only_select("SELECT INTERVAL 1 MONTH");
5464+
verified_only_select("SELECT INTERVAL 1 WEEK");
54425465
verified_only_select("SELECT INTERVAL 1 DAY");
54435466
verified_only_select("SELECT INTERVAL 1 HOUR");
54445467
verified_only_select("SELECT INTERVAL 1 MINUTE");
54455468
verified_only_select("SELECT INTERVAL 1 SECOND");
5469+
verified_only_select("SELECT INTERVAL 1 YEARS");
5470+
verified_only_select("SELECT INTERVAL 1 MONTHS");
5471+
verified_only_select("SELECT INTERVAL 1 WEEKS");
5472+
verified_only_select("SELECT INTERVAL 1 DAYS");
5473+
verified_only_select("SELECT INTERVAL 1 HOURS");
5474+
verified_only_select("SELECT INTERVAL 1 MINUTES");
5475+
verified_only_select("SELECT INTERVAL 1 SECONDS");
5476+
verified_only_select(
5477+
"SELECT '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::INTERVAL",
5478+
);
54465479
}
54475480

54485481
#[test]
@@ -11356,16 +11389,12 @@ fn test_group_by_nothing() {
1135611389
#[test]
1135711390
fn test_extract_seconds_ok() {
1135811391
let dialects = all_dialects_where(|d| d.allow_extract_custom());
11359-
let stmt = dialects.verified_expr("EXTRACT(seconds FROM '2 seconds'::INTERVAL)");
11392+
let stmt = dialects.verified_expr("EXTRACT(SECONDS FROM '2 seconds'::INTERVAL)");
1136011393

1136111394
assert_eq!(
1136211395
stmt,
1136311396
Expr::Extract {
11364-
field: DateTimeField::Custom(Ident {
11365-
value: "seconds".to_string(),
11366-
quote_style: None,
11367-
span: Span::empty(),
11368-
}),
11397+
field: Seconds,
1136911398
syntax: ExtractSyntax::From,
1137011399
expr: Box::new(Expr::Cast {
1137111400
kind: CastKind::DoubleColon,
@@ -11376,7 +11405,59 @@ fn test_extract_seconds_ok() {
1137611405
format: None,
1137711406
}),
1137811407
}
11379-
)
11408+
);
11409+
11410+
let actual_ast = dialects
11411+
.parse_sql_statements("SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)")
11412+
.unwrap();
11413+
11414+
let expected_ast = vec![Statement::Query(Box::new(Query {
11415+
with: None,
11416+
body: Box::new(SetExpr::Select(Box::new(Select {
11417+
select_token: AttachedToken::empty(),
11418+
distinct: None,
11419+
top: None,
11420+
top_before_distinct: false,
11421+
projection: vec![UnnamedExpr(Expr::Extract {
11422+
field: Seconds,
11423+
syntax: ExtractSyntax::From,
11424+
expr: Box::new(Expr::Cast {
11425+
kind: CastKind::DoubleColon,
11426+
expr: Box::new(Expr::Value(Value::SingleQuotedString(
11427+
"2 seconds".to_string(),
11428+
))),
11429+
data_type: DataType::Interval,
11430+
format: None,
11431+
}),
11432+
})],
11433+
into: None,
11434+
from: vec![],
11435+
lateral_views: vec![],
11436+
prewhere: None,
11437+
selection: None,
11438+
group_by: GroupByExpr::Expressions(vec![], vec![]),
11439+
cluster_by: vec![],
11440+
distribute_by: vec![],
11441+
sort_by: vec![],
11442+
having: None,
11443+
named_window: vec![],
11444+
qualify: None,
11445+
window_before_qualify: false,
11446+
value_table_mode: None,
11447+
connect_by: None,
11448+
}))),
11449+
order_by: None,
11450+
limit: None,
11451+
limit_by: vec![],
11452+
offset: None,
11453+
fetch: None,
11454+
locks: vec![],
11455+
for_clause: None,
11456+
settings: None,
11457+
format_clause: None,
11458+
}))];
11459+
11460+
assert_eq!(actual_ast, expected_ast);
1138011461
}
1138111462

1138211463
#[test]
@@ -11405,17 +11486,6 @@ fn test_extract_seconds_single_quote_ok() {
1140511486
)
1140611487
}
1140711488

11408-
#[test]
11409-
fn test_extract_seconds_err() {
11410-
let sql = "SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)";
11411-
let dialects = all_dialects_except(|d| d.allow_extract_custom());
11412-
let err = dialects.parse_sql_statements(sql).unwrap_err();
11413-
assert_eq!(
11414-
err.to_string(),
11415-
"sql parser error: Expected: date/time field, found: seconds"
11416-
);
11417-
}
11418-
1141911489
#[test]
1142011490
fn test_extract_seconds_single_quote_err() {
1142111491
let sql = r#"SELECT EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#;

0 commit comments

Comments
 (0)