Repository: gluesql/gluesql
Pull request #169 (2021-03-10 14:43:25)

Replace Value::Opt- and Empty to Value::Null!
Remove `Value::OptBool`, `OptI64`, `OptF64`, `OptStr` and `Empty`.
Now all nullable values are changed to use `Value::Null`.


- **Fix null comparison - now `Value::Null != Value::Null`.**
Due to this change, I need to change `test!` macro to compare `Null` properly.
https://github.com/gluesql/gluesql/compare/add-null-value?expand=1#diff-0332555274be05c9e038aa9524f43b29f55c1b112aea18edcea3a1dbffb4c8cdR94-R165

- **Fix `update.rs` to use `TryFromLiteral` rather than `value.clone_by`.**
It was unavoidable because `Value::Null` cannot provide original type info.

resolve https://github.com/gluesql/gluesql/issues/166

@KyGost yeaaaaah 🚀🚀🚀🚀🚀




Patch:
diff --git a/src/data/value/ast_value.rs b/src/data/value/ast_value.rs
index 1c508bbe..ae346a28 100644
--- a/src/data/value/ast_value.rs
+++ b/src/data/value/ast_value.rs
@@ -8,30 +8,22 @@ use {
 impl PartialEq<AstValue> for Value {
     fn eq(&self, other: &AstValue) -> bool {
         match (self, other) {
-            (Value::Bool(l), AstValue::Boolean(r))
-            | (Value::OptBool(Some(l)), AstValue::Boolean(r)) => l == r,
-            (Value::I64(l), AstValue::Number(r, false))
-            | (Value::OptI64(Some(l)), AstValue::Number(r, false)) => match r.parse::<i64>() {
+            (Value::Bool(l), AstValue::Boolean(r)) => l == r,
+            (Value::I64(l), AstValue::Number(r, false)) => match r.parse::<i64>() {
                 Ok(r) => l == &r,
                 Err(_) => match r.parse::<f64>() {
                     Ok(r) => (*l as f64) == r,
                     Err(_) => false,
                 },
             },
-            (Value::F64(l), AstValue::Number(r, false))
-            | (Value::OptF64(Some(l)), AstValue::Number(r, false)) => match r.parse::<f64>() {
+            (Value::F64(l), AstValue::Number(r, false)) => match r.parse::<f64>() {
                 Ok(r) => l == &r,
                 Err(_) => match r.parse::<i64>() {
                     Ok(r) => *l == (r as f64),
                     Err(_) => false,
                 },
             },
-            (Value::Str(l), AstValue::SingleQuotedString(r))
-            | (Value::OptStr(Some(l)), AstValue::SingleQuotedString(r)) => l == r,
-            (Value::OptBool(None), AstValue::Null)
-            | (Value::OptI64(None), AstValue::Null)
-            | (Value::OptF64(None), AstValue::Null)
-            | (Value::OptStr(None), AstValue::Null) => true,
+            (Value::Str(l), AstValue::SingleQuotedString(r)) => l == r,
             _ => false,
         }
     }
@@ -40,24 +32,21 @@ impl PartialEq<AstValue> for Value {
 impl PartialOrd<AstValue> for Value {
     fn partial_cmp(&self, other: &AstValue) -> Option<Ordering> {
         match (self, other) {
-            (Value::I64(l), AstValue::Number(r, false))
-            | (Value::OptI64(Some(l)), AstValue::Number(r, false)) => match r.parse::<i64>() {
+            (Value::I64(l), AstValue::Number(r, false)) => match r.parse::<i64>() {
                 Ok(r) => Some(l.cmp(&r)),
                 Err(_) => match r.parse::<f64>() {
                     Ok(r) => (*l as f64).partial_cmp(&r),
                     Err(_) => None,
                 },
             },
-            (Value::F64(l), AstValue::Number(r, false))
-            | (Value::OptF64(Some(l)), AstValue::Number(r, false)) => match r.parse::<f64>() {
+            (Value::F64(l), AstValue::Number(r, false)) => match r.parse::<f64>() {
                 Ok(r) => l.partial_cmp(&r),
                 Err(_) => match r.parse::<i64>() {
                     Ok(r) => l.partial_cmp(&(r as f64)),
                     Err(_) => None,
                 },
             },
-            (Value::Str(l), AstValue::SingleQuotedString(r))
-            | (Value::OptStr(Some(l)), AstValue::SingleQuotedString(r)) => Some(l.cmp(r)),
+            (Value::Str(l), AstValue::SingleQuotedString(r)) => Some(l.cmp(r)),
             _ => None,
         }
     }
@@ -92,7 +81,6 @@ impl TryFromLiteral for Value {
                 "FALSE" | "0" => Ok(Value::Bool(false)),
                 _ => Err(ValueError::LiteralCastToBooleanFailed(v.to_string()).into()),
             },
-            (DataType::Boolean, AstValue::Null) => Ok(Value::OptBool(None)),
             (DataType::Int, AstValue::SingleQuotedString(v)) => v
                 .parse::<i64>()
                 .map(Value::I64)
@@ -108,7 +96,6 @@ impl TryFromLiteral for Value {
 
                 Ok(Value::I64(v))
             }
-            (DataType::Int, AstValue::Null) => Ok(Value::OptI64(None)),
             (DataType::Float(_), AstValue::SingleQuotedString(v))
             | (DataType::Float(_), AstValue::Number(v, false)) => v
                 .parse::<f64>()
@@ -119,14 +106,16 @@ impl TryFromLiteral for Value {
 
                 Ok(Value::F64(v))
             }
-            (DataType::Float(_), AstValue::Null) => Ok(Value::OptF64(None)),
             (DataType::Text, AstValue::Number(v, false)) => Ok(Value::Str(v.to_string())),
             (DataType::Text, AstValue::Boolean(v)) => {
                 let v = if *v { "TRUE" } else { "FALSE" };
 
                 Ok(Value::Str(v.to_owned()))
             }
-            (DataType::Text, AstValue::Null) => Ok(Value::OptStr(None)),
+            (DataType::Boolean, AstValue::Null)
+            | (DataType::Int, AstValue::Null)
+            | (DataType::Float(_), AstValue::Null)
+            | (DataType::Text, AstValue::Null) => Ok(Value::Null),
             _ => Err(ValueError::UnimplementedLiteralCast {
                 data_type: data_type.to_string(),
                 literal: literal.to_string(),
diff --git a/src/data/value/group_key.rs b/src/data/value/group_key.rs
index 80df4605..566a936b 100644
--- a/src/data/value/group_key.rs
+++ b/src/data/value/group_key.rs
@@ -14,11 +14,11 @@ impl TryInto<GroupKey> for &Value {
         use Value::*;
 
         match self {
-            Bool(v) | OptBool(Some(v)) => Ok(GroupKey::Bool(*v)),
-            I64(v) | OptI64(Some(v)) => Ok(GroupKey::I64(*v)),
-            Str(v) | OptStr(Some(v)) => Ok(GroupKey::Str(v.clone())),
-            Empty | OptBool(None) | OptI64(None) | OptStr(None) => Ok(GroupKey::Null),
-            F64(_) | OptF64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
+            Bool(v) => Ok(GroupKey::Bool(*v)),
+            I64(v) => Ok(GroupKey::I64(*v)),
+            Str(v) => Ok(GroupKey::Str(v.clone())),
+            Null => Ok(GroupKey::None),
+            F64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
         }
     }
 }
@@ -30,11 +30,11 @@ impl TryInto<GroupKey> for Value {
         use Value::*;
 
         match self {
-            Bool(v) | OptBool(Some(v)) => Ok(GroupKey::Bool(v)),
-            I64(v) | OptI64(Some(v)) => Ok(GroupKey::I64(v)),
-            Str(v) | OptStr(Some(v)) => Ok(GroupKey::Str(v)),
-            Empty | OptBool(None) | OptI64(None) | OptStr(None) => Ok(GroupKey::Null),
-            F64(_) | OptF64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
+            Bool(v) => Ok(GroupKey::Bool(v)),
+            I64(v) => Ok(GroupKey::I64(v)),
+            Str(v) => Ok(GroupKey::Str(v)),
+            Null => Ok(GroupKey::None),
+            F64(_) => Err(ValueError::FloatCannotBeGroupedBy.into()),
         }
     }
 }
diff --git a/src/data/value/mod.rs b/src/data/value/mod.rs
index 690da91e..e18a7445 100644
--- a/src/data/value/mod.rs
+++ b/src/data/value/mod.rs
@@ -20,37 +20,16 @@ pub enum Value {
     I64(i64),
     F64(f64),
     Str(String),
-    OptBool(Option<bool>),
-    OptI64(Option<i64>),
-    OptF64(Option<f64>),
-    OptStr(Option<String>),
-    Empty,
+    Null,
 }
 
 impl PartialEq<Value> for Value {
     fn eq(&self, other: &Value) -> bool {
         match (self, other) {
-            (Value::Bool(l), Value::Bool(r))
-            | (Value::OptBool(Some(l)), Value::Bool(r))
-            | (Value::Bool(l), Value::OptBool(Some(r)))
-            | (Value::OptBool(Some(l)), Value::OptBool(Some(r))) => l == r,
-            (Value::I64(l), Value::I64(r))
-            | (Value::OptI64(Some(l)), Value::I64(r))
-            | (Value::I64(l), Value::OptI64(Some(r)))
-            | (Value::OptI64(Some(l)), Value::OptI64(Some(r))) => l == r,
-            (Value::F64(l), Value::F64(r))
-            | (Value::OptF64(Some(l)), Value::F64(r))
-            | (Value::F64(l), Value::OptF64(Some(r)))
-            | (Value::OptF64(Some(l)), Value::OptF64(Some(r))) => l == r,
-            (Value::Str(l), Value::Str(r))
-            | (Value::OptStr(Some(l)), Value::Str(r))
-            | (Value::Str(l), Value::OptStr(Some(r)))
-            | (Value::OptStr(Some(l)), Value::OptStr(Some(r))) => l == r,
-            (Value::OptBool(None), Value::OptBool(None))
-            | (Value::OptI64(None), Value::OptI64(None))
-            | (Value::OptF64(None), Value::OptF64(None))
-            | (Value::OptStr(None), Value::OptStr(None))
-            | (Value::Empty, Value::Empty) => true,
+            (Value::Bool(l), Value::Bool(r)) => l == r,
+            (Value::I64(l), Value::I64(r)) => l == r,
+            (Value::F64(l), Value::F64(r)) => l == r,
+            (Value::Str(l), Value::Str(r)) => l == r,
             _ => false,
         }
     }
@@ -59,30 +38,12 @@ impl PartialEq<Value> for Value {
 impl PartialOrd<Value> for Value {
     fn partial_cmp(&self, other: &Value) -> Option<Ordering> {
         match (self, other) {
-            (Value::Bool(l), Value::Bool(r))
-            | (Value::OptBool(Some(l)), Value::Bool(r))
-            | (Value::Bool(l), Value::OptBool(Some(r)))
-            | (Value::OptBool(Some(l)), Value::OptBool(Some(r))) => Some(l.cmp(r)),
-            (Value::I64(l), Value::I64(r))
-            | (Value::OptI64(Some(l)), Value::I64(r))
-            | (Value::I64(l), Value::OptI64(Some(r)))
-            | (Value::OptI64(Some(l)), Value::OptI64(Some(r))) => Some(l.cmp(r)),
-            (Value::I64(l), Value::F64(r))
-            | (Value::OptI64(Some(l)), Value::F64(r))
-            | (Value::I64(l), Value::OptF64(Some(r)))
-            | (Value::OptI64(Some(l)), Value::OptF64(Some(r))) => (*l as f64).partial_cmp(r),
-            (Value::F64(l), Value::F64(r))
-            | (Value::OptF64(Some(l)), Value::F64(r))
-            | (Value::F64(l), Value::OptF64(Some(r)))
-            | (Value::OptF64(Some(l)), Value::OptF64(Some(r))) => l.partial_cmp(r),
-            (Value::F64(l), Value::I64(r))
-            | (Value::OptF64(Some(l)), Value::I64(r))
-            | (Value::F64(l), Value::OptI64(Some(r)))
-            | (Value::OptF64(Some(l)), Value::OptI64(Some(r))) => l.partial_cmp(&(*r as f64)),
-            (Value::Str(l), Value::Str(r))
-            | (Value::OptStr(Some(l)), Value::Str(r))
-            | (Value::Str(l), Value::OptStr(Some(r)))
-            | (Value::OptStr(Some(l)), Value::OptStr(Some(r))) => Some(l.cmp(r)),
+            (Value::Bool(l), Value::Bool(r)) => Some(l.cmp(r)),
+            (Value::I64(l), Value::I64(r)) => Some(l.cmp(r)),
+            (Value::I64(l), Value::F64(r)) => (*l as f64).partial_cmp(r),
+            (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)),
             _ => None,
         }
     }
@@ -116,17 +77,13 @@ impl Value {
         matches!(
             (data_type, self),
             (DataType::Boolean, Value::Bool(_))
-                | (DataType::Boolean, Value::OptBool(Some(_)))
-                | (DataType::Text, Value::Str(_))
-                | (DataType::Text, Value::OptStr(Some(_)))
                 | (DataType::Int, Value::I64(_))
-                | (DataType::Int, Value::OptI64(Some(_)))
                 | (DataType::Float(_), Value::F64(_))
-                | (DataType::Float(_), Value::OptF64(Some(_)))
-                | (_, Value::OptBool(None))
-                | (_, Value::OptStr(None))
-                | (_, Value::OptI64(None))
-                | (_, Value::OptF64(None))
+                | (DataType::Text, Value::Str(_))
+                | (DataType::Boolean, Value::Null)
+                | (DataType::Int, Value::Null)
+                | (DataType::Float(_), Value::Null)
+                | (DataType::Text, Value::Null)
         )
     }
 
@@ -138,34 +95,20 @@ impl Value {
         match (data_type, literal) {
             (DataType::Int, AstValue::Number(v, false)) => v
                 .parse()
-                .map(|v| nullable.into_value(Value::OptI64(Some(v)), Value::I64(v)))
+                .map(Value::I64)
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
             (DataType::Float(_), AstValue::Number(v, false)) => v
                 .parse()
-                .map(|v| nullable.into_value(Value::OptF64(Some(v)), Value::F64(v)))
+                .map(Value::F64)
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
-            (DataType::Boolean, AstValue::Boolean(v)) => {
-                Ok(nullable.into_value(Value::OptBool(Some(*v)), Value::Bool(*v)))
-            }
-            (DataType::Text, AstValue::SingleQuotedString(v)) => {
-                Ok(nullable.into_value(Value::OptStr(Some(v.clone())), Value::Str(v.clone())))
+            (DataType::Boolean, AstValue::Boolean(v)) => Ok(Value::Bool(*v)),
+            (DataType::Text, AstValue::SingleQuotedString(v)) => Ok(Value::Str(v.clone())),
+            (DataType::Int, AstValue::Null)
+            | (DataType::Float(_), AstValue::Null)
+            | (DataType::Boolean, AstValue::Null)
+            | (DataType::Text, AstValue::Null) => {
+                nullable.as_result(Value::Null, ValueError::NullValueOnNotNullField.into())
             }
-            (DataType::Int, AstValue::Null) => nullable.as_result(
-                Value::OptI64(None),
-                ValueError::NullValueOnNotNullField.into(),
-            ),
-            (DataType::Float(_), AstValue::Null) => nullable.as_result(
-                Value::OptF64(None),
-                ValueError::NullValueOnNotNullField.into(),
-            ),
-            (DataType::Boolean, AstValue::Null) => nullable.as_result(
-                Value::OptBool(None),
-                ValueError::NullValueOnNotNullField.into(),
-            ),
-            (DataType::Text, AstValue::Null) => nullable.as_result(
-                Value::OptStr(None),
-                ValueError::NullValueOnNotNullField.into(),
-            ),
             _ => Err(ValueError::SqlTypeNotSupported.into()),
         }
     }
@@ -174,13 +117,15 @@ impl Value {
         match (data_type, self) {
             // Same as
             (DataType::Boolean, Value::Bool(_))
-            | (DataType::Boolean, Value::OptBool(_))
-            | (DataType::Text, Value::Str(_))
-            | (DataType::Text, Value::OptStr(_))
             | (DataType::Int, Value::I64(_))
-            | (DataType::Int, Value::OptI64(_))
             | (DataType::Float(_), Value::F64(_))
-            | (DataType::Float(_), Value::OptF64(_)) => Ok(self.clone()),
+            | (DataType::Text, Value::Str(_)) => Ok(self.clone()),
+
+            // Null
+            (DataType::Boolean, Value::Null)
+            | (DataType::Int, Value::Null)
+            | (DataType::Float(_), Value::Null)
+            | (DataType::Text, Value::Null) => Ok(Value::Null),
 
             // Boolean
             (DataType::Boolean, Value::Str(value)) => match value.to_uppercase().as_str() {
@@ -188,23 +133,11 @@ impl Value {
                 "FALSE" => Ok(Value::Bool(false)),
                 _ => Err(ValueError::ImpossibleCast.into()),
             },
-            (DataType::Boolean, Value::OptStr(Some(value))) => {
-                match value.to_uppercase().as_str() {
-                    "TRUE" => Ok(Value::OptBool(Some(true))),
-                    "FALSE" => Ok(Value::OptBool(Some(false))),
-                    _ => Err(ValueError::ImpossibleCast.into()),
-                }
-            }
             (DataType::Boolean, Value::I64(value)) => match value {
                 1 => Ok(Value::Bool(true)),
                 0 => Ok(Value::Bool(false)),
                 _ => Err(ValueError::ImpossibleCast.into()),
             },
-            (DataType::Boolean, Value::OptI64(Some(value))) => match value {
-                1 => Ok(Value::OptBool(Some(true))),
-                0 => Ok(Value::OptBool(Some(false))),
-                _ => Err(ValueError::ImpossibleCast.into()),
-            },
             (DataType::Boolean, Value::F64(value)) => {
                 if value.eq(&1.0) {
                     Ok(Value::Bool(true))
@@ -214,83 +147,31 @@ impl Value {
                     Err(ValueError::ImpossibleCast.into())
                 }
             }
-            (DataType::Boolean, Value::OptF64(Some(value))) => {
-                if value.eq(&1.0) {
-                    Ok(Value::OptBool(Some(true)))
-                } else if value.eq(&0.0) {
-                    Ok(Value::OptBool(Some(false)))
-                } else {
-                    Err(ValueError::ImpossibleCast.into())
-                }
-            }
-            (DataType::Boolean, Value::OptI64(None))
-            | (DataType::Boolean, Value::OptF64(None))
-            | (DataType::Boolean, Value::OptStr(None)) => Ok(Value::OptBool(None)),
 
             // Integer
             (DataType::Int, Value::Bool(value)) => Ok(Value::I64(if *value { 1 } else { 0 })),
-            (DataType::Int, Value::OptBool(Some(value))) => {
-                Ok(Value::OptI64(Some(if *value { 1 } else { 0 })))
-            }
             (DataType::Int, Value::F64(value)) => Ok(Value::I64(value.trunc() as i64)),
-            (DataType::Int, Value::OptF64(Some(value))) => {
-                Ok(Value::OptI64(Some(value.trunc() as i64)))
-            }
             (DataType::Int, Value::Str(value)) => value
                 .parse::<i64>()
                 .map(Value::I64)
                 .map_err(|_| ValueError::ImpossibleCast.into()),
-            (DataType::Int, Value::OptStr(Some(value))) => value
-                .parse::<i64>()
-                .map(Some)
-                .map(Value::OptI64)
-                .map_err(|_| ValueError::ImpossibleCast.into()),
-            (DataType::Int, Value::OptBool(None))
-            | (DataType::Int, Value::OptF64(None))
-            | (DataType::Int, Value::OptStr(None)) => Ok(Value::OptI64(None)),
 
             // Float
             (DataType::Float(_), Value::Bool(value)) => {
                 Ok(Value::F64(if *value { 1.0 } else { 0.0 }))
             }
-            (DataType::Float(_), Value::OptBool(Some(value))) => {
-                Ok(Value::OptF64(Some(if *value { 1.0 } else { 0.0 })))
-            }
             (DataType::Float(_), Value::I64(value)) => Ok(Value::F64((*value as f64).trunc())),
-            (DataType::Float(_), Value::OptI64(Some(value))) => {
-                Ok(Value::OptF64(Some((*value as f64).trunc())))
-            }
             (DataType::Float(_), Value::Str(value)) => value
                 .parse::<f64>()
                 .map(Value::F64)
                 .map_err(|_| ValueError::ImpossibleCast.into()),
-            (DataType::Float(_), Value::OptStr(Some(value))) => value
-                .parse::<f64>()
-                .map(Some)
-                .map(Value::OptF64)
-                .map_err(|_| ValueError::ImpossibleCast.into()),
-            (DataType::Float(_), Value::OptBool(None))
-            | (DataType::Float(_), Value::OptI64(None))
-            | (DataType::Float(_), Value::OptStr(None)) => Ok(Value::OptF64(None)),
 
             // Text
             (DataType::Text, Value::Bool(value)) => Ok(Value::Str(
                 (if *value { "TRUE" } else { "FALSE" }).to_string(),
             )),
-            (DataType::Text, Value::OptBool(Some(value))) => Ok(Value::OptStr(Some(
-                (if *value { "TRUE" } else { "FALSE" }).to_string(),
-            ))),
             (DataType::Text, Value::I64(value)) => Ok(Value::Str(value.to_string())),
-            (DataType::Text, Value::OptI64(Some(value))) => {
-                Ok(Value::OptStr(Some(value.to_string())))
-            }
             (DataType::Text, Value::F64(value)) => Ok(Value::Str(value.to_string())),
-            (DataType::Text, Value::OptF64(Some(value))) => {
-                Ok(Value::OptStr(Some(value.to_string())))
-            }
-            (DataType::Text, Value::OptBool(None))
-            | (DataType::Text, Value::OptI64(None))
-            | (DataType::Text, Value::OptF64(None)) => Ok(Value::OptStr(None)),
 
             _ => Err(ValueError::UnimplementedCast.into()),
         }
@@ -302,28 +183,20 @@ impl Value {
                 .parse()
                 .map(Value::I64)
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
-            (Value::OptI64(_), AstValue::Number(v, false)) => v
-                .parse()
-                .map(|v| Value::OptI64(Some(v)))
-                .map_err(|_| ValueError::FailedToParseNumber.into()),
-            (Value::OptI64(_), AstValue::Null) => Ok(Value::OptI64(None)),
             (Value::F64(_), AstValue::Number(v, false)) => v
                 .parse()
                 .map(Value::F64)
                 .map_err(|_| ValueError::FailedToParseNumber.into()),
-            (Value::OptF64(_), AstValue::Number(v, false)) => v
-                .parse()
-                .map(|v| Value::OptF64(Some(v)))
-                .map_err(|_| ValueError::FailedToParseNumber.into()),
-            (Value::OptF64(_), AstValue::Null) => Ok(Value::OptF64(None)),
-            (Value::Str(_), AstValue::SingleQuotedString(v)) => Ok(Value::Str(v.clone())),
-            (Value::OptStr(_), AstValue::SingleQuotedString(v)) => {
-                Ok(Value::OptStr(Some(v.clone())))
+            (Value::Str(_), AstValue::SingleQuotedString(v))
+            | (Value::Null, AstValue::SingleQuotedString(v)) => Ok(Value::Str(v.clone())),
+            (Value::Bool(_), AstValue::Boolean(v)) | (Value::Null, AstValue::Boolean(v)) => {
+                Ok(Value::Bool(*v))
             }
-            (Value::OptStr(_), AstValue::Null) => Ok(Value::OptStr(None)),
-            (Value::Bool(_), AstValue::Boolean(v)) => Ok(Value::Bool(*v)),
-            (Value::OptBool(_), AstValue::Boolean(v)) => Ok(Value::OptBool(Some(*v))),
-            (Value::OptBool(_), AstValue::Null) => Ok(Value::OptBool(None)),
+            (Value::Null, AstValue::Number(v, false)) => v
+                .parse::<i64>()
+                .map_or_else(|_| v.parse::<f64>().map(Value::F64), |v| Ok(Value::I64(v)))
+                .map_err(|_| ValueError::FailedToParseNumber.into()),
+            (_, AstValue::Null) => Ok(Value::Null),
             _ => Err(ValueError::LiteralNotSupported.into()),
         }
     }
@@ -333,18 +206,8 @@ impl Value {
 
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a + b)),
-            (I64(a), OptI64(Some(b))) | (OptI64(Some(a)), I64(b)) => Ok(OptI64(Some(a + b))),
-            (OptI64(Some(a)), OptI64(Some(b))) => Ok(OptI64(Some(a + b))),
             (F64(a), F64(b)) => Ok(F64(a + b)),
-            (F64(a), OptF64(Some(b))) | (OptF64(Some(a)), F64(b)) => Ok(OptF64(Some(a + b))),
-            (OptI64(None), OptI64(_))
-            | (OptI64(_), OptI64(None))
-            | (OptI64(None), I64(_))
-            | (I64(_), OptI64(None)) => Ok(OptI64(None)),
-            (OptF64(_), OptF64(None))
-            | (OptF64(None), OptF64(_))
-            | (F64(_), OptF64(None))
-            | (OptF64(None), F64(_)) => Ok(OptF64(None)),
+            (Null, _) | (_, Null) => Ok(Null),
             _ => Err(ValueError::AddOnNonNumeric.into()),
         }
     }
@@ -354,17 +217,8 @@ impl Value {
 
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a - b)),
-            (I64(a), OptI64(Some(b))) | (OptI64(Some(a)), I64(b)) => Ok(OptI64(Some(a - b))),
             (F64(a), F64(b)) => Ok(F64(a - b)),
-            (F64(a), OptF64(Some(b))) | (OptF64(Some(a)), F64(b)) => Ok(OptF64(Some(a - b))),
-            (OptI64(None), OptI64(_))
-            | (OptI64(_), OptI64(None))
-            | (OptI64(None), I64(_))
-            | (I64(_), OptI64(None)) => Ok(OptI64(None)),
-            (OptF64(_), OptF64(None))
-            | (OptF64(None), OptF64(_))
-            | (F64(_), OptF64(None))
-            | (OptF64(None), F64(_)) => Ok(OptF64(None)),
+            (Null, _) | (_, Null) => Ok(Null),
             _ => Err(ValueError::SubtractOnNonNumeric.into()),
         }
     }
@@ -374,17 +228,8 @@ impl Value {
 
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a * b)),
-            (I64(a), OptI64(Some(b))) | (OptI64(Some(a)), I64(b)) => Ok(OptI64(Some(a * b))),
             (F64(a), F64(b)) => Ok(F64(a * b)),
-            (F64(a), OptF64(Some(b))) | (OptF64(Some(a)), F64(b)) => Ok(OptF64(Some(a * b))),
-            (OptI64(None), OptI64(_))
-            | (OptI64(_), OptI64(None))
-            | (OptI64(None), I64(_))
-            | (I64(_), OptI64(None)) => Ok(OptI64(None)),
-            (OptF64(_), OptF64(None))
-            | (OptF64(None), OptF64(_))
-            | (F64(_), OptF64(None))
-            | (OptF64(None), F64(_)) => Ok(OptF64(None)),
+            (Null, _) | (_, Null) => Ok(Null),
             _ => Err(ValueError::MultiplyOnNonNumeric.into()),
         }
     }
@@ -394,17 +239,8 @@ impl Value {
 
         match (self, other) {
             (I64(a), I64(b)) => Ok(I64(a / b)),
-            (I64(a), OptI64(Some(b))) | (OptI64(Some(a)), I64(b)) => Ok(OptI64(Some(a / b))),
             (F64(a), F64(b)) => Ok(F64(a / b)),
-            (F64(a), OptF64(Some(b))) | (OptF64(Some(a)), F64(b)) => Ok(OptF64(Some(a / b))),
-            (OptI64(None), OptI64(_))
-            | (OptI64(_), OptI64(None))
-            | (OptI64(None), I64(_))
-            | (I64(_), OptI64(None)) => Ok(OptI64(None)),
-            (OptF64(_), OptF64(None))
-            | (OptF64(None), OptF64(_))
-            | (F64(_), OptF64(None))
-            | (OptF64(None), F64(_)) => Ok(OptF64(None)),
+            (Null, _) | (_, Null) => Ok(Null),
             _ => Err(ValueError::DivideOnNonNumeric.into()),
         }
     }
@@ -412,17 +248,15 @@ impl Value {
     pub fn is_some(&self) -> bool {
         use Value::*;
 
-        !matches!(
-            self,
-            Empty | OptBool(None) | OptI64(None) | OptF64(None) | OptStr(None)
-        )
+        !matches!(self, Null)
     }
 
     pub fn unary_plus(&self) -> Result<Value> {
         use Value::*;
 
         match self {
-            I64(_) | OptI64(_) | F64(_) | OptF64(_) => Ok(self.clone()),
+            I64(_) | F64(_) => Ok(self.clone()),
+            Null => Ok(Null),
             _ => Err(ValueError::UnaryPlusOnNonNumeric.into()),
         }
     }
@@ -432,11 +266,8 @@ impl Value {
 
         match self {
             I64(a) => Ok(I64(-a)),
-            OptI64(Some(a)) => Ok(OptI64(Some(-a))),
             F64(a) => Ok(F64(-a)),
-            OptF64(Some(a)) => Ok(OptF64(Some(-a))),
-            OptI64(None) => Ok(OptI64(None)),
-            OptF64(None) => Ok(OptF64(None)),
+            Null => Ok(Null),
             _ => Err(ValueError::UnaryMinusOnNonNumeric.into()),
         }
     }
@@ -444,105 +275,68 @@ impl Value {
 
 #[cfg(test)]
 mod tests {
-    use super::Value;
+    use super::Value::*;
 
     #[test]
     fn eq() {
-        assert_eq!(Value::Bool(true), Value::OptBool(Some(true)));
-        assert_eq!(Value::OptBool(Some(false)), Value::Bool(false));
-        assert_eq!(Value::OptBool(None), Value::OptBool(None));
-
-        assert_eq!(Value::I64(1), Value::OptI64(Some(1)));
-        assert_eq!(Value::OptI64(Some(1)), Value::I64(1));
-        assert_eq!(Value::OptI64(None), Value::OptI64(None));
-
-        assert_eq!(Value::F64(6.11), Value::OptF64(Some(6.11)));
-        assert_eq!(Value::OptF64(Some(6.11)), Value::F64(6.11));
-        assert_eq!(Value::OptF64(None), Value::OptF64(None));
-
-        let glue = || "Glue".to_owned();
-
-        assert_eq!(Value::Str(glue()), Value::OptStr(Some(glue())));
-        assert_eq!(Value::OptStr(Some(glue())), Value::Str(glue()));
-        assert_eq!(Value::OptStr(None), Value::OptStr(None));
-
-        assert_eq!(Value::Empty, Value::Empty);
+        assert_ne!(Null, Null);
+        assert_eq!(Bool(true), Bool(true));
+        assert_eq!(I64(1), I64(1));
+        assert_eq!(F64(6.11), F64(6.11));
+        assert_eq!(Str("Glue".to_owned()), Str("Glue".to_owned()));
     }
 
     #[test]
     fn cast() {
-        use {sqlparser::ast::DataType::*, Value::*};
+        use sqlparser::ast::DataType::*;
 
         macro_rules! cast {
-            ($input: expr => $data_type: expr, $output: expr) => {
-                assert_eq!($input.cast(&$data_type), Ok($output));
+            ($input: expr => $data_type: expr, $expected: expr) => {
+                let found = $input.cast(&$data_type).unwrap();
+
+                match ($expected, found) {
+                    (Null, Null) => {}
+                    (expected, found) => {
+                        assert_eq!(expected, found);
+                    }
+                }
             };
         }
 
         // Same as
-        cast!(Bool(true)                    => Boolean      , Bool(true));
-        cast!(OptBool(Some(true))           => Boolean      , OptBool(Some(true)));
-        cast!(Str("a".to_owned())           => Text         , Str("a".to_owned()));
-        cast!(OptStr(Some("a".to_owned()))  => Text         , OptStr(Some("a".to_owned())));
-        cast!(I64(1)                        => Int          , I64(1));
-        cast!(OptI64(Some(1))               => Int          , OptI64(Some(1)));
-        cast!(F64(1.0)                      => Float(None)  , F64(1.0));
-        cast!(OptF64(Some(1.0))             => Float(None)  , OptF64(Some(1.0)));
+        cast!(Bool(true)            => Boolean      , Bool(true));
+        cast!(Str("a".to_owned())   => Text         , Str("a".to_owned()));
+        cast!(I64(1)                => Int          , I64(1));
+        cast!(F64(1.0)              => Float(None)  , F64(1.0));
 
         // Boolean
-        cast!(Str("TRUE".to_owned())            => Boolean, Bool(true));
-        cast!(Str("FALSE".to_owned())           => Boolean, Bool(false));
-        cast!(OptStr(Some("TRUE".to_owned()))   => Boolean, OptBool(Some(true)));
-        cast!(OptStr(Some("FALSE".to_owned()))  => Boolean, OptBool(Some(false)));
-        cast!(I64(1)                            => Boolean, Bool(true));
-        cast!(I64(0)                            => Boolean, Bool(false));
-        cast!(OptI64(Some(1))                   => Boolean, OptBool(Some(true)));
-        cast!(OptI64(Some(0))                   => Boolean, OptBool(Some(false)));
-        cast!(F64(1.0)                          => Boolean, Bool(true));
-        cast!(F64(0.0)                          => Boolean, Bool(false));
-        cast!(OptF64(Some(1.0))                 => Boolean, OptBool(Some(true)));
-        cast!(OptF64(Some(0.0))                 => Boolean, OptBool(Some(false)));
-        cast!(OptI64(None)                      => Boolean, OptBool(None));
-        cast!(OptF64(None)                      => Boolean, OptBool(None));
-        cast!(OptStr(None)                      => Boolean, OptBool(None));
+        cast!(Str("TRUE".to_owned())    => Boolean, Bool(true));
+        cast!(Str("FALSE".to_owned())   => Boolean, Bool(false));
+        cast!(I64(1)                    => Boolean, Bool(true));
+        cast!(I64(0)                    => Boolean, Bool(false));
+        cast!(F64(1.0)                  => Boolean, Bool(true));
+        cast!(F64(0.0)                  => Boolean, Bool(false));
+        cast!(Null                      => Boolean, Null);
 
         // Integer
-        cast!(Bool(true)                    => Int, I64(1));
-        cast!(Bool(false)                   => Int, I64(0));
-        cast!(OptBool(Some(true))           => Int, OptI64(Some(1)));
-        cast!(OptBool(Some(false))          => Int, OptI64(Some(0)));
-        cast!(F64(1.1)                      => Int, I64(1));
-        cast!(OptF64(Some(1.1))             => Int, OptI64(Some(1)));
-        cast!(Str("11".to_owned())          => Int, I64(11));
-        cast!(OptStr(Some("11".to_owned())) => Int, OptI64(Some(11)));
-        cast!(OptBool(None)                 => Int, OptI64(None));
-        cast!(OptF64(None)                  => Int, OptI64(None));
-        cast!(OptStr(None)                  => Int, OptI64(None));
+        cast!(Bool(true)            => Int, I64(1));
+        cast!(Bool(false)           => Int, I64(0));
+        cast!(F64(1.1)              => Int, I64(1));
+        cast!(Str("11".to_owned())  => Int, I64(11));
+        cast!(Null                  => Int, Null);
 
         // Float
-        cast!(Bool(true)                    => Float(None), F64(1.0));
-        cast!(Bool(false)                   => Float(None), F64(0.0));
-        cast!(OptBool(Some(true))           => Float(None), OptF64(Some(1.0)));
-        cast!(OptBool(Some(false))          => Float(None), OptF64(Some(0.0)));
-        cast!(I64(1)                        => Float(None), F64(1.0));
-        cast!(OptI64(Some(1))               => Float(None), OptF64(Some(1.0)));
-        cast!(Str("11".to_owned())          => Float(None), F64(11.0));
-        cast!(OptStr(Some("11".to_owned())) => Float(None), OptF64(Some(11.0)));
-        cast!(OptBool(None)                 => Float(None), OptF64(None));
-        cast!(OptI64(None)                  => Float(None), OptF64(None));
-        cast!(OptStr(None)                  => Float(None), OptF64(None));
+        cast!(Bool(true)            => Float(None), F64(1.0));
+        cast!(Bool(false)           => Float(None), F64(0.0));
+        cast!(I64(1)                => Float(None), F64(1.0));
+        cast!(Str("11".to_owned())  => Float(None), F64(11.0));
+        cast!(Null                  => Float(None), Null);
 
         // Text
-        cast!(Bool(true)            => Text, Str("TRUE".to_owned()));
-        cast!(Bool(false)           => Text, Str("FALSE".to_owned()));
-        cast!(OptBool(Some(true))   => Text, OptStr(Some("TRUE".to_owned())));
-        cast!(OptBool(Some(false))  => Text, OptStr(Some("FALSE".to_owned())));
-        cast!(I64(11)               => Text, Str("11".to_owned()));
-        cast!(OptI64(Some(11))      => Text, OptStr(Some("11".to_owned())));
-        cast!(F64(1.0)              => Text, Str("1".to_owned()));
-        cast!(OptF64(Some(1.0))     => Text, OptStr(Some("1".to_owned())));
-        cast!(OptBool(None)         => Text, OptStr(None));
-        cast!(OptI64(None)          => Text, OptStr(None));
-        cast!(OptF64(None)          => Text, OptStr(None));
+        cast!(Bool(true)    => Text, Str("TRUE".to_owned()));
+        cast!(Bool(false)   => Text, Str("FALSE".to_owned()));
+        cast!(I64(11)       => Text, Str("11".to_owned()));
+        cast!(F64(1.0)      => Text, Str("1".to_owned()));
+        cast!(Null          => Text, Null);
     }
 }
diff --git a/src/data/value/unique_key.rs b/src/data/value/unique_key.rs
index 63ac814e..6f8e5824 100644
--- a/src/data/value/unique_key.rs
+++ b/src/data/value/unique_key.rs
@@ -7,18 +7,22 @@ use {
     std::convert::TryInto,
 };
 
-impl TryInto<UniqueKey> for &Value {
+impl TryInto<Option<UniqueKey>> for &Value {
     type Error = Error;
 
-    fn try_into(self) -> Result<UniqueKey> {
+    fn try_into(self) -> Result<Option<UniqueKey>> {
         use Value::*;
 
-        match self {
-            Bool(v) | OptBool(Some(v)) => Ok(UniqueKey::Bool(*v)),
-            I64(v) | OptI64(Some(v)) => Ok(UniqueKey::I64(*v)),
-            Str(v) | OptStr(Some(v)) => Ok(UniqueKey::Str(v.clone())),
-            Empty | OptBool(None) | OptI64(None) | OptStr(None) => Ok(UniqueKey::Null),
-            F64(_) | OptF64(_) => Err(ValueError::ConflictOnFloatWithUniqueConstraint.into()),
-        }
+        let unique_key = match self {
+            Bool(v) => Some(UniqueKey::Bool(*v)),
+            I64(v) => Some(UniqueKey::I64(*v)),
+            Str(v) => Some(UniqueKey::Str(v.clone())),
+            Null => None,
+            F64(_) => {
+                return Err(ValueError::ConflictOnFloatWithUniqueConstraint.into());
+            }
+        };
+
+        Ok(unique_key)
     }
 }
diff --git a/src/executor/aggregate/hash.rs b/src/executor/aggregate/hash.rs
index 7c210c72..4845966d 100644
--- a/src/executor/aggregate/hash.rs
+++ b/src/executor/aggregate/hash.rs
@@ -9,7 +9,7 @@ pub enum GroupKey {
     I64(i64),
     Bool(bool),
     Str(String),
-    Null,
+    None,
 }
 
 impl TryFrom<&Evaluated<'_>> for GroupKey {
diff --git a/src/executor/aggregate/state.rs b/src/executor/aggregate/state.rs
index 805377b1..c7c69578 100644
--- a/src/executor/aggregate/state.rs
+++ b/src/executor/aggregate/state.rs
@@ -27,7 +27,7 @@ impl<'a> State<'a> {
     pub fn new() -> Self {
         State {
             index: 0,
-            group: Rc::new(vec![GroupKey::Null]),
+            group: Rc::new(vec![GroupKey::None]),
             values: IndexMap::new(),
             groups: HashSet::new(),
             contexts: Vector::new(),
diff --git a/src/executor/context/blend_context.rs b/src/executor/context/blend_context.rs
index 3d5853fe..04c501a8 100644
--- a/src/executor/context/blend_context.rs
+++ b/src/executor/context/blend_context.rs
@@ -70,7 +70,7 @@ impl<'a> BlendContext<'a> {
         if self.table_alias == alias {
             let values = match &self.row {
                 Some(Row(values)) => values.clone(),
-                None => self.columns.iter().map(|_| Value::Empty).collect(),
+                None => self.columns.iter().map(|_| Value::Null).collect(),
             };
 
             Some(values)
@@ -84,7 +84,7 @@ impl<'a> BlendContext<'a> {
     pub fn get_all_values(&'a self) -> Vec<Value> {
         let values: Vec<Value> = match &self.row {
             Some(Row(values)) => values.clone(),
-            None => self.columns.iter().map(|_| Value::Empty).collect(),
+            None => self.columns.iter().map(|_| Value::Null).collect(),
         };
 
         match &self.next {
diff --git a/src/executor/evaluate/mod.rs b/src/executor/evaluate/mod.rs
index 6694c727..095b8b65 100644
--- a/src/executor/evaluate/mod.rs
+++ b/src/executor/evaluate/mod.rs
@@ -53,7 +53,7 @@ pub async fn evaluate<'a, T: 'static + Debug>(
 
                 match (context.get_value(&ident.value), use_empty) {
                     (Some(value), _) => Ok(value.clone()),
-                    (None, true) => Ok(Value::Empty),
+                    (None, true) => Ok(Value::Null),
                     (None, false) => {
                         Err(EvaluateError::ValueNotFound(ident.value.to_string()).into())
                     }
@@ -73,7 +73,7 @@ pub async fn evaluate<'a, T: 'static + Debug>(
 
             match (context.get_alias_value(table_alias, column), use_empty) {
                 (Some(value), _) => Ok(value.clone()),
-                (None, true) => Ok(Value::Empty),
+                (None, true) => Ok(Value::Null),
                 (None, false) => Err(EvaluateError::ValueNotFound(column.to_string()).into()),
             }
             .map(Evaluated::Value)
@@ -171,7 +171,7 @@ async fn evaluate_function<'a, T: 'static + Debug>(
             let expr = get_arg_expr(arg)?;
             let value: Value = match eval(expr).await?.try_into()? {
                 Value::Str(s) => Value::Str(convert(s)),
-                Value::OptStr(s) => Value::OptStr(s.map(convert)),
+                Value::Null => Value::Null,
                 _ => {
                     return Err(EvaluateError::FunctionRequiresStringValue(name.to_string()).into());
                 }
@@ -180,23 +180,6 @@ async fn evaluate_function<'a, T: 'static + Debug>(
             Ok(Evaluated::Value(value))
         }
         name @ "LEFT" | name @ "RIGHT" => {
-            let return_wrap = |value| Ok(Evaluated::Value(Value::OptStr(value)));
-
-            let convert = |string: String, number: usize| {
-                if name == "LEFT" {
-                    string.get(..number)
-                } else {
-                    let start_pos = if number > string.len() {
-                        0
-                    } else {
-                        string.len() - number
-                    };
-                    string.get(start_pos..)
-                }
-                .or(Some(&string))
-                .map(|value| value.to_string())
-            };
-
             match args.len() {
                 2 => (),
                 found => {
@@ -209,20 +192,41 @@ async fn evaluate_function<'a, T: 'static + Debug>(
             }
 
             let string = match eval(get_arg_expr(&args[0])?).await?.try_into()? {
-                Value::Str(string) | Value::OptStr(Some(string)) => Ok(string),
-                Value::OptStr(None) => return return_wrap(None),
+                Value::Str(string) => Ok(string),
+                Value::Null => {
+                    return Ok(Evaluated::Value(Value::Null));
+                }
                 _ => Err(EvaluateError::FunctionRequiresStringValue(name.to_string())),
             }?;
+
             let number = match eval(get_arg_expr(&args[1])?).await?.try_into()? {
-                Value::I64(number) | Value::OptI64(Some(number)) => usize::try_from(number)
+                Value::I64(number) => usize::try_from(number)
                     .map_err(|_| EvaluateError::FunctionRequiresUSizeValue(name.to_string())), // Unlikely to occur hence the imperfect error
-                Value::OptI64(None) => return return_wrap(None),
+                Value::Null => {
+                    return Ok(Evaluated::Value(Value::Null));
+                }
                 _ => Err(EvaluateError::FunctionRequiresIntegerValue(
                     name.to_string(),
                 )),
             }?;
 
-            return_wrap(convert(string, number))
+            let converted = {
+                if name == "LEFT" {
+                    string.get(..number)
+                } else {
+                    let start_pos = if number > string.len() {
+                        0
+                    } else {
+                        string.len() - number
+                    };
+
+                    string.get(start_pos..)
+                }
+                .map(|value| value.to_string())
+                .unwrap_or(string)
+            };
+
+            Ok(Evaluated::Value(Value::Str(converted)))
         }
         name => Err(EvaluateError::FunctionNotSupported(name.to_owned()).into()),
     }
diff --git a/src/executor/update.rs b/src/executor/update.rs
index ddf3ad9b..b4330995 100644
--- a/src/executor/update.rs
+++ b/src/executor/update.rs
@@ -8,6 +8,7 @@ use sqlparser::ast::{Assignment, ColumnDef, Ident};
 
 use super::context::FilterContext;
 use super::evaluate::{evaluate, Evaluated};
+use crate::data::value::TryFromLiteral;
 use crate::data::{Row, Value};
 use crate::result::Result;
 use crate::store::Store;
@@ -19,9 +20,6 @@ pub enum UpdateError {
 
     #[error("conflict on schema, row data does not fit to schema")]
     ConflictOnSchema,
-
-    #[error("unreachable")]
-    Unreachable,
 }
 
 pub struct Update<'a, T: 'static + Debug> {
@@ -69,22 +67,12 @@ impl<'a, T: 'static + Debug> Update<'a, T> {
         {
             None => Ok(None),
             Some(assignment) => {
-                let Assignment { id, value } = &assignment;
-
-                let index = self
-                    .column_defs
-                    .iter()
-                    .position(|col_def| col_def.name.value == id.value)
-                    .ok_or(UpdateError::Unreachable)?;
-
-                let evaluated = evaluate(self.storage, context, None, value, false).await?;
-
-                let Row(values) = &row;
-                let value = &values[index];
+                let Assignment { value, .. } = &assignment;
+                let ColumnDef { data_type, .. } = column_def;
 
-                match evaluated {
-                    Evaluated::LiteralRef(v) => value.clone_by(v),
-                    Evaluated::Literal(v) => value.clone_by(&v),
+                match evaluate(self.storage, context, None, value, false).await? {
+                    Evaluated::LiteralRef(v) => Value::try_from_literal(data_type, v),
+                    Evaluated::Literal(v) => Value::try_from_literal(data_type, &v),
                     Evaluated::StringRef(v) => Ok(Value::Str(v.to_string())),
                     Evaluated::ValueRef(v) => Ok(v.clone()),
                     Evaluated::Value(v) => Ok(v),
diff --git a/src/executor/validate/unique.rs b/src/executor/validate/unique.rs
index 4f780e3c..e00fa076 100644
--- a/src/executor/validate/unique.rs
+++ b/src/executor/validate/unique.rs
@@ -6,6 +6,7 @@ use {
         store::Store,
         utils::Vector,
     },
+    boolinator::Boolinator,
     im_rc::HashSet,
     sqlparser::ast::{ColumnDef, ColumnOption, Ident},
     std::{convert::TryInto, fmt::Debug, rc::Rc},
@@ -16,7 +17,6 @@ pub enum UniqueKey {
     Bool(bool),
     I64(i64),
     Str(String),
-    Null,
 }
 
 #[derive(Debug)]
@@ -36,10 +36,12 @@ impl UniqueConstraint {
     }
 
     fn add(self, value: &Value) -> Result<Self> {
-        let new_key = self.check(value)?;
-        if new_key == UniqueKey::Null {
-            return Ok(self);
-        }
+        let new_key = match self.check(value)? {
+            Some(new_key) => new_key,
+            None => {
+                return Ok(self);
+            }
+        };
 
         let keys = self.keys.update(new_key);
 
@@ -50,18 +52,20 @@ impl UniqueConstraint {
         })
     }
 
-    fn check(&self, value: &Value) -> Result<UniqueKey> {
-        let new_key = value.try_into()?;
-        if new_key != UniqueKey::Null && self.keys.contains(&new_key) {
-            // The input values are duplicate.
-            return Err(ValidateError::DuplicateEntryOnUniqueField(
-                format!("{:?}", value),
-                self.column_name.to_owned(),
-            )
-            .into());
+    fn check(&self, value: &Value) -> Result<Option<UniqueKey>> {
+        match value.try_into()? {
+            Some(new_key) => (!self.keys.contains(&new_key)).as_result_from(
+                || Some(new_key),
+                || {
+                    ValidateError::DuplicateEntryOnUniqueField(
+                        format!("{:?}", value),
+                        self.column_name.to_owned(),
+                    )
+                    .into()
+                },
+            ),
+            None => Ok(None),
         }
-
-        Ok(new_key)
     }
 }
 
@@ -93,7 +97,9 @@ pub async fn validate_unique<T: 'static + Debug>(
                 let val = row
                     .get_value(col_idx)
                     .ok_or(ValidateError::ConflictOnStorageColumnIndex(col_idx))?;
+
                 constraint.check(val)?;
+
                 Ok(())
             })
     })


Test patch:
diff --git a/src/tests/aggregate.rs b/src/tests/aggregate.rs
index d2aa571c..74678862 100644
--- a/src/tests/aggregate.rs
+++ b/src/tests/aggregate.rs
@@ -44,15 +44,14 @@ test_case!(aggregate, async move {
         ),
         (
             "SELECT SUM(age), MAX(age), MIN(age) FROM Item",
-            select!(
-                "SUM(age)" | "MAX(age)" | "MIN(age)"
-                OptI64     | OptI64     | OptI64;
-                None         Some(90)     Some(3)
+            select_with_null!(
+                "SUM(age)" | "MAX(age)" | "MIN(age)";
+                Null         I64(90)     I64(3)
             ),
         ),
         (
             "SELECT SUM(age) + SUM(quantity) FROM Item",
-            select!("SUM(age) + SUM(quantity)"; OptI64; None),
+            select_with_null!("SUM(age) + SUM(quantity)"; Null),
         ),
         (
             "SELECT COUNT(age), COUNT(quantity) FROM Item",
@@ -124,14 +123,13 @@ test_case!(group_by, async move {
         ),
         (
             "SELECT SUM(quantity), COUNT(*), city FROM Item GROUP BY city",
-            select!(
-                "SUM(quantity)" | "COUNT(*)" | city
-                OptI64          | I64        | Str;
-                Some(21)          2            "Seoul".to_owned();
-                Some(0)           1            "Dhaka".to_owned();
-                None              1            "Beijing".to_owned();
-                Some(30)          1            "Daejeon".to_owned();
-                Some(24)          1            "Seattle".to_owned()
+            select_with_null!(
+                "SUM(quantity)" | "COUNT(*)" | city;
+                I64(21)           I64(2)       Str("Seoul".to_owned());
+                I64(0)            I64(1)       Str("Dhaka".to_owned());
+                Null              I64(1)       Str("Beijing".to_owned());
+                I64(30)           I64(1)       Str("Daejeon".to_owned());
+                I64(24)           I64(1)       Str("Seattle".to_owned())
             ),
         ),
         (
@@ -158,8 +156,8 @@ test_case!(group_by, async move {
             "SELECT SUM(quantity), COUNT(*), city FROM Item GROUP BY city HAVING COUNT(*) > 1",
             select!(
                 "SUM(quantity)" | "COUNT(*)" | city
-                OptI64          | I64        | Str;
-                Some(21)          2            "Seoul".to_owned()
+                I64          | I64        | Str;
+                21             2            "Seoul".to_owned()
             ),
         ),
     ];
diff --git a/src/tests/alter_table.rs b/src/tests/alter_table.rs
index ed4e6491..9a02e39a 100644
--- a/src/tests/alter_table.rs
+++ b/src/tests/alter_table.rs
@@ -60,11 +60,10 @@ test_case!(add_drop, async move {
         ),
         (
             "SELECT * FROM Foo;",
-            Ok(select!(
-                id  | amount | opt
-                I64 | I64    | OptBool;
-                1     10       None;
-                2     10       None
+            Ok(select_with_null!(
+                id     | amount  | opt;
+                I64(1)   I64(10)   Null;
+                I64(2)   I64(10)   Null
             )),
         ),
         (
@@ -73,11 +72,10 @@ test_case!(add_drop, async move {
         ),
         (
             "SELECT * FROM Foo;",
-            Ok(select!(
-                id  | amount | opt     | opt2
-                I64 | I64    | OptBool | OptBool;
-                1     10       None      Some(true);
-                2     10       None      Some(true)
+            Ok(select_with_null!(
+                id     | amount  | opt  | opt2;
+                I64(1)   I64(10)   Null   Bool(true);
+                I64(2)   I64(10)   Null   Bool(true)
             )),
         ),
         (
@@ -98,11 +96,10 @@ test_case!(add_drop, async move {
         ),
         (
             "SELECT * FROM Foo;",
-            Ok(select!(
-                id  | opt     | opt2
-                I64 | OptBool | OptBool;
-                1     None      Some(true);
-                2     None      Some(true)
+            Ok(select_with_null!(
+                id     | opt  | opt2;
+                I64(1)   Null   Bool(true);
+                I64(2)   Null   Bool(true)
             )),
         ),
         (
@@ -111,11 +108,10 @@ test_case!(add_drop, async move {
         ),
         (
             "SELECT * FROM Foo;",
-            Ok(select!(
-                id  | opt
-                I64 | OptBool;
-                1     None;
-                2     None
+            Ok(select_with_null!(
+                id     | opt;
+                I64(1)   Null;
+                I64(2)   Null
             )),
         ),
     ];
diff --git a/src/tests/default.rs b/src/tests/default.rs
index adea24ce..d44dfa15 100644
--- a/src/tests/default.rs
+++ b/src/tests/default.rs
@@ -24,14 +24,13 @@ test_case!(default, async move {
         ),
         (
             "SELECT * FROM Test;",
-            select!(
-                id  | num | flag
-                I64 | I64 | OptBool;
-                8     80    Some(true);
-                1     10    Some(false);
-                2     20    Some(false);
-                1     30    None;
-                1     40    Some(true)
+            select_with_null!(
+                id     | num     | flag;
+                I64(8)   I64(80)   Bool(true);
+                I64(1)   I64(10)   Bool(false);
+                I64(2)   I64(20)   Bool(false);
+                I64(1)   I64(30)   Null;
+                I64(1)   I64(40)   Bool(true)
             ),
         ),
     ];
diff --git a/src/tests/function/cast.rs b/src/tests/function/cast.rs
index 73c7f2bd..9410f829 100644
--- a/src/tests/function/cast.rs
+++ b/src/tests/function/cast.rs
@@ -24,7 +24,7 @@ test_case!(cast_literal, async move {
         ),
         (
             r#"SELECT CAST(NULL AS BOOLEAN) AS cast FROM Item"#,
-            Ok(select!(cast OptBool; None)),
+            Ok(select_with_null!(cast; Null)),
         ),
         (
             r#"SELECT CAST("1" AS INTEGER) AS cast FROM Item"#,
@@ -44,7 +44,7 @@ test_case!(cast_literal, async move {
         ),
         (
             r#"SELECT CAST(NULL AS INTEGER) AS cast FROM Item"#,
-            Ok(select!(cast OptI64; None)),
+            Ok(select_with_null!(cast; Null)),
         ),
         (
             r#"SELECT CAST("1.1" AS FLOAT) AS cast FROM Item"#,
@@ -64,7 +64,7 @@ test_case!(cast_literal, async move {
         ),
         (
             r#"SELECT CAST(NULL AS FLOAT) AS cast FROM Item"#,
-            Ok(select!(cast OptF64; None)),
+            Ok(select_with_null!(cast; Null)),
         ),
         (
             r#"SELECT CAST(1 AS TEXT) AS cast FROM Item"#,
@@ -80,7 +80,7 @@ test_case!(cast_literal, async move {
         ),
         (
             r#"SELECT CAST(NULL AS TEXT) AS cast FROM Item"#,
-            Ok(select!(cast OptStr; None)),
+            Ok(select_with_null!(cast; Null)),
         ),
         (
             r#"SELECT CAST(NULL AS NULL) FROM Item"#,
@@ -123,7 +123,7 @@ test_case!(cast_value, async move {
         ),
         (
             r#"SELECT CAST(id AS BOOLEAN) AS cast FROM Item"#,
-            Ok(select!(cast OptBool; Some(false))),
+            Ok(select!(cast Bool; false)),
         ),
         (
             r#"SELECT CAST(flag AS TEXT) AS cast FROM Item"#,
@@ -131,7 +131,7 @@ test_case!(cast_value, async move {
         ),
         (
             r#"SELECT CAST(ratio AS INTEGER) AS cast FROM Item"#,
-            Ok(select!(cast OptI64; None)),
+            Ok(select_with_null!(cast; Null)),
         ),
         (
             r#"SELECT CAST(number AS BOOLEAN) FROM Item"#,
diff --git a/src/tests/function/left_right.rs b/src/tests/function/left_right.rs
index 5e74608a..a70b3b74 100644
--- a/src/tests/function/left_right.rs
+++ b/src/tests/function/left_right.rs
@@ -1,7 +1,8 @@
 use crate::*;
 
 test_case!(left_right, async move {
-    use Value::OptStr;
+    use Value::{Null, Str};
+
     let test_cases = vec![
         ("CREATE TABLE Item (name TEXT)", Ok(Payload::Create)),
         (
@@ -44,20 +45,20 @@ test_case!(left_right, async move {
             r#"SELECT LEFT(name, 3) AS test FROM Item"#,
             Ok(select!(
                 "test"
-                OptStr;
-                Some("Blo".to_owned());
-                Some("B".to_owned());
-                Some("Ste".to_owned())
+                Str;
+                "Blo".to_owned();
+                "B".to_owned();
+                "Ste".to_owned()
             )),
         ),
         (
             r#"SELECT RIGHT(name, 10) AS test FROM Item"#,
             Ok(select!(
                 "test"
-                OptStr;
-                Some("op mc blee".to_owned());
-                Some("B".to_owned());
-                Some("d$ folken!".to_owned())
+                Str;
+                "op mc blee".to_owned();
+                "B".to_owned();
+                "d$ folken!".to_owned()
             )),
         ),
         // TODO Concatenation
@@ -75,48 +76,36 @@ test_case!(left_right, async move {
             r#"SELECT LEFT('blue', 10) AS test FROM SingleItem"#,
             Ok(select!(
                 "test"
-                OptStr;
-                Some("blue".to_owned())
+                Str;
+                "blue".to_owned()
             )),
         ),
         (
             r#"SELECT LEFT("blunder", 3) AS test FROM SingleItem"#,
             Ok(select!(
                 "test"
-                OptStr;
-                Some("blu".to_owned())
+                Str;
+                "blu".to_owned()
             )),
         ),
         (
             r#"SELECT LEFT(name, 3) AS test FROM NullName"#,
-            Ok(select!(
-                "test"
-                OptStr;
-                None
-            )),
+            Ok(select_with_null!(test; Null)),
         ),
         (
             r#"SELECT LEFT('Words', number) AS test FROM NullNumber"#,
-            Ok(select!(
-                "test"
-                OptStr;
-                None
-            )),
+            Ok(select_with_null!(test; Null)),
         ),
         (
             r#"SELECT LEFT(name, number) AS test FROM NullNumber INNER JOIN NullName ON 1 = 1"#,
-            Ok(select!(
-                "test"
-                OptStr;
-                None
-            )),
+            Ok(select_with_null!(test; Null)),
         ),
         (
             r#"SELECT LEFT(name, 1) AS test FROM NullableName"#,
             Ok(select!(
                 "test"
-                OptStr;
-                Some("n".to_owned())
+                Str;
+                "n".to_owned()
             )),
         ),
         // TODO: Cast cannot handle
diff --git a/src/tests/function/upper_lower.rs b/src/tests/function/upper_lower.rs
index e3701944..b3de21a6 100644
--- a/src/tests/function/upper_lower.rs
+++ b/src/tests/function/upper_lower.rs
@@ -1,7 +1,7 @@
 use crate::*;
 
 test_case!(upper_lower, async move {
-    use Value::{OptStr, Str};
+    use Value::{Null, Str};
 
     let test_cases = vec![
         (
@@ -46,12 +46,11 @@ test_case!(upper_lower, async move {
         ),
         (
             "SELECT LOWER(opt_name), UPPER(opt_name) FROM Item;",
-            Ok(select!(
-                "LOWER(opt_name)"       | "UPPER(opt_name)"
-                OptStr                  | OptStr;
-                Some("efgi".to_owned())   Some("EFGI".to_owned());
-                None                      None;
-                Some("efgi".to_owned())   Some("EFGI".to_owned())
+            Ok(select_with_null!(
+                "LOWER(opt_name)"      | "UPPER(opt_name)";
+                Str("efgi".to_owned())   Str("EFGI".to_owned());
+                Null                     Null;
+                Str("efgi".to_owned())   Str("EFGI".to_owned())
             )),
         ),
         (
diff --git a/src/tests/join.rs b/src/tests/join.rs
index 4cf3fe1f..3196f2ea 100644
--- a/src/tests/join.rs
+++ b/src/tests/join.rs
@@ -183,7 +183,7 @@ test_case!(blend, async move {
         run!(insert_sql);
     }
 
-    use Value::{Empty, Str, I64};
+    use Value::{Null, Str, I64};
 
     let sql = "
         SELECT p.id, i.id
@@ -191,16 +191,15 @@ test_case!(blend, async move {
         LEFT JOIN Item i
         ON p.id = i.player_id
     ";
-    let found = run!(sql);
-    let expected = select_with_empty!(
+    let expected = select_with_null!(
         id     | id;
         I64(1)   I64(101);
         I64(2)   I64(102);
-        I64(3)   Empty;
+        I64(3)   Null;
         I64(4)   I64(103);
-        I64(5)   Empty
+        I64(5)   Null
     );
-    assert_eq!(expected, found);
+    test!(Ok(expected), sql);
 
     let sql = "
         SELECT p.id, player_id
@@ -208,16 +207,15 @@ test_case!(blend, async move {
         LEFT JOIN Item
         ON p.id = player_id
     ";
-    let found = run!(sql);
-    let expected = select_with_empty!(
+    let expected = select_with_null!(
         id     | player_id;
         I64(1)   I64(1);
         I64(2)   I64(2);
-        I64(3)   Empty;
+        I64(3)   Null;
         I64(4)   I64(4);
-        I64(5)   Empty
+        I64(5)   Null
     );
-    assert_eq!(expected, found);
+    test!(Ok(expected), sql);
 
     let sql = "
         SELECT Item.*
@@ -225,16 +223,15 @@ test_case!(blend, async move {
         LEFT JOIN Item
         ON p.id = player_id
     ";
-    let found = run!(sql);
-    let expected = select_with_empty!(
+    let expected = select_with_null!(
         id       | quantity | player_id;
         I64(101)   I64(1)     I64(1);
         I64(102)   I64(4)     I64(2);
-        Empty      Empty      Empty;
+        Null       Null       Null;
         I64(103)   I64(9)     I64(4);
-        Empty      Empty      Empty
+        Null       Null       Null
     );
-    assert_eq!(expected, found);
+    test!(Ok(expected), sql);
 
     let sql = "
         SELECT *
@@ -242,14 +239,13 @@ test_case!(blend, async move {
         LEFT JOIN Item
         ON p.id = player_id
     ";
-    let found = run!(sql);
-    let expected = select_with_empty!(
+    let expected = select_with_null!(
         id     | name                      | id       | quantity | player_id;
         I64(1)   Str("Taehoon".to_owned())   I64(101)   I64(1)     I64(1);
         I64(2)   Str("Mike".to_owned())      I64(102)   I64(4)     I64(2);
-        I64(3)   Str("Jorno".to_owned())     Empty      Empty      Empty;
+        I64(3)   Str("Jorno".to_owned())     Null       Null       Null;
         I64(4)   Str("Berry".to_owned())     I64(103)   I64(9)     I64(4);
-        I64(5)   Str("Hwan".to_owned())      Empty      Empty      Empty
+        I64(5)   Str("Hwan".to_owned())      Null       Null       Null
     );
-    assert_eq!(expected, found);
+    test!(Ok(expected), sql);
 });
diff --git a/src/tests/macros.rs b/src/tests/macros.rs
index bb922440..3c16b046 100644
--- a/src/tests/macros.rs
+++ b/src/tests/macros.rs
@@ -46,7 +46,7 @@ macro_rules! concat_with {
 }
 
 #[macro_export]
-macro_rules! select_with_empty {
+macro_rules! select_with_null {
     ( $( $c: tt )|* ; $( $v: expr )* ) => (
         Payload::Select {
             labels: vec![$( stringify!($c).to_owned().replace("\"", "")),+],
@@ -60,13 +60,13 @@ macro_rules! select_with_empty {
 
         Payload::Select {
             labels: vec![$( stringify!($c).to_owned().replace("\"", "")),+],
-            rows: concat_with_empty!(rows ; $( $( $v2 )* );*),
+            rows: concat_with_null!(rows ; $( $( $v2 )* );*),
         }
     });
 }
 
 #[macro_export]
-macro_rules! concat_with_empty {
+macro_rules! concat_with_null {
     ( $rows: ident ; $( $v: expr )* ) => ({
         $rows.push(Row(vec![$( $v ),*]));
 
@@ -75,6 +75,6 @@ macro_rules! concat_with_empty {
     ( $rows: ident ; $( $v: expr )* ; $( $( $v2: expr )* );* ) => ({
         $rows.push(Row(vec![$( $v ),*]));
 
-        concat_with_empty!($rows ; $( $( $v2 )* );* )
+        concat_with_null!($rows ; $( $( $v2 )* );* )
     });
 }
diff --git a/src/tests/nullable.rs b/src/tests/nullable.rs
index 0d3666e4..a2cbf26c 100644
--- a/src/tests/nullable.rs
+++ b/src/tests/nullable.rs
@@ -23,54 +23,48 @@ CREATE TABLE Test (
     let test_cases = vec![
         (
             "SELECT id, num, name FROM Test",
-            select!(
-                id      | num | name
-                OptI64  | I64 | Str;
-                None      2     "Hello".to_owned();
-                Some(1)   9     "World".to_owned();
-                Some(3)   4     "Great".to_owned()
+            select_with_null!(
+                id     | num    | name;
+                Null     I64(2)   Str("Hello".to_owned());
+                I64(1)   I64(9)   Str("World".to_owned());
+                I64(3)   I64(4)   Str("Great".to_owned())
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = NULL AND name = \'Hello\'",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE id IS NULL AND name = \'Hello\'",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
             "SELECT id, num FROM Test WHERE id IS NULL",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
             "SELECT id, num FROM Test WHERE id IS NOT NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
             "SELECT id, num FROM Test WHERE id + 1 IS NULL",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
             "SELECT id, num FROM Test WHERE id + 1 IS NOT NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
@@ -79,12 +73,11 @@ CREATE TABLE Test (
         ),
         (
             "SELECT id, num FROM Test WHERE 100 IS NOT NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                None      2;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                Null     I64(2);
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
@@ -93,22 +86,20 @@ CREATE TABLE Test (
         ),
         (
             "SELECT id, num FROM Test WHERE 8 + 3 IS NOT NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                None      2;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                Null     I64(2);
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
             "SELECT id, num FROM Test WHERE NULL IS NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                None      2;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                Null     I64(2);
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
@@ -121,84 +112,74 @@ CREATE TABLE Test (
         ),
         (
             "SELECT id, num FROM Test WHERE \"NULL\" IS NOT NULL",
-            select!(
-                id      | num
-                OptI64  | I64;
-                None      2;
-                Some(1)   9;
-                Some(3)   4
+            select_with_null!(
+                id     | num;
+                Null     I64(2);
+                I64(1)   I64(9);
+                I64(3)   I64(4)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = NULL + 1;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE id + 1 IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = 1 + NULL;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE 1 + id IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = NULL - 1;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE id - 1 IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = 1 - NULL;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE 1 - id IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = NULL * 1;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE id * 1 IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = 1 * NULL;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE 1 * id IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = NULL / 1;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE id / 1 IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id, num FROM Test WHERE id = 1 / NULL;",
-            select!(
-                id     | num
-                OptI64 | I64;
-                None     2
+            "SELECT id, num FROM Test WHERE 1 / id IS NULL;",
+            select_with_null!(
+                id   | num;
+                Null   I64(2)
             ),
         ),
         (
-            "SELECT id + 1, 1 + id, id - 1, 1 - id, id * 1, 1 * id, id / 1, 1 / id FROM Test WHERE id = NULL;",
-            select!(
+            "SELECT id + 1, 1 + id, id - 1, 1 - id, id * 1, 1 * id, id / 1, 1 / id FROM Test WHERE id IS NULL;",
+            select_with_null!(
                 "id + 1" | "1 + id" | "id - 1" | "1 - id" | "id * 1" | "1 * id" | "id / 1" | "1 / id";
-                OptI64   | OptI64   | OptI64   | OptI64   | OptI64   | OptI64   | OptI64   | OptI64;
-                None       None       None       None       None       None       None       None
+                Null       Null       Null       Null       Null       Null       Null       Null
             ),
         ),
     ];
@@ -210,24 +191,15 @@ CREATE TABLE Test (
     run!("UPDATE Test SET id = 2");
 
     let test_cases = vec![
-        (
-            "SELECT id FROM Test",
-            Ok(select!(
-                id
-                OptI64;
-                Some(2);
-                Some(2);
-                Some(2)
-            )),
-        ),
+        ("SELECT id FROM Test", Ok(select!(id I64; 2; 2; 2))),
         (
             "SELECT id, num FROM Test",
             Ok(select!(
-                id      | num
-                OptI64  | I64;
-                Some(2) 2;
-                Some(2) 9;
-                Some(2) 4
+                id  | num
+                I64 | I64;
+                2     2;
+                2     9;
+                2     4
             )),
         ),
         (
diff --git a/src/tests/tester.rs b/src/tests/tester.rs
index dd690a68..24e4ff73 100644
--- a/src/tests/tester.rs
+++ b/src/tests/tester.rs
@@ -87,10 +87,83 @@ macro_rules! test_case {
                 ($expected: expr, $sql: expr) => {
                     let found = tests::run(Rc::clone(&cell), $sql).await;
 
-                    assert_eq!($expected, found);
+                    test($expected, found);
                 };
             }
 
+            #[allow(unused)]
+            fn test(expected: Result<Payload>, found: Result<Payload>) {
+                let (expected, found): (Payload, Payload) = match (expected, found) {
+                    (Ok(a), Ok(b)) => (a, b),
+                    (a, b) => {
+                        assert_eq!(a, b);
+
+                        return;
+                    }
+                };
+
+                let (expected, found) = match (expected, found) {
+                    (
+                        Payload::Select {
+                            labels: expected_labels,
+                            rows: a,
+                        },
+                        Payload::Select {
+                            labels: found_labels,
+                            rows: b,
+                        },
+                    ) => {
+                        assert_eq!(expected_labels, found_labels);
+
+                        (a, b)
+                    }
+                    (a, b) => {
+                        assert_eq!(a, b);
+
+                        return;
+                    }
+                };
+
+                assert_eq!(
+                    expected.len(),
+                    found.len(),
+                    "\n[err: number of rows]\nexpected: {:?}\n   found: {:?}",
+                    expected,
+                    found
+                );
+
+                let rows = expected.into_iter().zip(found.into_iter()).enumerate();
+
+                for (i, (expected, found)) in rows.into_iter() {
+                    let Row(expected) = expected;
+                    let Row(found) = found;
+
+                    assert_eq!(
+                        expected.len(),
+                        found.len(),
+                        "\n[err: size of row] row index: {}\nexpected: {:?}\n   found: {:?}",
+                        i,
+                        expected,
+                        found
+                    );
+
+                    expected
+                        .iter()
+                        .zip(found.iter())
+                        .for_each(|(expected_val, found_val)| {
+                            if matches!((expected_val, found_val), (&Value::Null, &Value::Null)) {
+                                return;
+                            }
+
+                            assert_eq!(
+                                expected_val, found_val,
+                                "\n[err: value] row index: {}\nexpected: {:?}\n   found: {:?}",
+                                i, expected, found
+                            );
+                        });
+                }
+            }
+
             $content.await
         }
     };
diff --git a/src/tests/validate/unique.rs b/src/tests/validate/unique.rs
index 83847ea4..7c214ae5 100644
--- a/src/tests/validate/unique.rs
+++ b/src/tests/validate/unique.rs
@@ -95,7 +95,7 @@ CREATE TABLE TestC (
         ),
         (
             ValidateError::DuplicateEntryOnUniqueField(
-                format!("{:?}", Value::OptI64(Some(2))),
+                format!("{:?}", Value::I64(2)),
                 "id".to_owned(),
             )
             .into(),
@@ -103,7 +103,7 @@ CREATE TABLE TestC (
         ),
         (
             ValidateError::DuplicateEntryOnUniqueField(
-                format!("{:?}", Value::OptI64(Some(3))),
+                format!("{:?}", Value::I64(3)),
                 "id".to_owned(),
             )
             .into(),
@@ -111,7 +111,7 @@ CREATE TABLE TestC (
         ),
         (
             ValidateError::DuplicateEntryOnUniqueField(
-                format!("{:?}", Value::OptI64(Some(1))),
+                format!("{:?}", Value::I64(1)),
                 "id".to_owned(),
             )
             .into(),