Skip to content

Commit 8ffe0ee

Browse files
committed
Support for interval literals
1 parent d393d99 commit 8ffe0ee

File tree

5 files changed

+317
-47
lines changed

5 files changed

+317
-47
lines changed

src/sqlast/mod.rs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub use self::query::{
2626
SQLSelectItem, SQLSetExpr, SQLSetOperator, TableFactor,
2727
};
2828
pub use self::sqltype::SQLType;
29-
pub use self::value::Value;
29+
pub use self::value::{SQLDateTimeField, SQLIntervalQualifier, Value};
3030

3131
pub use self::sql_operator::SQLOperator;
3232

@@ -611,29 +611,6 @@ impl ToString for SQLColumnDef {
611611
}
612612
}
613613

614-
#[derive(Debug, Clone, PartialEq, Hash)]
615-
pub enum SQLDateTimeField {
616-
Year,
617-
Month,
618-
Day,
619-
Hour,
620-
Minute,
621-
Second,
622-
}
623-
624-
impl ToString for SQLDateTimeField {
625-
fn to_string(&self) -> String {
626-
match self {
627-
SQLDateTimeField::Year => "YEAR".to_string(),
628-
SQLDateTimeField::Month => "MONTH".to_string(),
629-
SQLDateTimeField::Day => "DAY".to_string(),
630-
SQLDateTimeField::Hour => "HOUR".to_string(),
631-
SQLDateTimeField::Minute => "MINUTE".to_string(),
632-
SQLDateTimeField::Second => "SECOND".to_string(),
633-
}
634-
}
635-
}
636-
637614
/// External table's available file format
638615
#[derive(Debug, Clone, PartialEq)]
639616
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: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ pub enum Value {
1717
Time(String),
1818
/// Timestamp literals, which include both a date and time
1919
Timestamp(String),
20+
/// Time intervals
21+
Interval {
22+
value: String,
23+
start_qualifier: SQLIntervalQualifier,
24+
end_qualifier: SQLIntervalQualifier,
25+
},
2026
/// NULL value in insert statements,
2127
Null,
2228
}
@@ -29,14 +35,98 @@ impl ToString for Value {
2935
Value::SingleQuotedString(v) => format!("'{}'", escape_single_quote_string(v)),
3036
Value::NationalStringLiteral(v) => format!("N'{}'", v),
3137
Value::Boolean(v) => v.to_string(),
32-
Value::Date(v) => format!("date '{}'", escape_single_quote_string(v)),
33-
Value::Time(v) => format!("time '{}'", escape_single_quote_string(v)),
34-
Value::Timestamp(v) => format!("timestamp '{}'", escape_single_quote_string(v)),
38+
Value::Date(v) => format!("DATE '{}'", escape_single_quote_string(v)),
39+
Value::Time(v) => format!("TIME '{}'", escape_single_quote_string(v)),
40+
Value::Timestamp(v) => format!("TIMESTAMP '{}'", escape_single_quote_string(v)),
41+
Value::Interval {
42+
value,
43+
start_qualifier,
44+
end_qualifier,
45+
} => format_interval(value, start_qualifier, end_qualifier),
3546
Value::Null => "NULL".to_string(),
3647
}
3748
}
3849
}
3950

51+
fn format_interval(
52+
value: &str,
53+
start_qualifier: &SQLIntervalQualifier,
54+
end_qualifier: &SQLIntervalQualifier,
55+
) -> String {
56+
let mut s = format!("INTERVAL '{}' ", escape_single_quote_string(value),);
57+
match (start_qualifier, end_qualifier) {
58+
(
59+
SQLIntervalQualifier {
60+
field: SQLDateTimeField::Second,
61+
precision: Some(p1),
62+
},
63+
SQLIntervalQualifier {
64+
field: SQLDateTimeField::Second,
65+
precision: Some(p2),
66+
},
67+
) => {
68+
// Both the start and end fields are in seconds, and both have
69+
// precisions. The SQL standard special cases how this is formatted.
70+
s += &format!("SECOND ({}, {})", p1, p2);
71+
}
72+
73+
(start, end) if start == end => {
74+
// The start and end qualifiers are the same. In this case we can
75+
// output only the start field.
76+
s += &start_qualifier.to_string()
77+
}
78+
79+
_ => {
80+
// General case: output both, with precisions.
81+
s += &format!(
82+
"{} TO {}",
83+
start_qualifier.to_string(),
84+
end_qualifier.to_string()
85+
);
86+
}
87+
}
88+
s
89+
}
90+
91+
#[derive(Debug, Clone, PartialEq, Hash)]
92+
pub struct SQLIntervalQualifier {
93+
pub field: SQLDateTimeField,
94+
pub precision: Option<u64>,
95+
}
96+
97+
impl ToString for SQLIntervalQualifier {
98+
fn to_string(&self) -> String {
99+
let mut s = self.field.to_string();
100+
if let Some(precision) = self.precision {
101+
s += &format!(" ({})", precision);
102+
}
103+
s
104+
}
105+
}
106+
107+
#[derive(Debug, Clone, PartialEq, Hash)]
108+
pub enum SQLDateTimeField {
109+
Year,
110+
Month,
111+
Day,
112+
Hour,
113+
Minute,
114+
Second,
115+
}
116+
117+
impl ToString for SQLDateTimeField {
118+
fn to_string(&self) -> String {
119+
match self {
120+
SQLDateTimeField::Year => "YEAR".to_string(),
121+
SQLDateTimeField::Month => "MONTH".to_string(),
122+
SQLDateTimeField::Day => "DAY".to_string(),
123+
SQLDateTimeField::Hour => "HOUR".to_string(),
124+
SQLDateTimeField::Minute => "MINUTE".to_string(),
125+
SQLDateTimeField::Second => "SECOND".to_string(),
126+
}
127+
}
128+
}
129+
40130
fn escape_single_quote_string(s: &str) -> String {
41131
let mut escaped = String::new();
42132
for c in s.chars() {

src/sqlparser.rs

Lines changed: 97 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ impl Parser {
194194
"DATE" => Ok(ASTNode::SQLValue(Value::Date(self.parse_literal_string()?))),
195195
"EXISTS" => self.parse_exists_expression(),
196196
"EXTRACT" => self.parse_extract_expression(),
197+
"INTERVAL" => self.parse_literal_interval(),
197198
"NOT" => Ok(ASTNode::SQLUnary {
198199
operator: SQLOperator::Not,
199200
expr: Box::new(self.parse_subexpr(Self::UNARY_NOT_PREC)?),
@@ -421,20 +422,7 @@ impl Parser {
421422

422423
pub fn parse_extract_expression(&mut self) -> Result<ASTNode, ParserError> {
423424
self.expect_token(&Token::LParen)?;
424-
let tok = self.next_token();
425-
let field = if let Some(Token::SQLWord(ref k)) = tok {
426-
match k.keyword.as_ref() {
427-
"YEAR" => SQLDateTimeField::Year,
428-
"MONTH" => SQLDateTimeField::Month,
429-
"DAY" => SQLDateTimeField::Day,
430-
"HOUR" => SQLDateTimeField::Hour,
431-
"MINUTE" => SQLDateTimeField::Minute,
432-
"SECOND" => SQLDateTimeField::Second,
433-
_ => self.expected("Date/time field inside of EXTRACT function", tok)?,
434-
}
435-
} else {
436-
self.expected("Date/time field inside of EXTRACT function", tok)?
437-
};
425+
let field = self.parse_date_time_field()?;
438426
self.expect_keyword("FROM")?;
439427
let expr = self.parse_expr()?;
440428
self.expect_token(&Token::RParen)?;
@@ -444,6 +432,100 @@ impl Parser {
444432
})
445433
}
446434

435+
pub fn parse_date_time_field(&mut self) -> Result<SQLDateTimeField, ParserError> {
436+
let tok = self.next_token();
437+
if let Some(Token::SQLWord(ref k)) = tok {
438+
match k.keyword.as_ref() {
439+
"YEAR" => Ok(SQLDateTimeField::Year),
440+
"MONTH" => Ok(SQLDateTimeField::Month),
441+
"DAY" => Ok(SQLDateTimeField::Day),
442+
"HOUR" => Ok(SQLDateTimeField::Hour),
443+
"MINUTE" => Ok(SQLDateTimeField::Minute),
444+
"SECOND" => Ok(SQLDateTimeField::Second),
445+
_ => self.expected("date/time field", tok)?,
446+
}
447+
} else {
448+
self.expected("date/time field", tok)?
449+
}
450+
}
451+
452+
/// Parse an INTERVAL literal.
453+
///
454+
/// Some valid intervals:
455+
/// 1. `INTERVAL '1' DAY`
456+
/// 2. `INTERVAL '1-1' YEAR TO MONTH`
457+
/// 3. `INTERVAL '1' SECONDS`
458+
/// 4. `INTERVAL '1:1:1.1' HOUR (5) TO SECONDS (5)`
459+
/// 5. `INTERVAL '1.1` SECONDS (2, 2)`
460+
/// 6. `INTERVAL '1:1' HOUR (5) TO MINUTE (5)`
461+
/// 7. `INTERVAL '1:1' SECOND TO SECOND`
462+
///
463+
/// Note that (6) is not technically standards compliant, as the only
464+
/// end qualifier which can specify a precision is `SECOND`. (7) is also
465+
/// not standards compliant, as `SECOND` is not permitted to appear as a
466+
/// start qualifier, except in the special form of (5). In the interest of
467+
/// sanity, for the time being, we accept all the forms listed above.
468+
pub fn parse_literal_interval(&mut self) -> Result<ASTNode, ParserError> {
469+
// The first token in an interval is a string literal which specifies
470+
// the duration of the interval.
471+
let value = self.parse_literal_string()?;
472+
473+
// Following the string literal is a qualifier which indicates the units
474+
// of the duration specified in the string literal.
475+
let start_field = self.parse_date_time_field()?;
476+
477+
// The start qualifier is optionally followed by a numeric precision.
478+
// If the the start qualifier has the same units as the end qualifier,
479+
// we'll actually get *two* precisions here, for both the start and the
480+
// end.
481+
let (start_precision, end_precision) = if self.consume_token(&Token::LParen) {
482+
let start_precision = Some(self.parse_literal_uint()?);
483+
let end_precision = if self.consume_token(&Token::Comma) {
484+
Some(self.parse_literal_uint()?)
485+
} else {
486+
None
487+
};
488+
self.expect_token(&Token::RParen)?;
489+
(start_precision, end_precision)
490+
} else {
491+
(None, None)
492+
};
493+
494+
// The start qualifier is optionally followed by an end qualifier.
495+
let (end_field, end_precision) = if self.parse_keyword("TO") {
496+
if end_precision.is_some() {
497+
// This is an interval like `INTERVAL '1' HOUR (2, 2) TO MINUTE (3)`.
498+
// We've already seen the end precision, so allowing an explicit
499+
// end qualifier would raise the question of which to keep.
500+
// Note that while technically `INTERVAL '1:1' HOUR (2, 2) TO MINUTE`
501+
// is unambiguous, it is extremely confusing and non-standard,
502+
// so just reject it.
503+
return parser_err!("Cannot use dual-precision syntax with TO in INTERVAL literal");
504+
}
505+
(
506+
self.parse_date_time_field()?,
507+
self.parse_optional_precision()?,
508+
)
509+
} else {
510+
// If no end qualifier is specified, use the values from the start
511+
// qualifier. Note that we might have an explicit end precision even
512+
// if we don't have an explicit start field.
513+
(start_field.clone(), end_precision.or(start_precision))
514+
};
515+
516+
Ok(ASTNode::SQLValue(Value::Interval {
517+
value,
518+
start_qualifier: SQLIntervalQualifier {
519+
field: start_field,
520+
precision: start_precision,
521+
},
522+
end_qualifier: SQLIntervalQualifier {
523+
field: end_field,
524+
precision: end_precision,
525+
},
526+
}))
527+
}
528+
447529
/// Parse an operator following an expression
448530
pub fn parse_infix(&mut self, expr: ASTNode, precedence: u8) -> Result<ASTNode, ParserError> {
449531
debug!("parsing infix");
@@ -1151,6 +1233,7 @@ impl Parser {
11511233
}
11521234
Ok(SQLType::Time)
11531235
}
1236+
"INTERVAL" => Ok(SQLType::Interval),
11541237
"REGCLASS" => Ok(SQLType::Regclass),
11551238
"TEXT" => {
11561239
if self.consume_token(&Token::LBracket) {

0 commit comments

Comments
 (0)