Skip to content

Commit 2d1e05e

Browse files
authored
Merge pull request #103 from benesch/intervals
Support interval literals
2 parents 5536cd1 + 2798ddf commit 2d1e05e

File tree

5 files changed

+260
-41
lines changed

5 files changed

+260
-41
lines changed

src/sqlast/mod.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub use self::query::{
3030
SQLSelectItem, SQLSetExpr, SQLSetOperator, SQLValues, TableAlias, TableFactor,
3131
};
3232
pub use self::sqltype::SQLType;
33-
pub use self::value::Value;
33+
pub use self::value::{SQLDateTimeField, Value};
3434

3535
pub use self::sql_operator::SQLOperator;
3636

@@ -637,29 +637,6 @@ impl ToString for SQLFunction {
637637
}
638638
}
639639

640-
#[derive(Debug, Clone, PartialEq, Hash)]
641-
pub enum SQLDateTimeField {
642-
Year,
643-
Month,
644-
Day,
645-
Hour,
646-
Minute,
647-
Second,
648-
}
649-
650-
impl ToString for SQLDateTimeField {
651-
fn to_string(&self) -> String {
652-
match self {
653-
SQLDateTimeField::Year => "YEAR".to_string(),
654-
SQLDateTimeField::Month => "MONTH".to_string(),
655-
SQLDateTimeField::Day => "DAY".to_string(),
656-
SQLDateTimeField::Hour => "HOUR".to_string(),
657-
SQLDateTimeField::Minute => "MINUTE".to_string(),
658-
SQLDateTimeField::Second => "SECOND".to_string(),
659-
}
660-
}
661-
}
662-
663640
/// External table's available file format
664641
#[derive(Debug, Clone, PartialEq, Hash)]
665642
pub enum FileFormat {

src/sqlast/sqltype.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ pub enum SQLType {
3939
Time,
4040
/// Timestamp
4141
Timestamp,
42+
/// Interval
43+
Interval,
4244
/// Regclass used in postgresql serial
4345
Regclass,
4446
/// Text
@@ -78,6 +80,7 @@ impl ToString for SQLType {
7880
SQLType::Date => "date".to_string(),
7981
SQLType::Time => "time".to_string(),
8082
SQLType::Timestamp => "timestamp".to_string(),
83+
SQLType::Interval => "interval".to_string(),
8184
SQLType::Regclass => "regclass".to_string(),
8285
SQLType::Text => "text".to_string(),
8386
SQLType::Bytea => "bytea".to_string(),

src/sqlast/value.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ pub enum Value {
2121
Time(String),
2222
/// Timestamp literals, which include both a date and time
2323
Timestamp(String),
24+
/// INTERVAL literals, e.g. INTERVAL '12:34.56' MINUTE TO SECOND (2)
25+
Interval {
26+
value: String,
27+
leading_field: SQLDateTimeField,
28+
leading_precision: Option<u64>,
29+
last_field: Option<SQLDateTimeField>,
30+
fractional_seconds_precision: Option<u64>,
31+
},
2432
/// NULL value in insert statements,
2533
Null,
2634
}
@@ -37,11 +45,74 @@ impl ToString for Value {
3745
Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)),
3846
Value::Time(v) => format!("TIME '{}'", escape_single_quote_string(v)),
3947
Value::Timestamp(v) => format!("TIMESTAMP '{}'", escape_single_quote_string(v)),
48+
Value::Interval {
49+
value,
50+
leading_field: SQLDateTimeField::Second,
51+
leading_precision: Some(leading_precision),
52+
last_field,
53+
fractional_seconds_precision: Some(fractional_seconds_precision),
54+
} => {
55+
// When the leading field is SECOND, the parser guarantees that
56+
// the last field is None.
57+
assert!(last_field.is_none());
58+
format!(
59+
"INTERVAL '{}' SECOND ({}, {})",
60+
escape_single_quote_string(value),
61+
leading_precision,
62+
fractional_seconds_precision
63+
)
64+
}
65+
Value::Interval {
66+
value,
67+
leading_field,
68+
leading_precision,
69+
last_field,
70+
fractional_seconds_precision,
71+
} => {
72+
let mut s = format!(
73+
"INTERVAL '{}' {}",
74+
escape_single_quote_string(value),
75+
leading_field.to_string()
76+
);
77+
if let Some(leading_precision) = leading_precision {
78+
s += &format!(" ({})", leading_precision);
79+
}
80+
if let Some(last_field) = last_field {
81+
s += &format!(" TO {}", last_field.to_string());
82+
}
83+
if let Some(fractional_seconds_precision) = fractional_seconds_precision {
84+
s += &format!(" ({})", fractional_seconds_precision);
85+
}
86+
s
87+
}
4088
Value::Null => "NULL".to_string(),
4189
}
4290
}
4391
}
4492

93+
#[derive(Debug, Clone, PartialEq, Hash)]
94+
pub enum SQLDateTimeField {
95+
Year,
96+
Month,
97+
Day,
98+
Hour,
99+
Minute,
100+
Second,
101+
}
102+
103+
impl ToString for SQLDateTimeField {
104+
fn to_string(&self) -> String {
105+
match self {
106+
SQLDateTimeField::Year => "YEAR".to_string(),
107+
SQLDateTimeField::Month => "MONTH".to_string(),
108+
SQLDateTimeField::Day => "DAY".to_string(),
109+
SQLDateTimeField::Hour => "HOUR".to_string(),
110+
SQLDateTimeField::Minute => "MINUTE".to_string(),
111+
SQLDateTimeField::Second => "SECOND".to_string(),
112+
}
113+
}
114+
}
115+
45116
fn escape_single_quote_string(s: &str) -> String {
46117
let mut escaped = String::new();
47118
for c in s.chars() {

src/sqlparser.rs

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ impl Parser {
180180
"DATE" => Ok(ASTNode::SQLValue(Value::Date(self.parse_literal_string()?))),
181181
"EXISTS" => self.parse_exists_expression(),
182182
"EXTRACT" => self.parse_extract_expression(),
183+
"INTERVAL" => self.parse_literal_interval(),
183184
"NOT" => Ok(ASTNode::SQLUnary {
184185
operator: SQLOperator::Not,
185186
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
@@ -410,20 +411,7 @@ impl Parser {
410411

411412
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
412413
self.expect_token(&Token::LParen)?;
413-
let tok = self.next_token();
414-
let field = if let Some(Token::SQLWord(ref k)) = tok {
415-
match k.keyword.as_ref() {
416-
"YEAR" => SQLDateTimeField::Year,
417-
"MONTH" => SQLDateTimeField::Month,
418-
"DAY" => SQLDateTimeField::Day,
419-
"HOUR" => SQLDateTimeField::Hour,
420-
"MINUTE" => SQLDateTimeField::Minute,
421-
"SECOND" => SQLDateTimeField::Second,
422-
_ => self.expected("Date/time field inside of EXTRACT function", tok)?,
423-
}
424-
} else {
425-
self.expected("Date/time field inside of EXTRACT function", tok)?
426-
};
414+
let field = self.parse_date_time_field()?;
427415
self.expect_keyword("FROM")?;
428416
let expr = self.parse_expr()?;
429417
self.expect_token(&Token::RParen)?;
@@ -433,6 +421,90 @@ impl Parser {
433421
})
434422
}
435423

424+
// This function parses date/time fields for both the EXTRACT function-like
425+
// operator and interval qualifiers. EXTRACT supports a wider set of
426+
// date/time fields than interval qualifiers, so this function may need to
427+
// be split in two.
428+
pub fn parse_date_time_field(&mut self) -> Result<SQLDateTimeField, ParserError> {
429+
let tok = self.next_token();
430+
if let Some(Token::SQLWord(ref k)) = tok {
431+
match k.keyword.as_ref() {
432+
"YEAR" => Ok(SQLDateTimeField::Year),
433+
"MONTH" => Ok(SQLDateTimeField::Month),
434+
"DAY" => Ok(SQLDateTimeField::Day),
435+
"HOUR" => Ok(SQLDateTimeField::Hour),
436+
"MINUTE" => Ok(SQLDateTimeField::Minute),
437+
"SECOND" => Ok(SQLDateTimeField::Second),
438+
_ => self.expected("date/time field", tok)?,
439+
}
440+
} else {
441+
self.expected("date/time field", tok)?
442+
}
443+
}
444+
445+
/// Parse an INTERVAL literal.
446+
///
447+
/// Some syntactically valid intervals:
448+
///
449+
/// 1. `INTERVAL '1' DAY`
450+
/// 2. `INTERVAL '1-1' YEAR TO MONTH`
451+
/// 3. `INTERVAL '1' SECOND`
452+
/// 4. `INTERVAL '1:1:1.1' HOUR (5) TO SECOND (5)`
453+
/// 5. `INTERVAL '1.1' SECOND (2, 2)`
454+
/// 6. `INTERVAL '1:1' HOUR (5) TO MINUTE (5)`
455+
///
456+
/// Note that we do not currently attempt to parse the quoted value.
457+
pub fn parse_literal_interval(&mut self) -> Result<ASTNode, ParserError> {
458+
// The SQL standard allows an optional sign before the value string, but
459+
// it is not clear if any implementations support that syntax, so we
460+
// don't currently try to parse it. (The sign can instead be included
461+
// inside the value string.)
462+
463+
// The first token in an interval is a string literal which specifies
464+
// the duration of the interval.
465+
let value = self.parse_literal_string()?;
466+
467+
// Following the string literal is a qualifier which indicates the units
468+
// of the duration specified in the string literal.
469+
//
470+
// Note that PostgreSQL allows omitting the qualifier, but we currently
471+
// require at least the leading field, in accordance with the ANSI spec.
472+
let leading_field = self.parse_date_time_field()?;
473+
474+
let (leading_precision, last_field, fsec_precision) =
475+
if leading_field == SQLDateTimeField::Second {
476+
// SQL mandates special syntax for `SECOND TO SECOND` literals.
477+
// Instead of
478+
// `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`
479+
// one must use the special format:
480+
// `SECOND [( <leading precision> [ , <fractional seconds precision>] )]`
481+
let last_field = None;
482+
let (leading_precision, fsec_precision) = self.parse_optional_precision_scale()?;
483+
(leading_precision, last_field, fsec_precision)
484+
} else {
485+
let leading_precision = self.parse_optional_precision()?;
486+
if self.parse_keyword("TO") {
487+
let last_field = Some(self.parse_date_time_field()?);
488+
let fsec_precision = if last_field == Some(SQLDateTimeField::Second) {
489+
self.parse_optional_precision()?
490+
} else {
491+
None
492+
};
493+
(leading_precision, last_field, fsec_precision)
494+
} else {
495+
(leading_precision, None, None)
496+
}
497+
};
498+
499+
Ok(ASTNode::SQLValue(Value::Interval {
500+
value,
501+
leading_field,
502+
leading_precision,
503+
last_field,
504+
fractional_seconds_precision: fsec_precision,
505+
}))
506+
}
507+
436508
/// Parse an operator following an expression
437509
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
438510
debug!("parsing infix");
@@ -1202,6 +1274,10 @@ impl Parser {
12021274
}
12031275
Ok(SQLType::Time)
12041276
}
1277+
// Interval types can be followed by a complicated interval
1278+
// qualifier that we don't currently support. See
1279+
// parse_interval_literal for a taste.
1280+
"INTERVAL" => Ok(SQLType::Interval),
12051281
"REGCLASS" => Ok(SQLType::Regclass),
12061282
"TEXT" => {
12071283
if self.consume_token(&Token::LBracket) {

tests/sqlparser_common.rs

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -819,9 +819,7 @@ fn parse_extract() {
819819

820820
let res = parse_sql_statements("SELECT EXTRACT(MILLISECOND FROM d)");
821821
assert_eq!(
822-
ParserError::ParserError(
823-
"Expected Date/time field inside of EXTRACT function, found: MILLISECOND".to_string()
824-
),
822+
ParserError::ParserError("Expected date/time field, found: MILLISECOND".to_string()),
825823
res.unwrap_err()
826824
);
827825
}
@@ -1189,6 +1187,100 @@ fn parse_literal_timestamp() {
11891187
);
11901188
}
11911189

1190+
#[test]
1191+
fn parse_literal_interval() {
1192+
let sql = "SELECT INTERVAL '1-1' YEAR TO MONTH";
1193+
let select = verified_only_select(sql);
1194+
assert_eq!(
1195+
&ASTNode::SQLValue(Value::Interval {
1196+
value: "1-1".into(),
1197+
leading_field: SQLDateTimeField::Year,
1198+
leading_precision: None,
1199+
last_field: Some(SQLDateTimeField::Month),
1200+
fractional_seconds_precision: None,
1201+
}),
1202+
expr_from_projection(only(&select.projection)),
1203+
);
1204+
1205+
let sql = "SELECT INTERVAL '01:01.01' MINUTE (5) TO SECOND (5)";
1206+
let select = verified_only_select(sql);
1207+
assert_eq!(
1208+
&ASTNode::SQLValue(Value::Interval {
1209+
value: "01:01.01".into(),
1210+
leading_field: SQLDateTimeField::Minute,
1211+
leading_precision: Some(5),
1212+
last_field: Some(SQLDateTimeField::Second),
1213+
fractional_seconds_precision: Some(5),
1214+
}),
1215+
expr_from_projection(only(&select.projection)),
1216+
);
1217+
1218+
let sql = "SELECT INTERVAL '1' SECOND (5, 4)";
1219+
let select = verified_only_select(sql);
1220+
assert_eq!(
1221+
&ASTNode::SQLValue(Value::Interval {
1222+
value: "1".into(),
1223+
leading_field: SQLDateTimeField::Second,
1224+
leading_precision: Some(5),
1225+
last_field: None,
1226+
fractional_seconds_precision: Some(4),
1227+
}),
1228+
expr_from_projection(only(&select.projection)),
1229+
);
1230+
1231+
let sql = "SELECT INTERVAL '10' HOUR";
1232+
let select = verified_only_select(sql);
1233+
assert_eq!(
1234+
&ASTNode::SQLValue(Value::Interval {
1235+
value: "10".into(),
1236+
leading_field: SQLDateTimeField::Hour,
1237+
leading_precision: None,
1238+
last_field: None,
1239+
fractional_seconds_precision: None,
1240+
}),
1241+
expr_from_projection(only(&select.projection)),
1242+
);
1243+
1244+
let sql = "SELECT INTERVAL '10' HOUR (1)";
1245+
let select = verified_only_select(sql);
1246+
assert_eq!(
1247+
&ASTNode::SQLValue(Value::Interval {
1248+
value: "10".into(),
1249+
leading_field: SQLDateTimeField::Hour,
1250+
leading_precision: Some(1),
1251+
last_field: None,
1252+
fractional_seconds_precision: None,
1253+
}),
1254+
expr_from_projection(only(&select.projection)),
1255+
);
1256+
1257+
let result = parse_sql_statements("SELECT INTERVAL '1' SECOND TO SECOND");
1258+
assert_eq!(
1259+
ParserError::ParserError("Expected end of statement, found: SECOND".to_string()),
1260+
result.unwrap_err(),
1261+
);
1262+
1263+
let result = parse_sql_statements("SELECT INTERVAL '10' HOUR (1) TO HOUR (2)");
1264+
assert_eq!(
1265+
ParserError::ParserError("Expected end of statement, found: (".to_string()),
1266+
result.unwrap_err(),
1267+
);
1268+
1269+
verified_only_select("SELECT INTERVAL '1' YEAR");
1270+
verified_only_select("SELECT INTERVAL '1' MONTH");
1271+
verified_only_select("SELECT INTERVAL '1' DAY");
1272+
verified_only_select("SELECT INTERVAL '1' HOUR");
1273+
verified_only_select("SELECT INTERVAL '1' MINUTE");
1274+
verified_only_select("SELECT INTERVAL '1' SECOND");
1275+
verified_only_select("SELECT INTERVAL '1' YEAR TO MONTH");
1276+
verified_only_select("SELECT INTERVAL '1' DAY TO HOUR");
1277+
verified_only_select("SELECT INTERVAL '1' DAY TO MINUTE");
1278+
verified_only_select("SELECT INTERVAL '1' DAY TO SECOND");
1279+
verified_only_select("SELECT INTERVAL '1' HOUR TO MINUTE");
1280+
verified_only_select("SELECT INTERVAL '1' HOUR TO SECOND");
1281+
verified_only_select("SELECT INTERVAL '1' MINUTE TO SECOND");
1282+
}
1283+
11921284
#[test]
11931285
fn parse_simple_math_expr_plus() {
11941286
let sql = "SELECT a + b, 2 + a, 2.5 + a, a_f + b_f, 2 + a_f, 2.5 + a_f FROM c";

0 commit comments

Comments
 (0)