From e35c2b964dea330122dcc710d993f6787f3c29ca Mon Sep 17 00:00:00 2001 From: Brandon W Maister Date: Tue, 13 Aug 2019 15:07:00 -0400 Subject: [PATCH 1/6] Parse INTERVAL values into a `ParsedDateTime` type ParsedDateTimes just represent all the values that were present in a datetime string. For a regular `DateTime` object they will all be present, but for `INTERVAL` they depend on the following token. --- src/ast/mod.rs | 2 +- src/ast/value.rs | 74 +++++++++++- src/parser.rs | 62 ++++++++-- src/parser/datetime.rs | 240 ++++++++++++++++++++++++++++++++++++++ tests/sqlparser_common.rs | 28 +++++ 5 files changed, 393 insertions(+), 13 deletions(-) create mode 100644 src/parser/datetime.rs diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 24a7fd1..0c68192 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -49,7 +49,7 @@ pub use self::query::{ Cte, Fetch, Join, JoinConstraint, JoinOperator, OrderByExpr, Query, Select, SelectItem, SetExpr, SetOperator, TableAlias, TableFactor, TableWithJoins, Values, }; -pub use self::value::{DateTimeField, Value}; +pub use self::value::{DateTimeField, ParsedDateTime, Value}; struct DisplaySeparated<'a, T> where diff --git a/src/ast/value.rs b/src/ast/value.rs index 3d10824..2458e27 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -36,7 +36,7 @@ pub enum Value { Timestamp(String), /// INTERVAL literals, roughly in the following format: /// - /// ```ignore + /// ```text /// INTERVAL '' [ () ] /// [ TO [ () ] ] /// ``` @@ -46,8 +46,10 @@ pub enum Value { /// that the `` units >= the units in ``, /// so the user will have to reject intervals like `HOUR TO YEAR`. Interval { - /// The raw [value] that was present in `INTERVAL '[value]'` + /// The raw `[value]` that was present in `INTERVAL '[value]'` value: String, + /// the fully parsed date time + parsed: ParsedDateTime, /// The unit of the first field in the interval. `INTERVAL 'T' MINUTE` /// means `T` is in minutes leading_field: DateTimeField, @@ -98,6 +100,7 @@ impl fmt::Display for Value { Value::Time(v) => write!(f, "TIME '{}'", escape_single_quote_string(v)), Value::Timestamp(v) => write!(f, "TIMESTAMP '{}'", escape_single_quote_string(v)), Value::Interval { + parsed: _, value, leading_field: DateTimeField::Second, leading_precision: Some(leading_precision), @@ -116,6 +119,7 @@ impl fmt::Display for Value { ) } Value::Interval { + parsed: _, value, leading_field, leading_precision, @@ -145,6 +149,33 @@ impl fmt::Display for Value { } #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ParsedDateTime { + pub is_positive: bool, + pub year: Option, + pub month: Option, + pub day: Option, + pub hour: Option, + pub minute: Option, + pub second: Option, + pub nano: Option, +} + +impl Default for ParsedDateTime { + fn default() -> ParsedDateTime { + ParsedDateTime { + is_positive: true, + year: None, + month: None, + day: None, + hour: None, + minute: None, + second: None, + nano: None, + } + } +} + +#[derive(Debug, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)] pub enum DateTimeField { Year, Month, @@ -154,6 +185,45 @@ pub enum DateTimeField { Second, } +impl IntoIterator for DateTimeField { + type Item = DateTimeField; + type IntoIter = DateTimeFieldIterator; + fn into_iter(self) -> DateTimeFieldIterator { + DateTimeFieldIterator(Some(self)) + } +} + +/// An iterator over DateTimeFields +/// +/// Always starts with the value smaller than the current one. +/// +/// ``` +/// use sqlparser::ast::DateTimeField::*; +/// let mut itr = Hour.into_iter(); +/// assert_eq!(itr.next(), Some(Minute)); +/// assert_eq!(itr.next(), Some(Second)); +/// assert_eq!(itr.next(), None); +/// ``` +pub struct DateTimeFieldIterator(Option); + +/// Go through fields in descending significance order +impl Iterator for DateTimeFieldIterator { + type Item = DateTimeField; + fn next(&mut self) -> Option { + use DateTimeField::*; + self.0 = match self.0 { + Some(Year) => Some(Month), + Some(Month) => Some(Day), + Some(Day) => Some(Hour), + Some(Hour) => Some(Minute), + Some(Minute) => Some(Second), + Some(Second) => None, + None => None, + }; + self.0.clone() + } +} + impl fmt::Display for DateTimeField { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match self { diff --git a/src/parser.rs b/src/parser.rs index 7a313d5..4904e58 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -22,17 +22,22 @@ use super::tokenizer::*; use std::error::Error; use std::fmt; -#[derive(Debug, Clone, PartialEq)] -pub enum ParserError { - TokenizerError(String), - ParserError(String), -} - // Use `Parser::expected` instead, if possible macro_rules! parser_err { ($MSG:expr) => { Err(ParserError::ParserError($MSG.to_string())) }; + ($($arg:tt)*) => { + Err(ParserError::ParserError(format!($($arg)*))) + }; +} + +mod datetime; + +#[derive(Debug, Clone, PartialEq)] +pub enum ParserError { + TokenizerError(String), + ParserError(String), } #[derive(PartialEq)] @@ -466,14 +471,14 @@ impl Parser { /// /// Note that we do not currently attempt to parse the quoted value. pub fn parse_literal_interval(&mut self) -> Result { - // The SQL standard allows an optional sign before the value string, but + // The SQL standard allows an optional sign before the raw_value string, but // it is not clear if any implementations support that syntax, so we // don't currently try to parse it. (The sign can instead be included - // inside the value string.) + // inside the raw_value string.) // The first token in an interval is a string literal which specifies // the duration of the interval. - let value = self.parse_literal_string()?; + let raw_value = self.parse_literal_string()?; // Following the string literal is a qualifier which indicates the units // of the duration specified in the string literal. @@ -507,8 +512,12 @@ impl Parser { } }; + let (value, _warnings) = + Self::parse_interval_string(&raw_value, &leading_field, &last_field)?; + Ok(Expr::Value(Value::Interval { - value, + value: raw_value, + parsed: value, leading_field, leading_precision, last_field, @@ -588,6 +597,39 @@ impl Parser { } } + /// parse + /// + /// ```text + /// ::= + /// [ ] { | } + /// ::= + /// [ ] + /// | + /// ::= + /// + /// |