Repository: gluesql/gluesql
Pull request #221 (2021-05-07 12:04:02)

Add TIME data type support
Arithmetic operations below are supported.

* `TIME + INTERVAL => TIME`
* `TIME - INTERVAL => TIME`
* `TIME - TIME => INTERVAL`
* `DATE + TIME => TIMESTAMP`


Patch:
diff --git a/src/data/interval/error.rs b/src/data/interval/error.rs
index 0e57879a..bb5887bd 100644
--- a/src/data/interval/error.rs
+++ b/src/data/interval/error.rs
@@ -11,6 +11,12 @@ pub enum IntervalError {
     #[error("cannot subtract between YEAR TO MONTH and HOUR TO SECOND")]
     SubtractBetweenYearToMonthAndHourToSecond,
 
+    #[error("cannot add year or month to TIME: {time} + {interval}")]
+    AddYearOrMonthToTime { time: String, interval: String },
+
+    #[error("cannot subtract year or month to TIME: {time} - {interval}")]
+    SubtractYearOrMonthToTime { time: String, interval: String },
+
     #[error("failed to parse integer: {0}")]
     FailedToParseInteger(String),
 
diff --git a/src/data/interval/mod.rs b/src/data/interval/mod.rs
index 0736dec1..136dcd93 100644
--- a/src/data/interval/mod.rs
+++ b/src/data/interval/mod.rs
@@ -107,6 +107,28 @@ impl Interval {
         }
     }
 
+    pub fn add_time(&self, time: &NaiveTime) -> Result<NaiveTime> {
+        match self {
+            Interval::Month(_) => Err(IntervalError::AddYearOrMonthToTime {
+                time: time.to_string(),
+                interval: String::from(self),
+            }
+            .into()),
+            Interval::Microsecond(n) => Ok(*time + Duration::microseconds(*n)),
+        }
+    }
+
+    pub fn subtract_from_time(&self, time: &NaiveTime) -> Result<NaiveTime> {
+        match self {
+            Interval::Month(_) => Err(IntervalError::SubtractYearOrMonthToTime {
+                time: time.to_string(),
+                interval: String::from(self),
+            }
+            .into()),
+            Interval::Microsecond(n) => Ok(*time - Duration::microseconds(*n)),
+        }
+    }
+
     pub fn years(years: i32) -> Self {
         Interval::Month(12 * years)
     }
@@ -263,7 +285,7 @@ mod tests {
 
     #[test]
     fn arithmetic() {
-        use chrono::NaiveDate;
+        use chrono::{NaiveDate, NaiveTime};
         use Interval::*;
 
         macro_rules! test {
@@ -273,9 +295,12 @@ mod tests {
         }
 
         let date = |y, m, d| NaiveDate::from_ymd(y, m, d);
+        let time = |h, m, s| NaiveTime::from_hms(h, m, s);
 
         assert_eq!(Month(1).unary_minus(), Month(-1));
         assert_eq!(Microsecond(1).unary_minus(), Microsecond(-1));
+
+        // date
         assert_eq!(
             Month(2).add_date(&date(2021, 11, 11)),
             Ok(date(2022, 1, 11).and_hms(0, 0, 0))
@@ -312,6 +337,8 @@ mod tests {
             }
             .into())
         );
+
+        // timestamp
         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))
@@ -349,6 +376,40 @@ mod tests {
             .into())
         );
 
+        // time
+        assert_eq!(
+            Interval::minutes(30).add_time(&time(23, 0, 1)),
+            Ok(time(23, 30, 1))
+        );
+        assert_eq!(
+            Interval::hours(20).add_time(&time(5, 30, 0)),
+            Ok(time(1, 30, 0))
+        );
+        assert_eq!(
+            Interval::years(1).add_time(&time(23, 0, 1)),
+            Err(IntervalError::AddYearOrMonthToTime {
+                time: time(23, 0, 1).to_string(),
+                interval: String::from(Interval::years(1)),
+            }
+            .into())
+        );
+        assert_eq!(
+            Interval::minutes(30).subtract_from_time(&time(23, 0, 1)),
+            Ok(time(22, 30, 1))
+        );
+        assert_eq!(
+            Interval::hours(20).subtract_from_time(&time(5, 30, 0)),
+            Ok(time(9, 30, 0))
+        );
+        assert_eq!(
+            Interval::months(3).subtract_from_time(&time(23, 0, 1)),
+            Err(IntervalError::SubtractYearOrMonthToTime {
+                time: time(23, 0, 1).to_string(),
+                interval: String::from(Interval::months(3)),
+            }
+            .into())
+        );
+
         test!(add      Month(1), Month(2) => Month(3));
         test!(subtract Month(1), Month(2) => Month(-1));
 
diff --git a/src/data/value/error.rs b/src/data/value/error.rs
index 98b75e8c..2c6ce0b1 100644
--- a/src/data/value/error.rs
+++ b/src/data/value/error.rs
@@ -20,6 +20,9 @@ pub enum ValueError {
     #[error("failed to parse timestamp: {0}")]
     FailedToParseTimestamp(String),
 
+    #[error("failed to parse time: {0}")]
+    FailedToParseTime(String),
+
     #[error("add on non-numeric values: {0} + {1}")]
     AddOnNonNumeric(String, String),
 
diff --git a/src/data/value/group_key.rs b/src/data/value/group_key.rs
index 707afbbd..b0e59a97 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)),
+            Time(v) => Ok(GroupKey::Time(*v)),
             Interval(v) => Ok(GroupKey::Interval(*v)),
             Null => Ok(GroupKey::None),
             F64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
@@ -38,6 +39,7 @@ impl TryInto<GroupKey> for Value {
             Str(v) => Ok(GroupKey::Str(v)),
             Date(v) => Ok(GroupKey::Date(v)),
             Timestamp(v) => Ok(GroupKey::Timestamp(v)),
+            Time(v) => Ok(GroupKey::Time(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 3f925983..6ffd15a1 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::Time(value) => value.to_string(),
             Value::Interval(value) => String::from(value),
             Value::Null => String::from("NULL"),
         }
@@ -54,9 +55,11 @@ impl TryInto<bool> for &Value {
                 "FALSE" => false,
                 _ => return Err(ValueError::ImpossibleCast.into()),
             },
-            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
-                return Err(ValueError::ImpossibleCast.into())
-            }
+            Value::Date(_)
+            | Value::Timestamp(_)
+            | Value::Time(_)
+            | Value::Interval(_)
+            | Value::Null => return Err(ValueError::ImpossibleCast.into()),
         })
     }
 }
@@ -86,9 +89,11 @@ impl TryInto<i64> for &Value {
             Value::Str(value) => value
                 .parse::<i64>()
                 .map_err(|_| ValueError::ImpossibleCast)?,
-            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
-                return Err(ValueError::ImpossibleCast.into())
-            }
+            Value::Date(_)
+            | Value::Timestamp(_)
+            | Value::Time(_)
+            | Value::Interval(_)
+            | Value::Null => return Err(ValueError::ImpossibleCast.into()),
         })
     }
 }
@@ -110,9 +115,11 @@ impl TryInto<f64> for &Value {
             Value::Str(value) => value
                 .parse::<f64>()
                 .map_err(|_| ValueError::ImpossibleCast)?,
-            Value::Date(_) | Value::Timestamp(_) | Value::Interval(_) | Value::Null => {
-                return Err(ValueError::ImpossibleCast.into())
-            }
+            Value::Date(_)
+            | Value::Timestamp(_)
+            | Value::Time(_)
+            | Value::Interval(_)
+            | Value::Null => return Err(ValueError::ImpossibleCast.into()),
         })
     }
 }
diff --git a/src/data/value/literal.rs b/src/data/value/literal.rs
index 5aead04b..f36fc9ab 100644
--- a/src/data/value/literal.rs
+++ b/src/data/value/literal.rs
@@ -4,7 +4,7 @@ use {
         data::Literal,
         result::{Error, Result},
     },
-    chrono::{offset::Utc, DateTime, NaiveDate, NaiveDateTime},
+    chrono::{offset::Utc, DateTime, NaiveDate, NaiveDateTime, NaiveTime},
     sqlparser::ast::DataType,
     std::{cmp::Ordering, convert::TryFrom},
 };
@@ -36,6 +36,10 @@ impl PartialEq<Literal<'_>> for Value {
                 Ok(r) => l == &r,
                 Err(_) => false,
             },
+            (Value::Time(l), Literal::Text(r)) => match parse_time(r) {
+                Ok(r) => l == &r,
+                Err(_) => false,
+            },
             (Value::Interval(l), Literal::Interval(r)) => l == r,
             _ => false,
         }
@@ -68,6 +72,10 @@ impl PartialOrd<Literal<'_>> for Value {
                 Ok(r) => l.partial_cmp(&r),
                 Err(_) => None,
             },
+            (Value::Time(l), Literal::Text(r)) => match parse_time(r) {
+                Ok(r) => l.partial_cmp(&r),
+                Err(_) => None,
+            },
             (Value::Interval(l), Literal::Interval(r)) => l.partial_cmp(&r),
             _ => None,
         }
@@ -128,6 +136,9 @@ impl Value {
             (DataType::Timestamp, Literal::Text(v)) => parse_timestamp(v)
                 .map(Value::Timestamp)
                 .map_err(|_| ValueError::FailedToParseTimestamp(v.to_string()).into()),
+            (DataType::Time, Literal::Text(v)) => parse_time(v)
+                .map(Value::Time)
+                .map_err(|_| ValueError::FailedToParseTime(v.to_string()).into()),
             (DataType::Interval, Literal::Interval(v)) => Ok(Value::Interval(*v)),
             (DataType::Boolean, Literal::Null)
             | (DataType::Int, Literal::Null)
@@ -135,6 +146,7 @@ impl Value {
             | (DataType::Text, Literal::Null)
             | (DataType::Date, Literal::Null)
             | (DataType::Timestamp, Literal::Null)
+            | (DataType::Time, Literal::Null)
             | (DataType::Interval, Literal::Null) => Ok(Value::Null),
             _ => Err(ValueError::IncompatibleLiteralForDataType {
                 data_type: data_type.to_string(),
@@ -210,3 +222,34 @@ fn parse_timestamp(v: &str) -> Result<NaiveDateTime> {
         Err(ValueError::FailedToParseTimestamp(v.to_string()).into())
     }
 }
+
+fn parse_time(v: &str) -> Result<NaiveTime> {
+    if let Ok(v) = v.parse::<NaiveTime>() {
+        return Ok(v);
+    }
+
+    let forms = [
+        "%P %I:%M",
+        "%P %l:%M",
+        "%P %I:%M:%S",
+        "%P %l:%M:%S",
+        "%P %I:%M:%S%.f",
+        "%P %l:%M:%S%.f",
+        "%I:%M %P",
+        "%l:%M %P",
+        "%I:%M:%S %P",
+        "%l:%M:%S %P",
+        "%I:%M:%S%.f %P",
+        "%l:%M:%S%.f %P",
+    ];
+
+    let v = v.to_uppercase();
+
+    for form in forms.iter() {
+        if let Ok(v) = NaiveTime::parse_from_str(&v, form) {
+            return Ok(v);
+        }
+    }
+
+    Err(ValueError::FailedToParseTime(v).into())
+}
diff --git a/src/data/value/mod.rs b/src/data/value/mod.rs
index cc62aafc..8d11dd24 100644
--- a/src/data/value/mod.rs
+++ b/src/data/value/mod.rs
@@ -1,7 +1,7 @@
 use {
     super::{Interval, Literal},
     crate::result::Result,
-    chrono::{NaiveDate, NaiveDateTime},
+    chrono::{NaiveDate, NaiveDateTime, NaiveTime},
     core::ops::Sub,
     serde::{Deserialize, Serialize},
     sqlparser::ast::{DataType, Expr},
@@ -28,6 +28,7 @@ pub enum Value {
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Time(NaiveTime),
     Interval(Interval),
     Null,
 }
@@ -45,6 +46,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::Time(l), Value::Time(r)) => l == r,
             (Value::Interval(l), Value::Interval(r)) => l == r,
             _ => false,
         }
@@ -64,6 +66,7 @@ impl PartialOrd<Value> for Value {
             (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::Time(l), Value::Time(r)) => Some(l.cmp(r)),
             (Value::Interval(l), Value::Interval(r)) => l.partial_cmp(r),
             _ => None,
         }
@@ -96,6 +99,7 @@ impl Value {
                 | (DataType::Text, Value::Null)
                 | (DataType::Date, Value::Null)
                 | (DataType::Timestamp, Value::Null)
+                | (DataType::Time, Value::Null)
                 | (DataType::Interval, Value::Null)
         );
 
@@ -126,6 +130,7 @@ impl Value {
             | (DataType::Text, Value::Str(_))
             | (DataType::Date, Value::Date(_))
             | (DataType::Timestamp, Value::Timestamp(_))
+            | (DataType::Time, Value::Time(_))
             | (DataType::Interval, Value::Interval(_)) => Ok(self.clone()),
 
             (_, Value::Null) => Ok(Value::Null),
@@ -155,10 +160,10 @@ 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)),
-            (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)
-            }
+            (Date(a), Time(b)) => Ok(Timestamp(NaiveDateTime::new(*a, *b))),
+            (Date(a), Interval(b)) => b.add_date(a).map(Timestamp),
+            (Timestamp(a), Interval(b)) => b.add_timestamp(a).map(Timestamp),
+            (Time(a), Interval(b)) => b.add_time(a).map(Time),
             (Interval(a), Interval(b)) => a.add(b).map(Interval),
             (Null, I64(_))
             | (Null, F64(_))
@@ -169,6 +174,7 @@ impl Value {
             | (F64(_), Null)
             | (Date(_), Null)
             | (Timestamp(_), Null)
+            | (Time(_), Null)
             | (Interval(_), Null)
             | (Null, Null) => Ok(Null),
             _ => Err(
@@ -196,16 +202,26 @@ impl Value {
                     ValueError::UnreachableIntegerOverflow(format!("{:?} - {:?}", a, b)).into()
                 })
                 .map(|v| Interval(I::microseconds(v))),
+            (Time(a), Time(b)) => a
+                .sub(*b)
+                .num_microseconds()
+                .ok_or_else(|| {
+                    ValueError::UnreachableIntegerOverflow(format!("{:?} - {:?}", a, b)).into()
+                })
+                .map(|v| Interval(I::microseconds(v))),
+            (Time(a), Interval(b)) => b.subtract_from_time(a).map(Time),
             (Interval(a), Interval(b)) => a.subtract(b).map(Interval),
             (Null, I64(_))
             | (Null, F64(_))
             | (Null, Date(_))
             | (Null, Timestamp(_))
+            | (Null, Time(_))
             | (Null, Interval(_))
             | (I64(_), Null)
             | (F64(_), Null)
             | (Date(_), Null)
             | (Timestamp(_), Null)
+            | (Time(_), Null)
             | (Interval(_), Null)
             | (Null, Null) => Ok(Null),
             _ => Err(ValueError::SubtractOnNonNumeric(
@@ -302,7 +318,7 @@ mod tests {
     #[test]
     fn eq() {
         use super::Interval;
-        use chrono::NaiveDateTime;
+        use chrono::{NaiveDateTime, NaiveTime};
 
         assert_ne!(Null, Null);
         assert_eq!(Bool(true), Bool(true));
@@ -312,6 +328,10 @@ mod tests {
         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));
+        assert_eq!(
+            Time(NaiveTime::from_hms(12, 30, 11)),
+            Time(NaiveTime::from_hms(12, 30, 11))
+        );
 
         let date = Date("2020-05-01".parse().unwrap());
         let timestamp = Timestamp("2020-05-01T00:00:00".parse::<NaiveDateTime>().unwrap());
@@ -322,7 +342,7 @@ mod tests {
 
     #[test]
     fn cmp() {
-        use chrono::NaiveDate;
+        use chrono::{NaiveDate, NaiveTime};
         use std::cmp::Ordering;
 
         let date = Date(NaiveDate::from_ymd(2020, 5, 1));
@@ -331,6 +351,10 @@ mod tests {
         assert_eq!(date.partial_cmp(&timestamp), Some(Ordering::Greater));
         assert_eq!(timestamp.partial_cmp(&date), Some(Ordering::Less));
 
+        assert_eq!(
+            Time(NaiveTime::from_hms(23, 0, 1)).partial_cmp(&Time(NaiveTime::from_hms(10, 59, 59))),
+            Some(Ordering::Greater)
+        );
         assert_eq!(
             Interval::Month(1).partial_cmp(&Interval::Month(2)),
             Some(Ordering::Less)
@@ -343,7 +367,7 @@ mod tests {
 
     #[test]
     fn arithmetic() {
-        use chrono::NaiveDate;
+        use chrono::{NaiveDate, NaiveTime};
 
         macro_rules! test {
             ($op: ident $a: expr, $b: expr => $c: expr) => {
@@ -357,6 +381,7 @@ mod tests {
             };
         }
 
+        let time = |h, m, s| NaiveTime::from_hms(h, m, s);
         let date = |y, m, d| NaiveDate::from_ymd(y, m, d);
 
         test!(add I64(1),   I64(2)   => I64(3));
@@ -370,10 +395,10 @@ mod tests {
             Timestamp(date(2023, 1, 11).and_hms(0, 0, 0))
         );
         test!(add
-            Interval(Interval::hours(3)),
-            Date(date(2021, 11, 11))
+            Date(date(2021, 5, 7)),
+            Time(time(12, 0, 0))
             =>
-            Timestamp(date(2021, 11, 11).and_hms(3, 0, 0))
+            Timestamp(date(2021, 5, 7).and_hms(12, 0, 0))
         );
         test!(add
             Timestamp(date(2021, 11, 11).and_hms(0, 0, 0)),
@@ -382,10 +407,16 @@ mod tests {
             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))
+            Time(time(1, 4, 6)),
+            Interval(Interval::hours(20))
             =>
-            Timestamp(date(2021, 11, 11).and_hms(3, 0, 0))
+            Time(time(21, 4, 6))
+        );
+        test!(add
+            Time(time(23, 10, 0)),
+            Interval(Interval::hours(5))
+            =>
+            Time(time(4, 10, 0))
         );
         test!(add mon!(1),  mon!(2)  => mon!(3));
 
@@ -417,6 +448,18 @@ mod tests {
             =>
             Timestamp(NaiveDate::from_ymd(2020, 1, 2).and_hms(0, 3, 0))
         );
+        test!(subtract
+            Time(time(1, 4, 6)),
+            Interval(Interval::hours(20))
+            =>
+            Time(time(5, 4, 6))
+        );
+        test!(subtract
+            Time(time(23, 10, 0)),
+            Interval(Interval::hours(5))
+            =>
+            Time(time(18, 10, 0))
+        );
         test!(subtract mon!(1),  mon!(2)  => mon!(-1));
 
         test!(multiply I64(3),   I64(2)   => I64(6));
@@ -444,17 +487,20 @@ mod tests {
         }
 
         let date = || Date(NaiveDate::from_ymd(1989, 3, 1));
+        let time = || Time(NaiveTime::from_hms(6, 1, 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      time(),   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 time(),   Null);
         null_test!(subtract mon!(1),  Null);
         null_test!(multiply I64(1),   Null);
         null_test!(multiply F64(1.0), Null);
@@ -472,6 +518,7 @@ mod tests {
         null_test!(subtract Null, F64(1.0));
         null_test!(subtract Null, date());
         null_test!(subtract Null, ts());
+        null_test!(subtract Null, time());
         null_test!(subtract Null, mon!(1));
         null_test!(multiply Null, I64(1));
         null_test!(multiply Null, F64(1.0));
diff --git a/src/data/value/unique_key.rs b/src/data/value/unique_key.rs
index f90eac66..ca50764d 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)),
+            Time(v) => Some(UniqueKey::Time(*v)),
             Interval(v) => Some(UniqueKey::Interval(*v)),
             Null => None,
             F64(_) => {
diff --git a/src/executor/aggregate/hash.rs b/src/executor/aggregate/hash.rs
index b387b16e..c2618b49 100644
--- a/src/executor/aggregate/hash.rs
+++ b/src/executor/aggregate/hash.rs
@@ -4,17 +4,21 @@ use {
         executor::evaluate::Evaluated,
         result::{Error, Result},
     },
-    chrono::{NaiveDate, NaiveDateTime},
-    std::convert::{TryFrom, TryInto},
+    chrono::{NaiveDate, NaiveDateTime, NaiveTime},
+    std::{
+        convert::{TryFrom, TryInto},
+        fmt::Debug,
+    },
 };
 
-#[derive(PartialEq, Eq, Hash, Clone, std::fmt::Debug)]
+#[derive(PartialEq, Eq, Hash, Clone, Debug)]
 pub enum GroupKey {
     I64(i64),
     Bool(bool),
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Time(NaiveTime),
     Interval(Interval),
     None,
 }
diff --git a/src/executor/alter/validate.rs b/src/executor/alter/validate.rs
index 7d919f58..7cc55c62 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::Time
             | DataType::Interval
     ) {
         return Err(AlterError::UnsupportedDataType(data_type.to_string()).into());
diff --git a/src/executor/validate.rs b/src/executor/validate.rs
index 81c57061..712b9b56 100644
--- a/src/executor/validate.rs
+++ b/src/executor/validate.rs
@@ -6,7 +6,7 @@ use {
         utils::Vector,
     },
     boolinator::Boolinator,
-    chrono::{NaiveDate, NaiveDateTime},
+    chrono::{NaiveDate, NaiveDateTime, NaiveTime},
     im_rc::HashSet,
     serde::Serialize,
     sqlparser::ast::{ColumnDef, ColumnOption, Ident},
@@ -35,6 +35,7 @@ pub enum UniqueKey {
     Str(String),
     Date(NaiveDate),
     Timestamp(NaiveDateTime),
+    Time(NaiveTime),
     Interval(Interval),
 }
 


Test patch:
diff --git a/src/tests/data_type/mod.rs b/src/tests/data_type/mod.rs
index 6b78e04f..19a08371 100644
--- a/src/tests/data_type/mod.rs
+++ b/src/tests/data_type/mod.rs
@@ -1,4 +1,5 @@
 pub mod date;
 pub mod interval;
 pub mod sql_types;
+pub mod time;
 pub mod timestamp;
diff --git a/src/tests/data_type/time.rs b/src/tests/data_type/time.rs
new file mode 100644
index 00000000..0b58012c
--- /dev/null
+++ b/src/tests/data_type/time.rs
@@ -0,0 +1,142 @@
+use crate::*;
+
+test_case!(time, async move {
+    run!(
+        r#"
+CREATE TABLE TimeLog (
+    id INTEGER,
+    time1 TIME,
+    time2 TIME,
+)"#
+    );
+
+    run!(
+        r#"
+INSERT INTO TimeLog VALUES
+    (1, "12:30:00", "13:31:01.123"),
+    (2, "9:2:1", "AM 08:02:01.001"),
+    (3, "PM 2:59", "9:00:00 AM");
+"#
+    );
+
+    use chrono::{NaiveDate, NaiveTime};
+    use Value::*;
+
+    let t = |h, m, s, ms| NaiveTime::from_hms_milli(h, m, s, ms);
+    let i = |h, m, s, ms| {
+        data::Interval::milliseconds(
+            (t(h, m, s, ms) - NaiveTime::from_hms(0, 0, 0)).num_milliseconds(),
+        )
+    };
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            1     t(12, 30, 0, 0)   t(13, 31, 1, 123);
+            2     t(9, 2, 1, 0)     t(8, 2, 1, 1);
+            3     t(14, 59, 0, 0)   t(9, 0, 0, 0)
+        )),
+        "SELECT id, time1, time2 FROM TimeLog;"
+    );
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            2     t(9, 2, 1, 0)     t(8, 2, 1, 1);
+            3     t(14, 59, 0, 0)   t(9, 0, 0, 0)
+        )),
+        "SELECT * FROM TimeLog WHERE time1 > time2"
+    );
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            1     t(12, 30, 0, 0)   t(13, 31, 1, 123)
+        )),
+        "SELECT * FROM TimeLog WHERE time1 <= time2"
+    );
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            3     t(14, 59, 0, 0)   t(9, 0, 0, 0)
+        )),
+        r#"SELECT * FROM TimeLog WHERE time1 = TIME "14:59:00""#
+    );
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            1     t(12, 30, 0, 0)   t(13, 31, 1, 123);
+            2     t(9, 2, 1, 0)     t(8, 2, 1, 1)
+        )),
+        r#"SELECT * FROM TimeLog WHERE time1 < "1:00 PM""#
+    );
+
+    test!(
+        Ok(select!(
+            id  | time1           | time2
+            I64 | Time            | Time;
+            1     t(12, 30, 0, 0)   t(13, 31, 1, 123);
+            2     t(9, 2, 1, 0)     t(8, 2, 1, 1);
+            3     t(14, 59, 0, 0)   t(9, 0, 0, 0)
+        )),
+        r#"SELECT * FROM TimeLog WHERE TIME "23:00:00.123" > "PM 1:00";"#
+    );
+
+    test!(
+        Ok(select!(
+            id  | time_sub                      | add             | sub
+            I64 | Interval                      | Time            | Time;
+            1     i(1, 1, 1, 123).unary_minus()   t(13, 30, 0, 0)    t(9, 21, 1, 123);
+            2     i(0, 59, 59, 999)               t(10, 2, 1, 0)     t(3, 52, 1, 1);
+            3     i(5, 59, 0, 0)                  t(15, 59, 0, 0)    t(4, 50, 0, 0)
+        )),
+        r#"SELECT
+            id,
+            time1 - time2 AS time_sub,
+            time1 + INTERVAL "1" HOUR AS add,
+            time2 - INTERVAL "250" MINUTE AS sub
+        FROM TimeLog;"#
+    );
+
+    test!(
+        Ok(select!(
+            id  | timestamp
+            I64 | Timestamp;
+            1     NaiveDate::from_ymd(2021, 1, 5).and_hms_milli(13, 31, 1, 123)
+        )),
+        r#"SELECT
+            id,
+            DATE "2021-01-05" + time2 AS timestamp
+        FROM TimeLog LIMIT 1;"#
+    );
+
+    test!(
+        Err(IntervalError::AddYearOrMonthToTime {
+            time: t(13, 31, 1, 123).to_string(),
+            interval: data::Interval::years(1).into(),
+        }
+        .into()),
+        r#"SELECT * FROM TimeLog WHERE time1 > time2 + INTERVAL "1" YEAR"#
+    );
+
+    test!(
+        Err(IntervalError::SubtractYearOrMonthToTime {
+            time: t(13, 31, 1, 123).to_string(),
+            interval: data::Interval::months(14).into(),
+        }
+        .into()),
+        r#"SELECT * FROM TimeLog WHERE time1 > time2 - INTERVAL "1-2" YEAR TO MONTH"#
+    );
+
+    test!(
+        Err(ValueError::FailedToParseTime("12345-678".to_owned()).into()),
+        r#"INSERT INTO TimeLog VALUES (1, "12345-678", "20:05:01")"#
+    );
+});
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 1f5e977c..66952317 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -82,6 +82,7 @@ macro_rules! generate_tests {
         glue!(sql_types, data_type::sql_types::sql_types);
         glue!(date, data_type::date::date);
         glue!(timestamp, data_type::timestamp::timestamp);
+        glue!(time, data_type::time::time);
         glue!(interval, data_type::interval::interval);
         glue!(synthesize, synthesize::synthesize);
         glue!(validate_unique, validate::unique::unique);