Repository: gluesql/gluesql
Pull request #240 (2021-06-13 13:21:09)

Split ast::Function into Function & Aggregate,
Now executor can handle aggregate functions & non-aggregate functions separately.
Function argument parsing is now done in translation, not in execution

Now AST explicitly provides function names & args using enum

Below enums show all functions which are currently supported.
```rust
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Function {
    Lower(Box<Expr>),
    Upper(Box<Expr>),
    Left { expr: Box<Expr>, size: Box<Expr> },
    Right { expr: Box<Expr>, size: Box<Expr> },
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Aggregate {
    Count(Box<Expr>),
    Sum(Box<Expr>),
    Max(Box<Expr>),
    Min(Box<Expr>),
}
```


Patch:
diff --git a/src/ast/expr.rs b/src/ast/expr.rs
index be621db4..cfefe672 100644
--- a/src/ast/expr.rs
+++ b/src/ast/expr.rs
@@ -1,5 +1,5 @@
 use {
-    super::{AstLiteral, BinaryOperator, DataType, Function, Query, UnaryOperator},
+    super::{Aggregate, AstLiteral, BinaryOperator, DataType, Function, Query, UnaryOperator},
     serde::{Deserialize, Serialize},
 };
 
@@ -47,6 +47,7 @@ pub enum Expr {
         value: String,
     },
     Function(Function),
+    Aggregate(Aggregate),
     Exists(Box<Query>),
     Subquery(Box<Query>),
 }
diff --git a/src/ast/function.rs b/src/ast/function.rs
index 8b6cf28f..0d6edfd7 100644
--- a/src/ast/function.rs
+++ b/src/ast/function.rs
@@ -1,16 +1,20 @@
 use {
-    super::{Expr, ObjectName},
+    super::Expr,
     serde::{Deserialize, Serialize},
 };
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub enum FunctionArg {
-    Named { name: String, arg: Expr },
-    Unnamed(Expr),
+pub enum Function {
+    Lower(Box<Expr>),
+    Upper(Box<Expr>),
+    Left { expr: Box<Expr>, size: Box<Expr> },
+    Right { expr: Box<Expr>, size: Box<Expr> },
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-pub struct Function {
-    pub name: ObjectName,
-    pub args: Vec<FunctionArg>,
+pub enum Aggregate {
+    Count(Box<Expr>),
+    Sum(Box<Expr>),
+    Max(Box<Expr>),
+    Min(Box<Expr>),
 }
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index e1b02576..af125b04 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -10,7 +10,7 @@ pub use ast_literal::{AstLiteral, DateTimeField};
 pub use data_type::DataType;
 pub use ddl::*;
 pub use expr::Expr;
-pub use function::{Function, FunctionArg};
+pub use function::{Aggregate, Function};
 pub use operator::*;
 pub use query::*;
 
diff --git a/src/executor/aggregate/error.rs b/src/executor/aggregate/error.rs
index f73b4a59..670bc730 100644
--- a/src/executor/aggregate/error.rs
+++ b/src/executor/aggregate/error.rs
@@ -5,18 +5,9 @@ pub enum AggregateError {
     #[error("unsupported compound identifier: {0:#?}")]
     UnsupportedCompoundIdentifier(Expr),
 
-    #[error("unsupported aggregation: {0}")]
-    UnsupportedAggregation(String),
-
     #[error("only identifier is allowed in aggregation")]
     OnlyIdentifierAllowed,
 
     #[error("value not found: {0}")]
     ValueNotFound(String),
-
-    #[error("unreachable")]
-    Unreachable,
-
-    #[error("unreachable named function arg: {0}")]
-    UnreachableNamedFunctionArg(String),
 }
diff --git a/src/executor/aggregate/mod.rs b/src/executor/aggregate/mod.rs
index 220dc8cf..896c9439 100644
--- a/src/executor/aggregate/mod.rs
+++ b/src/executor/aggregate/mod.rs
@@ -10,8 +10,8 @@ use {
         filter::check_expr,
     },
     crate::{
-        ast::{Expr, Function, FunctionArg, SelectItem},
-        data::{get_name, Value},
+        ast::{Aggregate, Expr, SelectItem},
+        data::Value,
         result::{Error, Result},
         store::GStore,
     },
@@ -22,7 +22,7 @@ use {
 
 pub use {error::AggregateError, hash::GroupKey};
 
-pub struct Aggregate<'a, T: 'static + Debug> {
+pub struct Aggregator<'a, T: 'static + Debug> {
     storage: &'a dyn GStore<T>,
     fields: &'a [SelectItem],
     group_by: &'a [Expr],
@@ -33,7 +33,7 @@ pub struct Aggregate<'a, T: 'static + Debug> {
 type Applied<'a> = dyn TryStream<Ok = AggregateContext<'a>, Error = Error, Item = Result<AggregateContext<'a>>>
     + 'a;
 
-impl<'a, T: 'static + Debug> Aggregate<'a, T> {
+impl<'a, T: 'static + Debug> Aggregator<'a, T> {
     pub fn new(
         storage: &'a dyn GStore<T>,
         fields: &'a [SelectItem],
@@ -183,18 +183,6 @@ fn aggregate<'a>(
         }
         _ => Err(AggregateError::OnlyIdentifierAllowed),
     };
-    let get_first_arg_expr =
-        |args: &'a [FunctionArg]| match args.get(0).ok_or(AggregateError::Unreachable)? {
-            FunctionArg::Unnamed(expr) => Ok(expr),
-            FunctionArg::Named { name, .. } => Err(AggregateError::UnreachableNamedFunctionArg(
-                name.to_string(),
-            )),
-        };
-    let get_first_value = |args: &'a [FunctionArg]| {
-        let expr = get_first_arg_expr(args)?;
-
-        get_value(expr)
-    };
 
     match expr {
         Expr::Between {
@@ -207,31 +195,25 @@ fn aggregate<'a>(
             .try_fold(state, |state, expr| aggr(state, expr)),
         Expr::UnaryOp { expr, .. } => aggr(state, expr),
         Expr::Nested(expr) => aggr(state, expr),
-        Expr::Function(func) => {
-            let Function { name, args, .. } = func;
-
-            match get_name(name)?.to_uppercase().as_str() {
-                "COUNT" => {
-                    let expr = get_first_arg_expr(args)?;
-                    let value = Value::I64(match expr {
-                        Expr::Wildcard => 1,
-                        _ => {
-                            if get_value(expr)?.is_null() {
-                                0
-                            } else {
-                                1
-                            }
+        Expr::Aggregate(aggr) => match aggr {
+            Aggregate::Count(expr) => {
+                let value = Value::I64(match expr.as_ref() {
+                    Expr::Wildcard => 1,
+                    _ => {
+                        if get_value(expr)?.is_null() {
+                            0
+                        } else {
+                            1
                         }
-                    });
+                    }
+                });
 
-                    state.add(func, &value)
-                }
-                "SUM" => state.add(func, get_first_value(args)?),
-                "MAX" => Ok(state.set_max(func, get_first_value(args)?)),
-                "MIN" => Ok(state.set_min(func, get_first_value(args)?)),
-                name => Err(AggregateError::UnsupportedAggregation(name.to_string()).into()),
+                state.add(aggr, &value)
             }
-        }
+            Aggregate::Sum(expr) => state.add(aggr, get_value(expr)?),
+            Aggregate::Max(expr) => Ok(state.set_max(aggr, get_value(expr)?)),
+            Aggregate::Min(expr) => Ok(state.set_min(aggr, get_value(expr)?)),
+        },
         _ => Ok(state),
     }
 }
@@ -244,10 +226,7 @@ fn check(expr: &Expr) -> Result<bool> {
         Expr::BinaryOp { left, right, .. } => check(left)? || check(right)?,
         Expr::UnaryOp { expr, .. } => check(expr)?,
         Expr::Nested(expr) => check(expr)?,
-        Expr::Function(func) => matches!(
-            get_name(&func.name)?.to_uppercase().as_str(),
-            "COUNT" | "SUM" | "MAX" | "MIN" | "AVG"
-        ),
+        Expr::Aggregate(_) => true,
         _ => false,
     };
 
diff --git a/src/executor/aggregate/state.rs b/src/executor/aggregate/state.rs
index 8da9ca2c..6d131736 100644
--- a/src/executor/aggregate/state.rs
+++ b/src/executor/aggregate/state.rs
@@ -1,7 +1,7 @@
 use {
     super::hash::GroupKey,
     crate::{
-        ast::Function,
+        ast::Aggregate,
         data::Value,
         executor::context::BlendContext,
         result::Result,
@@ -13,12 +13,12 @@ use {
 };
 
 type Group = Rc<Vec<GroupKey>>;
-type ValuesMap<'a> = HashMap<&'a Function, Value>;
+type ValuesMap<'a> = HashMap<&'a Aggregate, Value>;
 
 pub struct State<'a> {
     index: usize,
     group: Group,
-    values: IndexMap<(Group, &'a Function), (usize, Value)>,
+    values: IndexMap<(Group, &'a Aggregate), (usize, Value)>,
     groups: HashSet<Group>,
     contexts: Vector<Rc<BlendContext<'a>>>,
 }
@@ -54,8 +54,8 @@ impl<'a> State<'a> {
         }
     }
 
-    fn update(self, func: &'a Function, value: Value) -> Self {
-        let key = (Rc::clone(&self.group), func);
+    fn update(self, aggr: &'a Aggregate, value: Value) -> Self {
+        let key = (Rc::clone(&self.group), aggr);
         let (values, _) = self.values.insert(key, (self.index, value));
 
         Self {
@@ -67,10 +67,10 @@ impl<'a> State<'a> {
         }
     }
 
-    fn get(&self, func: &'a Function) -> Option<&(usize, Value)> {
+    fn get(&self, aggr: &'a Aggregate) -> Option<&(usize, Value)> {
         let group = Rc::clone(&self.group);
 
-        self.values.get(&(group, func))
+        self.values.get(&(group, aggr))
     }
 
     pub fn export(self) -> Vec<(Option<ValuesMap<'a>>, Option<Rc<BlendContext<'a>>>)> {
@@ -96,8 +96,8 @@ impl<'a> State<'a> {
             .enumerate()
             .map(|(i, entries)| {
                 let aggregated = entries
-                    .map(|((_, func), value)| (func, value))
-                    .collect::<HashMap<&'a Function, Value>>();
+                    .map(|((_, aggr), value)| (aggr, value))
+                    .collect::<HashMap<&'a Aggregate, Value>>();
                 let next = contexts.get(i).map(Rc::clone);
 
                 (Some(aggregated), next)
@@ -105,8 +105,8 @@ impl<'a> State<'a> {
             .collect::<Vec<(Option<ValuesMap<'a>>, Option<Rc<BlendContext<'a>>>)>>()
     }
 
-    pub fn add(self, func: &'a Function, target: &Value) -> Result<Self> {
-        let value = match self.get(func) {
+    pub fn add(self, aggr: &'a Aggregate, target: &Value) -> Result<Self> {
+        let value = match self.get(aggr) {
             Some((index, value)) => {
                 if &self.index <= index {
                     return Ok(self);
@@ -117,11 +117,11 @@ impl<'a> State<'a> {
             None => target.clone(),
         };
 
-        Ok(self.update(func, value))
+        Ok(self.update(aggr, value))
     }
 
-    pub fn set_max(self, func: &'a Function, target: &Value) -> Self {
-        if let Some((index, value)) = self.get(func) {
+    pub fn set_max(self, aggr: &'a Aggregate, target: &Value) -> Self {
+        if let Some((index, value)) = self.get(aggr) {
             if &self.index <= index {
                 return self;
             }
@@ -134,11 +134,11 @@ impl<'a> State<'a> {
             }
         };
 
-        self.update(func, target.clone())
+        self.update(aggr, target.clone())
     }
 
-    pub fn set_min(self, func: &'a Function, target: &Value) -> Self {
-        if let Some((index, value)) = self.get(func) {
+    pub fn set_min(self, aggr: &'a Aggregate, target: &Value) -> Self {
+        if let Some((index, value)) = self.get(aggr) {
             if &self.index <= index {
                 return self;
             }
@@ -151,6 +151,6 @@ impl<'a> State<'a> {
             }
         }
 
-        self.update(func, target.clone())
+        self.update(aggr, target.clone())
     }
 }
diff --git a/src/executor/blend.rs b/src/executor/blend.rs
index 429726b1..d49c94cc 100644
--- a/src/executor/blend.rs
+++ b/src/executor/blend.rs
@@ -4,7 +4,7 @@ use {
         evaluate::evaluate,
     },
     crate::{
-        ast::{Function, SelectItem},
+        ast::{Aggregate, SelectItem},
         data::{get_name, Row, Value},
         result::{Error, Result},
         store::GStore,
@@ -42,7 +42,7 @@ impl<'a, T: 'static + Debug> Blend<'a, T> {
 
     async fn blend(
         &self,
-        aggregated: Option<HashMap<&'a Function, Value>>,
+        aggregated: Option<HashMap<&'a Aggregate, Value>>,
         context: Rc<BlendContext<'a>>,
     ) -> Result<Vec<Value>> {
         let filter_context = FilterContext::concat(None, Some(Rc::clone(&context)));
diff --git a/src/executor/context/aggregate_context.rs b/src/executor/context/aggregate_context.rs
index df5b5975..0112f51d 100644
--- a/src/executor/context/aggregate_context.rs
+++ b/src/executor/context/aggregate_context.rs
@@ -1,12 +1,12 @@
 use {
     super::BlendContext,
-    crate::{ast::Function, data::Value},
+    crate::{ast::Aggregate, data::Value},
     im_rc::HashMap,
     std::{fmt::Debug, rc::Rc},
 };
 
 #[derive(Debug)]
 pub struct AggregateContext<'a> {
-    pub aggregated: Option<HashMap<&'a Function, Value>>,
+    pub aggregated: Option<HashMap<&'a Aggregate, Value>>,
     pub next: Rc<BlendContext<'a>>,
 }
diff --git a/src/executor/evaluate/error.rs b/src/executor/evaluate/error.rs
index dda8d598..4fdef3c7 100644
--- a/src/executor/evaluate/error.rs
+++ b/src/executor/evaluate/error.rs
@@ -1,4 +1,9 @@
-use {crate::ast::Expr, serde::Serialize, std::fmt::Debug, thiserror::Error};
+use {
+    crate::ast::{Aggregate, Expr},
+    serde::Serialize,
+    std::fmt::Debug,
+    thiserror::Error,
+};
 
 #[derive(Error, Serialize, Debug, PartialEq)]
 pub enum EvaluateError {
@@ -8,9 +13,6 @@ pub enum EvaluateError {
     #[error("literal add on non-numeric")]
     LiteralAddOnNonNumeric,
 
-    #[error("function is not supported: {0}")]
-    FunctionNotSupported(String),
-
     #[error("function requires string value: {0}")]
     FunctionRequiresStringValue(String),
 
@@ -20,11 +22,6 @@ pub enum EvaluateError {
     #[error("function requires usize value: {0}")]
     FunctionRequiresUSizeValue(String),
 
-    #[error(
-        "number of function parameters not matching (expected: {expected:?}, found: {found:?})"
-    )]
-    NumberOfFunctionParamsNotMatching { expected: usize, found: usize },
-
     #[error("value not found: {0}")]
     ValueNotFound(String),
 
@@ -34,15 +31,15 @@ pub enum EvaluateError {
     #[error("unsupported compound identifier {0:#?}")]
     UnsupportedCompoundIdentifier(Expr),
 
-    #[error("unreachable wildcard expression")]
-    UnreachableWildcardExpr,
-
     #[error("unsupported stateless expression: {0:#?}")]
     UnsupportedStatelessExpr(Expr),
 
+    #[error("unreachable wildcard expression")]
+    UnreachableWildcardExpr,
+
     #[error("unreachable empty context")]
     UnreachableEmptyContext,
 
-    #[error("unreachable named function argument: {0}")]
-    UnreachableFunctionArg(String),
+    #[error("unreachable empty aggregate value: {0:?}")]
+    UnreachableEmptyAggregateValue(Aggregate),
 }
diff --git a/src/executor/evaluate/mod.rs b/src/executor/evaluate/mod.rs
index ed4e011b..5615517b 100644
--- a/src/executor/evaluate/mod.rs
+++ b/src/executor/evaluate/mod.rs
@@ -6,9 +6,9 @@ mod stateless;
 use {
     super::{context::FilterContext, select::select},
     crate::{
-        ast::{Expr, Function, FunctionArg},
-        data::{get_name, Value},
-        result::Result,
+        ast::{Aggregate, Expr, Function},
+        data::Value,
+        result::{Error, Result},
         store::GStore,
     },
     async_recursion::async_recursion,
@@ -29,7 +29,7 @@ pub use {error::EvaluateError, evaluated::Evaluated, stateless::evaluate_statele
 pub async fn evaluate<'a, T: 'static + Debug>(
     storage: &'a dyn GStore<T>,
     context: Option<Rc<FilterContext<'a>>>,
-    aggregated: Option<Rc<HashMap<&'a Function, Value>>>,
+    aggregated: Option<Rc<HashMap<&'a Aggregate, Value>>>,
     expr: &'a Expr,
 ) -> Result<Evaluated<'a>> {
     let eval = |expr| {
@@ -89,15 +89,20 @@ pub async fn evaluate<'a, T: 'static + Debug>(
 
             expr::unary_op(op, v)
         }
-        Expr::Function(func) => match aggregated.as_ref().map(|aggr| aggr.get(func)).flatten() {
+        Expr::Aggregate(aggr) => match aggregated
+            .as_ref()
+            .map(|aggregated| aggregated.get(aggr))
+            .flatten()
+        {
             Some(value) => Ok(Evaluated::from(value.clone())),
-            None => {
-                let context = context.as_ref().map(Rc::clone);
-                let aggregated = aggregated.as_ref().map(Rc::clone);
-
-                evaluate_function(storage, context, aggregated, func).await
-            }
+            None => Err(EvaluateError::UnreachableEmptyAggregateValue(aggr.clone()).into()),
         },
+        Expr::Function(func) => {
+            let context = context.as_ref().map(Rc::clone);
+            let aggregated = aggregated.as_ref().map(Rc::clone);
+
+            evaluate_function(storage, context, aggregated, func).await
+        }
         Expr::Cast { expr, data_type } => eval(expr).await?.cast(data_type),
         Expr::InList {
             expr,
@@ -199,7 +204,7 @@ pub async fn evaluate<'a, T: 'static + Debug>(
 async fn evaluate_function<'a, T: 'static + Debug>(
     storage: &'a dyn GStore<T>,
     context: Option<Rc<FilterContext<'a>>>,
-    aggregated: Option<Rc<HashMap<&'a Function, Value>>>,
+    aggregated: Option<Rc<HashMap<&'a Aggregate, Value>>>,
     func: &'a Function,
 ) -> Result<Evaluated<'a>> {
     let eval = |expr| {
@@ -209,98 +214,73 @@ async fn evaluate_function<'a, T: 'static + Debug>(
         evaluate(storage, context, aggregated, expr)
     };
 
-    let get_arg_expr = |arg: &'a FunctionArg| -> Result<&'a Expr> {
-        match arg {
-            FunctionArg::Unnamed(expr) => Ok(expr),
-            FunctionArg::Named { name, .. } => {
-                Err(EvaluateError::UnreachableFunctionArg(name.to_string()).into())
+    enum Nullable<T> {
+        Value(T),
+        Null,
+    }
+
+    let eval_to_str = |name: &'static str, expr| async move {
+        match eval(expr).await?.try_into()? {
+            Value::Str(s) => Ok(Nullable::Value(s)),
+            Value::Null => Ok(Nullable::Null),
+            _ => {
+                Err::<_, Error>(EvaluateError::FunctionRequiresStringValue(name.to_owned()).into())
             }
         }
     };
 
-    let Function { name, args, .. } = func;
-
-    match get_name(name)?.to_uppercase().as_str() {
-        name @ "LOWER" | name @ "UPPER" => {
-            let convert = |s: String| {
-                if name == "LOWER" {
-                    s.to_lowercase()
-                } else {
-                    s.to_uppercase()
-                }
-            };
-
-            let arg = match args.len() {
-                1 => &args[0],
-                found => {
-                    return Err(EvaluateError::NumberOfFunctionParamsNotMatching {
-                        expected: 1,
-                        found,
-                    }
-                    .into());
-                }
-            };
-
-            let expr = get_arg_expr(arg)?;
-            let value: Value = match eval(expr).await?.try_into()? {
-                Value::Str(s) => Value::Str(convert(s)),
-                Value::Null => Value::Null,
-                _ => {
-                    return Err(EvaluateError::FunctionRequiresStringValue(name.to_string()).into());
-                }
-            };
-
-            Ok(Evaluated::from(value))
+    match func {
+        Function::Lower(expr) => match eval_to_str("LOWER", expr).await? {
+            Nullable::Value(v) => Ok(Value::Str(v.to_lowercase())),
+            Nullable::Null => Ok(Value::Null),
         }
-        name @ "LEFT" | name @ "RIGHT" => {
-            match args.len() {
-                2 => (),
-                found => {
-                    return Err(EvaluateError::NumberOfFunctionParamsNotMatching {
-                        expected: 2,
-                        found,
-                    }
-                    .into())
-                }
-            }
+        .map(Evaluated::from),
+        Function::Upper(expr) => match eval_to_str("UPPER", expr).await? {
+            Nullable::Value(v) => Ok(Value::Str(v.to_uppercase())),
+            Nullable::Null => Ok(Value::Null),
+        }
+        .map(Evaluated::from),
+        Function::Left { expr, size } | Function::Right { expr, size } => {
+            let name = if matches!(func, Function::Left { .. }) {
+                "LEFT"
+            } else {
+                "RIGHT"
+            };
 
-            let string = match eval(get_arg_expr(&args[0])?).await?.try_into()? {
-                Value::Str(string) => Ok(string),
-                Value::Null => {
+            let string = match eval_to_str(name, expr).await? {
+                Nullable::Value(v) => v,
+                Nullable::Null => {
                     return Ok(Evaluated::from(Value::Null));
                 }
-                _ => Err(EvaluateError::FunctionRequiresStringValue(name.to_string())),
-            }?;
+            };
 
-            let number = match eval(get_arg_expr(&args[1])?).await?.try_into()? {
+            let size = match eval(size).await?.try_into()? {
                 Value::I64(number) => usize::try_from(number)
-                    .map_err(|_| EvaluateError::FunctionRequiresUSizeValue(name.to_string())), // Unlikely to occur hence the imperfect error
+                    .map_err(|_| EvaluateError::FunctionRequiresUSizeValue(name.to_owned()))?,
                 Value::Null => {
                     return Ok(Evaluated::from(Value::Null));
                 }
-                _ => Err(EvaluateError::FunctionRequiresIntegerValue(
-                    name.to_string(),
-                )),
-            }?;
+                _ => {
+                    return Err(EvaluateError::FunctionRequiresIntegerValue(name.to_owned()).into());
+                }
+            };
 
-            let converted = {
-                if name == "LEFT" {
-                    string.get(..number)
+            let converted = if name == "LEFT" {
+                string.get(..size).map(|v| v.to_string()).unwrap_or(string)
+            } else {
+                let start_pos = if size > string.len() {
+                    0
                 } else {
-                    let start_pos = if number > string.len() {
-                        0
-                    } else {
-                        string.len() - number
-                    };
+                    string.len() - size
+                };
 
-                    string.get(start_pos..)
-                }
-                .map(|value| value.to_string())
-                .unwrap_or(string)
+                string
+                    .get(start_pos..)
+                    .map(|value| value.to_string())
+                    .unwrap_or(string)
             };
 
             Ok(Evaluated::from(Value::Str(converted)))
         }
-        name => Err(EvaluateError::FunctionNotSupported(name.to_owned()).into()),
     }
 }
diff --git a/src/executor/filter.rs b/src/executor/filter.rs
index 23ced74e..26063654 100644
--- a/src/executor/filter.rs
+++ b/src/executor/filter.rs
@@ -4,7 +4,7 @@ use {
         evaluate::evaluate,
     },
     crate::{
-        ast::{Expr, Function},
+        ast::{Aggregate, Expr},
         data::Value,
         result::Result,
         store::GStore,
@@ -17,7 +17,7 @@ pub struct Filter<'a, T: 'static + Debug> {
     storage: &'a dyn GStore<T>,
     where_clause: Option<&'a Expr>,
     context: Option<Rc<FilterContext<'a>>>,
-    aggregated: Option<Rc<HashMap<&'a Function, Value>>>,
+    aggregated: Option<Rc<HashMap<&'a Aggregate, Value>>>,
 }
 
 impl<'a, T: 'static + Debug> Filter<'a, T> {
@@ -25,7 +25,7 @@ impl<'a, T: 'static + Debug> Filter<'a, T> {
         storage: &'a dyn GStore<T>,
         where_clause: Option<&'a Expr>,
         context: Option<Rc<FilterContext<'a>>>,
-        aggregated: Option<Rc<HashMap<&'a Function, Value>>>,
+        aggregated: Option<Rc<HashMap<&'a Aggregate, Value>>>,
     ) -> Self {
         Self {
             storage,
@@ -53,7 +53,7 @@ impl<'a, T: 'static + Debug> Filter<'a, T> {
 pub async fn check_expr<'a, T: 'static + Debug>(
     storage: &'a dyn GStore<T>,
     context: Option<Rc<FilterContext<'a>>>,
-    aggregated: Option<Rc<HashMap<&'a Function, Value>>>,
+    aggregated: Option<Rc<HashMap<&'a Aggregate, Value>>>,
     expr: &'a Expr,
 ) -> Result<bool> {
     evaluate(storage, context, aggregated, expr)
diff --git a/src/executor/select.rs b/src/executor/select.rs
index 02c4a435..c9625022 100644
--- a/src/executor/select.rs
+++ b/src/executor/select.rs
@@ -1,6 +1,6 @@
 use {
     super::{
-        aggregate::Aggregate,
+        aggregate::Aggregator,
         blend::Blend,
         context::{BlendContext, FilterContext},
         fetch::fetch_columns,
@@ -213,7 +213,7 @@ pub async fn select_with_labels<'a, T: 'static + Debug>(
         joins,
         filter_context.as_ref().map(Rc::clone),
     ));
-    let aggregate = Aggregate::new(
+    let aggregate = Aggregator::new(
         storage,
         projection,
         group_by,
diff --git a/src/translate/error.rs b/src/translate/error.rs
index 131206c1..4f0560ba 100644
--- a/src/translate/error.rs
+++ b/src/translate/error.rs
@@ -14,6 +14,19 @@ pub enum TranslateError {
     #[error("too many params in drop index")]
     TooManyParamsInDropIndex,
 
+    #[error("function args.length not matching: {name}, expected: {expected}, found: {found}")]
+    FunctionArgsLengthNotMatching {
+        name: String,
+        expected: usize,
+        found: usize,
+    },
+
+    #[error("named function arg is not supported")]
+    NamedFunctionArgNotSupported,
+
+    #[error("unsupported function: {0}")]
+    UnsupportedFunction(String),
+
     #[error("unsupported statement: {0}")]
     UnsupportedStatement(String),
 
diff --git a/src/translate/expr.rs b/src/translate/expr.rs
index 9162920f..b484a18e 100644
--- a/src/translate/expr.rs
+++ b/src/translate/expr.rs
@@ -76,7 +76,7 @@ pub fn translate_expr(sql_expr: &SqlExpr) -> Result<Expr> {
             data_type: translate_data_type(data_type)?,
             value: value.to_owned(),
         }),
-        SqlExpr::Function(function) => translate_function(function).map(Expr::Function),
+        SqlExpr::Function(function) => translate_function(function),
         SqlExpr::Exists(query) => translate_query(query).map(Box::new).map(Expr::Exists),
         SqlExpr::Subquery(query) => translate_query(query).map(Box::new).map(Expr::Subquery),
         _ => Err(TranslateError::UnsupportedExpr(sql_expr.to_string()).into()),
diff --git a/src/translate/function.rs b/src/translate/function.rs
index bb58c2b2..6c37293e 100644
--- a/src/translate/function.rs
+++ b/src/translate/function.rs
@@ -1,26 +1,90 @@
 use {
-    super::{expr::translate_expr, translate_object_name},
+    super::{expr::translate_expr, translate_object_name, TranslateError},
     crate::{
-        ast::{Function, FunctionArg},
+        ast::{Aggregate, Expr, Function, ObjectName},
         result::Result,
     },
     sqlparser::ast::{Function as SqlFunction, FunctionArg as SqlFunctionArg},
 };
 
-pub fn translate_function(sql_function: &SqlFunction) -> Result<Function> {
+pub fn translate_function(sql_function: &SqlFunction) -> Result<Expr> {
     let SqlFunction { name, args, .. } = sql_function;
+    let name = {
+        let ObjectName(names) = translate_object_name(name);
 
-    let name = translate_object_name(name);
+        names[0].to_uppercase()
+    };
     let args = args
         .iter()
         .map(|arg| match arg {
-            SqlFunctionArg::Named { name, arg } => Ok(FunctionArg::Named {
-                name: name.value.to_owned(),
-                arg: translate_expr(arg)?,
-            }),
-            SqlFunctionArg::Unnamed(expr) => translate_expr(expr).map(FunctionArg::Unnamed),
+            SqlFunctionArg::Named { .. } => {
+                Err(TranslateError::NamedFunctionArgNotSupported.into())
+            }
+            SqlFunctionArg::Unnamed(expr) => Ok(expr),
         })
-        .collect::<Result<_>>()?;
+        .collect::<Result<Vec<_>>>()?;
 
-    Ok(Function { name, args })
+    let check_len = |name, found, expected| -> Result<_> {
+        if found == expected {
+            Ok(())
+        } else {
+            Err(TranslateError::FunctionArgsLengthNotMatching {
+                name,
+                expected,
+                found,
+            }
+            .into())
+        }
+    };
+
+    macro_rules! aggr {
+        ($aggregate: expr) => {{
+            check_len(name, args.len(), 1)?;
+
+            translate_expr(args[0])
+                .map(Box::new)
+                .map($aggregate)
+                .map(Expr::Aggregate)
+        }};
+    }
+
+    match name.as_str() {
+        "LOWER" => {
+            check_len(name, args.len(), 1)?;
+
+            translate_expr(args[0])
+                .map(Box::new)
+                .map(Function::Lower)
+                .map(Expr::Function)
+        }
+        "UPPER" => {
+            check_len(name, args.len(), 1)?;
+
+            translate_expr(args[0])
+                .map(Box::new)
+                .map(Function::Upper)
+                .map(Expr::Function)
+        }
+        "LEFT" => {
+            check_len(name, args.len(), 2)?;
+
+            let expr = translate_expr(args[0]).map(Box::new)?;
+            let size = translate_expr(args[1]).map(Box::new)?;
+
+            Ok(Expr::Function(Function::Left { expr, size }))
+        }
+        "RIGHT" => {
+            check_len(name, args.len(), 2)?;
+
+            let expr = translate_expr(args[0]).map(Box::new)?;
+            let size = translate_expr(args[1]).map(Box::new)?;
+
+            Ok(Expr::Function(Function::Right { expr, size }))
+        }
+        "COUNT" => aggr!(Aggregate::Count),
+        "SUM" => aggr!(Aggregate::Sum),
+        "MIN" => aggr!(Aggregate::Min),
+        "MAX" => aggr!(Aggregate::Max),
+        _ => Err(TranslateError::UnsupportedFunction(name).into()),
+    }
 }


Test patch:
diff --git a/src/tests/aggregate.rs b/src/tests/aggregate.rs
index 74380c82..8bbd2409 100644
--- a/src/tests/aggregate.rs
+++ b/src/tests/aggregate.rs
@@ -69,7 +69,7 @@ test_case!(aggregate, async move {
             "SELECT SUM(id.name.ok) FROM Item;",
         ),
         (
-            AggregateError::UnsupportedAggregation("AVG".to_owned()).into(),
+            TranslateError::UnsupportedFunction("AVG".to_owned()).into(),
             "SELECT AVG(*) FROM Item;",
         ),
         (
diff --git a/src/tests/function/left_right.rs b/src/tests/function/left_right.rs
index a70b3b74..4c0d6d08 100644
--- a/src/tests/function/left_right.rs
+++ b/src/tests/function/left_right.rs
@@ -108,26 +108,10 @@ test_case!(left_right, async move {
                 "n".to_owned()
             )),
         ),
-        // TODO: Cast cannot handle
-        /*(
-            r#"SELECT LEFT('Words', CAST(NULL AS INTEGER)) AS test FROM SingleItem"#,
-            Ok(select!(
-                "Words"
-                OptStr;
-                Some("blu".to_owned())
-            )),
-        ),
-        (
-            r#"SELECT LEFT(CAST(NULL AS TEXT), 10) AS test FROM SingleItem"#,
-            Ok(select!(
-                ""
-                OptStr;
-                Some("blu".to_owned())
-            )),
-        ),*/
         (
             r#"SELECT RIGHT(name, 10, 10) AS test FROM SingleItem"#,
-            Err(EvaluateError::NumberOfFunctionParamsNotMatching {
+            Err(TranslateError::FunctionArgsLengthNotMatching {
+                name: "RIGHT".to_owned(),
                 expected: 2,
                 found: 3,
             }
@@ -135,7 +119,8 @@ test_case!(left_right, async move {
         ),
         (
             r#"SELECT RIGHT(name) AS test FROM SingleItem"#,
-            Err(EvaluateError::NumberOfFunctionParamsNotMatching {
+            Err(TranslateError::FunctionArgsLengthNotMatching {
+                name: "RIGHT".to_owned(),
                 expected: 2,
                 found: 1,
             }
@@ -143,7 +128,8 @@ test_case!(left_right, async move {
         ),
         (
             r#"SELECT RIGHT() AS test FROM SingleItem"#,
-            Err(EvaluateError::NumberOfFunctionParamsNotMatching {
+            Err(TranslateError::FunctionArgsLengthNotMatching {
+                name: "RIGHT".to_owned(),
                 expected: 2,
                 found: 0,
             }
diff --git a/src/tests/function/upper_lower.rs b/src/tests/function/upper_lower.rs
index b3de21a6..144e59be 100644
--- a/src/tests/function/upper_lower.rs
+++ b/src/tests/function/upper_lower.rs
@@ -55,7 +55,8 @@ test_case!(upper_lower, async move {
         ),
         (
             "SELECT LOWER() FROM Item",
-            Err(EvaluateError::NumberOfFunctionParamsNotMatching {
+            Err(TranslateError::FunctionArgsLengthNotMatching {
+                name: "LOWER".to_owned(),
                 expected: 1,
                 found: 0,
             }
@@ -67,7 +68,11 @@ test_case!(upper_lower, async move {
         ),
         (
             "SELECT WHATEVER(1) FROM Item",
-            Err(EvaluateError::FunctionNotSupported("WHATEVER".to_owned()).into()),
+            Err(TranslateError::UnsupportedFunction("WHATEVER".to_owned()).into()),
+        ),
+        (
+            "SELECT LOWER(a => 2) FROM Item",
+            Err(TranslateError::NamedFunctionArgNotSupported.into()),
         ),
     ];