-
Notifications
You must be signed in to change notification settings - Fork 605
Support interval literals #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Pull Request Test Coverage Report for Build 288
💛 - Coveralls |
Pull Request Test Coverage Report for Build 325
💛 - Coveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again and sorry it took me a while - I had to read up on INTERVAL first.
The major question is about the design of Value::Interval
: I feel that there at least ought to be a comment on the semantics of the end_qualifier
in the AST docs if we keep the current design.
I also noticed two bits that have yet to be implemented (SQLType::Interval parameters and the sign in Value::Interval) - I'm fine with not doing them now, but maybe add a comment about that?
@@ -39,6 +39,8 @@ pub enum SQLType { | |||
Time, | |||
/// Timestamp | |||
Timestamp, | |||
/// Interval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The INTERVAL type is supposed to have an <interval qualifier>
, I think. Is it optional in some DBs? (edit:) Ah, Postgres supports INTERVAL [<interval qualifier>] [(<seconds precision>)]
and appears limit the allowed <interval qualifier>
s to those without the "leading field precision".
(It's fine with me if you don't want to implement this as part of this PR. I just noticed that we don't support seconds precision for TIME / TIMESTAMP either.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copying/pasting one of my comments from below that's relevant here:
I don't currently have a need to support Postgres's nonstandard interval syntax, so I'm going to omit for now. I've left a note, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine by me! Note though that <interval qualifier>
after the INTERVAL
type is required by the standard, it's the way postgres allows to omit it is non-standard.
src/sqlparser.rs
Outdated
/// Some valid intervals: | ||
/// 1. `INTERVAL '1' DAY` | ||
/// 2. `INTERVAL '1-1' YEAR TO MONTH` | ||
/// 3. `INTERVAL '1' SECONDS` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"SECOND", I believe (same in the next few examples).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, fixed.
@@ -448,6 +436,100 @@ impl Parser { | |||
}) | |||
} | |||
|
|||
pub fn parse_date_time_field(&mut self) -> Result<SQLDateTimeField, ParserError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the list of keywords allowed in an INTERVAL qualifier is a subset of the list of keywords allowed in EXTRACT
. We're able to share this piece of code for INTERVAL and EXTRACT only because we limit ourselves to only allowing the smaller set of fields in EXTRACT. Perhaps we should make a note of this here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You bet. I've added a comment.
src/sqlparser.rs
Outdated
/// Note that (6) is not technically standards compliant, as the only | ||
/// end qualifier which can specify a precision is `SECOND`. (7) is also | ||
/// not standards compliant, as `SECOND` is not permitted to appear as a | ||
/// start qualifier, except in the special form of (5). In the interest of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or as INTERVAL '1' SECOND(p)
, of course.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. I've removed this whole bit, since we no longer allow the non-standard SECONDS TO SECONDS
.
src/sqlparser.rs
Outdated
// of the duration specified in the string literal. | ||
let start_field = self.parse_date_time_field()?; | ||
|
||
// The start qualifier is optionally followed by a numeric precision. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this bit can be simplified to:
let (end_field, start_precision, end_precision) = if start_field == SQLDateTimeField::Second
{
// SQL mandates special syntax for `SECOND TO SECOND` literals. Instead of
// `SECOND [(<leading precision>)] TO SECOND[(<fractional seconds precision>)]`
// one must use the special format:
// `SECOND [( <leading precision> [ , <fractional seconds precision>] )]`
let (start_precision, end_precision) = self.parse_optional_precision_scale()?;
(None, start_precision, end_precision)
} else {
let start_precision = self.parse_optional_precision()?;
if self.parse_keyword("TO") {
(
Some(self.parse_date_time_field()?),
start_precision,
self.parse_optional_precision()?,
)
} else {
(None, start_precision, start_precision)
// though I suggest (None, start_precision, None)
}
};
let end_field = end_field.unwrap_or_else(|| start_field.clone()); // I'd keep it an Option in the AST
...unless we need to support SECOND TO SECOND
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...unless we need to support SECOND TO SECOND.
Nope, not for any reason I can think of! I think it's actually better to reject it, since I can't find a database that supports it, and the spec certainly seems to think it shouldn't be supported.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simplified this using your approach, but added a bit of complexity to not parse the fractional_seconds_precision unless the last field was actually seconds.
src/sqlast/value.rs
Outdated
@@ -21,6 +21,12 @@ pub enum Value { | |||
Time(String), | |||
/// Timestamp literals, which include both a date and time | |||
Timestamp(String), | |||
/// Time intervals |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps /// INTERVAL literals, e.g. INTERVAL '12:34.56' MINUTE TO SECOND(2)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -21,6 +21,12 @@ pub enum Value { | |||
Time(String), | |||
/// Timestamp literals, which include both a date and time | |||
Timestamp(String), | |||
/// Time intervals | |||
Interval { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SQL allows an optional sign here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, but I haven't found a database that actually supports that yet. I'm going to omit for now because we don't seem to have a SQLSign type handy. I've left a note in the parsing code, though.
src/sqlast/value.rs
Outdated
Interval { | ||
value: String, | ||
start_qualifier: SQLIntervalQualifier, | ||
end_qualifier: SQLIntervalQualifier, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given what I learned about INTERVALs in SQL (see my comment in #62), it was counter-intuitive to me that this stores HOUR(2)
as if it was equivalent to HOUR(2) TO HOUR(2)
.
I think it makes more sense to model this as leading_field: SQLDateTimeField, leading_precision: Option<u64>, last_field: Option<SQLDateTimeField>, fractional_seconds_precision: Option<u64>
. (If we want to support Postgres' INTERVAL '2 years 13 months'
, the leading_field
will have to be an Option too.)
SECONDS(p,q)
would parse to{leading_field: Seconds, leading_precision: p, last_field: None, fractional_seconds_precision: q}
A(p) TO B(q)
would parse to{leading_field: A, leading_precision: p, last_field: B, fractional_seconds_precision: q}
. We might want to forbidA = Seconds
, andq = Some
whenB != Seconds
.A(p)
would parse to{leading_field: A, leading_precision: p, last_field: None, fractional_seconds_precision: None}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it makes more sense to model this as...
Sure, that works for me. Updated!
If we want to support Postgres'
INTERVAL '2 years 13 months'
, the leading_field will have to be an Option too.
I don't currently have a need to support Postgres's nonstandard interval syntax, so I'm going to omit for now. I've left a note, though.
b02ed00
to
5324acb
Compare
No problem at all! The INTERVAL type is so far my least favorite part of SQL. What a confusing design! Thanks for the through review. I've adopted all of your suggestions, with just a few modifications that I've noted above.
Yep, done. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks a lot! You might want to remove SQLIntervalQualifier
as it doesn't appear to be used anymore.
@@ -39,6 +39,8 @@ pub enum SQLType { | |||
Time, | |||
/// Timestamp | |||
Timestamp, | |||
/// Interval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fine by me! Note though that <interval qualifier>
after the INTERVAL
type is required by the standard, it's the way postgres allows to omit it is non-standard.
Oh, good spot. I guess dead code detection didn't catch it because the struct was public. Removed, so merging on green! |
As promised in #99.