Repository: gluesql/gluesql
Pull request #111 (2020-10-31 02:36:20)

[Revised] Support Plus(+) and Minus(-) signs in WHERE
This pull request resolves issue #101 by supporting unary operations of Value. Specific modifications are the following.
- value.rs & evaluated.rs: Functions to evaluate unary operations of the expression were added.
- mod.rs: Expressions now handle unary operations.
- test.rs: Two test cases to check if WHERE supports plus(+) and minus(-) signs were added.

yejihan-dev: @panarch I really appreciate your thorough reviews! It was my first time making a pull request to an open source project. I learning so much. :)

I finished changing function names, simplifying `match` cases, and adding codes for a float type. I'm currently working on errors regarding `SELECT id FROM Hunter WHERE +2.0 > 1`. It seems like I need to add more cases inside `fn partial_cmp(&self, other: &Evaluated<'a>) -> Option<Ordering> {` which is in `src/data/evaluated.rs`. It should be done by tonight. Again, thanks for your review!


yejihan-dev: I found out that the error comes from unnecessary reverse operations. Thank you for pointing out! :) I changed the code and added some additional test cases in `src/tests/filter.rs`.

- Proper uses of unary operations
```
SELECT name FROM Boss WHERE -2.0 < -1.0
SELECT id FROM Hunter WHERE +2 > +1.0
SELECT name FROM Boss WHERE id <= +2
SELECT name FROM Boss WHERE +id <= 2
```
- Erroneous uses of unary operations
```
SELECT id FROM Hunter WHERE +'abcd' > 1.0 // EvaluateError::LiteralUnaryPlusOnNonNumeric
SELECT id FROM Hunter WHERE -'abcd' < 1.0 // EvaluateError::LiteralUnaryMinusOnNonNumeric
SELECT id FROM Hunter WHERE +name > 1.0 // ValueError::UnaryPlusOnNonNumeric
SELECT id FROM Hunter WHERE -name < 1.0 // ValueError::UnaryMinusOnNonNumeric
```

Patch:
diff --git a/src/data/value.rs b/src/data/value.rs
index 14fad91d..96a13f64 100644
--- a/src/data/value.rs
+++ b/src/data/value.rs
@@ -41,6 +41,12 @@ pub enum ValueError {
 
     #[error("floating numbers cannot be grouped by")]
     FloatCannotBeGroupedBy,
+
+    #[error("unary plus operation for non numeric value")]
+    UnaryPlusOnNonNumeric,
+
+    #[error("unary minus operation for non numeric value")]
+    UnaryMinusOnNonNumeric,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -93,12 +99,18 @@ impl PartialEq<AstValue> for Value {
             (Value::I64(l), AstValue::Number(r))
             | (Value::OptI64(Some(l)), AstValue::Number(r)) => match r.parse::<i64>() {
                 Ok(r) => l == &r,
-                Err(_) => false,
+                Err(_) => match r.parse::<f64>() {
+                    Ok(r) => (*l as f64) == r,
+                    Err(_) => false,
+                },
             },
             (Value::F64(l), AstValue::Number(r))
             | (Value::OptF64(Some(l)), AstValue::Number(r)) => match r.parse::<f64>() {
                 Ok(r) => l == &r,
-                Err(_) => false,
+                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,
@@ -118,10 +130,18 @@ impl PartialOrd<Value> for Value {
             | (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)))
@@ -137,12 +157,18 @@ impl PartialOrd<AstValue> for Value {
             (Value::I64(l), AstValue::Number(r))
             | (Value::OptI64(Some(l)), AstValue::Number(r)) => match r.parse::<i64>() {
                 Ok(r) => Some(l.cmp(&r)),
-                Err(_) => None,
+                Err(_) => match r.parse::<f64>() {
+                    Ok(r) => (*l as f64).partial_cmp(&r),
+                    Err(_) => None,
+                },
             },
             (Value::F64(l), AstValue::Number(r))
             | (Value::OptF64(Some(l)), AstValue::Number(r)) => match r.parse::<f64>() {
                 Ok(r) => l.partial_cmp(&r),
-                Err(_) => None,
+                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)),
@@ -380,6 +406,29 @@ impl Value {
             Empty | OptBool(None) | OptI64(None) | OptF64(None) | OptStr(None)
         )
     }
+
+    pub fn unary_plus(&self) -> Result<Value> {
+        use Value::*;
+
+        match self {
+            I64(_) | OptI64(_) | F64(_) | OptF64(_) => Ok(self.clone()),
+            _ => Err(ValueError::UnaryPlusOnNonNumeric.into()),
+        }
+    }
+
+    pub fn unary_minus(&self) -> Result<Value> {
+        use 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)),
+            _ => Err(ValueError::UnaryMinusOnNonNumeric.into()),
+        }
+    }
 }
 
 #[cfg(test)]
diff --git a/src/executor/evaluate/error.rs b/src/executor/evaluate/error.rs
index b4a271c4..7e9e9ec6 100644
--- a/src/executor/evaluate/error.rs
+++ b/src/executor/evaluate/error.rs
@@ -10,17 +10,18 @@ pub enum EvaluateError {
     #[error("literal add on non-numeric")]
     LiteralAddOnNonNumeric,
 
+    #[error("unary plus operation on non-numeric")]
+    LiteralUnaryPlusOnNonNumeric,
+
+    #[error("unary minus operation on non-numeric")]
+    LiteralUnaryMinusOnNonNumeric,
+
     #[error("function is not supported: {0}")]
     FunctionNotSupported(String),
 
     #[error("function requires string value: {0}")]
     FunctionRequiresStringValue(String),
 
-    #[error(
-        "number of function parameters not matching (expected: {expected:?}, found: {found:?})"
-    )]
-    NumberOfFunctionParamsNotMatching { expected: usize, found: usize },
-
     #[error("unsupported compound identifier {0}")]
     UnsupportedCompoundIdentifier(String),
 
@@ -33,6 +34,9 @@ pub enum EvaluateError {
     #[error("unreachable literal arithmetic")]
     UnreachableLiteralArithmetic,
 
+    #[error("unreachable empty function argument")]
+    UnreachableEmptyFunctionArg,
+
     #[error("unimplemented")]
     Unimplemented,
 }
diff --git a/src/executor/evaluate/evaluated.rs b/src/executor/evaluate/evaluated.rs
index 5c46e75a..b9f229aa 100644
--- a/src/executor/evaluate/evaluated.rs
+++ b/src/executor/evaluate/evaluated.rs
@@ -56,7 +56,7 @@ impl<'a> PartialEq for Evaluated<'a> {
                     StringRef(r) => eq_ast(l, r),
                     ValueRef(r) => r == l,
                     Value(r) => &r == l,
-                    Literal(_) => panic!(),
+                    Literal(r) => *l == r,
                 },
                 StringRef(l) => match other {
                     LiteralRef(r) => eq_ast(r, l),
@@ -77,12 +77,14 @@ impl<'a> PartialEq for Evaluated<'a> {
                     StringRef(r) => eq_val(&l, r),
                     ValueRef(r) => &l == r,
                     Value(r) => l == r,
-                    Literal(_) => panic!(),
+                    Literal(r) => l == r,
                 },
                 Literal(l) => match other {
+                    Literal(r) => l == r,
+                    LiteralRef(r) => &l == r,
                     ValueRef(r) => r == &l,
                     StringRef(_) => false,
-                    _ => panic!(),
+                    Value(_) => panic!(),
                 },
             }
         }
@@ -99,7 +101,7 @@ impl<'a> PartialOrd for Evaluated<'a> {
                 ValueRef(r) => r.partial_cmp(l).map(|o| o.reverse()),
                 Value(r) => r.partial_cmp(*l).map(|o| o.reverse()),
                 StringRef(_) => None,
-                Literal(_) => panic!(),
+                Literal(r) => literal_partial_cmp(&l, &r),
             },
             ValueRef(l) => match other {
                 LiteralRef(r) => l.partial_cmp(r),
@@ -109,7 +111,7 @@ impl<'a> PartialOrd for Evaluated<'a> {
                     data::Value::Str(l) => (&l.as_str()).partial_cmp(r),
                     _ => None,
                 },
-                Literal(_) => panic!(),
+                Literal(r) => l.partial_cmp(&r),
             },
             Value(l) => match other {
                 LiteralRef(r) => l.partial_cmp(*r),
@@ -119,7 +121,7 @@ impl<'a> PartialOrd for Evaluated<'a> {
                     data::Value::Str(l) => (&l.as_str()).partial_cmp(r),
                     _ => None,
                 },
-                Literal(_) => panic!(),
+                Literal(r) => l.partial_cmp(r),
             },
             StringRef(l) => match other {
                 LiteralRef(_) => None,
@@ -129,16 +131,33 @@ impl<'a> PartialOrd for Evaluated<'a> {
                 Literal(_) => panic!(),
                 _ => None,
             },
-            Literal(_) => panic!(),
+            Literal(l) => match other {
+                LiteralRef(r) => literal_partial_cmp(&l, &r),
+                ValueRef(r) => r.partial_cmp(&l).map(|o| o.reverse()),
+                Value(r) => r.partial_cmp(l).map(|o| o.reverse()),
+                StringRef(_) => None,
+                Literal(r) => literal_partial_cmp(l, r),
+            },
         }
     }
 }
 
 fn literal_partial_cmp(a: &AstValue, b: &AstValue) -> Option<Ordering> {
     match (a, b) {
-        (AstValue::Number(l), AstValue::Number(r)) => match (l.parse::<i64>(), r.parse::<i64>()) {
-            (Ok(l), Ok(r)) => Some(l.cmp(&r)),
-            _ => None,
+        (AstValue::Number(a), AstValue::Number(b)) => match (a.parse::<i64>(), b.parse::<i64>()) {
+            (Ok(a), Ok(b)) => Some(a.cmp(&b)),
+            (_, Ok(b)) => match a.parse::<f64>() {
+                Ok(a) => a.partial_cmp(&(b as f64)),
+                _ => None,
+            },
+            (Ok(a), _) => match b.parse::<f64>() {
+                Ok(b) => (a as f64).partial_cmp(&b),
+                _ => None,
+            },
+            _ => match (a.parse::<f64>(), b.parse::<f64>()) {
+                (Ok(a), Ok(b)) => a.partial_cmp(&b),
+                _ => None,
+            },
         },
         (AstValue::SingleQuotedString(l), AstValue::SingleQuotedString(r)) => Some(l.cmp(r)),
         _ => None,
@@ -289,6 +308,40 @@ impl<'a> Evaluated<'a> {
             Evaluated::StringRef(_v) => true,
         }
     }
+
+    pub fn unary_plus(&self) -> Result<Evaluated<'a>> {
+        use Evaluated::*;
+
+        let unreachable = || Err(EvaluateError::UnreachableEvaluatedArithmetic.into());
+
+        let plus_literal = |v| literal_plus(v).map(Evaluated::Literal);
+        let plus_value = |v: &data::Value| v.unary_plus().map(Evaluated::Value);
+
+        match self {
+            LiteralRef(v) => plus_literal(v),
+            Literal(v) => plus_literal(&v),
+            ValueRef(v) => plus_value(v),
+            Value(v) => plus_value(&v),
+            StringRef(_) => unreachable(),
+        }
+    }
+
+    pub fn unary_minus(&self) -> Result<Evaluated<'a>> {
+        use Evaluated::*;
+
+        let unreachable = || Err(EvaluateError::UnreachableEvaluatedArithmetic.into());
+
+        let minus_literal = |v| literal_minus(v).map(Evaluated::Literal);
+        let minus_value = |v: &data::Value| v.unary_minus().map(Evaluated::Value);
+
+        match self {
+            LiteralRef(v) => minus_literal(v),
+            Literal(v) => minus_literal(&v),
+            ValueRef(v) => minus_value(v),
+            Value(v) => minus_value(&v),
+            StringRef(_) => unreachable(),
+        }
+    }
 }
 
 fn literal_add(a: &AstValue, b: &AstValue) -> Result<AstValue> {
@@ -342,3 +395,31 @@ fn literal_divide(a: &AstValue, b: &AstValue) -> Result<AstValue> {
         _ => Err(EvaluateError::UnreachableLiteralArithmetic.into()),
     }
 }
+
+fn literal_plus(v: &AstValue) -> Result<AstValue> {
+    match v {
+        AstValue::Number(v) => v
+            .parse::<i64>()
+            .map_or_else(
+                |_| v.parse::<f64>().map(|_| AstValue::Number(v.to_string())),
+                |v| Ok(AstValue::Number(v.to_string())),
+            )
+            .map_err(|_| EvaluateError::LiteralUnaryPlusOnNonNumeric.into()),
+        AstValue::Null => Ok(AstValue::Null),
+        _ => Err(EvaluateError::LiteralUnaryPlusOnNonNumeric.into()),
+    }
+}
+
+fn literal_minus(v: &AstValue) -> Result<AstValue> {
+    match v {
+        AstValue::Number(v) => v
+            .parse::<i64>()
+            .map_or_else(
+                |_| v.parse::<f64>().map(|v| AstValue::Number((-v).to_string())),
+                |v| Ok(AstValue::Number((-v).to_string())),
+            )
+            .map_err(|_| EvaluateError::LiteralUnaryMinusOnNonNumeric.into()),
+        AstValue::Null => Ok(AstValue::Null),
+        _ => Err(EvaluateError::LiteralUnaryMinusOnNonNumeric.into()),
+    }
+}
diff --git a/src/executor/evaluate/mod.rs b/src/executor/evaluate/mod.rs
index 613496ec..a57d58e1 100644
--- a/src/executor/evaluate/mod.rs
+++ b/src/executor/evaluate/mod.rs
@@ -6,7 +6,7 @@ use std::convert::TryInto;
 use std::fmt::Debug;
 use std::rc::Rc;
 
-use sqlparser::ast::{BinaryOperator, Expr, Function, Value as AstValue};
+use sqlparser::ast::{BinaryOperator, Expr, Function, UnaryOperator, Value as AstValue};
 
 use super::context::FilterContext;
 use super::select::select;
@@ -91,6 +91,16 @@ pub fn evaluate<'a, T: 'static + Debug>(
                 _ => Err(EvaluateError::Unimplemented.into()),
             }
         }
+        Expr::UnaryOp { op, expr } => {
+            let v = eval(expr)?;
+
+            match op {
+                UnaryOperator::Plus => v.unary_plus(),
+                UnaryOperator::Minus => v.unary_minus(),
+                _ => Err(EvaluateError::Unimplemented.into()),
+            }
+        }
+
         Expr::Function(func) => aggregated
             .as_ref()
             .map(|aggr| aggr.get(func))
@@ -123,6 +133,10 @@ fn evaluate_function<'a, T: 'static + Debug>(
 
     match get_name(name)?.to_uppercase().as_str() {
         name @ "LOWER" | name @ "UPPER" => {
+            let expr = args
+                .get(0)
+                .ok_or(EvaluateError::UnreachableEmptyFunctionArg)?;
+
             let convert = |s: String| {
                 if name == "LOWER" {
                     s.to_lowercase()
@@ -131,15 +145,6 @@ fn evaluate_function<'a, T: 'static + Debug>(
                 }
             };
 
-            if args.len() != 1 {
-                return Err(EvaluateError::NumberOfFunctionParamsNotMatching {
-                    expected: 1,
-                    found: args.len(),
-                }
-                .into());
-            }
-
-            let expr = &args[0];
             let value: Value = match eval(expr)?.try_into()? {
                 Value::Str(s) => Value::Str(convert(s)),
                 Value::OptStr(s) => Value::OptStr(s.map(convert)),
diff --git a/src/executor/execute.rs b/src/executor/execute.rs
index 4a35a0ef..198c5801 100644
--- a/src/executor/execute.rs
+++ b/src/executor/execute.rs
@@ -31,9 +31,9 @@ pub enum ExecuteError {
     #[error("unsupported alter table operation: {0}")]
     UnsupportedAlterTableOperation(String),
 
-    #[error("table does not exist")]
+    #[error("TableNotExists")]
     TableNotExists,
-    #[error("table already exists")]
+    #[error("TableAlreadyExists")]
     TableAlreadyExists,
 }
 
@@ -57,33 +57,26 @@ pub fn execute<T: 'static + Debug, U: Store<T> + StoreMut<T> + AlterTable>(
     storage: U,
     query: &Query,
 ) -> MutResult<U, Payload> {
-    macro_rules! try_into {
-        ($storage: expr, $expr: expr) => {
-            match $expr {
-                Err(e) => {
-                    return Err(($storage, e.into()));
-                }
-                Ok(v) => v,
-            }
-        };
-    }
-
     let Query(query) = query;
-    let prepared = try_into!(storage, prepare(&storage, query));
+    let prepared = match prepare(&storage, query) {
+        Ok(prepared) => prepared,
+        Err(error) => {
+            return Err((storage, error));
+        }
+    };
 
     match prepared {
         Prepared::Create {
             schema,
             if_not_exists,
         } => {
-            if try_into!(storage, storage.fetch_schema(&schema.table_name)).is_some() {
+            if storage.fetch_schema(&schema.table_name).is_ok() {
                 return if if_not_exists {
                     Ok((storage, Payload::Create))
                 } else {
                     Err((storage, ExecuteError::TableAlreadyExists.into()))
                 };
             }
-
             storage
                 .insert_schema(&schema)
                 .map(|(storage, _)| (storage, Payload::Create))
@@ -124,20 +117,29 @@ pub fn execute<T: 'static + Debug, U: Store<T> + StoreMut<T> + AlterTable>(
         Prepared::DropTable {
             schema_names,
             if_exists,
-        } => schema_names
-            .iter()
-            .try_fold(storage, |storage, table_name| {
-                let schema = try_into!(storage, storage.fetch_schema(table_name));
+        } => {
+            let storage = match if_exists {
+                true => Ok(storage),
+                false => schema_names
+                    .iter()
+                    .try_fold(storage, |storage, table_name| {
+                        if storage.fetch_schema(table_name).is_ok() {
+                            Ok(storage)
+                        } else {
+                            Err((storage, ExecuteError::TableNotExists.into()))
+                        }
+                    }),
+            }?;
+            let storage = schema_names
+                .iter()
+                .try_fold(storage, |storage, table_name| {
+                    let (storage, _) = storage.delete_schema(table_name)?;
 
-                if !if_exists {
-                    try_into!(storage, schema.ok_or(ExecuteError::TableNotExists));
-                }
+                    Ok(storage)
+                })?;
 
-                storage
-                    .delete_schema(table_name)
-                    .map(|(storage, _)| storage)
-            })
-            .map(|storage| (storage, Payload::DropTable)),
+            Ok((storage, Payload::DropTable))
+        }
         Prepared::Select { labels, rows } => Ok((storage, Payload::Select { labels, rows })),
 
         #[cfg(feature = "alter-table")]
@@ -227,10 +229,7 @@ fn prepare<'a, T: 'static + Debug>(
             source,
         } => {
             let table_name = get_name(table_name)?;
-            let Schema { column_defs, .. } = storage
-                .fetch_schema(table_name)?
-                .ok_or(ExecuteError::TableNotExists)?;
-
+            let Schema { column_defs, .. } = storage.fetch_schema(table_name)?;
             let values_list = match &source.body {
                 SetExpr::Values(Values(values_list)) => values_list,
                 set_expr => {
diff --git a/src/executor/fetch.rs b/src/executor/fetch.rs
index b8c019fb..82d64535 100644
--- a/src/executor/fetch.rs
+++ b/src/executor/fetch.rs
@@ -1,7 +1,5 @@
 use boolinator::Boolinator;
-use serde::Serialize;
 use std::fmt::Debug;
-use thiserror::Error;
 
 use sqlparser::ast::{ColumnDef, Ident};
 
@@ -10,19 +8,12 @@ use crate::data::Row;
 use crate::result::Result;
 use crate::store::Store;
 
-#[derive(Error, Serialize, Debug, PartialEq)]
-pub enum FetchError {
-    #[error("table not found: {0}")]
-    TableNotFound(String),
-}
-
 pub fn fetch_columns<T: 'static + Debug>(
     storage: &dyn Store<T>,
     table_name: &str,
 ) -> Result<Vec<Ident>> {
     Ok(storage
         .fetch_schema(table_name)?
-        .ok_or_else(|| FetchError::TableNotFound(table_name.to_string()))?
         .column_defs
         .into_iter()
         .map(|ColumnDef { name, .. }| name)
diff --git a/src/executor/mod.rs b/src/executor/mod.rs
index 0d9844b3..1598e093 100644
--- a/src/executor/mod.rs
+++ b/src/executor/mod.rs
@@ -15,7 +15,6 @@ pub use blend::BlendError;
 pub use context::{BlendContextError, FilterContextError};
 pub use evaluate::EvaluateError;
 pub use execute::{execute, ExecuteError, Payload};
-pub use fetch::FetchError;
 pub use filter::FilterError;
 pub use join::JoinError;
 pub use limit::LimitError;
diff --git a/src/result.rs b/src/result.rs
index 8afa13fb..1dea8e5c 100644
--- a/src/result.rs
+++ b/src/result.rs
@@ -3,15 +3,19 @@ use thiserror::Error as ThisError;
 
 use crate::data::{RowError, TableError, ValueError};
 use crate::executor::{
-    AggregateError, BlendContextError, BlendError, EvaluateError, ExecuteError, FetchError,
-    FilterContextError, FilterError, JoinError, LimitError, SelectError, UpdateError,
+    AggregateError, BlendContextError, BlendError, EvaluateError, ExecuteError, FilterContextError,
+    FilterError, JoinError, LimitError, SelectError, UpdateError,
 };
 
 #[cfg(feature = "alter-table")]
 use crate::store::AlterTableError;
+use crate::store::StoreError;
 
 #[derive(ThisError, Serialize, Debug)]
 pub enum Error {
+    #[error(transparent)]
+    Store(#[from] StoreError),
+
     #[cfg(feature = "alter-table")]
     #[error(transparent)]
     AlterTable(#[from] AlterTableError),
@@ -23,8 +27,6 @@ pub enum Error {
     #[error(transparent)]
     Execute(#[from] ExecuteError),
     #[error(transparent)]
-    Fetch(#[from] FetchError),
-    #[error(transparent)]
     Evaluate(#[from] EvaluateError),
     #[error(transparent)]
     Select(#[from] SelectError),
@@ -60,10 +62,10 @@ impl PartialEq for Error {
         use Error::*;
 
         match (self, other) {
+            (Store(e), Store(e2)) => e == e2,
             #[cfg(feature = "alter-table")]
             (AlterTable(e), AlterTable(e2)) => e == e2,
             (Execute(e), Execute(e2)) => e == e2,
-            (Fetch(e), Fetch(e2)) => e == e2,
             (Evaluate(e), Evaluate(e2)) => e == e2,
             (Select(e), Select(e2)) => e == e2,
             (Join(e), Join(e2)) => e == e2,
diff --git a/src/storages/sled_storage/alter_table.rs b/src/storages/sled_storage/alter_table.rs
index 130f16c5..976125d9 100644
--- a/src/storages/sled_storage/alter_table.rs
+++ b/src/storages/sled_storage/alter_table.rs
@@ -19,21 +19,10 @@ macro_rules! try_self {
     };
 }
 
-macro_rules! fetch_schema {
-    ($self: expr, $tree: expr, $table_name: expr) => {{
-        let (key, schema) = try_self!($self, fetch_schema($tree, $table_name));
-        let schema = try_into!(
-            $self,
-            schema.ok_or_else(|| AlterTableError::TableNotFound($table_name.to_string()))
-        );
-
-        (key, schema)
-    }};
-}
-
 impl AlterTable for SledStorage {
     fn rename_schema(self, table_name: &str, new_table_name: &str) -> MutResult<Self, ()> {
-        let (_, Schema { column_defs, .. }) = fetch_schema!(self, &self.tree, table_name);
+        let (_, Schema { column_defs, .. }) = try_self!(self, fetch_schema(&self.tree, table_name));
+
         let schema = Schema {
             table_name: new_table_name.to_string(),
             column_defs,
@@ -73,7 +62,8 @@ impl AlterTable for SledStorage {
         old_column_name: &str,
         new_column_name: &str,
     ) -> MutResult<Self, ()> {
-        let (key, Schema { column_defs, .. }) = fetch_schema!(self, &self.tree, table_name);
+        let (key, Schema { column_defs, .. }) =
+            try_self!(self, fetch_schema(&self.tree, table_name));
 
         let i = column_defs
             .iter()
@@ -116,7 +106,7 @@ impl AlterTable for SledStorage {
                 table_name,
                 column_defs,
             },
-        ) = fetch_schema!(self, &self.tree, table_name);
+        ) = try_self!(self, fetch_schema(&self.tree, table_name));
 
         if column_defs
             .iter()
@@ -200,7 +190,7 @@ impl AlterTable for SledStorage {
                 table_name,
                 column_defs,
             },
-        ) = fetch_schema!(self, &self.tree, table_name);
+        ) = try_self!(self, fetch_schema(&self.tree, table_name));
 
         let index = column_defs
             .iter()
diff --git a/src/storages/sled_storage/mod.rs b/src/storages/sled_storage/mod.rs
index 348c8e8b..dce0d445 100644
--- a/src/storages/sled_storage/mod.rs
+++ b/src/storages/sled_storage/mod.rs
@@ -7,7 +7,7 @@ use thiserror::Error as ThisError;
 use crate::AlterTable;
 #[cfg(feature = "alter-table")]
 use crate::AlterTableError;
-use crate::{Error, Result, Schema};
+use crate::{Error, Result, Schema, StoreError};
 
 #[cfg(feature = "alter-table")]
 mod alter_table;
@@ -18,6 +18,9 @@ mod store;
 
 #[derive(ThisError, Debug)]
 enum StorageError {
+    #[error(transparent)]
+    Store(#[from] StoreError),
+
     #[cfg(feature = "alter-table")]
     #[error(transparent)]
     AlterTable(#[from] AlterTableError),
@@ -38,6 +41,7 @@ impl Into<Error> for StorageError {
             Sled(e) => Error::Storage(Box::new(e)),
             Bincode(e) => Error::Storage(e),
             Str(e) => Error::Storage(Box::new(e)),
+            Store(e) => e.into(),
 
             #[cfg(feature = "alter-table")]
             AlterTable(e) => e.into(),
@@ -91,10 +95,11 @@ impl TryFrom<Config> for SledStorage {
     }
 }
 
-fn fetch_schema(tree: &Db, table_name: &str) -> Result<(String, Option<Schema>)> {
+fn fetch_schema(tree: &Db, table_name: &str) -> Result<(String, Schema)> {
     let key = format!("schema/{}", table_name);
     let value = try_into!(tree.get(&key.as_bytes()));
-    let schema = try_into!(value.map(|v| bincode::deserialize(&v)).transpose());
+    let value = value.ok_or(StoreError::SchemaNotFound)?;
+    let schema = try_into!(bincode::deserialize(&value));
 
     Ok((key, schema))
 }
diff --git a/src/storages/sled_storage/store.rs b/src/storages/sled_storage/store.rs
index 42e74109..270ed119 100644
--- a/src/storages/sled_storage/store.rs
+++ b/src/storages/sled_storage/store.rs
@@ -54,7 +54,7 @@ impl StoreMut<IVec> for SledStorage {
 }
 
 impl Store<IVec> for SledStorage {
-    fn fetch_schema(&self, table_name: &str) -> Result<Option<Schema>> {
+    fn fetch_schema(&self, table_name: &str) -> Result<Schema> {
         fetch_schema(&self.tree, table_name).map(|(_, schema)| schema)
     }
 
diff --git a/src/store/alter_table.rs b/src/store/alter_table.rs
index 29b735b8..3bd52fe4 100644
--- a/src/store/alter_table.rs
+++ b/src/store/alter_table.rs
@@ -8,9 +8,6 @@ use crate::result::MutResult;
 
 #[derive(Error, Serialize, Debug, PartialEq)]
 pub enum AlterTableError {
-    #[error("Table not found: {0}")]
-    TableNotFound(String),
-
     #[error("Renaming column not found")]
     RenamingColumnNotFound,
 
diff --git a/src/store/mod.rs b/src/store/mod.rs
index 8db9c8d6..076cb70b 100644
--- a/src/store/mod.rs
+++ b/src/store/mod.rs
@@ -5,17 +5,25 @@ pub use alter_table::*;
 #[cfg(not(feature = "alter-table"))]
 pub trait AlterTable {}
 
+use serde::Serialize;
 use std::fmt::Debug;
 use std::marker::Sized;
+use thiserror::Error;
 
 use crate::data::{Row, Schema};
 use crate::result::{MutResult, Result};
 
+#[derive(Error, Serialize, Debug, PartialEq)]
+pub enum StoreError {
+    #[error("Schema not found")]
+    SchemaNotFound,
+}
+
 pub type RowIter<T> = Box<dyn Iterator<Item = Result<(T, Row)>>>;
 
 /// By implementing `Store` trait, you can run `SELECT` queries.
 pub trait Store<T: Debug> {
-    fn fetch_schema(&self, table_name: &str) -> Result<Option<Schema>>;
+    fn fetch_schema(&self, table_name: &str) -> Result<Schema>;
 
     fn scan_data(&self, table_name: &str) -> Result<RowIter<T>>;
 }


Test patch:
diff --git a/src/tests/alter_table.rs b/src/tests/alter_table.rs
index 7c245bd8..e73e6622 100644
--- a/src/tests/alter_table.rs
+++ b/src/tests/alter_table.rs
@@ -19,10 +19,6 @@ pub fn rename(tester: impl tests::Tester) {
             Ok(Payload::Insert(3)),
         ),
         ("SELECT id FROM Foo", Ok(select!(id; I64; 1; 2; 3))),
-        (
-            "ALTER TABLE Foo2 RENAME TO Bar;",
-            Err(AlterTableError::TableNotFound("Foo2".to_owned()).into()),
-        ),
         ("ALTER TABLE Foo RENAME TO Bar;", Ok(Payload::AlterTable)),
         ("SELECT id FROM Bar", Ok(select!(id; I64; 1; 2; 3))),
         (
diff --git a/src/tests/drop_table.rs b/src/tests/drop_table.rs
index 844de747..a27b79fc 100644
--- a/src/tests/drop_table.rs
+++ b/src/tests/drop_table.rs
@@ -45,7 +45,7 @@ CREATE TABLE DropTable (
         ("DROP TABLE IF EXISTS DropTable;", Ok(Payload::DropTable)),
         (
             "SELECT id, num, name FROM DropTable;",
-            Err(FetchError::TableNotFound("DropTable".to_owned()).into()),
+            Err(StoreError::SchemaNotFound.into()),
         ),
         (create_sql, Ok(Payload::Create)),
         (
diff --git a/src/tests/error.rs b/src/tests/error.rs
index b334e156..28f63c3c 100644
--- a/src/tests/error.rs
+++ b/src/tests/error.rs
@@ -6,10 +6,7 @@ pub fn error(mut tester: impl tests::Tester) {
 
     let test_cases = vec![
         (ExecuteError::QueryNotSupported.into(), "COMMIT;"),
-        (
-            FetchError::TableNotFound("Nothing".to_owned()).into(),
-            "SELECT * FROM Nothing;",
-        ),
+        (StoreError::SchemaNotFound.into(), "SELECT * FROM Nothing;"),
         (
             SelectError::TooManyTables.into(),
             "SELECT * FROM TableA, TableB",
diff --git a/src/tests/filter.rs b/src/tests/filter.rs
index 64b78361..34af83c0 100644
--- a/src/tests/filter.rs
+++ b/src/tests/filter.rs
@@ -60,9 +60,38 @@ pub fn filter(mut tester: impl tests::Tester) {
                 SELECT * FROM Hunter WHERE Hunter.name = Boss.name
              )",
         ),
+        (5, "SELECT name FROM Boss WHERE +1 = 1"),
+        (3, "SELECT id FROM Hunter WHERE -1 = -1"),
+        (5, "SELECT name FROM Boss WHERE -2.0 < -1.0"),
+        (3, "SELECT id FROM Hunter WHERE +2 > +1.0"),
+        (2, "SELECT name FROM Boss WHERE id <= +2"),
+        (2, "SELECT name FROM Boss WHERE +id <= 2"),
     ];
 
     select_sqls
         .iter()
         .for_each(|(num, sql)| tester.test_rows(sql, *num));
+
+    let select_sqls_err = vec![
+        (
+            EvaluateError::LiteralUnaryPlusOnNonNumeric.into(),
+            "SELECT id FROM Hunter WHERE +'abcd' > 1.0",
+        ),
+        (
+            EvaluateError::LiteralUnaryMinusOnNonNumeric.into(),
+            "SELECT id FROM Hunter WHERE -'abcd' < 1.0",
+        ),
+        (
+            ValueError::UnaryPlusOnNonNumeric.into(),
+            "SELECT id FROM Hunter WHERE +name > 1.0",
+        ),
+        (
+            ValueError::UnaryMinusOnNonNumeric.into(),
+            "SELECT id FROM Hunter WHERE -name < 1.0",
+        ),
+    ];
+
+    select_sqls_err
+        .into_iter()
+        .for_each(|(error, sql)| tester.test_error(sql, error));
 }
diff --git a/src/tests/function.rs b/src/tests/function.rs
index a4732e8c..3e201b23 100644
--- a/src/tests/function.rs
+++ b/src/tests/function.rs
@@ -43,14 +43,6 @@ pub fn function(mut tester: impl tests::Tester) {
                 "abcd".to_owned()   "ABCD".to_owned()
             )),
         ),
-        (
-            "SELECT LOWER() FROM Item",
-            Err(EvaluateError::NumberOfFunctionParamsNotMatching {
-                expected: 1,
-                found: 0,
-            }
-            .into()),
-        ),
         (
             "SELECT LOWER(1) FROM Item",
             Err(EvaluateError::FunctionRequiresStringValue("LOWER".to_owned()).into()),