Skip to content
This repository was archived by the owner on Dec 25, 2019. It is now read-only.

Commit 03ba9f8

Browse files
committed
Add a computed value to IntervalValue to get something useful
Knowing that we've got some optional number of years/seconds/minutes in a `ParsedDateTime` isn't nearly as useful as being able to state that you have 13 months or 250,000 milliseconds. Now we can get either/or of those.
1 parent 01d6133 commit 03ba9f8

5 files changed

Lines changed: 476 additions & 73 deletions

File tree

src/ast/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ pub use self::query::{
4949
Cte, Fetch, Join, JoinConstraint, JoinOperator, OrderByExpr, Query, Select, SelectItem,
5050
SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Values,
5151
};
52-
pub use self::value::{DateTimeField, IntervalValue, ParsedDateTime, Value};
52+
pub use self::value::{DateTimeField, Interval, IntervalValue, ParsedDateTime, Value};
5353

5454
struct DisplaySeparated<'a, T>
5555
where

src/ast/value.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,18 @@ use bigdecimal::BigDecimal;
1414
use std::fmt;
1515

1616
mod datetime;
17-
pub use datetime::{DateTimeField, IntervalValue, ParsedDateTime};
17+
pub use datetime::{DateTimeField, Interval, IntervalValue, ParsedDateTime};
18+
19+
#[derive(Debug)]
20+
pub struct ValueError(String);
21+
22+
impl std::error::Error for ValueError {}
23+
24+
impl fmt::Display for ValueError {
25+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26+
write!(f, "ValueError({})", self.0)
27+
}
28+
}
1829

1930
/// Primitive SQL values such as number and string
2031
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
@@ -54,6 +65,7 @@ pub enum Value {
5465
}
5566

5667
impl fmt::Display for Value {
68+
#[allow(clippy::unneeded_field_pattern)] // want to be warned if we add another field
5769
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
5870
match self {
5971
Value::Long(v) => write!(f, "{}", v),
@@ -132,3 +144,39 @@ impl<'a> fmt::Display for EscapeSingleQuoteString<'a> {
132144
pub fn escape_single_quote_string(s: &str) -> EscapeSingleQuoteString<'_> {
133145
EscapeSingleQuoteString(s)
134146
}
147+
148+
#[cfg(test)]
149+
mod test {
150+
use super::*;
151+
152+
/// An extremely default interval value
153+
fn ivalue() -> IntervalValue {
154+
IntervalValue {
155+
value: "".into(),
156+
parsed: ParsedDateTime::default(),
157+
leading_field: DateTimeField::Year,
158+
leading_precision: None,
159+
last_field: None,
160+
fractional_seconds_precision: None,
161+
}
162+
}
163+
164+
#[test]
165+
fn interval_values() {
166+
let mut iv = ivalue();
167+
iv.parsed.year = None;
168+
match iv.computed() {
169+
Err(ValueError { .. }) => {}
170+
Ok(why) => panic!("should not be okay: {:?}", why),
171+
}
172+
}
173+
174+
#[test]
175+
fn iterate_datetimefield() {
176+
use DateTimeField::*;
177+
assert_eq!(
178+
Year.into_iter().take(10).collect::<Vec<_>>(),
179+
vec![Month, Day, Hour, Minute, Second]
180+
)
181+
}
182+
}

src/ast/value/datetime.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use std::fmt;
2+
use std::time::Duration;
3+
4+
use super::ValueError;
25

36
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47
pub struct IntervalValue {
@@ -40,6 +43,128 @@ pub struct IntervalValue {
4043
pub fractional_seconds_precision: Option<u64>,
4144
}
4245

46+
impl IntervalValue {
47+
/// Get Either the number of Months or the Duration specified by this interval
48+
///
49+
/// # Errors
50+
///
51+
/// If a required field is missing (i.e. there is no value) or the `TO` field is wrong
52+
pub fn computed(&self) -> Result<Interval, ValueError> {
53+
use DateTimeField::*;
54+
match &self.leading_field {
55+
Year => match &self.last_field {
56+
Some(Month) => Ok(Interval::Months(
57+
self.positivity() * self.parsed.year.unwrap_or(0) as i64 * 12
58+
+ self.parsed.month.unwrap_or(0) as i64,
59+
)),
60+
Some(Year) | None => self
61+
.parsed
62+
.year
63+
.ok_or_else(|| ValueError("No YEAR provided".into()))
64+
.map(|year| Interval::Months(self.positivity() * year as i64 * 12)),
65+
Some(invalid) => Err(ValueError(format!(
66+
"Invalid specifier for YEAR precision: {}",
67+
&invalid
68+
))),
69+
},
70+
Month => match &self.last_field {
71+
Some(Month) | None => self
72+
.parsed
73+
.month
74+
.ok_or_else(|| ValueError("No MONTH provided".into()))
75+
.map(|m| Interval::Months(self.positivity() * m as i64)),
76+
Some(invalid) => Err(ValueError(format!(
77+
"Invalid specifier for MONTH precision: {}",
78+
&invalid
79+
))),
80+
},
81+
durationlike_field => {
82+
let mut seconds = 0u64;
83+
match self.units_of(&durationlike_field) {
84+
Some(time) => seconds += time * seconds_multiplier(&durationlike_field),
85+
None => {
86+
return Err(ValueError(format!(
87+
"No {} provided in value string for {}",
88+
durationlike_field, self.value
89+
)))
90+
}
91+
}
92+
let min_field = &self
93+
.last_field
94+
.clone()
95+
.unwrap_or_else(|| durationlike_field.clone());
96+
for field in durationlike_field
97+
.clone()
98+
.into_iter()
99+
.take_while(|f| f <= min_field)
100+
{
101+
if let Some(time) = self.units_of(&field) {
102+
seconds += time * seconds_multiplier(&field);
103+
}
104+
}
105+
let duration = match (min_field, self.parsed.nano) {
106+
(DateTimeField::Second, Some(nanos)) => Duration::new(seconds, nanos),
107+
(_, _) => Duration::from_secs(seconds),
108+
};
109+
Ok(Interval::Duration {
110+
is_positive: self.parsed.is_positive,
111+
duration,
112+
})
113+
}
114+
}
115+
}
116+
117+
/// Retrieve the number that we parsed out of the literal string for the `field`
118+
fn units_of(&self, field: &DateTimeField) -> Option<u64> {
119+
match field {
120+
DateTimeField::Year => self.parsed.year,
121+
DateTimeField::Month => self.parsed.month,
122+
DateTimeField::Day => self.parsed.day,
123+
DateTimeField::Hour => self.parsed.hour,
124+
DateTimeField::Minute => self.parsed.minute,
125+
DateTimeField::Second => self.parsed.second,
126+
}
127+
}
128+
129+
/// `1` if is_positive, otherwise `-1`
130+
fn positivity(&self) -> i64 {
131+
if self.parsed.is_positive {
132+
1
133+
} else {
134+
-1
135+
}
136+
}
137+
}
138+
139+
fn seconds_multiplier(field: &DateTimeField) -> u64 {
140+
match field {
141+
DateTimeField::Day => 60 * 60 * 24,
142+
DateTimeField::Hour => 60 * 60,
143+
DateTimeField::Minute => 60,
144+
DateTimeField::Second => 1,
145+
_other => unreachable!("Do not call with a non-duration field"),
146+
}
147+
}
148+
149+
/// The result of parsing an `INTERVAL '<value>' <unit> [TO <precision>]`
150+
///
151+
/// Units of type `YEAR` or `MONTH` are semantically some multiple of months,
152+
/// which are not well defined, and this parser normalizes them to some number
153+
/// of months.
154+
///
155+
/// Intervals of unit [`DateTimeField::Day`] or smaller are semantically a
156+
/// multiple of seconds.
157+
#[derive(Debug, Clone, Copy, PartialEq)]
158+
pub enum Interval {
159+
/// A possibly negative number of months for field types like `YEAR`
160+
Months(i64),
161+
/// An actual timespan, possibly negative, because why not
162+
Duration {
163+
is_positive: bool,
164+
duration: Duration,
165+
},
166+
}
167+
43168
/// All of the fields that can appear in a literal `TIMESTAMP` or `INTERVAL` string
44169
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
45170
pub struct ParsedDateTime {

src/parser.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,7 +627,7 @@ impl Parser {
627627
));
628628
}
629629
let toks = datetime::tokenize_interval(value)?;
630-
datetime::build_parsed_datetime(&toks, &leading_field, trailing_field)
630+
datetime::build_parsed_datetime(&toks, leading_field, trailing_field)
631631
}
632632

633633
/// Parses the parens following the `[ NOT ] IN` operator

0 commit comments

Comments
 (0)