diff --git a/src/context/test/CMakeLists.txt b/src/context/test/CMakeLists.txt
index 3246fe025cfbfa273e467d70c6213bce59e1d86b..7d7aec6c5ca9716f0109ed6c8d8cf2d8a9b413f1 100644
--- a/src/context/test/CMakeLists.txt
+++ b/src/context/test/CMakeLists.txt
@@ -9,6 +9,7 @@ SET(CONTEXT_TEST_LIBS
     $<TARGET_OBJECTS:common_encryption_obj>
     $<TARGET_OBJECTS:common_expression_obj>
     $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_agg_function_manager_obj>
     $<TARGET_OBJECTS:common_fs_obj>
     $<TARGET_OBJECTS:common_time_obj>
     $<TARGET_OBJECTS:common_base_obj>
diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt
index 972b90c6f5bd861112e7e19d03af698a445d55da..bf9eeeda9661ca9cc6fdda49fe92627e0233e0bd 100644
--- a/src/daemons/CMakeLists.txt
+++ b/src/daemons/CMakeLists.txt
@@ -55,6 +55,7 @@ nebula_add_executable(
         $<TARGET_OBJECTS:common_charset_obj>
         $<TARGET_OBJECTS:common_encryption_obj>
         $<TARGET_OBJECTS:common_function_manager_obj>
+        $<TARGET_OBJECTS:common_agg_function_manager_obj>
         $<TARGET_OBJECTS:common_conf_obj>
         $<TARGET_OBJECTS:common_time_utils_obj>
         $<TARGET_OBJECTS:common_graph_obj>
diff --git a/src/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt
index 789a5f7151de01d1a7bc6e6bc03a343aab5a403d..6c9ff1010397d9d0c21c3af9af4a219553ab5bcf 100644
--- a/src/executor/test/CMakeLists.txt
+++ b/src/executor/test/CMakeLists.txt
@@ -27,6 +27,7 @@ SET(EXEC_QUERY_TEST_OBJS
     $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
     $<TARGET_OBJECTS:common_charset_obj>
     $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_agg_function_manager_obj>
     $<TARGET_OBJECTS:common_encryption_obj>
     $<TARGET_OBJECTS:common_http_client_obj>
     $<TARGET_OBJECTS:common_time_utils_obj>
diff --git a/src/optimizer/test/CMakeLists.txt b/src/optimizer/test/CMakeLists.txt
index c58b2da6ffd1921a49db356aa5752f2044dd3f7e..9dbd228d5fde414b9fe96faf0b8f0999a9971b3b 100644
--- a/src/optimizer/test/CMakeLists.txt
+++ b/src/optimizer/test/CMakeLists.txt
@@ -10,6 +10,7 @@ set(OPTIMIZER_TEST_LIB
     $<TARGET_OBJECTS:common_datatypes_obj>
     $<TARGET_OBJECTS:common_expression_obj>
     $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_agg_function_manager_obj>
     $<TARGET_OBJECTS:common_time_obj>
     $<TARGET_OBJECTS:common_meta_thrift_obj>
     $<TARGET_OBJECTS:common_meta_client_obj>
diff --git a/src/parser/parser.yy b/src/parser/parser.yy
index dd02a1c9e5935b0d02dbbe988b6e9f1dd1b91664..1235bea0ad0d82052b6ae852808dbbe28fffc1d2 100644
--- a/src/parser/parser.yy
+++ b/src/parser/parser.yy
@@ -26,6 +26,7 @@
 #include "common/expression/PredicateExpression.h"
 #include "common/expression/ListComprehensionExpression.h"
 #include "common/expression/AggregateExpression.h"
+#include "common/function/FunctionManager.h"
 
 #include "common/expression/ReduceExpression.h"
 #include "util/ParserUtil.h"
@@ -158,7 +159,6 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL;
 %token KW_ATOMIC_EDGE
 %token KW_DROP KW_REMOVE KW_SPACES KW_INGEST KW_INDEX KW_INDEXES
 %token KW_IF KW_NOT KW_EXISTS KW_WITH
-%token KW_COUNT KW_COUNT_DISTINCT KW_SUM KW_AVG KW_MAX KW_MIN KW_STD KW_BIT_AND KW_BIT_OR KW_BIT_XOR KW_COLLECT KW_COLLECT_SET
 %token KW_BY KW_DOWNLOAD KW_HDFS KW_UUID KW_CONFIGS KW_FORCE
 %token KW_GET KW_DECLARE KW_GRAPH KW_META KW_STORAGE
 %token KW_TTL KW_TTL_DURATION KW_TTL_COL KW_DATA KW_STOP
@@ -199,7 +199,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL;
 %token <doubleval> DOUBLE
 %token <strval> STRING VARIABLE LABEL IPV4
 
-%type <strval> name_label unreserved_keyword agg_function predicate_name
+%type <strval> name_label unreserved_keyword predicate_name
 %type <expr> expression
 %type <expr> property_expression
 %type <expr> vertex_prop_expression
@@ -223,7 +223,6 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL;
 %type <expr> list_comprehension_expression
 %type <expr> reduce_expression
 %type <expr> compound_expression
-%type <expr> aggregate_expression
 %type <expr> text_search_expression
 %type <expr> constant_expression
 %type <argument_list> argument_list opt_argument_list
@@ -422,17 +421,6 @@ unreserved_keyword
     | KW_DBA                { $$ = new std::string("dba"); }
     | KW_GUEST              { $$ = new std::string("guest"); }
     | KW_GROUP              { $$ = new std::string("group"); }
-    | KW_COUNT              { $$ = new std::string("count"); }
-    | KW_SUM                { $$ = new std::string("sum"); }
-    | KW_AVG                { $$ = new std::string("avg"); }
-    | KW_MAX                { $$ = new std::string("max"); }
-    | KW_MIN                { $$ = new std::string("min"); }
-    | KW_STD                { $$ = new std::string("std"); }
-    | KW_BIT_AND            { $$ = new std::string("bit_and"); }
-    | KW_BIT_OR             { $$ = new std::string("bit_or"); }
-    | KW_BIT_XOR            { $$ = new std::string("bit_xor"); }
-    | KW_COLLECT            { $$ = new std::string("collect"); }
-    | KW_COLLECT_SET        { $$ = new std::string("collect_set"); }
     | KW_PATH               { $$ = new std::string("path"); }
     | KW_DATA               { $$ = new std::string("data"); }
     | KW_LEADER             { $$ = new std::string("leader"); }
@@ -467,7 +455,6 @@ unreserved_keyword
     | KW_REDUCE             { $$ = new std::string("reduce"); }
     | KW_SHORTEST           { $$ = new std::string("shortest"); }
     | KW_NOLOOP             { $$ = new std::string("noloop"); }
-    | KW_COUNT_DISTINCT     { $$ = new std::string("count_distinct"); }
     | KW_CONTAINS           { $$ = new std::string("contains"); }
     | KW_STARTS             { $$ = new std::string("starts"); }
     | KW_ENDS               { $$ = new std::string("ends"); }
@@ -507,20 +494,6 @@ unreserved_keyword
     | KW_PLAN               { $$ = new std::string("plan"); }
     ;
 
-agg_function
-    : KW_COUNT              { $$ = new std::string("COUNT"); }
-    | KW_SUM                { $$ = new std::string("SUM"); }
-    | KW_AVG                { $$ = new std::string("AVG"); }
-    | KW_MAX                { $$ = new std::string("MAX"); }
-    | KW_MIN                { $$ = new std::string("MIN"); }
-    | KW_STD                { $$ = new std::string("STD"); }
-    | KW_BIT_AND            { $$ = new std::string("BIT_AND"); }
-    | KW_BIT_OR             { $$ = new std::string("BIT_OR"); }
-    | KW_BIT_XOR            { $$ = new std::string("BIT_XOR"); }
-    | KW_COLLECT            { $$ = new std::string("COLLECT"); }
-    | KW_COLLECT_SET        { $$ = new std::string("COLLECT_SET"); }
-    ;
-
 expression
     : constant_expression {
         $$ = $1;
@@ -534,9 +507,6 @@ expression
     | compound_expression {
         $$ = $1;
     }
-    | aggregate_expression {
-        $$ = $1;
-    }
     | MINUS {
         scanner.setUnaryMinus(true);
     } expression %prec UNARY_MINUS {
@@ -892,7 +862,51 @@ edge_prop_expression
 
 function_call_expression
     : LABEL L_PAREN opt_argument_list R_PAREN {
-        $$ = new FunctionCallExpression($1, $3);
+        if (!$3) {
+            if (FunctionManager::find(*$1, 0).ok()) {
+                $$ = new FunctionCallExpression($1, $3);
+            } else {
+                throw nebula::GraphParser::syntax_error(@1, "Unknown function ");
+            }
+        } else if ($3->numArgs() == 1 && AggFunctionManager::find(*$1).ok()) {
+            $$ = new AggregateExpression($1, $3->args()[0].release(), false);
+            delete($3);
+        } else if (FunctionManager::find(*$1, $3->numArgs()).ok()) {
+            $$ = new FunctionCallExpression($1, $3);
+        } else {
+            delete($1);
+            delete($3);
+            throw nebula::GraphParser::syntax_error(@1, "Unknown function ");
+        }
+    }
+    | LABEL L_PAREN KW_DISTINCT expression R_PAREN {
+        if (AggFunctionManager::find(*$1).ok()) {
+            $$ = new AggregateExpression($1, $4, true);
+        } else {
+            delete($1);
+            delete($4);
+            throw nebula::GraphParser::syntax_error(@1, "Unknown aggregate function ");
+        }
+    }
+    | LABEL L_PAREN STAR R_PAREN {
+        std::transform($1->begin(), $1->end(), $1->begin(), ::toupper);
+        if (!$1->compare("COUNT")) {
+            auto star = new ConstantExpression(std::string("*"));
+            $$ = new AggregateExpression($1, star, false);
+        } else {
+            delete($1);
+            throw nebula::GraphParser::syntax_error(@1, "Could not apply aggregation function on `*`");
+        }
+    }
+    | LABEL L_PAREN KW_DISTINCT STAR R_PAREN {
+        std::transform($1->begin(), $1->end(), $1->begin(), ::toupper);
+        if (!$1->compare("COUNT")) {
+            auto star = new ConstantExpression(std::string("*"));
+            $$ = new AggregateExpression($1, star, true);
+        } else {
+            delete($1);
+            throw nebula::GraphParser::syntax_error(@1, "Could not apply aggregation function on `*`");
+        }
     }
     | KW_TIMESTAMP L_PAREN opt_argument_list R_PAREN {
         $$ = new FunctionCallExpression(new std::string("timestamp"), $3);
@@ -914,23 +928,6 @@ function_call_expression
     }
     ;
 
-aggregate_expression
-    : agg_function L_PAREN expression R_PAREN {
-        $$ = new AggregateExpression($1, $3, false/*distinct*/);
-    }
-    | agg_function L_PAREN KW_DISTINCT expression R_PAREN {
-        $$ = new AggregateExpression($1, $4, true/*distinct*/);
-    }
-    | agg_function L_PAREN STAR R_PAREN {
-        auto star = new ConstantExpression(std::string("*"));
-        $$ = new AggregateExpression($1, star, false/*distinct*/);
-    }
-    | agg_function L_PAREN KW_DISTINCT STAR R_PAREN {
-        auto star = new ConstantExpression(std::string("*"));
-        $$ = new AggregateExpression($1, star, true/*distinct*/);
-    }
-    ;
-
 uuid_expression
     : KW_UUID L_PAREN STRING R_PAREN {
         $$ = new UUIDExpression($3);
diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex
index 695a5d23fdbf13a48edfd7fe8fbf4cd3128f2a74..cde1f455adcd38b90c48db809d3b5ab8747da61d 100644
--- a/src/parser/scanner.lex
+++ b/src/parser/scanner.lex
@@ -183,18 +183,6 @@ IP_OCTET                    ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])
 "ACCOUNT"                   { return TokenType::KW_ACCOUNT; }
 "JOBS"                      { return TokenType::KW_JOBS; }
 "JOB"                       { return TokenType::KW_JOB; }
-"COUNT"                     { return TokenType::KW_COUNT; }
-"COUNT_DISTINCT"            { return TokenType::KW_COUNT_DISTINCT; }
-"SUM"                       { return TokenType::KW_SUM; }
-"AVG"                       { return TokenType::KW_AVG; }
-"MAX"                       { return TokenType::KW_MAX; }
-"MIN"                       { return TokenType::KW_MIN; }
-"STD"                       { return TokenType::KW_STD; }
-"BIT_AND"                   { return TokenType::KW_BIT_AND; }
-"BIT_OR"                    { return TokenType::KW_BIT_OR; }
-"BIT_XOR"                   { return TokenType::KW_BIT_XOR; }
-"COLLECT"                   { return TokenType::KW_COLLECT; }
-"COLLECT_SET"               { return TokenType::KW_COLLECT_SET; }
 "PATH"                      { return TokenType::KW_PATH; }
 "BIDIRECT"                  { return TokenType::KW_BIDIRECT; }
 "STATS"                     { return TokenType::KW_STATS; }
diff --git a/src/parser/test/CMakeLists.txt b/src/parser/test/CMakeLists.txt
index bf0da49348256be184c1421461cd3733461e15a2..626b0a84a5977b378bee642e984714be6184df57 100644
--- a/src/parser/test/CMakeLists.txt
+++ b/src/parser/test/CMakeLists.txt
@@ -17,6 +17,7 @@ set(PARSER_TEST_LIBS
     $<TARGET_OBJECTS:common_datatypes_obj>
     $<TARGET_OBJECTS:common_base_obj>
     $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_agg_function_manager_obj>
     $<TARGET_OBJECTS:common_meta_thrift_obj>
     $<TARGET_OBJECTS:common_graph_thrift_obj>
     $<TARGET_OBJECTS:common_http_client_obj>
diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp
index c3db9582f9133ab64b64730c03017b3e8e47fe3c..c7204e8143edc4d76d0ec13ff6e83851a38e61c7 100644
--- a/src/parser/test/ParserTest.cpp
+++ b/src/parser/test/ParserTest.cpp
@@ -1887,10 +1887,10 @@ TEST(Parser, GroupBy) {
                             "BIT_OR(DISTINCT $^.person.name ), "
                             "BIT_XOR(DISTINCT $$.person.name ),"
                             "BIT_XOR(DISTINCT $-.name ),"
-                            "F_STD($^.person.name ), "
-                            "F_BIT_AND($^.person.name ), "
-                            "F_BIT_OR($^.person.name ), "
-                            "F_BIT_XOR($^.person.name )";
+                            "STD($^.person.name ), "
+                            "BIT_AND($^.person.name ), "
+                            "BIT_OR($^.person.name ), "
+                            "BIT_XOR($^.person.name )";
 
         auto result = parse(query);
         ASSERT_TRUE(result.ok()) << result.status();
@@ -1922,10 +1922,10 @@ TEST(Parser, GroupBy) {
                             "BIT_OR(DISTINCT $^.person.name ), "
                             "BIT_XOR(DISTINCT $$.person.name ),"
                             "BIT_XOR(DISTINCT $-.name ),"
-                            "F_STD($^.person.name ), "
-                            "F_BIT_AND($^.person.name ), "
-                            "F_BIT_OR($^.person.name ), "
-                            "F_BIT_XOR($^.person.name )";
+                            "STD($^.person.name ), "
+                            "BIT_AND($^.person.name ), "
+                            "BIT_OR($^.person.name ), "
+                            "BIT_XOR($^.person.name )";
 
         auto result = parse(query);
         ASSERT_TRUE(result.ok()) << result.status();
diff --git a/src/parser/test/ScannerTest.cpp b/src/parser/test/ScannerTest.cpp
index ff020d250f2ad77798d8fde25641d5f213bf28f7..e4cc3391d1f22d8f33873e6bdea9a5d7e42d2cef 100644
--- a/src/parser/test/ScannerTest.cpp
+++ b/src/parser/test/ScannerTest.cpp
@@ -407,15 +407,6 @@ TEST(Scanner, Basic) {
         CHECK_SEMANTIC_TYPE("SNAPSHOTS", TokenType::KW_SNAPSHOTS),
         CHECK_SEMANTIC_TYPE("Snapshots", TokenType::KW_SNAPSHOTS),
         CHECK_SEMANTIC_TYPE("snapshots", TokenType::KW_SNAPSHOTS),
-        CHECK_SEMANTIC_TYPE("BIT_AND", TokenType::KW_BIT_AND),
-        CHECK_SEMANTIC_TYPE("Bit_and", TokenType::KW_BIT_AND),
-        CHECK_SEMANTIC_TYPE("bit_and", TokenType::KW_BIT_AND),
-        CHECK_SEMANTIC_TYPE("BIT_OR", TokenType::KW_BIT_OR),
-        CHECK_SEMANTIC_TYPE("Bit_or", TokenType::KW_BIT_OR),
-        CHECK_SEMANTIC_TYPE("bit_or", TokenType::KW_BIT_OR),
-        CHECK_SEMANTIC_TYPE("BIT_XOR", TokenType::KW_BIT_XOR),
-        CHECK_SEMANTIC_TYPE("Bit_xor", TokenType::KW_BIT_XOR),
-        CHECK_SEMANTIC_TYPE("bit_xor", TokenType::KW_BIT_XOR),
         CHECK_SEMANTIC_TYPE("SHORTEST", TokenType::KW_SHORTEST),
         CHECK_SEMANTIC_TYPE("Shortest", TokenType::KW_SHORTEST),
         CHECK_SEMANTIC_TYPE("shortest", TokenType::KW_SHORTEST),
diff --git a/src/util/ExpressionUtils.cpp b/src/util/ExpressionUtils.cpp
index bd28d80afc23e09045c151a2454861acb1561be0..a39d4dc82d5f01ba9c973d3a3e07869cd3445b4f 100644
--- a/src/util/ExpressionUtils.cpp
+++ b/src/util/ExpressionUtils.cpp
@@ -9,6 +9,7 @@
 #include <memory>
 
 #include "common/expression/PropertyExpression.h"
+#include "common/function/AggFunctionManager.h"
 #include "visitor/FoldConstantExprVisitor.h"
 #include "visitor/EvaluableExprVisitor.h"
 
@@ -204,16 +205,10 @@ std::vector<std::unique_ptr<Expression>> ExpressionUtils::expandImplOr(const Exp
 
 
 Status ExpressionUtils::checkAggExpr(const AggregateExpression* aggExpr) {
-    auto func = aggExpr->name();
-    if (!func) {
-        return Status::SemanticError("`%s' aggregate function not set.",
-                                     aggExpr->toString().c_str());
-    }
+    auto func = *aggExpr->name();
+    std::transform(func.begin(), func.end(), func.begin(), ::toupper);
 
-    auto iter = AggregateExpression::NAME_ID_MAP.find(func->c_str());
-    if (iter == AggregateExpression::NAME_ID_MAP.end()) {
-        return Status::SemanticError("Unknown aggregate function `%s'", func->c_str());
-    }
+    NG_RETURN_IF_ERROR(AggFunctionManager::find(func));
 
     auto* aggArg = aggExpr->arg();
     if (graph::ExpressionUtils::findAny(aggArg,
@@ -222,7 +217,7 @@ Status ExpressionUtils::checkAggExpr(const AggregateExpression* aggExpr) {
                                      aggExpr->toString().c_str());
     }
 
-    if (iter->second != AggregateExpression::Function::kCount) {
+    if (func.compare("COUNT")) {
         if (aggArg->toString() == "*") {
             return Status::SemanticError("Could not apply aggregation function `%s' on `*`",
                                          aggExpr->toString().c_str());
diff --git a/src/util/test/CMakeLists.txt b/src/util/test/CMakeLists.txt
index 6cc021d50a2ccdfd2070cd71f0adabecc9ee43d8..aeabd61504c70d49ebcc490c39d538a83f742368 100644
--- a/src/util/test/CMakeLists.txt
+++ b/src/util/test/CMakeLists.txt
@@ -10,6 +10,7 @@ nebula_add_test(
         $<TARGET_OBJECTS:common_datatypes_obj>
         $<TARGET_OBJECTS:common_expression_obj>
         $<TARGET_OBJECTS:common_function_manager_obj>
+        $<TARGET_OBJECTS:common_agg_function_manager_obj>
         $<TARGET_OBJECTS:common_time_obj>
         $<TARGET_OBJECTS:common_meta_thrift_obj>
         $<TARGET_OBJECTS:common_meta_client_obj>
diff --git a/src/validator/GroupByValidator.cpp b/src/validator/GroupByValidator.cpp
index 475398acc8c81c092edc280dc779d030c2051aaa..2a5161000fa3bc97770fc4ef5d9ebfc9d9ad32e5 100644
--- a/src/validator/GroupByValidator.cpp
+++ b/src/validator/GroupByValidator.cpp
@@ -197,16 +197,9 @@ Status GroupByValidator::rewriteInnerAggExpr(YieldColumn* col, bool& rewrited) {
 }
 
 Status GroupByValidator::checkAggExpr(AggregateExpression* aggExpr) {
-    auto func = aggExpr->name();
-    if (!func) {
-        return Status::SemanticError("`%s' aggregate function not set.",
-                                     aggExpr->toString().c_str());
-    }
-
-    auto iter = AggregateExpression::NAME_ID_MAP.find(func->c_str());
-    if (iter == AggregateExpression::NAME_ID_MAP.end()) {
-        return Status::SemanticError("Unknown aggregate function `%s'", func->c_str());
-    }
+    auto func = *aggExpr->name();
+    std::transform(func.begin(), func.end(), func.begin(), ::toupper);
+    NG_RETURN_IF_ERROR(AggFunctionManager::find(func));
 
     auto* aggArg = aggExpr->arg();
     if (graph::ExpressionUtils::findAny(aggArg,
@@ -215,8 +208,8 @@ Status GroupByValidator::checkAggExpr(AggregateExpression* aggExpr) {
                                      aggExpr->toString().c_str());
     }
 
-    if (iter->second != AggregateExpression::Function::kCount) {
-        if (aggArg->toString() == "\"*\"") {
+    if (func.compare("COUNT")) {
+        if (aggArg->toString() == "*") {
             return Status::SemanticError("Could not apply aggregation function `%s' on `*`",
                                          aggExpr->toString().c_str());
         }
diff --git a/src/validator/test/CMakeLists.txt b/src/validator/test/CMakeLists.txt
index 51dc4be115bc884d76cd5a64e733f685750b3748..0934d88689f8745fdbbbfb08f63519e3e230348f 100644
--- a/src/validator/test/CMakeLists.txt
+++ b/src/validator/test/CMakeLists.txt
@@ -41,6 +41,7 @@ set(VALIDATOR_TEST_LIBS
     $<TARGET_OBJECTS:common_meta_client_obj>
     $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
     $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_agg_function_manager_obj>
     $<TARGET_OBJECTS:common_conf_obj>
     $<TARGET_OBJECTS:common_encryption_obj>
     $<TARGET_OBJECTS:common_http_client_obj>
diff --git a/src/validator/test/GroupByValidatorTest.cpp b/src/validator/test/GroupByValidatorTest.cpp
index 761546768b6f1cd2d0c571c4c9e2ebbeebd85318..b39b839348a8585f65bf794b440e624935b66434 100644
--- a/src/validator/test/GroupByValidatorTest.cpp
+++ b/src/validator/test/GroupByValidatorTest.cpp
@@ -290,19 +290,21 @@ TEST_F(GroupByValidatorTest, InvalidTest) {
                    "SemanticError: Group `noexist' invalid");
     }
     {
+        // TODO: move to parser UT
         // use sum(*)
         std::string query = "GO FROM \"1\" OVER like YIELD like._dst AS id, $^.person.age AS age "
                             "| GROUP BY $-.id YIELD SUM(*)";
         auto result = checkResult(query);
         EXPECT_EQ(std::string(result.message()),
-                  "SemanticError: Could not apply aggregation function `SUM(*)' on `*`");
+                  "SyntaxError: Could not apply aggregation function on `*` near `SUM'");
     }
     {
+        // TODO: move to parser UT
         // use agg fun has more than two inputs
         std::string query = "GO FROM \"1\" OVER like YIELD like._dst AS id, $^.person.age AS age "
                             "| GROUP BY $-.id YIELD COUNT($-.id, $-.age)";
         auto result = checkResult(query);
-        EXPECT_EQ(std::string(result.message()), "SyntaxError: syntax error near `, $-.age'");
+        EXPECT_EQ(std::string(result.message()), "SyntaxError: Unknown function  near `COUNT'");
     }
     {
         // group col has agg fun
diff --git a/src/validator/test/YieldValidatorTest.cpp b/src/validator/test/YieldValidatorTest.cpp
index bcd6503180aa51c0889803334439e59f33be35b1..68775e43624ebfd5e017cf5a8928fb7b11fd5f63 100644
--- a/src/validator/test/YieldValidatorTest.cpp
+++ b/src/validator/test/YieldValidatorTest.cpp
@@ -123,11 +123,11 @@ TEST_F(YieldValidatorTest, FuncitonCall) {
                   "Parameter's type error");
     }
     {
+        // TODO: move to parser UT
         std::string query = "YIELD noexist(12)";
         auto result = checkResult(query);
         EXPECT_EQ(std::string(result.message()),
-                  "SemanticError: `noexist(12)' is not a valid expression : Function `noexist' not "
-                  "defined");
+                  "SyntaxError: Unknown function  near `noexist'");
     }
 }
 
diff --git a/src/visitor/DeduceTypeVisitor.cpp b/src/visitor/DeduceTypeVisitor.cpp
index 674b74d87fffba46ba0de488031147248923bb47..e1ddd573d74b39bab4436695fbb8da52ad128add 100644
--- a/src/visitor/DeduceTypeVisitor.cpp
+++ b/src/visitor/DeduceTypeVisitor.cpp
@@ -414,60 +414,7 @@ void DeduceTypeVisitor::visit(FunctionCallExpression *expr) {
 void DeduceTypeVisitor::visit(AggregateExpression *expr) {
     expr->arg()->accept(this);
     if (!ok()) return;
-    auto arg_type = type_;
-
-    auto func = AggregateExpression::NAME_ID_MAP[expr->name()->c_str()];
-    switch (func) {
-        case AggregateExpression::Function::kCount: {
-            type_ = Value::Type::INT;
-            break;
-        }
-        case AggregateExpression::Function::kSum: {
-            type_ = arg_type;
-            break;
-        }
-        case AggregateExpression::Function::kAvg: {
-            type_ = Value::Type::FLOAT;
-            break;
-        }
-        case AggregateExpression::Function::kMax: {
-            type_ = arg_type;
-            break;
-        }
-        case AggregateExpression::Function::kMin: {
-            type_ = arg_type;
-            break;
-        }
-        case AggregateExpression::Function::kStdev: {
-            type_ = Value::Type::FLOAT;
-            break;
-        }
-        case AggregateExpression::Function::kBitAnd: {
-            type_ = Value::Type::INT;
-            break;
-        }
-        case AggregateExpression::Function::kBitOr: {
-            type_ = Value::Type::INT;
-            break;
-        }
-        case AggregateExpression::Function::kBitXor: {
-            type_ = Value::Type::INT;
-            break;
-        }
-        case AggregateExpression::Function::kCollect: {
-            type_ = Value::Type::LIST;
-            break;
-        }
-        case AggregateExpression::Function::kCollectSet: {
-            type_ = Value::Type::SET;
-            break;
-        }
-        default: {
-            LOG(FATAL) << "Invalid Aggregate expression kind: "
-                       << expr->name()->c_str();
-            break;
-        }
-    }
+    type_ = Value::Type::__EMPTY__;
 }
 
 void DeduceTypeVisitor::visit(UUIDExpression *) {
diff --git a/src/visitor/test/CMakeLists.txt b/src/visitor/test/CMakeLists.txt
index cb009d7801fcbf5daa3715af3cdf4a54a4895cfa..fb9a87f14b923f95c266d9536cf71ac75386181c 100644
--- a/src/visitor/test/CMakeLists.txt
+++ b/src/visitor/test/CMakeLists.txt
@@ -42,6 +42,7 @@ nebula_add_test(
         $<TARGET_OBJECTS:common_meta_client_obj>
         $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
         $<TARGET_OBJECTS:common_function_manager_obj>
+        $<TARGET_OBJECTS:common_agg_function_manager_obj>
         $<TARGET_OBJECTS:common_conf_obj>
         $<TARGET_OBJECTS:common_encryption_obj>
         $<TARGET_OBJECTS:common_http_client_obj>
diff --git a/tests/tck/features/agg/Agg.feature b/tests/tck/features/agg/Agg.feature
index 72cb247a9dd9e407d27c0e98ad1c0232babde0d8..7bb2f02f70fa2f731471fd42dbc00e90692f76d4 100644
--- a/tests/tck/features/agg/Agg.feature
+++ b/tests/tck/features/agg/Agg.feature
@@ -13,10 +13,10 @@ Feature: Basic Aggregate and GroupBy
       | 1        | 2     |
     When executing query:
       """
-      YIELD count(*)+1 ,1+2 ,(INT)abs(count(2))
+      YIELD COUNT(*)+1 ,1+2 ,(INT)abs(count(2))
       """
     Then the result should be, in any order, with relax comparison:
-      | (COUNT(*)+1) | (1+2) | (INT)abs(COUNT(2)) |
+      | (COUNT(*)+1) | (1+2) | (INT)abs(count(2)) |
       | 2            | 3     | 1                  |
 
   Scenario: [1] Basic GroupBy
@@ -473,7 +473,7 @@ Feature: Basic Aggregate and GroupBy
       """
       YIELD avg(*)+1 ,1+2 ,(INT)abs(min(2))
       """
-    Then a SemanticError should be raised at runtime: Could not apply aggregation function `AVG(*)' on `*`
+    Then a SyntaxError should be raised at runtime: Could not apply aggregation function on `*` near `avg'
     When executing query:
       """
       GO FROM "Tim Duncan" OVER like YIELD like._dst AS dst, $$.player.age AS age
@@ -503,19 +503,19 @@ Feature: Basic Aggregate and GroupBy
       GO FROM "Tim Duncan" OVER like YIELD like._dst AS dst, $$.player.age AS age
       | GROUP BY $-.age+1 YIELD $-.age+1,abs(avg(distinct count($-.age))) AS age
       """
-    Then a SemanticError should be raised at runtime: Aggregate function nesting is not allowed: `abs(AVG(distinct COUNT($-.age)))'
+    Then a SemanticError should be raised at runtime: Aggregate function nesting is not allowed: `abs(avg(distinct count($-.age)))'
     When executing query:
       """
       GO FROM "Tim Duncan" OVER like YIELD like._dst AS dst, $$.player.age AS age
       | GROUP BY $-.age+1 YIELD $-.age+1,avg(distinct count($-.age+1)) AS age
       """
-    Then a SemanticError should be raised at runtime: Aggregate function nesting is not allowed: `AVG(distinct COUNT(($-.age+1)))'
+    Then a SemanticError should be raised at runtime: Aggregate function nesting is not allowed: `avg(distinct count(($-.age+1)))'
     When executing query:
       """
       GO FROM "Tim Duncan" OVER like YIELD like._dst AS dst, $$.player.age AS age
       | GROUP BY avg($-.age+1)+1 YIELD $-.age,avg(distinct $-.age) AS age
       """
-    Then a SemanticError should be raised at runtime:  Group `(AVG(($-.age+1))+1)' invalid
+    Then a SemanticError should be raised at runtime:  Group `(avg(($-.age+1))+1)' invalid
     When executing query:
       """
       $var=GO FROM "Tim Duncan" OVER like YIELD like._dst AS dst, $$.player.age AS age;
@@ -537,7 +537,7 @@ Feature: Basic Aggregate and GroupBy
     Then a SemanticError should be raised at runtime: `COUNT(*)', not support aggregate function in go sentence.
     When executing query:
       """
-      GO FROM "Tim Duncan" OVER like where count(*) > 2
+      GO FROM "Tim Duncan" OVER like where COUNT(*) > 2
       """
     Then a SemanticError should be raised at runtime: `(COUNT(*)>2)', not support aggregate function in where sentence.
     When executing query:
@@ -585,7 +585,7 @@ Feature: Basic Aggregate and GroupBy
          | GROUP BY $-.name
            YIELD SUM(*)
       """
-    Then a SemanticError should be raised at runtime:  Could not apply aggregation function `SUM(*)' on `*`
+    Then a SyntaxError should be raised at runtime: Could not apply aggregation function on `*` near `SUM'
     When executing query:
       """
       GO FROM "Marco Belinelli" OVER serve
@@ -594,7 +594,7 @@ Feature: Basic Aggregate and GroupBy
          | GROUP BY $-.name
            YIELD COUNT($-.name, $-.id)
       """
-    Then a SyntaxError should be raised at runtime: syntax error near `, $-.id)'
+    Then a SyntaxError should be raised at runtime: Unknown function  near `COUNT'
     When executing query:
       """
       GO FROM "Marco Belinelli" OVER serve
diff --git a/tests/tck/features/go/GroupbyLimit.IntVid.feature b/tests/tck/features/go/GroupbyLimit.IntVid.feature
index 5bceeed297bcc490be6003d439f861e6224d5dc0..b6d95cf403bde21f54e696ec2ff40070950cfcd6 100644
--- a/tests/tck/features/go/GroupbyLimit.IntVid.feature
+++ b/tests/tck/features/go/GroupbyLimit.IntVid.feature
@@ -50,7 +50,7 @@ Feature: Groupby & limit Sentence
       """
       GO FROM hash("Marco Belinelli") OVER serve YIELD $$.team.name AS name, serve._dst AS id | GROUP BY $-.name YIELD SUM(*)
       """
-    Then a SemanticError should be raised at runtime:
+    Then a SyntaxError should be raised at runtime:
 
   Scenario: Syntax test9
     When executing query:
@@ -263,10 +263,10 @@ Feature: Groupby & limit Sentence
       YIELD $$.player.name AS name, $$.player.age AS dst_age, $$.player.age AS src_age, like.likeness AS likeness
       | GROUP BY $-.name YIELD $-.name AS name, SUM($-.dst_age) AS sum_dst_age, AVG($-.dst_age) AS avg_dst_age,
       MAX($-.src_age) AS max_src_age, MIN($-.src_age) AS min_src_age, BIT_AND(1) AS bit_and, BIT_OR(2) AS bit_or, BIT_XOR(3) AS bit_xor,
-      COUNT($-.likeness), COUNT_DISTINCT($-.likeness)
+      COUNT($-.likeness), COUNT(DISTINCT $-.likeness)
       """
     Then the result should be, in any order, with relax comparison:
-      | name              | sum_dst_age | avg_dst_age | max_src_age | min_src_age | bit_and | bit_or | bit_xor | COUNT($-.likeness) | COUNT_DISTINCT($-.likeness) |
+      | name              | sum_dst_age | avg_dst_age | max_src_age | min_src_age | bit_and | bit_or | bit_xor | COUNT($-.likeness) | COUNT(DISTINCT $-.likeness) |
       | "Carmelo Anthony" | 34          | 34.0        | 34          | 34          | 1       | 2      | 3       | 1                  | 1                           |
       | "Dwyane Wade"     | 37          | 37.0        | 37          | 37          | 1       | 2      | 3       | 1                  | 1                           |
       | "Chris Paul"      | 66          | 33.0        | 33          | 33          | 1       | 2      | 0       | 2                  | 1                           |
diff --git a/tests/tck/features/go/GroupbyLimit.feature b/tests/tck/features/go/GroupbyLimit.feature
index fb2a65cb49150d885106af0841a63fb82ae33ecc..8bde7d5e409c054540625f48f17c95b686d1cdde 100644
--- a/tests/tck/features/go/GroupbyLimit.feature
+++ b/tests/tck/features/go/GroupbyLimit.feature
@@ -57,7 +57,7 @@ Feature: Groupby & limit Sentence
       """
       GO FROM "Marco Belinelli" OVER serve YIELD $$.team.name AS name, serve._dst AS id | GROUP BY $-.name YIELD SUM(*)
       """
-    Then a SemanticError should be raised at runtime:
+    Then a SyntaxError should be raised at runtime:
 
   Scenario: Syntax test9
     When executing query:
@@ -278,14 +278,14 @@ Feature: Groupby & limit Sentence
       YIELD $$.player.name AS name, $$.player.age AS dst_age, $$.player.age AS src_age, like.likeness AS likeness
       | GROUP BY $-.name YIELD $-.name AS name, SUM($-.dst_age) AS sum_dst_age, AVG($-.dst_age) AS avg_dst_age,
       MAX($-.src_age) AS max_src_age, MIN($-.src_age) AS min_src_age, BIT_AND(1) AS bit_and, BIT_OR(2) AS bit_or, BIT_XOR(3) AS bit_xor,
-      COUNT($-.likeness), COUNT_DISTINCT($-.likeness)
+      COUNT($-.likeness), COUNT(DISTINCT $-.likeness)
       """
     Then the result should be, in any order, with relax comparison:
-      | name              | sum_dst_age | avg_dst_age | max_src_age | min_src_age | bit_and | bit_or | bit_xor | COUNT($-.likeness) | COUNT_DISTINCT($-.likeness) |
-      | "Carmelo Anthony" | 34          | 34.0        | 34          | 34          | 1       | 2      | 3       | 1                  | 1                           |
-      | "Dwyane Wade"     | 37          | 37.0        | 37          | 37          | 1       | 2      | 3       | 1                  | 1                           |
-      | "Chris Paul"      | 66          | 33.0        | 33          | 33          | 1       | 2      | 0       | 2                  | 1                           |
-      | "LeBron James"    | 68          | 34.0        | 34          | 34          | 1       | 2      | 0       | 2                  | 1                           |
+      | name              | sum_dst_age | avg_dst_age | max_src_age | min_src_age | bit_and | bit_or | bit_xor | COUNT($-.likeness) | COUNT(DINSTINCT $-.likeness) |
+      | "Carmelo Anthony" | 34          | 34.0        | 34          | 34          | 1       | 2      | 3       | 1                  | 1                            |
+      | "Dwyane Wade"     | 37          | 37.0        | 37          | 37          | 1       | 2      | 3       | 1                  | 1                            |
+      | "Chris Paul"      | 66          | 33.0        | 33          | 33          | 1       | 2      | 0       | 2                  | 1                            |
+      | "LeBron James"    | 68          | 34.0        | 34          | 34          | 1       | 2      | 0       | 2                  | 1                            |
 
   Scenario: Groupby works with orderby or limit test
     When executing query:
diff --git a/tests/tck/features/match/MatchById.feature b/tests/tck/features/match/MatchById.feature
index b5bc328088de14cf811278be2477c49b8607834c..8216cadebb6c7f7793042acef38a4d8328b373d4 100644
--- a/tests/tck/features/match/MatchById.feature
+++ b/tests/tck/features/match/MatchById.feature
@@ -908,7 +908,7 @@ Feature: Match By Id
       RETURN 1 | YIELD count(1)
       """
     Then the result should be, in any order, with relax comparison:
-      | COUNT(1) |
+      | count(1) |
       | 292      |
     When executing query:
       """
@@ -916,7 +916,7 @@ Feature: Match By Id
       RETURN 1 | YIELD count(1)
       """
     Then the result should be, in any order, with relax comparison:
-      | COUNT(1) |
+      | count(1) |
       | 927      |
 
   Scenario: the referred vertex of id fn is not the first
diff --git a/tests/tck/features/match/WithUnwind.feature b/tests/tck/features/match/WithUnwind.feature
index 4fb1b9c591ec20ae3e8ee0b1c59b28d213d869a7..41812c09f6a562719c2b144540824bd3060bc3de 100644
--- a/tests/tck/features/match/WithUnwind.feature
+++ b/tests/tck/features/match/WithUnwind.feature
@@ -52,7 +52,7 @@ Feature: With clause and Unwind clause
       RETURN count(a)
       """
     Then the result should be, in any order:
-      | COUNT(a) |
+      | count(a) |
       | 1        |
 
   Scenario: match with return