Repository: gluesql/gluesql
Pull request #218 (2021-05-05 15:48:15)

Implement INTERVAL data type 
`INTERVAL` data type is now supported!

e.g.
```sql
INTERVAL "1" DAY
INTERVAL "1-2" YEAR TO MONTH
INTERVAL "3 12:33" DAY TO MINUTE
```
Any subset of either `YEAR TO MONTH` or `DAY TO SECOND` can be used.

Following arithmetic operations are supported.
```
DATE - DATE => INTERVAL
DATE + INTERVAL => TIMESTAMP
DATE - INTERVAL => TIMESTAMP

TIMESTAMP - TIMESTAMP => INTERVAL
TIMESTAMP + INTERVAL => TIMESTAMP
TIMESTAMP - INTERVAL => TIMESTAMP

INTERVAL + INTERVAL => INTERVAL
INTERVAL - INTERVAL => INTERVAL
INTERVAL + DATE => TIMESTAMP
INTERVAL + TIMESTAMP => TIMESTAMP

INTERVAL * {number} => INTERVAL
INTERVAL / {number} => INTERVAL
{number} * INTERVAL => INTERVAL
{number} / INTERVAL => INTERVAL
```


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 7230d87c..d219d8c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,7 @@ async-trait = "0.1.41"
 async-recursion = "0.3.1"
 boolinator = "2.4.0"
 chrono = { version = "0.4", features = ["serde", "wasmbind"] }
+rust_decimal = "1.13"
 futures = "0.3"
 indexmap = "1.6.0"
 im-rc = "15.0.0"
diff --git a/src/data/interval/error.rs b/src/data/interval/error.rs
new file mode 100644
index 00000000..0e57879a
--- /dev/null
+++ b/src/data/interval/error.rs
@@ -0,0 +1,40 @@
+use {serde::Serialize, std::fmt::Debug, thiserror::Error};
+
+#[derive(Error, Serialize, Debug, PartialEq)]
+pub enum IntervalError {
+    #[error("unsupported interval range: {0} to {1}")]
+    UnsupportedRange(String, String),
+
+    #[error("cannot add between YEAR TO MONTH and HOUR TO SECOND")]
+    AddBetweenYearToMonthAndHourToSecond,
+
+    #[error("cannot subtract between YEAR TO MONTH and HOUR TO SECOND")]
+    SubtractBetweenYearToMonthAndHourToSecond,
+
+    #[error("failed to parse integer: {0}")]
+    FailedToParseInteger(String),
+
+    #[error("failed to parse decimal: {0}")]
+    FailedToParseDecimal(String),
+
+    #[error("failed to parse time: {0}")]
+    FailedToParseTime(String),
+
+    #[error("failed to parse YEAR TO MONTH (year-month, ex. 2-8): {0}")]
+    FailedToParseYearToMonth(String),
+
+    #[error("failed to parse DAY TO HOUR (day hour, ex. 1 23): {0}")]
+    FailedToParseDayToHour(String),
+
+    #[error("failed to parse DAY TO MINUTE (day hh:mm, ex. 1 12:34): {0}")]
+    FailedToParseDayToMinute(String),
+
+    #[error("failed to parse DAY TO SECOND (day hh:mm:ss, ex. 1 12:34:55): {0}")]
+    FailedToParseDayToSecond(String),
+
+    #[error("date overflow: {year}-{month}")]
+    DateOverflow { year: i32, month: i32 },
+
+    #[error("unreachable")]
+    Unreachable,
+}
diff --git a/src/data/interval/mod.rs b/src/data/interval/mod.rs
new file mode 100644
index 00000000..310139db
--- /dev/null
+++ b/src/data/interval/mod.rs
@@ -0,0 +1,420 @@
+mod error;
+mod primitive;
+mod string;
+
+use {
+    crate::result::Result,
+    chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike},
+    core::str::FromStr,
+    rust_decimal::{prelude::ToPrimitive, Decimal},
+    serde::{Deserialize, Serialize},
+    sqlparser::ast::DateTimeField,
+    std::{cmp::Ordering, fmt::Debug},
+};
+
+pub use error::IntervalError;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum Interval {
+    Month(i32),
+    Microsecond(i64),
+}
+
+impl PartialOrd<Interval> for Interval {
+    fn partial_cmp(&self, other: &Interval) -> Option<Ordering> {
+        match (self, other) {
+            (Interval::Month(l), Interval::Month(r)) => Some(l.cmp(r)),
+            (Interval::Microsecond(l), Interval::Microsecond(r)) => Some(l.cmp(r)),
+            _ => None,
+        }
+    }
+}
+
+const SECOND: i64 = 1_000_000;
+const MINUTE: i64 = 60 * SECOND;
+const HOUR: i64 = 3600 * SECOND;
+const DAY: i64 = 24 * HOUR;
+
+impl Interval {
+    pub fn unary_minus(&self) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month(-v),
+            Interval::Microsecond(v) => Interval::Microsecond(-v),
+        }
+    }
+
+    pub fn add(&self, other: &Interval) -> Result<Self> {
+        use Interval::*;
+
+        match (self, other) {
+            (Month(l), Month(r)) => Ok(Month(l + r)),
+            (Microsecond(l), Microsecond(r)) => Ok(Microsecond(l + r)),
+            _ => Err(IntervalError::AddBetweenYearToMonthAndHourToSecond.into()),
+        }
+    }
+
+    pub fn subtract(&self, other: &Interval) -> Result<Self> {
+        use Interval::*;
+
+        match (self, other) {
+            (Month(l), Month(r)) => Ok(Month(l - r)),
+            (Microsecond(l), Microsecond(r)) => Ok(Microsecond(l - r)),
+            _ => Err(IntervalError::SubtractBetweenYearToMonthAndHourToSecond.into()),
+        }
+    }
+
+    pub fn add_date(&self, date: &NaiveDate) -> Result<NaiveDateTime> {
+        self.add_timestamp(&date.and_hms(0, 0, 0))
+    }
+
+    pub fn subtract_from_date(&self, date: &NaiveDate) -> Result<NaiveDateTime> {
+        self.subtract_from_timestamp(&date.and_hms(0, 0, 0))
+    }
+
+    pub fn add_timestamp(&self, timestamp: &NaiveDateTime) -> Result<NaiveDateTime> {
+        match self {
+            Interval::Month(n) => {
+                let month = timestamp.month() as i32 + n;
+
+                let year = timestamp.year() + month / 12;
+                let month = month % 12;
+
+                timestamp
+                    .with_year(year)
+                    .map(|d| d.with_month(month as u32))
+                    .flatten()
+                    .ok_or_else(|| IntervalError::DateOverflow { year, month }.into())
+            }
+            Interval::Microsecond(n) => Ok(*timestamp + Duration::microseconds(*n)),
+        }
+    }
+
+    pub fn subtract_from_timestamp(&self, timestamp: &NaiveDateTime) -> Result<NaiveDateTime> {
+        match self {
+            Interval::Month(n) => {
+                let months = timestamp.year() * 12 + timestamp.month() as i32 - n;
+
+                let year = months / 12;
+                let month = months % 12;
+
+                timestamp
+                    .with_year(year)
+                    .map(|d| d.with_month(month as u32))
+                    .flatten()
+                    .ok_or_else(|| IntervalError::DateOverflow { year, month }.into())
+            }
+            Interval::Microsecond(n) => Ok(*timestamp - Duration::microseconds(*n)),
+        }
+    }
+
+    pub fn years(years: i32) -> Self {
+        Interval::Month(12 * years)
+    }
+
+    pub fn months(months: i32) -> Self {
+        Interval::Month(months)
+    }
+
+    pub fn days(days: i32) -> Self {
+        Interval::Microsecond(days as i64 * DAY)
+    }
+
+    pub fn hours(hours: i32) -> Self {
+        Interval::Microsecond(hours as i64 * HOUR)
+    }
+
+    pub fn minutes(minutes: i32) -> Self {
+        Interval::Microsecond(minutes as i64 * MINUTE)
+    }
+
+    pub fn seconds(seconds: i64) -> Self {
+        Interval::Microsecond(seconds * SECOND)
+    }
+
+    pub fn milliseconds(milliseconds: i64) -> Self {
+        Interval::Microsecond(milliseconds * 1_000)
+    }
+
+    pub fn microseconds(microseconds: i64) -> Self {
+        Interval::Microsecond(microseconds)
+    }
+
+    pub fn try_from_literal(
+        value: &str,
+        leading_field: Option<&DateTimeField>,
+        last_field: Option<&DateTimeField>,
+    ) -> Result<Self> {
+        use DateTimeField::*;
+
+        let sign = || if value.get(0..1) == Some(&"-") { -1 } else { 1 };
+
+        let parse_integer = |v: &str| {
+            v.parse::<i32>()
+                .map_err(|_| IntervalError::FailedToParseInteger(value.to_owned()).into())
+        };
+
+        let parse_decimal = |duration: i64| {
+            let parsed = Decimal::from_str(value)
+                .map_err(|_| IntervalError::FailedToParseDecimal(value.to_owned()))?;
+
+            (parsed * Decimal::from(duration))
+                .to_i64()
+                .ok_or_else(|| IntervalError::FailedToParseDecimal(value.to_owned()).into())
+                .map(Interval::Microsecond)
+        };
+
+        let parse_time = |v: &str| {
+            let sign = if v.get(0..1) == Some(&"-") { -1 } else { 1 };
+            let v = v.trim_start_matches('-');
+            let time = NaiveTime::from_str(v)
+                .map_err(|_| IntervalError::FailedToParseTime(value.to_owned()))?;
+
+            let msec = time.hour() as i64 * HOUR
+                + time.minute() as i64 * MINUTE
+                + time.second() as i64 * SECOND
+                + time.nanosecond() as i64 / 1000;
+
+            Ok(Interval::Microsecond(sign as i64 * msec))
+        };
+
+        match (leading_field, last_field) {
+            (Some(Year), None) => parse_integer(value).map(Interval::years),
+            (Some(Month), None) => parse_integer(value).map(Interval::months),
+            (Some(Day), None) => parse_decimal(DAY),
+            (Some(Hour), None) => parse_decimal(HOUR),
+            (Some(Minute), None) => parse_decimal(MINUTE),
+            (Some(Second), None) => parse_decimal(SECOND),
+            (Some(Year), Some(Month)) => {
+                let nums = value
+                    .trim_start_matches('-')
+                    .split('-')
+                    .map(parse_integer)
+                    .collect::<Result<Vec<_>>>()?;
+
+                match (nums.get(0), nums.get(1)) {
+                    (Some(years), Some(months)) => {
+                        Ok(Interval::months(sign() * (12 * years + months)))
+                    }
+                    _ => Err(IntervalError::FailedToParseYearToMonth(value.to_owned()).into()),
+                }
+            }
+            (Some(Day), Some(Hour)) => {
+                let nums = value
+                    .trim_start_matches('-')
+                    .split(' ')
+                    .map(parse_integer)
+                    .collect::<Result<Vec<_>>>()?;
+
+                match (nums.get(0), nums.get(1)) {
+                    (Some(days), Some(hours)) => Ok(Interval::hours(sign() * (24 * days + hours))),
+                    _ => Err(IntervalError::FailedToParseDayToHour(value.to_owned()).into()),
+                }
+            }
+            (Some(Day), Some(Minute)) => {
+                let nums = value.trim_start_matches('-').split(' ').collect::<Vec<_>>();
+
+                match (nums.get(0), nums.get(1)) {
+                    (Some(days), Some(time)) => {
+                        let days = parse_integer(days)?;
+                        let time = NaiveTime::from_str(&format!("{}:00", time)).unwrap();
+                        let minutes = time.hour() * 60 + time.minute();
+
+                        Interval::days(days)
+                            .add(&Interval::minutes(minutes as i32))
+                            .map(|interval| sign() * interval)
+                    }
+                    _ => Err(IntervalError::FailedToParseDayToMinute(value.to_owned()).into()),
+                }
+            }
+            (Some(Day), Some(Second)) => {
+                let nums = value.trim_start_matches('-').split(' ').collect::<Vec<_>>();
+
+                match (nums.get(0), nums.get(1)) {
+                    (Some(days), Some(time)) => {
+                        let days = parse_integer(days)?;
+
+                        Interval::days(days)
+                            .add(&parse_time(&time)?)
+                            .map(|interval| sign() * interval)
+                    }
+                    _ => Err(IntervalError::FailedToParseDayToSecond(value.to_owned()).into()),
+                }
+            }
+            (Some(Hour), Some(Minute)) => parse_time(&format!("{}:00", value)),
+            (Some(Hour), Some(Second)) => parse_time(value),
+            (Some(Minute), Some(Second)) => {
+                let time = value.trim_start_matches('-');
+
+                parse_time(&format!("00:{}", time)).map(|v| sign() * v)
+            }
+            (Some(from), Some(to)) => Err(IntervalError::UnsupportedRange(
+                format!("{:?}", from),
+                format!("{:?}", to),
+            )
+            .into()),
+            (None, _) => Err(IntervalError::Unreachable.into()),
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{Interval, IntervalError};
+    use sqlparser::ast::DateTimeField;
+
+    #[test]
+    fn arithmetic() {
+        use chrono::NaiveDate;
+        use Interval::*;
+
+        macro_rules! test {
+            ($op: ident $a: expr, $b: expr => $c: expr) => {
+                assert_eq!($a.$op(&$b), Ok($c));
+            };
+        }
+
+        let date = |y, m, d| NaiveDate::from_ymd(y, m, d);
+
+        assert_eq!(Month(1).unary_minus(), Month(-1));
+        assert_eq!(Microsecond(1).unary_minus(), Microsecond(-1));
+        assert_eq!(
+            Month(2).add_date(&date(2021, 11, 11)),
+            Ok(date(2022, 1, 11).and_hms(0, 0, 0))
+        );
+        assert_eq!(
+            Interval::hours(30).add_date(&date(2021, 11, 11)),
+            Ok(date(2021, 11, 12).and_hms(6, 0, 0))
+        );
+        assert_eq!(
+            Interval::years(999_999).add_date(&date(2021, 11, 11)),
+            Err(IntervalError::DateOverflow {
+                year: 1_002_020,
+                month: 11,
+            }
+            .into())
+        );
+        assert_eq!(
+            Month(2).subtract_from_date(&date(2021, 11, 11)),
+            Ok(date(2021, 9, 11).and_hms(0, 0, 0))
+        );
+        assert_eq!(
+            Month(14).subtract_from_date(&date(2021, 11, 11)),
+            Ok(date(2020, 9, 11).and_hms(0, 0, 0))
+        );
+        assert_eq!(
+            Interval::hours(30).subtract_from_date(&date(2021, 11, 11)),
+            Ok(date(2021, 11, 9).and_hms(18, 0, 0))
+        );
+        assert_eq!(
+            Interval::years(999_999).subtract_from_date(&date(2021, 11, 11)),
+            Err(IntervalError::DateOverflow {
+                year: -997977,
+                month: -1,
+            }
+            .into())
+        );
+        assert_eq!(
+            Interval::minutes(2).add_timestamp(&date(2021, 11, 11).and_hms(12, 3, 1)),
+            Ok(date(2021, 11, 11).and_hms(12, 5, 1))
+        );
+        assert_eq!(
+            Interval::hours(30).add_timestamp(&date(2021, 11, 11).and_hms(0, 30, 0)),
+            Ok(date(2021, 11, 12).and_hms(6, 30, 0))
+        );
+        assert_eq!(
+            Interval::years(999_999).add_timestamp(&date(2021, 11, 11).and_hms(1, 1, 1)),
+            Err(IntervalError::DateOverflow {
+                year: 1_002_020,
+                month: 11,
+            }
+            .into())
+        );
+        assert_eq!(
+            Month(2).subtract_from_timestamp(&date(2021, 11, 11).and_hms(1, 3, 59)),
+            Ok(date(2021, 9, 11).and_hms(1, 3, 59))
+        );
+        assert_eq!(
+            Month(14).subtract_from_timestamp(&date(2021, 11, 11).and_hms(23, 1, 1)),
+            Ok(date(2020, 9, 11).and_hms(23, 1, 1))
+        );
+        assert_eq!(
+            Interval::seconds(30).subtract_from_timestamp(&date(2021, 11, 11).and_hms(0, 0, 0)),
+            Ok(date(2021, 11, 10).and_hms(23, 59, 30))
+        );
+        assert_eq!(
+            Interval::years(999_999).subtract_from_timestamp(&date(2021, 11, 11).and_hms(0, 0, 0)),
+            Err(IntervalError::DateOverflow {
+                year: -997977,
+                month: -1,
+            }
+            .into())
+        );
+
+        test!(add      Month(1), Month(2) => Month(3));
+        test!(subtract Month(1), Month(2) => Month(-1));
+
+        test!(add      Microsecond(1), Microsecond(2) => Microsecond(3));
+        test!(subtract Microsecond(1), Microsecond(2) => Microsecond(-1));
+    }
+
+    #[test]
+    fn try_from_literal() {
+        macro_rules! test {
+            ($value: expr, $datetime: ident => $expected_value: expr, $duration: ident) => {
+                let interval =
+                    Interval::try_from_literal($value, Some(&DateTimeField::$datetime), None);
+
+                assert_eq!(interval, Ok(Interval::$duration($expected_value)));
+            };
+            ($value: expr, $from: ident to $to: ident => $expected_value: expr, $duration: ident) => {
+                let interval = Interval::try_from_literal(
+                    $value,
+                    Some(&DateTimeField::$from),
+                    Some(&DateTimeField::$to),
+                );
+
+                assert_eq!(interval, Ok(Interval::$duration($expected_value)));
+            };
+        }
+
+        test!("11",   Year   => 11,  years);
+        test!("-11",  Year   => -11, years);
+        test!("18",   Month  => 18,  months);
+        test!("-19",  Month  => -19, months);
+        test!("2",    Day    => 2,   days);
+        test!("1.5",  Day    => 36,  hours);
+        test!("-1.5", Day    => -36, hours);
+        test!("2.5",  Hour   => 150, minutes);
+        test!("1",    Hour   => 60,  minutes);
+        test!("-1",   Hour   => -60, minutes);
+        test!("35",   Minute => 35,  minutes);
+        test!("-35",  Minute => -35, minutes);
+        test!("10.5", Minute => 630, seconds);
+        test!("10",   Second => 10,  seconds);
+        test!("-10",  Second => -10, seconds);
+        test!("10.5", Second => 10_500_000, microseconds);
+        test!("-1.5", Second => -1_500_000, microseconds);
+
+        test!("10-2", Year to Month => 122, months);
+        test!("2 12", Day to Hour => 60, hours);
+        test!("1 01:30", Day to Minute => 60 * 24 + 90, minutes);
+        test!("1 01:30:40", Day to Second => (60 * 24 + 90) * 60 + 40, seconds);
+        test!("3 02:30:40.1234", Day to Second =>
+            (((3 * 24 + 2) * 60 + 30) * 60 + 40) * 1_000_000 + 123_400, microseconds);
+        test!("12:34", Hour to Minute => 12 * 60 + 34, minutes);
+        test!("12:34:56", Hour to Second => (12 * 60 + 34) * 60 + 56, seconds);
+        test!("12:34:56.1234", Hour to Second => ((12 * 60 + 34) * 60 + 56) * 1_000_000 + 123_400, microseconds);
+        test!("34:56.1234", Minute to Second => (34 * 60 + 56) * 1_000_000 + 123_400, microseconds);
+
+        test!("-1-4", Year to Month => -16, months);
+        test!("-2 10", Day to Hour => -58, hours);
+        test!("-1 00:01", Day to Minute => -(24 * 60 + 1), minutes);
+        test!("-1 00:00:01", Day to Second => -(24 * 3600 + 1), seconds);
+        test!("-1 00:00:01.1", Day to Second => -((24 * 3600 + 1) * 1000 + 100), milliseconds);
+        test!("-21:10", Hour to Minute => -(21 * 60 + 10), minutes);
+        test!("-05:12:03", Hour to Second => -(5 * 3600 + 12 * 60 + 3), seconds);
+        test!("-03:59:22.372", Hour to Second => -((3 * 3600 + 59 * 60 + 22) * 1000 + 372), milliseconds);
+        test!("-09:33", Minute to Second => -(9 * 60 + 33), seconds);
+        test!("-09:33.192", Minute to Second => -((9 * 60 + 33) * 1000 + 192), milliseconds);
+    }
+}
diff --git a/src/data/interval/primitive.rs b/src/data/interval/primitive.rs
new file mode 100644
index 00000000..fac288a0
--- /dev/null
+++ b/src/data/interval/primitive.rs
@@ -0,0 +1,126 @@
+use {
+    super::Interval,
+    std::ops::{Div, Mul},
+};
+
+impl Mul<i32> for Interval {
+    type Output = Self;
+
+    fn mul(self, rhs: i32) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month((v * rhs) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond(v * rhs as i64),
+        }
+    }
+}
+
+impl Mul<Interval> for i32 {
+    type Output = Interval;
+
+    fn mul(self, rhs: Interval) -> Interval {
+        rhs * self
+    }
+}
+
+impl Mul<i64> for Interval {
+    type Output = Self;
+
+    fn mul(self, rhs: i64) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month(((v as i64) * rhs) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond((v * rhs) as i64),
+        }
+    }
+}
+
+impl Mul<Interval> for i64 {
+    type Output = Interval;
+
+    fn mul(self, rhs: Interval) -> Interval {
+        rhs * self
+    }
+}
+
+impl Mul<f64> for Interval {
+    type Output = Self;
+
+    fn mul(self, rhs: f64) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month((v as f64 * rhs) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond((v as f64 * rhs) as i64),
+        }
+    }
+}
+
+impl Div<i64> for Interval {
+    type Output = Self;
+
+    fn div(self, rhs: i64) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month(((v as i64) / rhs) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond((v / rhs) as i64),
+        }
+    }
+}
+
+impl Div<Interval> for i64 {
+    type Output = Interval;
+
+    fn div(self, rhs: Interval) -> Interval {
+        match rhs {
+            Interval::Month(v) => Interval::Month((self / (v as i64)) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond(self / v),
+        }
+    }
+}
+
+impl Div<f64> for Interval {
+    type Output = Self;
+
+    fn div(self, rhs: f64) -> Self {
+        match self {
+            Interval::Month(v) => Interval::Month((v as f64 / rhs) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond((v as f64 / rhs) as i64),
+        }
+    }
+}
+
+impl Div<Interval> for f64 {
+    type Output = Interval;
+
+    fn div(self, rhs: Interval) -> Interval {
+        match rhs {
+            Interval::Month(v) => Interval::Month((self / v as f64) as i32),
+            Interval::Microsecond(v) => Interval::Microsecond((self / v as f64) as i64),
+        }
+    }
+}
+
+impl Mul<Interval> for f64 {
+    type Output = Interval;
+
+    fn mul(self, rhs: Interval) -> Interval {
+        rhs * self
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Interval;
+
+    #[test]
+    fn arithmetic() {
+        use Interval::*;
+
+        assert_eq!(Month(2) * 3_i32, Month(6));
+        assert_eq!(2_i32 * Month(3), Month(6));
+        assert_eq!(Month(2) * 3_i64, Month(6));
+        assert_eq!(2_i64 * Month(3), Month(6));
+        assert_eq!(Month(2) * 3.0, Month(6));
+        assert_eq!(2.0 * Month(3), Month(6));
+        assert_eq!(Month(6) / 3, Month(2));
+        assert_eq!(6 / Month(2), Month(3));
+        assert_eq!(Month(8) / 4.0, Month(2));
+        assert_eq!(8.0 / Month(4), Month(2));
+    }
+}
diff --git a/src/data/interval/string.rs b/src/data/interval/string.rs
new file mode 100644
index 00000000..a66b6675
--- /dev/null
+++ b/src/data/interval/string.rs
@@ -0,0 +1,239 @@
+use super::{Interval, DAY, HOUR, MINUTE, SECOND};
+
+impl From<&Interval> for String {
+    fn from(interval: &Interval) -> Self {
+        match interval {
+            Interval::Month(v) => {
+                let v = *v;
+                let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
+
+                let year = v / 12;
+                let month = v % 12;
+
+                match (year, month) {
+                    (_, 0) if year != 0 => format!(r#""{}{}" YEAR"#, sign, year),
+                    (0, _) => format!(r#""{}{}" MONTH"#, sign, month),
+                    _ => format!(r#""{}{}-{}" YEAR TO MONTH"#, sign, year, month),
+                }
+            }
+            Interval::Microsecond(v) => {
+                let v = *v;
+                let (sign, v) = if v < 0 { ("-", -v) } else { ("", v) };
+
+                let day = v / DAY;
+                let hour = (v % DAY) / HOUR;
+                let minute = (v % HOUR) / MINUTE;
+                let second = (v % MINUTE) / SECOND;
+                let microsecond = v % SECOND;
+
+                macro_rules! micro {
+                    () => {
+                        format!("{:06}", microsecond).trim_end_matches('0')
+                    };
+                }
+
+                macro_rules! f {
+                    ($template: literal; $( $value: expr )*; $from_to: literal) => {
+                        format!(r#""{}{}" {}"#, sign, format!($template, $( $value ),*), $from_to)
+                    };
+
+                    (DAY $template: literal) => {
+                        f!($template; day; "DAY")
+                    };
+                    (HOUR $template: literal) => {
+                        f!($template; hour; "HOUR")
+                    };
+                    (MINUTE $template: literal) => {
+                        f!($template; minute; "MINUTE")
+                    };
+                    (SECOND $template: literal) => {
+                        f!($template; second; "SECOND")
+                    };
+                    (MICRO $template: literal) => {
+                        f!($template; second micro!(); "SECOND")
+                    };
+
+                    // MINUTE TO ..
+                    (MINUTE TO SECOND $template: literal) => {
+                        f!($template; minute second; "MINUTE TO SECOND")
+                    };
+                    (MINUTE TO MICRO $template: literal) => {
+                        f!($template; minute second micro!(); "MINUTE TO SECOND");
+                    };
+
+                    // HOUR TO ..
+                    (HOUR TO MINUTE $template: literal) => {
+                        f!($template; hour minute; "HOUR TO MINUTE")
+                    };
+                    (HOUR TO SECOND $template: literal) => {
+                        f!($template; hour minute second; "HOUR TO SECOND")
+                    };
+                    (HOUR TO MICRO $template: literal) => {
+                        f!($template; hour minute second micro!(); "HOUR TO SECOND");
+                    };
+
+                    // DAY TO ..
+                    (DAY TO HOUR $template: literal) => {
+                        f!($template; day hour; "DAY TO HOUR")
+                    };
+                    (DAY TO MINUTE $template: literal) => {
+                        f!($template; day hour minute; "DAY TO MINUTE")
+                    };
+                    (DAY TO SECOND $template: literal) => {
+                        f!($template; day hour minute second; "DAY TO SECOND")
+                    };
+                    (DAY TO MICRO $template: literal) => {
+                        f!($template; day hour minute second micro!(); "DAY TO SECOND")
+                    };
+                }
+
+                match (day, hour, minute, second, microsecond) {
+                    (_, 0, 0, 0, 0) if day != 0 => f!(DAY "{}"),
+                    (0, _, 0, 0, 0) if hour != 0 => f!(HOUR "{}"),
+                    (0, 0, _, 0, 0) if minute != 0 => f!(MINUTE "{}"),
+                    (0, 0, 0, _, 0) if second != 0 => f!(SECOND "{}"),
+                    (0, 0, 0, _, _) if microsecond != 0 => f!(MICRO "{}.{}"),
+
+                    // MINUTE TO ..
+                    (0, 0, _, _, 0) => f!(MINUTE TO SECOND "{:02}:{:02}"),
+                    (0, 0, _, _, _) => f!(MINUTE TO MICRO  "{:02}:{:02}.{}"),
+
+                    // HOUR TO ..
+                    (0, _, _, 0, 0) => f!(HOUR TO MINUTE "{:02}:{:02}"),
+                    (0, _, _, _, 0) => f!(HOUR TO SECOND "{:02}:{:02}:{:02}"),
+                    (0, _, _, _, _) => f!(HOUR TO MICRO  "{:02}:{:02}:{:02}.{}"),
+
+                    // DAY TO ..
+                    (_, _, 0, 0, 0) => f!(DAY TO HOUR   "{} {}"),
+                    (_, _, _, 0, 0) => f!(DAY TO MINUTE "{} {:02}:{:02}"),
+                    (_, _, _, _, 0) => f!(DAY TO SECOND "{} {:02}:{:02}:{:02}"),
+                    (_, _, _, _, _) => f!(DAY TO MICRO  "{} {:02}:{:02}:{:02}.{}"),
+                }
+            }
+        }
+    }
+}
+
+impl From<Interval> for String {
+    fn from(interval: Interval) -> Self {
+        (&interval).into()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::Interval;
+
+    #[test]
+    fn into_string() {
+        macro_rules! test {
+            ($( $value: literal $duration: ident ),* => $result: literal) => {
+                assert_eq!(interval!($( $value $duration ),* ), $result.to_owned());
+            };
+            ($( $value: literal $duration: ident ),* => $result: literal $from_to: tt) => {
+                assert_eq!(
+                    interval!($( $value $duration ),* ),
+                    format!(r#""{}" {}"#, $result, stringify!($from_to))
+                );
+            };
+            ($( $value: literal $duration: ident ),* => $result: literal $from: tt TO $to: tt) => {
+                assert_eq!(
+                    interval!($( $value $duration ),* ),
+                    format!(r#""{}" {} TO {}"#, $result, stringify!($from), stringify!($to))
+                );
+            };
+        }
+
+        macro_rules! interval {
+            ($value: literal $duration:ident) => {
+                String::from(Interval::$duration($value))
+            };
+            ($interval: expr ; $value: literal $duration:ident) => {
+                String::from($interval.add(&Interval::$duration($value)).unwrap())
+            };
+            ($v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
+                interval!(
+                    Interval::$d0($v0) ;
+                    $( $v1 $d1 ),*
+                )
+            };
+            ($interval: expr ; $v0: literal $d0: ident, $( $v1: literal $d1: ident ),*) => {
+                interval!(
+                    $interval.add(&Interval::$d0($v0)).unwrap() ;
+                    $( $v1 $d1 ),*
+                )
+            };
+        }
+
+        // YEAR and MONTH
+        test!(33 years            => "33" YEAR);
+        test!(-33 years           => "-33" YEAR);
+        test!(14 months           => "1-2" YEAR TO MONTH);
+        test!(-33 months          => "-2-9" YEAR TO MONTH);
+        test!(12 months           => "1" YEAR);
+        test!(11 months           => "11" MONTH);
+        test!(-3 months           => "-3" MONTH);
+        test!(33 years, 11 months => "33-11" YEAR TO MONTH);
+        test!(1 years, 100 months => "9-4" YEAR TO MONTH);
+
+        // DAY, HOUR, MINUTE and SECOND
+        test!(102 days          => "102" DAY);
+        test!(-20 days          => "-20" DAY);
+        test!(30 hours          => "1 6" DAY TO HOUR);
+        test!(24 hours          => "1" DAY);
+        test!(3 hours           => "3" HOUR);
+        test!(-5 hours          => "-5" HOUR);
+        test!(350 minutes       => "05:50" HOUR TO MINUTE);
+        test!(600 minutes       => "10" HOUR);
+        test!(59 minutes        => "59" MINUTE);
+        test!(3 minutes         => "3" MINUTE);
+        test!(-9 minutes        => "-9" MINUTE);
+        test!(7298 seconds      => "02:01:38" HOUR TO SECOND);
+        test!(98 seconds        => "01:38" MINUTE TO SECOND);
+        test!(120 seconds       => "2" MINUTE);
+        test!(30 seconds        => "30" SECOND);
+        test!(3 seconds         => "3" SECOND);
+        test!(-37 seconds       => "-37" SECOND);
+        test!(1234 milliseconds => "1.234" SECOND);
+        test!(-234 milliseconds  => "-0.234" SECOND);
+        test!(1234 microseconds => "0.001234" SECOND);
+
+        // MINUTE TO ..
+        test!(11 minutes, 30 seconds                     => "11:30" MINUTE TO SECOND);
+        test!(-11 minutes, -39 seconds                   => "-11:39" MINUTE TO SECOND);
+        test!(11 minutes, 36540 microseconds             => "11:00.03654" MINUTE TO SECOND);
+        test!(-30 minutes, -9876 microseconds            => "-30:00.009876" MINUTE TO SECOND);
+        test!(11 minutes, 30 seconds, 36540 microseconds => "11:30.03654" MINUTE TO SECOND);
+
+        // HOUR TO ..
+        test!(11 hours, 50 minutes                   => "11:50" HOUR TO MINUTE);
+        test!(-20 hours, -39 minutes                 => "-20:39" HOUR TO MINUTE);
+        test!(23 hours, 59 seconds                   => "23:00:59" HOUR TO SECOND);
+        test!(-23 hours, -59 seconds                 => "-23:00:59" HOUR TO SECOND);
+        test!(7 hours, 30040 microseconds            => "07:00:00.03004" HOUR TO SECOND);
+        test!(-7 hours, -30040 microseconds          => "-07:00:00.03004" HOUR TO SECOND);
+        test!(23 hours, 12 minutes, 59 seconds       => "23:12:59" HOUR TO SECOND);
+        test!(23 hours, 12 minutes, 300 microseconds => "23:12:00.0003" HOUR TO SECOND);
+        test!(
+            23 hours, 12 minutes, 9 seconds, 300 microseconds =>
+            "23:12:09.0003" HOUR TO SECOND
+        );
+
+        // DAY TO ..
+        test!(1 days, 1 hours                    => "1 1" DAY TO HOUR);
+        test!(-1 days, -7 hours                  => "-1 7" DAY TO HOUR);
+        test!(30 days, 5 minutes                 => "30 00:05" DAY TO MINUTE);
+        test!(-1 days, -130 minutes              => "-1 02:10" DAY TO MINUTE);
+        test!(30 days, 100 seconds               => "30 00:01:40" DAY TO SECOND);
+        test!(-5 days, -3800 seconds             => "-5 01:03:20" DAY TO SECOND);
+        test!(1 days, 1 microseconds             => "1 00:00:00.000001" DAY TO SECOND);
+        test!(-1 days, -1 microseconds           => "-1 00:00:00.000001" DAY TO SECOND);
+        test!(100 days, 2 hours, 3 minutes       => "100 02:03" DAY TO MINUTE);
+        test!(2 days, 20 hours, 3 seconds        => "2 20:00:03" DAY TO SECOND);
+        test!(2 days, 20 hours, 234 milliseconds => "2 20:00:00.234" DAY TO SECOND);
+        test!(
+            30 days, 30 hours, 100 seconds, 3 microseconds =>
+            "31 06:01:40.000003" DAY TO SECOND
+        );
+    }
+}
diff --git a/src/data/literal.rs b/src/data/literal.rs
index 12374433..91ea1682 100644
--- a/src/data/literal.rs
+++ b/src/data/literal.rs
@@ -1,12 +1,12 @@
 use {
     crate::result::{Error, Result},
+    serde::Serialize,
     sqlparser::ast::{Expr, Ident, Value as AstValue},
     std::{borrow::Cow, cmp::Ordering, convert::TryFrom, fmt::Debug},
+    thiserror::Error,
     Literal::*,
 };
 
-use {serde::Serialize, thiserror::Error};
-
 #[derive(Error, Serialize, Debug, PartialEq)]
 pub enum LiteralError {
     #[error("unsupported literal type: {0}")]
@@ -33,6 +33,7 @@ pub enum Literal<'a> {
     Boolean(bool),
     Number(Cow<'a, String>),
     Text(Cow<'a, String>),
+    Interval(super::Interval),
     Null,
 }
 
@@ -44,6 +45,16 @@ impl<'a> TryFrom<&'a AstValue> for Literal<'a> {
             AstValue::Boolean(v) => Boolean(*v),
             AstValue::Number(v, false) => Number(Cow::Borrowed(v)),
             AstValue::SingleQuotedString(v) => Text(Cow::Borrowed(v)),
+            AstValue::Interval {
+                value,
+                leading_field,
+                last_field,
+                ..
+            } => Interval(super::Interval::try_from_literal(
+                value,
+                leading_field.as_ref(),
+                last_field.as_ref(),
+            )?),
             AstValue::Null => Null,
             _ => {
                 return Err(LiteralError::UnsupportedLiteralType(ast_value.to_string()).into());
@@ -60,6 +71,7 @@ impl<'a> TryFrom<&'a Expr> for Literal<'a> {
     fn try_from(expr: &'a Expr) -> Result<Self> {
         match expr {
             Expr::Value(literal) => Literal::try_from(literal),
+            // TODO: Expr::TypedString support
             Expr::Identifier(Ident { value, .. }) => Ok(Literal::Text(Cow::Borrowed(value))),
             _ => Err(LiteralError::UnsupportedExpr(expr.to_string()).into()),
         }
@@ -71,6 +83,7 @@ impl PartialEq<Literal<'_>> for Literal<'_> {
         match (self, other) {
             (Boolean(l), Boolean(r)) => l == r,
             (Number(l), Number(r)) | (Text(l), Text(r)) => l == r,
+            (Interval(l), Interval(r)) => l == r,
             _ => false,
         }
     }
@@ -96,48 +109,12 @@ impl PartialOrd<Literal<'_>> for Literal<'_> {
                 },
             },
             (Text(l), Text(r)) => Some(l.cmp(r)),
+            (Interval(l), Interval(r)) => l.partial_cmp(r),
             _ => None,
         }
     }
 }
 
-macro_rules! binary_op {
-    ($name:ident, $op:tt) => {
-        pub fn $name<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
-            match (self, other) {
-                (Number(l), Number(r)) => {
-                    match (l.parse::<i64>(), r.parse::<i64>()) {
-                        (Ok(l), Ok(r)) => Ok((l $op r).to_string()),
-                        (Ok(l), _) => match r.parse::<f64>() {
-                            Ok(r) => Ok(((l as f64) $op r).to_string()),
-                            _ => Err(LiteralError::UnreachableBinaryArithmetic.into()),
-                        },
-                        (_, Ok(r)) => match l.parse::<f64>() {
-                            Ok(l) => Ok((l $op (r as f64)).to_string()),
-                            _ => Err(LiteralError::UnreachableBinaryArithmetic.into()),
-                        },
-                        (_, _) => match (l.parse::<f64>(), r.parse::<f64>()) {
-                            (Ok(l), Ok(r)) => Ok((l $op r).to_string()),
-                            _ => Err(LiteralError::UnreachableBinaryArithmetic.into()),
-                        },
-                    }.map(|v| Number(Cow::Owned(v)))
-                }
-                (Null, Number(_))
-                | (Number(_), Null)
-                | (Null, Null) => {
-                    Ok(Literal::Null)
-                }
-                _ => Err(
-                    LiteralError::UnsupportedBinaryArithmetic(
-                        format!("{:?}", self),
-                        format!("{:?}", other),
-                    ).into()
-                ),
-            }
-        }
-    }
-}
-
 impl<'a> Literal<'a> {
     pub fn unary_plus(&self) -> Result<Self> {
         match self {
@@ -146,6 +123,7 @@ impl<'a> Literal<'a> {
                 .map(|_| self.to_owned())
                 .or_else(|_| v.parse::<f64>().map(|_| self.to_owned()))
                 .map_err(|_| LiteralError::UnreachableUnaryOperation.into()),
+            Interval(v) => Ok(Interval(*v)),
             Null => Ok(Null),
             _ => Err(LiteralError::UnaryOperationOnNonNumeric.into()),
         }
@@ -159,6 +137,7 @@ impl<'a> Literal<'a> {
                 .or_else(|_| v.parse::<f64>().map(|v| (-v).to_string()))
                 .map(|v| Number(Cow::Owned(v)))
                 .map_err(|_| LiteralError::UnreachableUnaryOperation.into()),
+            Interval(v) => Ok(Interval(v.unary_minus())),
             Null => Ok(Null),
             _ => Err(LiteralError::UnaryOperationOnNonNumeric.into()),
         }
@@ -173,6 +152,7 @@ impl<'a> Literal<'a> {
             }),
             Number(v) => Some(v.into_owned()),
             Text(v) => Some(v.into_owned()),
+            Interval(v) => Some(v.into()),
             Null => None,
         };
 
@@ -182,10 +162,130 @@ impl<'a> Literal<'a> {
         }
     }
 
-    binary_op!(add, +);
-    binary_op!(subtract, -);
-    binary_op!(multiply, *);
-    binary_op!(divide, /);
+    pub fn add<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
+        match (self, other) {
+            (Number(l), Number(r)) => {
+                if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
+                    Ok(Number(Cow::Owned((l + r).to_string())))
+                } else if let (Ok(l), Ok(r)) = (l.parse::<f64>(), r.parse::<f64>()) {
+                    Ok(Number(Cow::Owned((l + r).to_string())))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Interval(l), Interval(r)) => l.add(r).map(Interval),
+            (Null, Number(_))
+            | (Null, Interval(_))
+            | (Number(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Literal::Null),
+            _ => Err(LiteralError::UnsupportedBinaryArithmetic(
+                format!("{:?}", self),
+                format!("{:?}", other),
+            )
+            .into()),
+        }
+    }
+
+    pub fn subtract<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
+        match (self, other) {
+            (Number(l), Number(r)) => {
+                if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
+                    Ok(Number(Cow::Owned((l - r).to_string())))
+                } else if let (Ok(l), Ok(r)) = (l.parse::<f64>(), r.parse::<f64>()) {
+                    Ok(Number(Cow::Owned((l - r).to_string())))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Interval(l), Interval(r)) => l.subtract(r).map(Interval),
+            (Null, Number(_))
+            | (Null, Interval(_))
+            | (Number(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Literal::Null),
+            _ => Err(LiteralError::UnsupportedBinaryArithmetic(
+                format!("{:?}", self),
+                format!("{:?}", other),
+            )
+            .into()),
+        }
+    }
+
+    pub fn multiply<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
+        match (self, other) {
+            (Number(l), Number(r)) => {
+                if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
+                    Ok(Number(Cow::Owned((l * r).to_string())))
+                } else if let (Ok(l), Ok(r)) = (l.parse::<f64>(), r.parse::<f64>()) {
+                    Ok(Number(Cow::Owned((l * r).to_string())))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Number(l), Interval(r)) | (Interval(r), Number(l)) => {
+                if let Ok(l) = l.parse::<i64>() {
+                    Ok(Interval(l * *r))
+                } else if let Ok(l) = l.parse::<f64>() {
+                    Ok(Interval(l * *r))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Null, Number(_))
+            | (Null, Interval(_))
+            | (Number(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Literal::Null),
+            _ => Err(LiteralError::UnsupportedBinaryArithmetic(
+                format!("{:?}", self),
+                format!("{:?}", other),
+            )
+            .into()),
+        }
+    }
+
+    pub fn divide<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
+        match (self, other) {
+            (Number(l), Number(r)) => {
+                if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
+                    Ok(Number(Cow::Owned((l / r).to_string())))
+                } else if let (Ok(l), Ok(r)) = (l.parse::<f64>(), r.parse::<f64>()) {
+                    Ok(Number(Cow::Owned((l / r).to_string())))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Number(l), Interval(r)) => {
+                if let Ok(l) = l.parse::<i64>() {
+                    Ok(Interval(l / *r))
+                } else if let Ok(l) = l.parse::<f64>() {
+                    Ok(Interval(l / *r))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Interval(l), Number(r)) => {
+                if let Ok(r) = r.parse::<i64>() {
+                    Ok(Interval(*l / r))
+                } else if let Ok(r) = r.parse::<f64>() {
+                    Ok(Interval(*l / r))
+                } else {
+                    Err(LiteralError::UnreachableBinaryArithmetic.into())
+                }
+            }
+            (Null, Number(_))
+            | (Null, Interval(_))
+            | (Number(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Literal::Null),
+            _ => Err(LiteralError::UnsupportedBinaryArithmetic(
+                format!("{:?}", self),
+                format!("{:?}", other),
+            )
+            .into()),
+        }
+    }
 }
 
 #[cfg(test)]
@@ -193,6 +293,21 @@ mod tests {
     use super::Literal::*;
     use std::borrow::Cow;
 
+    #[test]
+    fn arithmetic() {
+        use crate::data::Interval as I;
+
+        let mon = |n| Interval(I::months(n));
+        let num = |n: i32| Number(Cow::Owned(n.to_string()));
+
+        assert_eq!(mon(1).add(&mon(2)), Ok(mon(3)));
+        assert_eq!(mon(3).subtract(&mon(1)), Ok(mon(2)));
+        assert_eq!(mon(3).multiply(&num(-4)), Ok(mon(-12)));
+        assert_eq!(num(9).multiply(&mon(2)), Ok(mon(18)));
+        assert_eq!(mon(14).divide(&num(3)), Ok(mon(4)));
+        assert_eq!(num(27).divide(&mon(9)), Ok(mon(3)));
+    }
+
     #[test]
     fn concat() {
         macro_rules! text {
diff --git a/src/data/mod.rs b/src/data/mod.rs
index f0d107e1..3321bc10 100644
--- a/src/data/mod.rs
+++ b/src/data/mod.rs
@@ -1,10 +1,13 @@
+mod interval;
 mod literal;
 mod row;
-pub mod schema;
 mod table;
+
+pub mod schema;
 pub mod value;
 
 pub use {
+    interval::{Interval, IntervalError},
     literal::{Literal, LiteralError},
     row::{Row, RowError},
     schema::Schema,
diff --git a/src/data/value/error.rs b/src/data/value/error.rs
index 79313b56..98b75e8c 100644
--- a/src/data/value/error.rs
+++ b/src/data/value/error.rs
@@ -69,4 +69,7 @@ pub enum ValueError {
 
     #[error("unimplemented literal cast: {literal} as {data_type}")]
     UnimplementedLiteralCast { data_type: String, literal: String },
+
+    #[error("unreachable integer overflow: {0}")]
+    UnreachableIntegerOverflow(String),
 }
diff --git a/src/data/value/group_key.rs b/src/data/value/group_key.rs
index c6687d01..707afbbd 100644
--- a/src/data/value/group_key.rs
+++ b/src/data/value/group_key.rs
@@ -19,6 +19,7 @@ impl TryInto<GroupKey> for &Value {
             Str(v) => Ok(GroupKey::Str(v.clone())),
             Date(v) => Ok(GroupKey::Date(*v)),
             Timestamp(v) => Ok(GroupKey::Timestamp(*v)),
+            Interval(v) => Ok(GroupKey::Interval(*v)),
             Null => Ok(GroupKey::None),
             F64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
         }
@@ -37,6 +38,7 @@ impl TryInto<GroupKey> for Value {
             Str(v) => Ok(GroupKey::Str(v)),
             Date(v) => Ok(GroupKey::Date(v)),
             Timestamp(v) => Ok(GroupKey::Timestamp(v)),
+            Interval(v) => Ok(GroupKey::Interval(v)),
             Null => Ok(GroupKey::None),
             F64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
         }
diff --git a/src/data/value/into.rs b/src/data/value/into.rs
index fb813238..3f925983 100644
--- a/src/data/value/into.rs
+++ b/src/data/value/into.rs
@@ -14,6 +14,7 @@ impl From<&Value> for String {
             Value::F64(value) => value.to_string(),
             Value::Date(value) => value.to_string(),
             Value::Timestamp(value) => value.to_string(),
+            Value::Interval(value) => String::from(value),
             Value::Null => String::from("NULL"),
         }
     }
@@ -53,7 +54,7 @@ impl TryInto<bool> for &Value {
                 "FALSE" => false,
                 _ => return Err(ValueError::ImpossibleCast.into()),
             },
-            Value::Date(_) | Value::Timestamp(_) | Value::Null => {
+            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
                 return Err(ValueError::ImpossibleCast.into())
             }
         })
@@ -85,7 +86,7 @@ impl TryInto<i64> for &Value {
             Value::Str(value) => value
                 .parse::<i64>()
                 .map_err(|_| ValueError::ImpossibleCast)?,
-            Value::Date(_) | Value::Timestamp(_) | Value::Null => {
+            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
                 return Err(ValueError::ImpossibleCast.into())
             }
         })
@@ -109,7 +110,7 @@ impl TryInto<f64> for &Value {
             Value::Str(value) => value
                 .parse::<f64>()
                 .map_err(|_| ValueError::ImpossibleCast)?,
-            Value::Date(_) | Value::Timestamp(_) | Value::Null => {
+            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
                 return Err(ValueError::ImpossibleCast.into())
             }
         })
diff --git a/src/data/value/literal.rs b/src/data/value/literal.rs
index 16b82b4f..3bb71907 100644
--- a/src/data/value/literal.rs
+++ b/src/data/value/literal.rs
@@ -36,6 +36,7 @@ impl PartialEq<Literal<'_>> for Value {
                 Ok(r) => l == &r,
                 Err(_) => false,
             },
+            (Value::Interval(l), Literal::Interval(r)) => l == r,
             _ => false,
         }
     }
@@ -67,6 +68,7 @@ impl PartialOrd<Literal<'_>> for Value {
                 Ok(r) => l.partial_cmp(&r),
                 Err(_) => None,
             },
+            (Value::Interval(l), Literal::Interval(r)) => l.partial_cmp(&r),
             _ => None,
         }
     }
@@ -83,6 +85,7 @@ impl TryFrom<&Literal<'_>> for Value {
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
             Literal::Boolean(v) => Ok(Value::Bool(*v)),
             Literal::Text(v) => Ok(Value::Str(v.as_ref().to_owned())),
+            Literal::Interval(v) => Ok(Value::Interval(*v)),
             Literal::Null => Ok(Value::Null),
         }
     }
@@ -99,6 +102,7 @@ impl TryFrom<Literal<'_>> for Value {
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
             Literal::Boolean(v) => Ok(Value::Bool(v)),
             Literal::Text(v) => Ok(Value::Str(v.into_owned())),
+            Literal::Interval(v) => Ok(Value::Interval(v)),
             Literal::Null => Ok(Value::Null),
         }
     }
@@ -130,12 +134,14 @@ impl TryFromLiteral for Value {
             (DataType::Timestamp, Literal::Text(v)) => parse_timestamp(v)
                 .map(Value::Timestamp)
                 .map_err(|_| ValueError::FailedToParseTimestamp(v.to_string()).into()),
+            (DataType::Interval, Literal::Interval(v)) => Ok(Value::Interval(*v)),
             (DataType::Boolean, Literal::Null)
             | (DataType::Int, Literal::Null)
             | (DataType::Float(_), Literal::Null)
             | (DataType::Text, Literal::Null)
             | (DataType::Date, Literal::Null)
-            | (DataType::Timestamp, Literal::Null) => Ok(Value::Null),
+            | (DataType::Timestamp, Literal::Null)
+            | (DataType::Interval, Literal::Null) => Ok(Value::Null),
             _ => Err(ValueError::IncompatibleLiteralForDataType {
                 data_type: data_type.to_string(),
                 literal: format!("{:?}", literal),
diff --git a/src/data/value/mod.rs b/src/data/value/mod.rs
index 3f286e04..24d7fc44 100644
--- a/src/data/value/mod.rs
+++ b/src/data/value/mod.rs
@@ -1,7 +1,8 @@
 use {
-    super::Literal,
+    super::{Interval, Literal},
     crate::result::Result,
     chrono::{NaiveDate, NaiveDateTime},
+    core::ops::Sub,
     serde::{Deserialize, Serialize},
     sqlparser::ast::{DataType, Expr},
     std::{
@@ -27,6 +28,7 @@ pub enum Value {
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Interval(Interval),
     Null,
 }
 
@@ -43,6 +45,7 @@ impl PartialEq<Value> for Value {
             (Value::Date(l), Value::Timestamp(r)) => &l.and_hms(0, 0, 0) == r,
             (Value::Timestamp(l), Value::Date(r)) => l == &r.and_hms(0, 0, 0),
             (Value::Timestamp(l), Value::Timestamp(r)) => l == r,
+            (Value::Interval(l), Value::Interval(r)) => l == r,
             _ => false,
         }
     }
@@ -57,30 +60,16 @@ impl PartialOrd<Value> for Value {
             (Value::F64(l), Value::I64(r)) => l.partial_cmp(&(*r as f64)),
             (Value::F64(l), Value::F64(r)) => l.partial_cmp(r),
             (Value::Str(l), Value::Str(r)) => Some(l.cmp(r)),
-            (Value::Date(l), Value::Date(r)) => l.partial_cmp(r),
-            (Value::Date(l), Value::Timestamp(r)) => l.and_hms(0, 0, 0).partial_cmp(r),
-            (Value::Timestamp(l), Value::Date(r)) => l.partial_cmp(&r.and_hms(0, 0, 0)),
-            (Value::Timestamp(l), Value::Timestamp(r)) => l.partial_cmp(r),
+            (Value::Date(l), Value::Date(r)) => Some(l.cmp(r)),
+            (Value::Date(l), Value::Timestamp(r)) => Some(l.and_hms(0, 0, 0).cmp(r)),
+            (Value::Timestamp(l), Value::Date(r)) => Some(l.cmp(&r.and_hms(0, 0, 0))),
+            (Value::Timestamp(l), Value::Timestamp(r)) => Some(l.cmp(r)),
+            (Value::Interval(l), Value::Interval(r)) => l.partial_cmp(r),
             _ => None,
         }
     }
 }
 
-trait BoolToValue: Sized {
-    fn into_value(self, v1: Value, v2: Value) -> Value;
-}
-
-impl BoolToValue for bool {
-    #[inline]
-    fn into_value(self, v1: Value, v2: Value) -> Value {
-        if self {
-            v1
-        } else {
-            v2
-        }
-    }
-}
-
 impl Value {
     pub fn from_expr(data_type: &DataType, nullable: bool, expr: &Expr) -> Result<Self> {
         let literal = Literal::try_from(expr)?;
@@ -100,12 +89,14 @@ impl Value {
                 | (DataType::Text, Value::Str(_))
                 | (DataType::Date, Value::Date(_))
                 | (DataType::Timestamp, Value::Timestamp(_))
+                | (DataType::Interval, Value::Interval(_))
                 | (DataType::Boolean, Value::Null)
                 | (DataType::Int, Value::Null)
                 | (DataType::Float(_), Value::Null)
                 | (DataType::Text, Value::Null)
                 | (DataType::Date, Value::Null)
                 | (DataType::Timestamp, Value::Null)
+                | (DataType::Interval, Value::Null)
         );
 
         if !valid {
@@ -134,7 +125,8 @@ impl Value {
             | (DataType::Float(_), Value::F64(_))
             | (DataType::Text, Value::Str(_))
             | (DataType::Date, Value::Date(_))
-            | (DataType::Timestamp, Value::Timestamp(_)) => Ok(self.clone()),
+            | (DataType::Timestamp, Value::Timestamp(_))
+            | (DataType::Interval, Value::Interval(_)) => Ok(self.clone()),
 
             (_, Value::Null) => Ok(Value::Null),
 
@@ -163,9 +155,22 @@ impl Value {
             (I64(a), I64(b)) => Ok(I64(a + b)),
             (F64(a), F64(b)) => Ok(F64(a + b)),
             (I64(a), F64(b)) | (F64(b), I64(a)) => Ok(F64(*a as f64 + b)),
-            (Null, I64(_)) | (Null, F64(_)) | (I64(_), Null) | (F64(_), Null) | (Null, Null) => {
-                Ok(Null)
+            (Date(a), Interval(b)) | (Interval(b), Date(a)) => b.add_date(a).map(Timestamp),
+            (Timestamp(a), Interval(b)) | (Interval(b), Timestamp(a)) => {
+                b.add_timestamp(a).map(Timestamp)
             }
+            (Interval(a), Interval(b)) => a.add(b).map(Interval),
+            (Null, I64(_))
+            | (Null, F64(_))
+            | (Null, Date(_))
+            | (Null, Timestamp(_))
+            | (Null, Interval(_))
+            | (I64(_), Null)
+            | (F64(_), Null)
+            | (Date(_), Null)
+            | (Timestamp(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Null),
             _ => Err(
                 ValueError::AddOnNonNumeric(format!("{:?}", self), format!("{:?}", other)).into(),
             ),
@@ -173,6 +178,7 @@ impl Value {
     }
 
     pub fn subtract(&self, other: &Value) -> Result<Value> {
+        use super::Interval as I;
         use Value::*;
 
         match (self, other) {
@@ -180,9 +186,28 @@ impl Value {
             (I64(a), F64(b)) => Ok(F64(*a as f64 - b)),
             (F64(a), I64(b)) => Ok(F64(a - *b as f64)),
             (F64(a), F64(b)) => Ok(F64(a - b)),
-            (Null, I64(_)) | (Null, F64(_)) | (I64(_), Null) | (F64(_), Null) | (Null, Null) => {
-                Ok(Null)
-            }
+            (Date(a), Date(b)) => Ok(Interval(I::days((*a - *b).num_days() as i32))),
+            (Date(a), Interval(b)) => b.subtract_from_date(a).map(Timestamp),
+            (Timestamp(a), Interval(b)) => b.subtract_from_timestamp(a).map(Timestamp),
+            (Timestamp(a), Timestamp(b)) => a
+                .sub(*b)
+                .num_microseconds()
+                .ok_or_else(|| {
+                    ValueError::UnreachableIntegerOverflow(format!("{:?} - {:?}", a, b)).into()
+                })
+                .map(|v| Interval(I::microseconds(v))),
+            (Interval(a), Interval(b)) => a.subtract(b).map(Interval),
+            (Null, I64(_))
+            | (Null, F64(_))
+            | (Null, Date(_))
+            | (Null, Timestamp(_))
+            | (Null, Interval(_))
+            | (I64(_), Null)
+            | (F64(_), Null)
+            | (Date(_), Null)
+            | (Timestamp(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Null),
             _ => Err(ValueError::SubtractOnNonNumeric(
                 format!("{:?}", self),
                 format!("{:?}", other),
@@ -196,11 +221,19 @@ impl Value {
 
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a * b)),
+            (I64(a), Interval(b)) => Ok(Interval(*a * *b)),
             (F64(a), F64(b)) => Ok(F64(a * b)),
+            (F64(a), Interval(b)) => Ok(Interval(*a * *b)),
             (I64(a), F64(b)) | (F64(b), I64(a)) => Ok(F64(*a as f64 * b)),
-            (Null, I64(_)) | (Null, F64(_)) | (I64(_), Null) | (F64(_), Null) | (Null, Null) => {
-                Ok(Null)
-            }
+            (Interval(a), I64(b)) => Ok(Interval(*a * *b)),
+            (Interval(a), F64(b)) => Ok(Interval(*a * *b)),
+            (Null, I64(_))
+            | (Null, F64(_))
+            | (Null, Interval(_))
+            | (I64(_), Null)
+            | (F64(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Null),
             _ => Err(ValueError::MultiplyOnNonNumeric(
                 format!("{:?}", self),
                 format!("{:?}", other),
@@ -215,11 +248,19 @@ impl Value {
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a / b)),
             (I64(a), F64(b)) => Ok(F64(*a as f64 / b)),
+            (I64(a), Interval(b)) => Ok(Interval(*a / *b)),
             (F64(a), I64(b)) => Ok(F64(a / *b as f64)),
+            (F64(a), Interval(b)) => Ok(Interval(*a / *b)),
             (F64(a), F64(b)) => Ok(F64(a / b)),
-            (Null, I64(_)) | (Null, F64(_)) | (I64(_), Null) | (F64(_), Null) | (Null, Null) => {
-                Ok(Null)
-            }
+            (Interval(a), I64(b)) => Ok(Interval(*a / *b)),
+            (Interval(a), F64(b)) => Ok(Interval(*a / *b)),
+            (Null, I64(_))
+            | (Null, F64(_))
+            | (Null, Interval(_))
+            | (I64(_), Null)
+            | (F64(_), Null)
+            | (Interval(_), Null)
+            | (Null, Null) => Ok(Null),
             _ => Err(
                 ValueError::DivideOnNonNumeric(format!("{:?}", self), format!("{:?}", other))
                     .into(),
@@ -235,7 +276,7 @@ impl Value {
         use Value::*;
 
         match self {
-            I64(_) | F64(_) => Ok(self.clone()),
+            I64(_) | F64(_) | Interval(_) => Ok(self.clone()),
             Null => Ok(Null),
             _ => Err(ValueError::UnaryPlusOnNonNumeric.into()),
         }
@@ -247,6 +288,7 @@ impl Value {
         match self {
             I64(a) => Ok(I64(-a)),
             F64(a) => Ok(F64(-a)),
+            Interval(a) => Ok(Interval(a.unary_minus())),
             Null => Ok(Null),
             _ => Err(ValueError::UnaryMinusOnNonNumeric.into()),
         }
@@ -255,10 +297,11 @@ impl Value {
 
 #[cfg(test)]
 mod tests {
-    use super::Value::*;
+    use super::{Interval, Value::*};
 
     #[test]
     fn eq() {
+        use super::Interval;
         use chrono::NaiveDateTime;
 
         assert_ne!(Null, Null);
@@ -268,6 +311,7 @@ mod tests {
         assert_eq!(F64(1.0), I64(1));
         assert_eq!(F64(6.11), F64(6.11));
         assert_eq!(Str("Glue".to_owned()), Str("Glue".to_owned()));
+        assert_eq!(Interval::Month(1), Interval::Month(1));
 
         let date = Date("2020-05-01".parse().unwrap());
         let timestamp = Timestamp("2020-05-01T00:00:00".parse::<NaiveDateTime>().unwrap());
@@ -286,35 +330,112 @@ mod tests {
 
         assert_eq!(date.partial_cmp(&timestamp), Some(Ordering::Greater));
         assert_eq!(timestamp.partial_cmp(&date), Some(Ordering::Less));
+
+        assert_eq!(
+            Interval::Month(1).partial_cmp(&Interval::Month(2)),
+            Some(Ordering::Less)
+        );
+        assert_eq!(
+            Interval::Microsecond(1).partial_cmp(&Interval::Month(2)),
+            None
+        );
     }
 
     #[test]
     fn arithmetic() {
+        use chrono::NaiveDate;
+
         macro_rules! test {
             ($op: ident $a: expr, $b: expr => $c: expr) => {
                 assert_eq!($a.$op(&$b), Ok($c));
             };
         }
 
+        macro_rules! mon {
+            ($n: expr) => {
+                Interval(Interval::Month($n))
+            };
+        }
+
+        let date = |y, m, d| NaiveDate::from_ymd(y, m, d);
+
         test!(add I64(1),   I64(2)   => I64(3));
         test!(add I64(1),   F64(2.0) => F64(3.0));
         test!(add F64(1.0), I64(2)   => F64(3.0));
         test!(add F64(1.0), F64(2.0) => F64(3.0));
+        test!(add
+            Date(date(2021, 11, 11)),
+            mon!(14)
+            =>
+            Timestamp(date(2023, 1, 11).and_hms(0, 0, 0))
+        );
+        test!(add
+            Interval(Interval::hours(3)),
+            Date(date(2021, 11, 11))
+            =>
+            Timestamp(date(2021, 11, 11).and_hms(3, 0, 0))
+        );
+        test!(add
+            Timestamp(date(2021, 11, 11).and_hms(0, 0, 0)),
+            mon!(14)
+            =>
+            Timestamp(date(2023, 1, 11).and_hms(0, 0, 0))
+        );
+        test!(add
+            Interval(Interval::hours(3)),
+            Timestamp(date(2021, 11, 11).and_hms(0, 0, 0))
+            =>
+            Timestamp(date(2021, 11, 11).and_hms(3, 0, 0))
+        );
+        test!(add mon!(1),  mon!(2)  => mon!(3));
 
         test!(subtract I64(3),   I64(2)   => I64(1));
         test!(subtract I64(3),   F64(2.0) => F64(1.0));
         test!(subtract F64(3.0), I64(2)   => F64(1.0));
         test!(subtract F64(3.0), F64(2.0) => F64(1.0));
+        test!(subtract
+            Date(NaiveDate::from_ymd(2021, 11, 11)),
+            Date(NaiveDate::from_ymd(2021, 6, 11))
+            =>
+            Interval(Interval::days(153))
+        );
+        test!(subtract
+            Date(NaiveDate::from_ymd(2021, 1, 1)),
+            Interval(Interval::days(365))
+            =>
+            Timestamp(NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 0, 0))
+        );
+        test!(subtract
+            Timestamp(NaiveDate::from_ymd(2021, 1, 1).and_hms(15, 0, 0)),
+            Timestamp(NaiveDate::from_ymd(2021, 1, 1).and_hms(12, 0, 0))
+            =>
+            Interval(Interval::hours(3))
+        );
+        test!(subtract
+            Timestamp(NaiveDate::from_ymd(2021, 1, 1).and_hms(0, 3, 0)),
+            Interval(Interval::days(365))
+            =>
+            Timestamp(NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 3, 0))
+        );
+        test!(subtract mon!(1),  mon!(2)  => mon!(-1));
 
         test!(multiply I64(3),   I64(2)   => I64(6));
         test!(multiply I64(3),   F64(2.0) => F64(6.0));
+        test!(multiply I64(3),   mon!(3)  => mon!(9));
         test!(multiply F64(3.0), I64(2)   => F64(6.0));
         test!(multiply F64(3.0), F64(2.0) => F64(6.0));
+        test!(multiply F64(3.0), mon!(3)  => mon!(9));
+        test!(multiply mon!(3),  I64(2)   => mon!(6));
+        test!(multiply mon!(3),  F64(2.0) => mon!(6));
 
         test!(divide I64(6),   I64(2)   => I64(3));
         test!(divide I64(6),   F64(2.0) => F64(3.0));
+        test!(divide I64(6),   mon!(2)  => mon!(3));
         test!(divide F64(6.0), I64(2)   => F64(3.0));
         test!(divide F64(6.0), F64(2.0) => F64(3.0));
+        test!(divide F64(6.0), mon!(2)  => mon!(3));
+        test!(divide mon!(6),  I64(2)   => mon!(3));
+        test!(divide mon!(6),  F64(2.0) => mon!(3));
 
         macro_rules! null_test {
             ($op: ident $a: expr, $b: expr) => {
@@ -322,23 +443,42 @@ mod tests {
             };
         }
 
+        let date = || Date(NaiveDate::from_ymd(1989, 3, 1));
+        let ts = || Timestamp(NaiveDate::from_ymd(1989, 1, 1).and_hms(0, 0, 0));
+
         null_test!(add      I64(1),   Null);
         null_test!(add      F64(1.0), Null);
+        null_test!(add      date(),   Null);
+        null_test!(add      ts(),     Null);
+        null_test!(add      mon!(1),  Null);
         null_test!(subtract I64(1),   Null);
         null_test!(subtract F64(1.0), Null);
+        null_test!(subtract date(),   Null);
+        null_test!(subtract ts(),     Null);
+        null_test!(subtract mon!(1),  Null);
         null_test!(multiply I64(1),   Null);
         null_test!(multiply F64(1.0), Null);
+        null_test!(multiply mon!(1),  Null);
         null_test!(divide   I64(1),   Null);
         null_test!(divide   F64(1.0), Null);
+        null_test!(divide   mon!(1),  Null);
 
         null_test!(add      Null, I64(1));
         null_test!(add      Null, F64(1.0));
+        null_test!(add      Null, mon!(1));
+        null_test!(add      Null, date());
+        null_test!(add      Null, ts());
         null_test!(subtract Null, I64(1));
         null_test!(subtract Null, F64(1.0));
+        null_test!(subtract Null, date());
+        null_test!(subtract Null, ts());
+        null_test!(subtract Null, mon!(1));
         null_test!(multiply Null, I64(1));
         null_test!(multiply Null, F64(1.0));
+        null_test!(multiply Null, mon!(1));
         null_test!(divide   Null, I64(1));
         null_test!(divide   Null, F64(1.0));
+        null_test!(divide   Null, mon!(1));
 
         null_test!(add      Null, Null);
         null_test!(subtract Null, Null);
diff --git a/src/data/value/unique_key.rs b/src/data/value/unique_key.rs
index 835f11e5..f90eac66 100644
--- a/src/data/value/unique_key.rs
+++ b/src/data/value/unique_key.rs
@@ -19,6 +19,7 @@ impl TryInto<Option<UniqueKey>> for &Value {
             Str(v) => Some(UniqueKey::Str(v.clone())),
             Date(v) => Some(UniqueKey::Date(*v)),
             Timestamp(v) => Some(UniqueKey::Timestamp(*v)),
+            Interval(v) => Some(UniqueKey::Interval(*v)),
             Null => None,
             F64(_) => {
                 return Err(ValueError::ConflictOnFloatWithUniqueConstraint.into());
diff --git a/src/executor/aggregate/hash.rs b/src/executor/aggregate/hash.rs
index 87d9cb9f..b387b16e 100644
--- a/src/executor/aggregate/hash.rs
+++ b/src/executor/aggregate/hash.rs
@@ -1,6 +1,6 @@
 use {
     crate::{
-        data::Value,
+        data::{Interval, Value},
         executor::evaluate::Evaluated,
         result::{Error, Result},
     },
@@ -15,6 +15,7 @@ pub enum GroupKey {
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Interval(Interval),
     None,
 }
 
diff --git a/src/executor/alter/validate.rs b/src/executor/alter/validate.rs
index cfd12dd9..7d919f58 100644
--- a/src/executor/alter/validate.rs
+++ b/src/executor/alter/validate.rs
@@ -21,6 +21,7 @@ pub fn validate(column_def: &ColumnDef) -> Result<()> {
             | DataType::Text
             | DataType::Date
             | DataType::Timestamp
+            | DataType::Interval
     ) {
         return Err(AlterError::UnsupportedDataType(data_type.to_string()).into());
     }
diff --git a/src/executor/validate.rs b/src/executor/validate.rs
index 2fd225df..81c57061 100644
--- a/src/executor/validate.rs
+++ b/src/executor/validate.rs
@@ -1,6 +1,6 @@
 use {
     crate::{
-        data::{Row, Value},
+        data::{Interval, Row, Value},
         result::Result,
         store::Store,
         utils::Vector,
@@ -35,6 +35,7 @@ pub enum UniqueKey {
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Interval(Interval),
 }
 
 #[derive(Debug)]
diff --git a/src/result.rs b/src/result.rs
index 678bb2d0..ba19a005 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -1,6 +1,6 @@
 use {
     crate::{
-        data::{LiteralError, RowError, TableError, ValueError},
+        data::{IntervalError, LiteralError, RowError, TableError, ValueError},
         executor::{
             AggregateError, AlterError, BlendError, EvaluateError, ExecuteError, FetchError,
             JoinError, LimitError, SelectError, UpdateError, ValidateError,
@@ -53,6 +53,8 @@ pub enum Error {
     Value(#[from] ValueError),
     #[error(transparent)]
     Literal(#[from] LiteralError),
+    #[error(transparent)]
+    Interval(#[from] IntervalError),
 }
 
 pub type Result<T> = std::result::Result<T, Error>;
@@ -80,6 +82,7 @@ impl PartialEq for Error {
             (Validate(e), Validate(e2)) => e == e2,
             (Value(e), Value(e2)) => e == e2,
             (Literal(e), Literal(e2)) => e == e2,
+            (Interval(e), Interval(e2)) => e == e2,
             _ => false,
         }
     }


Test patch:
diff --git a/src/tests/date.rs b/src/tests/data_type/date.rs
similarity index 77%
rename from src/tests/date.rs
rename to src/tests/data_type/date.rs
index b860c1f2..765d49c8 100644
--- a/src/tests/date.rs
+++ b/src/tests/data_type/date.rs
@@ -86,6 +86,25 @@ INSERT INTO DateLog VALUES
         r#"SELECT * FROM DateLog WHERE "1999-01-03" < DATE "2000-01-01";"#
     );
 
+    let days = |n| data::Interval::days(n);
+    let timestamp = |y, m, d| chrono::NaiveDate::from_ymd(y, m, d).and_hms(0, 0, 0);
+
+    test!(
+        Ok(select!(
+            id  | date_sub     | sub                    | add
+            I64 | Interval     | Timestamp              | Timestamp;
+            1     days(-263)     timestamp(2020, 6, 10)   timestamp(2021, 4, 1);
+            2     days(11_595)   timestamp(2020, 9, 29)   timestamp(1989, 2, 1);
+            3     days(0)        timestamp(2021, 4, 30)   timestamp(2021, 6, 1)
+        )),
+        r#"SELECT
+            id,
+            date1 - date2 AS date_sub,
+            date1 - INTERVAL "1" DAY AS sub,
+            date2 + INTERVAL "1" MONTH AS add
+        FROM DateLog;"#
+    );
+
     test!(
         Err(ValueError::FailedToParseDate("12345-678".to_owned()).into()),
         r#"INSERT INTO DateLog VALUES (1, "12345-678", "2021-05-01")"#
diff --git a/src/tests/data_type/interval.rs b/src/tests/data_type/interval.rs
new file mode 100644
index 00000000..74505407
--- /dev/null
+++ b/src/tests/data_type/interval.rs
@@ -0,0 +1,125 @@
+use crate::*;
+
+test_case!(interval, async move {
+    run!(
+        r#"
+CREATE TABLE IntervalLog (
+    id INTEGER,
+    interval1 INTERVAL,
+    interval2 INTERVAL,
+)"#
+    );
+
+    run!(
+        r#"
+INSERT INTO IntervalLog VALUES
+    (1, INTERVAL "1-2" YEAR TO MONTH,         INTERVAL "30" MONTH),
+    (2, INTERVAL "12" DAY,                    INTERVAL "35" HOUR),
+    (3, INTERVAL "12" MINUTE,                 INTERVAL "300" SECOND),
+    (4, INTERVAL "-3 14" DAY TO HOUR,         INTERVAL "3 12:30" DAY TO MINUTE),
+    (5, INTERVAL "3 14:00:00" DAY TO SECOND,  INTERVAL "3 12:30:12.1324" DAY TO SECOND),
+    (6, INTERVAL "12:00" HOUR TO MINUTE,      INTERVAL "-12:30:12" HOUR TO SECOND),
+    (7, INTERVAL "-1000-11" YEAR TO MONTH,    INTERVAL "-30:11" MINUTE TO SECOND);
+"#
+    );
+
+    use data::Interval as I;
+    use Value::*;
+
+    test!(
+        Ok(select!(
+            id  | interval1           | interval2
+            I64 | Interval            | Interval;
+            1     I::months(14)         I::months(30);
+            2     I::days(12)           I::hours(35);
+            3     I::minutes(12)        I::minutes(5);
+            4     I::hours(-86)         I::minutes(84 * 60 + 30);
+            5     I::minutes(86 * 60)   I::microseconds((((84 * 60) + 30) * 60 + 12) * 1_000_000 + 132_400);
+            6     I::hours(12)          I::seconds(-(12 * 3600 + 30 * 60 + 12));
+            7     I::months(-12_011)    I::seconds(-(30 * 60 + 11))
+        )),
+        "SELECT * FROM IntervalLog;"
+    );
+
+    test!(
+        Ok(select!(
+            id  | i1            | i2
+            I64 | Interval      | Interval;
+            1     I::months(28)   I::months(66)
+        )),
+        r#"SELECT
+            id,
+            interval1 * 2 AS i1,
+            interval2 - INTERVAL "-3" YEAR AS i2
+        FROM IntervalLog WHERE id = 1"#
+    );
+
+    test!(
+        Ok(select!(
+            id  | i1         | i2           | i3
+            I64 | Interval   | Interval     | Interval;
+            2     I::days(4)   I::hours(34)   I::minutes(1)
+        )),
+        r#"SELECT
+            id,
+            interval1 / 3 AS i1,
+            interval2 - INTERVAL "3600" SECOND AS i2,
+            INTERVAL "30" SECOND + INTERVAL "10" SECOND * 3 AS i3
+        FROM IntervalLog WHERE id = 2;"#
+    );
+
+    test!(
+        Err(IntervalError::UnsupportedRange("Minute".to_owned(), "Hour".to_owned()).into()),
+        r#"INSERT INTO IntervalLog VALUES (1, INTERVAL "20:00" MINUTE TO HOUR, INTERVAL "1-2" YEAR TO MONTH)"#
+    );
+
+    test!(
+        Err(IntervalError::AddBetweenYearToMonthAndHourToSecond.into()),
+        r#"SELECT INTERVAL "1" YEAR + INTERVAL "1" HOUR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::SubtractBetweenYearToMonthAndHourToSecond.into()),
+        r#"SELECT INTERVAL "1" YEAR - INTERVAL "1" HOUR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseInteger("1.4".to_owned()).into()),
+        r#"SELECT INTERVAL "1.4" YEAR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseDecimal("1.4ab".to_owned()).into()),
+        r#"SELECT INTERVAL "1.4ab" HOUR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseTime("111:34".to_owned()).into()),
+        r#"SELECT INTERVAL "111:34" HOUR TO MINUTE FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseYearToMonth("111".to_owned()).into()),
+        r#"SELECT INTERVAL "111" YEAR TO MONTH FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseDayToHour("111".to_owned()).into()),
+        r#"SELECT INTERVAL "111" DAY TO HOUR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseDayToHour("111".to_owned()).into()),
+        r#"SELECT INTERVAL "111" DAY TO HOUR FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseDayToMinute("111".to_owned()).into()),
+        r#"SELECT INTERVAL "111" DAY TO MINUTE FROM IntervalLog;"#
+    );
+
+    test!(
+        Err(IntervalError::FailedToParseDayToSecond("111".to_owned()).into()),
+        r#"SELECT INTERVAL "111" DAY TO Second FROM IntervalLog;"#
+    );
+});
diff --git a/src/tests/data_type/mod.rs b/src/tests/data_type/mod.rs
new file mode 100644
index 00000000..6b78e04f
--- /dev/null
+++ b/src/tests/data_type/mod.rs
@@ -0,0 +1,4 @@
+pub mod date;
+pub mod interval;
+pub mod sql_types;
+pub mod timestamp;
diff --git a/src/tests/sql_types.rs b/src/tests/data_type/sql_types.rs
similarity index 100%
rename from src/tests/sql_types.rs
rename to src/tests/data_type/sql_types.rs
diff --git a/src/tests/timestamp.rs b/src/tests/data_type/timestamp.rs
similarity index 76%
rename from src/tests/timestamp.rs
rename to src/tests/data_type/timestamp.rs
index c9b4d461..723deae9 100644
--- a/src/tests/timestamp.rs
+++ b/src/tests/data_type/timestamp.rs
@@ -86,6 +86,32 @@ INSERT INTO TimestampLog VALUES
         r#"SELECT * FROM TimestampLog WHERE TIMESTAMP "1999-01-03" < "2000-01-01";"#
     );
 
+    test!(
+        Ok(select!(
+            id  | timestamp_sub
+            I64 | Interval;
+            1     data::Interval::seconds(-22_682_209);
+            2     data::Interval::seconds(1_001_908_740);
+            3     data::Interval::seconds(0)
+        )),
+        "SELECT id, t1 - t2 AS timestamp_sub FROM TimestampLog;"
+    );
+
+    test!(
+        Ok(select!(
+            id  | sub                            | add
+            I64 | Timestamp                      | Timestamp;
+            1     t!("2020-06-10T11:23:11")        t!("2021-04-01T00:00:00");
+            2     t!("2020-09-29T19:00:00")        t!("1989-01-31T15:01:00");
+            3     t!("2021-04-30T00:00:00.1234")   t!("2021-06-01T00:00:00.1234")
+        )),
+        r#"SELECT
+            id,
+            t1 - INTERVAL "1" DAY AS sub,
+            t2 + INTERVAL "1" MONTH AS add
+        FROM TimestampLog;"#
+    );
+
     test!(
         Err(ValueError::FailedToParseTimestamp("12345-678".to_owned()).into()),
         r#"INSERT INTO TimestampLog VALUES (1, "12345-678", "2021-05-01")"#
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 63ac7e97..1f5e977c 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -6,7 +6,7 @@ pub mod basic;
 pub mod blend;
 pub mod concat;
 pub mod create_table;
-pub mod date;
+pub mod data_type;
 pub mod default;
 pub mod drop_table;
 pub mod error;
@@ -17,9 +17,7 @@ pub mod migrate;
 pub mod nested_select;
 pub mod nullable;
 pub mod ordering;
-pub mod sql_types;
 pub mod synthesize;
-pub mod timestamp;
 pub mod validate;
 
 mod tester;
@@ -81,9 +79,10 @@ macro_rules! generate_tests {
         glue!(nullable, nullable::nullable);
         glue!(nullable_text, nullable::nullable_text);
         glue!(ordering, ordering::ordering);
-        glue!(sql_types, sql_types::sql_types);
-        glue!(date, date::date);
-        glue!(timestamp, timestamp::timestamp);
+        glue!(sql_types, data_type::sql_types::sql_types);
+        glue!(date, data_type::date::date);
+        glue!(timestamp, data_type::timestamp::timestamp);
+        glue!(interval, data_type::interval::interval);
         glue!(synthesize, synthesize::synthesize);
         glue!(validate_unique, validate::unique::unique);
         glue!(validate_types, validate::types::types);