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

Commit 1c69bee

Browse files
committed
Add an IntervalValue::fields_match_precision error-checking method
This allows parser-users to be more restrictive in the values that they accept if they want to use the `IntervalValue::computed` method, because afaict no databases in existence actually match any SQL spec for their interpretation of interval literal values[1]. [1]: #8 (comment)
1 parent 03ba9f8 commit 1c69bee

5 files changed

Lines changed: 230 additions & 37 deletions

File tree

src/ast/value.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl std::error::Error for ValueError {}
2323

2424
impl fmt::Display for ValueError {
2525
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26-
write!(f, "ValueError({})", self.0)
26+
write!(f, "{}", self.0)
2727
}
2828
}
2929

@@ -165,7 +165,7 @@ mod test {
165165
fn interval_values() {
166166
let mut iv = ivalue();
167167
iv.parsed.year = None;
168-
match iv.computed() {
168+
match iv.computed_permissive() {
169169
Err(ValueError { .. }) => {}
170170
Ok(why) => panic!("should not be okay: {:?}", why),
171171
}

src/ast/value/datetime.rs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,20 @@ pub struct IntervalValue {
4646
impl IntervalValue {
4747
/// Get Either the number of Months or the Duration specified by this interval
4848
///
49+
/// This computes the fiels permissively: it assumes that the leading field
50+
/// (i.e. the lead in `INTERVAL 'str' LEAD [TO LAST]`) is valid and parses
51+
/// all field in the `str` starting at the leading field, ignoring the
52+
/// truncation that should be specified by `LAST`.
53+
///
54+
/// See also the related [`fields_match_precision`] function that will give
55+
/// an error if the interval string does not exactly match the `FROM TO
56+
/// LAST` spec.
57+
///
4958
/// # Errors
5059
///
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> {
60+
/// If a required field is missing (i.e. there is no value) or the `TO
61+
/// LAST` field is larger than the `LEAD`.
62+
pub fn computed_permissive(&self) -> Result<Interval, ValueError> {
5363
use DateTimeField::*;
5464
match &self.leading_field {
5565
Year => match &self.last_field {
@@ -126,6 +136,89 @@ impl IntervalValue {
126136
}
127137
}
128138

139+
/// Verify that the fields in me make sense
140+
///
141+
/// Returns Ok if the fields are fully specified, otherwise an error
142+
///
143+
/// # Examples
144+
///
145+
/// ```sql
146+
/// INTERVAL '1 5' DAY TO HOUR -- Ok
147+
/// INTERVAL '1 5' DAY -- Err
148+
/// INTERVAL '1:2:3' HOUR TO SECOND -- Ok
149+
/// INTERVAL '1:2:3' HOUR TO MINUTE -- Err
150+
/// INTERVAL '1:2:3' MINUTE TO SECOND -- Err
151+
/// INTERVAL '1:2:3' DAY TO SECOND -- Err
152+
/// ```
153+
pub fn fields_match_precision(&self) -> Result<(), ValueError> {
154+
let mut errors = vec![];
155+
let last_field = self
156+
.last_field
157+
.as_ref()
158+
.unwrap_or_else(|| &self.leading_field);
159+
let mut extra_leading_fields = vec![];
160+
let mut extra_trailing_fields = vec![];
161+
// check for more data in the input string than was requested in <FIELD> TO <FIELD>
162+
for field in std::iter::once(DateTimeField::Year).chain(DateTimeField::Year.into_iter()) {
163+
if self.units_of(&field).is_none() {
164+
continue;
165+
}
166+
167+
if field < self.leading_field {
168+
extra_leading_fields.push(field.clone());
169+
}
170+
if &field > last_field {
171+
extra_trailing_fields.push(field.clone());
172+
}
173+
}
174+
175+
if !extra_leading_fields.is_empty() {
176+
errors.push(format!(
177+
"The interval string '{}' specifies {}s but the significance requested is {}",
178+
self.value,
179+
fields_msg(extra_leading_fields.into_iter()),
180+
self.leading_field
181+
));
182+
}
183+
if !extra_trailing_fields.is_empty() {
184+
errors.push(format!(
185+
"The interval string '{}' specifies {}s but the requested precision would truncate to {}",
186+
self.value, fields_msg(extra_trailing_fields.into_iter()), last_field
187+
));
188+
}
189+
190+
// check for data requested by the <FIELD> TO <FIELD> that does not exist in the data
191+
let missing_fields = match (
192+
self.units_of(&self.leading_field),
193+
self.units_of(&last_field),
194+
) {
195+
(Some(_), Some(_)) => vec![],
196+
(None, Some(_)) => vec![&self.leading_field],
197+
(Some(_), None) => vec![last_field],
198+
(None, None) => vec![&self.leading_field, last_field],
199+
};
200+
201+
if !missing_fields.is_empty() {
202+
errors.push(format!(
203+
"The interval string '{}' provides {} - which does not include the requested field(s) {}",
204+
self.value, self.present_fields(), fields_msg(missing_fields.into_iter().cloned())));
205+
}
206+
207+
if !errors.is_empty() {
208+
Err(ValueError(errors.join("; ")))
209+
} else {
210+
Ok(())
211+
}
212+
}
213+
214+
fn present_fields(&self) -> String {
215+
fields_msg(
216+
std::iter::once(DateTimeField::Year)
217+
.chain(DateTimeField::Year.into_iter())
218+
.filter(|field| self.units_of(&field).is_some()),
219+
)
220+
}
221+
129222
/// `1` if is_positive, otherwise `-1`
130223
fn positivity(&self) -> i64 {
131224
if self.parsed.is_positive {
@@ -136,6 +229,13 @@ impl IntervalValue {
136229
}
137230
}
138231

232+
fn fields_msg(fields: impl Iterator<Item = DateTimeField>) -> String {
233+
fields
234+
.map(|field: DateTimeField| field.to_string())
235+
.collect::<Vec<_>>()
236+
.join(", ")
237+
}
238+
139239
fn seconds_multiplier(field: &DateTimeField) -> u64 {
140240
match field {
141241
DateTimeField::Day => 60 * 60 * 24,

src/parser.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -512,8 +512,7 @@ impl Parser {
512512
}
513513
};
514514

515-
let (value, _warnings) =
516-
Self::parse_interval_string(&raw_value, &leading_field, &last_field)?;
515+
let value = Self::parse_interval_string(&raw_value, &leading_field)?;
517516

518517
Ok(Expr::Value(Value::Interval(IntervalValue {
519518
value: raw_value,
@@ -619,15 +618,14 @@ impl Parser {
619618
pub fn parse_interval_string(
620619
value: &str,
621620
leading_field: &DateTimeField,
622-
trailing_field: &Option<DateTimeField>,
623-
) -> Result<(ParsedDateTime, Vec<String>), ParserError> {
621+
) -> Result<ParsedDateTime, ParserError> {
624622
if value.is_empty() {
625623
return Err(ParserError::ParserError(
626624
"Interval date string is empty!".to_string(),
627625
));
628626
}
629627
let toks = datetime::tokenize_interval(value)?;
630-
datetime::build_parsed_datetime(&toks, leading_field, trailing_field)
628+
datetime::build_parsed_datetime(&toks, leading_field, value)
631629
}
632630

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

src/parser/datetime.rs

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -116,29 +116,13 @@ pub(crate) enum IntervalToken {
116116
pub(crate) fn build_parsed_datetime(
117117
tokens: &[IntervalToken],
118118
leading_field: &DateTimeField,
119-
precision: &Option<DateTimeField>,
120-
) -> Result<(ParsedDateTime, Vec<String>), ParserError> {
119+
value: &str,
120+
) -> Result<ParsedDateTime, ParserError> {
121121
use IntervalToken::*;
122122

123-
// if no precision is specified, then you should use the least possible precision
124-
let precision = match precision {
125-
Some(p) => p,
126-
None => leading_field,
127-
};
128-
129123
let expected = potential_interval_tokens(&leading_field);
130124
let mut actual = tokens.iter().peekable();
131125

132-
let mut warnings = vec![];
133-
134-
if expected.len() > tokens.len() - 1 {
135-
warnings.push(format!(
136-
"More precision requested than supplied. Requested {} but only provided {} fields",
137-
precision,
138-
tokens.len(),
139-
))
140-
}
141-
142126
let is_positive = match actual.peek() {
143127
Some(val) if val == &&IntervalToken::Dash => {
144128
actual.next();
@@ -152,7 +136,7 @@ pub(crate) fn build_parsed_datetime(
152136
..Default::default()
153137
};
154138
let mut seconds_seen = 0;
155-
for (atok, etok) in actual.zip(&expected) {
139+
for (i, (atok, etok)) in actual.zip(&expected).enumerate() {
156140
match (atok, etok) {
157141
(Dash, Dash) | (Space, Space) | (Colon, Colon) | (Dot, Dot) => {
158142
/* matching punctuation */
@@ -183,15 +167,17 @@ pub(crate) fn build_parsed_datetime(
183167
(Nanos(val), Nanos(_)) if seconds_seen == 1 => pdt.nano = Some(*val),
184168
(provided, expected) => {
185169
return parser_err!(
186-
"Invalid interval part: string provided {:?} but expected {:?}",
170+
"Invalid interval part at offset {}: '{}' provided {:?} but expected {:?}",
171+
i,
172+
value,
187173
provided,
188174
expected,
189175
)
190176
}
191177
}
192178
}
193179

194-
Ok((pdt, warnings))
180+
Ok(pdt)
195181
}
196182

197183
#[cfg(test)]

0 commit comments

Comments
 (0)