diff --git a/src/context/Iterator.h b/src/context/Iterator.h
index 8f0f22f69f4da0217c7c7340fcdd8e3f6aac25e1..e17f8b4b2fe2d5c1907c1a2ae0dbd8a45e390d89 100644
--- a/src/context/Iterator.h
+++ b/src/context/Iterator.h
@@ -116,17 +116,15 @@ public:
     // The derived class should rewrite get prop if the Value is kind of dataset.
     virtual const Value& getColumn(const std::string& col) const = 0;
 
-    virtual const Value& getTagProp(const std::string& tag,
-                                    const std::string& prop) const {
-        UNUSED(tag);
-        UNUSED(prop);
+    virtual const Value& getTagProp(const std::string&,
+                                    const std::string&) const {
+        DLOG(FATAL) << "Shouldn't call the unimplemented method";
         return Value::kEmpty;
     }
 
-    virtual const Value& getEdgeProp(const std::string& edge,
-                                     const std::string& prop) const {
-        UNUSED(edge);
-        UNUSED(prop);
+    virtual const Value& getEdgeProp(const std::string&,
+                                     const std::string&) const {
+        DLOG(FATAL) << "Shouldn't call the unimplemented method";
         return Value::kEmpty;
     }
 
diff --git a/src/exec/query/GetPropExecutor.h b/src/exec/query/GetPropExecutor.h
index 18e4413e54f7ca0f2ca80569554a663fe1ce344f..6c6ec88585baebd00bb01ad91bfdec48c6cbb7f9 100644
--- a/src/exec/query/GetPropExecutor.h
+++ b/src/exec/query/GetPropExecutor.h
@@ -4,7 +4,8 @@
  * attached with Common Clause Condition 1.0, found in the LICENSES directory.
  */
 
-#pragma once
+#ifndef _EXEC_QUERY_GET_PROP_EXECUTOR_H_
+#define _EXEC_QUERY_GET_PROP_EXECUTOR_H_
 
 #include "exec/Executor.h"
 #include "common/clients/storage/StorageClientBase.h"
@@ -69,3 +70,5 @@ protected:
 
 }   // namespace graph
 }   // namespace nebula
+
+#endif  // _EXEC_QUERY_GET_PROP_EXECUTOR_H_
diff --git a/src/exec/query/GetVerticesExecutor.cpp b/src/exec/query/GetVerticesExecutor.cpp
index 2eabc4795efc72ef8feed6d8e3023412510ea442..26549f502bd643183c1a748b4d02c77d908b8e66 100644
--- a/src/exec/query/GetVerticesExecutor.cpp
+++ b/src/exec/query/GetVerticesExecutor.cpp
@@ -32,15 +32,13 @@ folly::Future<Status> GetVerticesExecutor::getVertices() {
     nebula::DataSet vertices({kVid});
     std::unordered_set<Value> uniqueVid;
     if (!gv->vertices().empty()) {
+        // TODO(shylock) not dedup in here, do it when generate plan
         for (auto& v : gv->vertices()) {
             auto ret = uniqueVid.emplace(v.values.front());
             if (ret.second) {
                 vertices.emplace_back(std::move(v));
             }
         }
-        vertices.rows.insert(vertices.rows.end(),
-                             std::make_move_iterator(gv->vertices().begin()),
-                             std::make_move_iterator(gv->vertices().end()));
     }
     if (gv->src() != nullptr) {
         // Accept Table such as | $a | $b | $c |... as input which one column indicate src
diff --git a/src/exec/query/test/FilterTest.cpp b/src/exec/query/test/FilterTest.cpp
index 064e30410a19807ae7a816757863ee057ff1b906..7eb381dff91f1341957ec14f7b6f4463021d3402 100644
--- a/src/exec/query/test/FilterTest.cpp
+++ b/src/exec/query/test/FilterTest.cpp
@@ -6,10 +6,11 @@
 #include <gtest/gtest.h>
 
 #include "context/QueryContext.h"
-#include "planner/Query.h"
 #include "exec/query/FilterExecutor.h"
-#include "exec/query/test/QueryTestBase.h"
 #include "exec/query/ProjectExecutor.h"
+#include "exec/query/test/QueryTestBase.h"
+#include "planner/Query.h"
+#include "util/ExpressionUtils.h"
 
 namespace nebula {
 namespace graph {
@@ -21,32 +22,49 @@ public:
     }
 };
 
-#define FILTER_RESUTL_CHECK(inputName, outputName, sentence, expected)         \
-    do {                                                                       \
-        auto* plan = qctx_->plan();                                            \
-        auto yieldSentence = getYieldSentence(sentence);                       \
-        auto* filterNode =                                                     \
-            Filter::make(plan, nullptr, yieldSentence->where()->filter());     \
-        filterNode->setInputVar(inputName);                                    \
-        filterNode->setOutputVar(outputName);                                  \
-        auto filterExec =                                                      \
-            std::make_unique<FilterExecutor>(filterNode, qctx_.get());         \
-        EXPECT_TRUE(filterExec->execute().get().ok());                         \
-        auto& filterResult = qctx_->ectx()->getResult(filterNode->varName());  \
-        EXPECT_EQ(filterResult.state(), Result::State::kSuccess);              \
-                                                                               \
-        filterNode->setInputVar(outputName);                                   \
-        auto* project =                                                        \
-            Project::make(plan, nullptr, yieldSentence->yieldColumns());       \
-        project->setInputVar(filterNode->varName());                           \
-        project->setColNames(std::vector<std::string>{"name"});                \
-                                                                               \
-        auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get()); \
-        EXPECT_TRUE(proExe->execute().get().ok());                             \
-        auto& proSesult = qctx_->ectx()->getResult(project->varName());        \
-                                                                               \
-        EXPECT_EQ(proSesult.value().getDataSet(), expected);                   \
-        EXPECT_EQ(proSesult.state(), Result::State::kSuccess);                 \
+#define FILTER_RESUTL_CHECK(inputName, outputName, sentence, expected)                             \
+    do {                                                                                           \
+        auto* plan = qctx_->plan();                                                                \
+        auto yieldSentence = getYieldSentence(sentence);                                           \
+        auto columns = yieldSentence->columns();                                                   \
+        for (auto& col : columns) {                                                                \
+            if (col->expr()->kind() == Expression::Kind::kSymProperty) {                           \
+                auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr());             \
+                col->setExpr(                                                                      \
+                    ExpressionUtils ::transSymbolPropertyExpression<EdgePropertyExpression>(       \
+                        symbolExpr));                                                              \
+            } else {                                                                               \
+                ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr());  \
+            }                                                                                      \
+        }                                                                                          \
+        auto* filter = yieldSentence->where()->filter();                                           \
+        if (filter->kind() == Expression::Kind::kSymProperty) {                                    \
+            auto symbolExpr = static_cast<SymbolPropertyExpression*>(filter);                      \
+            yieldSentence->where()->setFilter(                                                     \
+                ExpressionUtils ::transSymbolPropertyExpression<EdgePropertyExpression>(           \
+                    symbolExpr));                                                                  \
+        } else {                                                                                   \
+            ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(filter);           \
+        }                                                                                          \
+        auto* filterNode = Filter::make(plan, nullptr, yieldSentence->where()->filter());          \
+        filterNode->setInputVar(inputName);                                                        \
+        filterNode->setOutputVar(outputName);                                                      \
+        auto filterExec = std::make_unique<FilterExecutor>(filterNode, qctx_.get());               \
+        EXPECT_TRUE(filterExec->execute().get().ok());                                             \
+        auto& filterResult = qctx_->ectx()->getResult(filterNode->varName());                      \
+        EXPECT_EQ(filterResult.state(), Result::State::kSuccess);                                  \
+                                                                                                   \
+        filterNode->setInputVar(outputName);                                                       \
+        auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns());               \
+        project->setInputVar(filterNode->varName());                                               \
+        project->setColNames(std::vector<std::string>{"name"});                                    \
+                                                                                                   \
+        auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get());                     \
+        EXPECT_TRUE(proExe->execute().get().ok());                                                 \
+        auto& proSesult = qctx_->ectx()->getResult(project->varName());                            \
+                                                                                                   \
+        EXPECT_EQ(proSesult.value().getDataSet(), expected);                                       \
+        EXPECT_EQ(proSesult.state(), Result::State::kSuccess);                                     \
     } while (false)
 
 TEST_F(FilterTest, TestGetNeighbors_src_dst) {
@@ -72,10 +90,8 @@ TEST_F(FilterTest, TestSequential) {
 
 TEST_F(FilterTest, TestNullValue) {
     DataSet expected({"name"});
-    FILTER_RESUTL_CHECK("input_sequential",
-                        "filter_sequential",
-                        "YIELD $-.v_name AS name WHERE NULL",
-                        expected);
+    FILTER_RESUTL_CHECK(
+        "input_sequential", "filter_sequential", "YIELD $-.v_name AS name WHERE NULL", expected);
 }
 
 TEST_F(FilterTest, TestEmpty) {
@@ -85,5 +101,5 @@ TEST_F(FilterTest, TestEmpty) {
                         "YIELD $^.person.name AS name WHERE study.start_year >= 2010",
                         expected);
 }
-}  // namespace graph
-}  // namespace nebula
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/exec/query/test/LimitTest.cpp b/src/exec/query/test/LimitTest.cpp
index 7e33ecd61b0a8217487da2bd0ff7f9fb33497750..3ab2c9a54af4f857cccae10ba8e1b1ab0578a5ec 100644
--- a/src/exec/query/test/LimitTest.cpp
+++ b/src/exec/query/test/LimitTest.cpp
@@ -6,6 +6,7 @@
 
 #include <gtest/gtest.h>
 
+#include "util/ExpressionUtils.h"
 #include "context/QueryContext.h"
 #include "planner/Query.h"
 #include "exec/query/LimitExecutor.h"
@@ -17,26 +18,37 @@ namespace graph {
 class LimitTest : public QueryTestBase {
 };
 
-#define LIMIT_RESUTL_CHECK(outputName, offset, count, expected)                       \
-    do {                                                                              \
-        auto* plan = qctx_->plan();                                                   \
-        auto* limitNode = Limit::make(plan, nullptr, offset, count);                  \
-        limitNode->setInputVar("input_neighbor");                                     \
-        limitNode->setOutputVar(outputName);                                          \
-        auto limitExec = std::make_unique<LimitExecutor>(limitNode, qctx_.get());     \
-        EXPECT_TRUE(limitExec->execute().get().ok());                                 \
-        auto& limitResult = qctx_->ectx()->getResult(limitNode->varName());           \
-        EXPECT_EQ(limitResult.state(), Result::State::kSuccess);                      \
-        auto yieldSentence = getYieldSentence(                                        \
-                "YIELD study._dst AS name, study.start_year AS start");               \
-        auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns());  \
-        project->setInputVar(limitNode->varName());                                   \
-        project->setColNames(std::vector<std::string>{"name", "start"});              \
-        auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get());        \
-        EXPECT_TRUE(proExe->execute().get().ok());                                    \
-        auto& proResult = qctx_->ectx()->getResult(project->varName());               \
-        EXPECT_EQ(proResult.value().getDataSet(), expected);                          \
-        EXPECT_EQ(proResult.state(), Result::State::kSuccess);                        \
+#define LIMIT_RESUTL_CHECK(outputName, offset, count, expected)                           \
+    do {                                                                                  \
+        auto* plan = qctx_->plan();                                                       \
+        auto* limitNode = Limit::make(plan, nullptr, offset, count);                      \
+        limitNode->setInputVar("input_neighbor");                                         \
+        limitNode->setOutputVar(outputName);                                              \
+        auto limitExec = std::make_unique<LimitExecutor>(limitNode, qctx_.get());         \
+        EXPECT_TRUE(limitExec->execute().get().ok());                                     \
+        auto& limitResult = qctx_->ectx()->getResult(limitNode->varName());               \
+        EXPECT_EQ(limitResult.state(), Result::State::kSuccess);                          \
+        auto yieldSentence = getYieldSentence(                                            \
+                "YIELD study._dst AS name, study.start_year AS start");                   \
+        auto columns = yieldSentence->columns();                                          \
+        for (auto& col : columns) {                                                       \
+            if (col->expr()->kind() == Expression::Kind::kSymProperty) {                  \
+                auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr());    \
+                col->setExpr(ExpressionUtils                                              \
+                    ::transSymbolPropertyExpression<EdgePropertyExpression>(symbolExpr)); \
+            } else {                                                                      \
+                ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(      \
+                    col->expr());                                                         \
+            }                                                                             \
+        }                                                                                 \
+        auto* project = Project::make(plan, nullptr, yieldSentence->yieldColumns());      \
+        project->setInputVar(limitNode->varName());                                       \
+        project->setColNames(std::vector<std::string>{"name", "start"});                  \
+        auto proExe = std::make_unique<ProjectExecutor>(project, qctx_.get());            \
+        EXPECT_TRUE(proExe->execute().get().ok());                                        \
+        auto& proResult = qctx_->ectx()->getResult(project->varName());                   \
+        EXPECT_EQ(proResult.value().getDataSet(), expected);                              \
+        EXPECT_EQ(proResult.state(), Result::State::kSuccess);                            \
     } while (false)
 
 
diff --git a/src/parser/Clauses.cpp b/src/parser/Clauses.cpp
index 9d5f263dc7c6926a17374039e34dcbdc18ca4c4e..85829799910da00efc6b0976e3f1eb110e510ded 100644
--- a/src/parser/Clauses.cpp
+++ b/src/parser/Clauses.cpp
@@ -155,21 +155,19 @@ std::string YieldColumns::toString() const {
 }
 
 bool operator==(const YieldColumn &l, const YieldColumn &r) {
-    if (l.alias() == nullptr && r.alias() == nullptr) {
-    } else if (l.alias() != nullptr && r.alias() != nullptr) {
+    if (l.alias() != nullptr && r.alias() != nullptr) {
         if (*l.alias() != *r.alias()) {
             return false;
         }
-    } else {
+    } else if (l.alias() != r.alias()) {
         return false;
     }
 
-    if (l.expr() == nullptr && r.expr() == nullptr) {
-    } else if (l.expr() != nullptr && r.expr() != nullptr) {
+    if (l.expr() != nullptr && r.expr() != nullptr) {
         if (*l.expr() != *r.expr()) {
             return false;
         }
-    } else {
+    } else if (l.expr() != r.expr()) {
         return false;
     }
 
diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h
index fdec0ca2bea9dcd4d9c640e4b06124fb377ebde6..6946241fe1b5788e4782c66f74860b821b829806 100644
--- a/src/parser/Clauses.h
+++ b/src/parser/Clauses.h
@@ -9,6 +9,7 @@
 #include "common/base/Base.h"
 #include "common/expression/Expression.h"
 #include "common/interface/gen-cpp2/storage_types.h"
+#include "util/ExpressionUtils.h"
 
 namespace nebula {
 class StepClause final {
@@ -203,6 +204,10 @@ public:
         return filter_.get();
     }
 
+    void setFilter(Expression* expr) {
+        filter_.reset(expr);
+    }
+
     std::string toString() const;
 
 private:
@@ -218,10 +223,30 @@ public:
         alias_.reset(alias);
     }
 
+    std::unique_ptr<YieldColumn> clone() const {
+        auto col = std::make_unique<YieldColumn>(
+            graph::ExpressionUtils::clone(expr_.get()).release());
+        if (alias_ != nullptr) {
+            col->setAlias(new std::string(*alias_));
+        }
+        if (aggFunName_ != nullptr) {
+            col->setAggFunction(new std::string(*aggFunName_));
+        }
+        return col;
+    }
+
+    void setExpr(Expression* expr) {
+        expr_.reset(expr);
+    }
+
     Expression* expr() const {
         return expr_.get();
     }
 
+    void setAlias(std::string* alias) {
+        alias_.reset(alias);
+    }
+
     std::string* alias() const {
         return alias_.get();
     }
diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h
index 75bdfb103921b83fe77c5f60dfec183bedb6d63c..e9a06fad044dbfb13deebac916f66320b1927ae1 100644
--- a/src/parser/TraverseSentences.h
+++ b/src/parser/TraverseSentences.h
@@ -56,10 +56,18 @@ public:
         return whereClause_.get();
     }
 
+    WhereClause* whereClause() {
+        return whereClause_.get();
+    }
+
     const YieldClause* yieldClause() const {
         return yieldClause_.get();
     }
 
+    YieldClause* yieldClause() {
+        return yieldClause_.get();
+    }
+
     std::string toString() const override;
 
 private:
diff --git a/src/parser/parser.yy b/src/parser/parser.yy
index 6489c8b6eddd7d846c97cedb0e819c068b8608b1..99f8ca2d09f96e5796b47a1e23182860852d4156 100644
--- a/src/parser/parser.yy
+++ b/src/parser/parser.yy
@@ -424,7 +424,11 @@ var_ref_expression
 
 alias_ref_expression
     : name_label DOT name_label {
-        $$ = new EdgePropertyExpression($1, $3);
+        // determine the detail in later stage
+        $$ = new SymbolPropertyExpression(Expression::Kind::kSymProperty,
+                                          new std::string(""),
+                                          $1,
+                                          $3);
     }
     | name_label DOT TYPE_PROP {
         $$ = new EdgeTypeExpression($1);
diff --git a/src/util/ExpressionUtils.h b/src/util/ExpressionUtils.h
new file mode 100644
index 0000000000000000000000000000000000000000..ede22afbc4621f9e04121286c4b99944fbc57ffb
--- /dev/null
+++ b/src/util/ExpressionUtils.h
@@ -0,0 +1,337 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef _UTIL_EXPRESSION_UTILS_H_
+#define _UTIL_EXPRESSION_UTILS_H_
+
+#include "common/expression/BinaryExpression.h"
+#include "common/expression/Expression.h"
+#include "common/expression/FunctionCallExpression.h"
+#include "common/expression/SymbolPropertyExpression.h"
+#include "common/expression/TypeCastingExpression.h"
+#include "common/expression/UnaryExpression.h"
+
+namespace nebula {
+namespace graph {
+
+class ExpressionUtils {
+public:
+    explicit ExpressionUtils(...) = delete;
+
+    // return true for continue, false return directly
+    using Visitor = std::function<bool(const Expression*)>;
+    using MutableVisitor = std::function<bool(Expression*)>;
+
+    // preorder traverse in fact for tail call optimization
+    // if want to do some thing like eval, don't try it
+    template <typename T,
+              typename V,
+              typename = std::enable_if_t<std::is_same<std::remove_const_t<T>, Expression>::value>>
+    static bool traverse(T* expr, V visitor) {
+        if (!visitor(expr)) {
+            return false;
+        }
+        switch (expr->kind()) {
+            case Expression::Kind::kDstProperty:
+            case Expression::Kind::kSrcProperty:
+            case Expression::Kind::kTagProperty:
+            case Expression::Kind::kEdgeProperty:
+            case Expression::Kind::kEdgeSrc:
+            case Expression::Kind::kEdgeType:
+            case Expression::Kind::kEdgeRank:
+            case Expression::Kind::kEdgeDst:
+            case Expression::Kind::kInputProperty:
+            case Expression::Kind::kVarProperty:
+            case Expression::Kind::kUUID:
+            case Expression::Kind::kVar:
+            case Expression::Kind::kVersionedVar:
+            case Expression::Kind::kSymProperty:
+            case Expression::Kind::kConstant: {
+                return true;
+            }
+            case Expression::Kind::kAdd:
+            case Expression::Kind::kMinus:
+            case Expression::Kind::kMultiply:
+            case Expression::Kind::kDivision:
+            case Expression::Kind::kMod:
+            case Expression::Kind::kRelEQ:
+            case Expression::Kind::kRelNE:
+            case Expression::Kind::kRelLT:
+            case Expression::Kind::kRelLE:
+            case Expression::Kind::kRelGT:
+            case Expression::Kind::kRelGE:
+            case Expression::Kind::kLogicalAnd:
+            case Expression::Kind::kLogicalOr:
+            case Expression::Kind::kLogicalXor: {
+                using ToType = keep_const_t<T, BinaryExpression>;
+                auto biExpr = static_cast<ToType*>(expr);
+                if (!traverse(biExpr->left(), visitor)) {
+                    return false;
+                }
+                return traverse(biExpr->right(), visitor);
+            }
+            case Expression::Kind::kRelIn:
+            case Expression::Kind::kUnaryIncr:
+            case Expression::Kind::kUnaryDecr:
+            case Expression::Kind::kUnaryPlus:
+            case Expression::Kind::kUnaryNegate:
+            case Expression::Kind::kUnaryNot: {
+                using ToType = keep_const_t<T, UnaryExpression>;
+                auto unaryExpr = static_cast<ToType*>(expr);
+                return traverse(unaryExpr->operand(), visitor);
+            }
+            case Expression::Kind::kTypeCasting: {
+                using ToType = keep_const_t<T, TypeCastingExpression>;
+                auto typeCastingExpr = static_cast<ToType*>(expr);
+                return traverse(typeCastingExpr->operand(), visitor);
+            }
+            case Expression::Kind::kFunctionCall: {
+                using ToType = keep_const_t<T, FunctionCallExpression>;
+                auto funcExpr = static_cast<ToType*>(expr);
+                for (auto& arg : funcExpr->args()->args()) {
+                    if (!traverse(arg.get(), visitor)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        DLOG(FATAL) << "Impossible expression kind " << static_cast<int>(expr->kind());
+        return false;
+    }
+
+    static inline bool isKindOf(const Expression* expr,
+                                const std::unordered_set<Expression::Kind>& expected) {
+        return expected.find(expr->kind()) != expected.end();
+    }
+
+    // null for not found
+    static const Expression* findAnyKind(const Expression* self,
+                                         const std::unordered_set<Expression::Kind>& expected) {
+        const Expression* found = nullptr;
+        traverse(self, [&expected, &found](const Expression* expr) -> bool {
+            if (isKindOf(expr, expected)) {
+                found = expr;
+                return false;   // Already find so return now
+            }
+            return true;   // Not find so continue traverse
+        });
+        return found;
+    }
+
+    // Find all expression fit any kind
+    // Empty for not found any one
+    static std::vector<const Expression*> findAnyKindInAll(
+        const Expression* self,
+        const std::unordered_set<Expression::Kind>& expected) {
+        std::vector<const Expression*> exprs;
+        traverse(self, [&expected, &exprs](const Expression* expr) -> bool {
+            if (isKindOf(expr, expected)) {
+                exprs.emplace_back(expr);
+            }
+            return true;   // Not return always to traverse entire expression tree
+        });
+        return exprs;
+    }
+
+    static bool hasAnyKind(const Expression* expr,
+                           const std::unordered_set<Expression::Kind>& expected) {
+        return findAnyKind(expr, expected) != nullptr;
+    }
+
+    // Require data from input/variable
+    static bool hasInput(const Expression* expr) {
+        return hasAnyKind(expr,
+                          {Expression::Kind::kInputProperty,
+                           Expression::Kind::kVarProperty,
+                           Expression::Kind::kVar,
+                           Expression::Kind::kVersionedVar});
+    }
+
+    // require data from graph storage
+    static const Expression* findStorage(const Expression* expr) {
+        return findAnyKind(expr,
+                           {Expression::Kind::kSymProperty,
+                            Expression::Kind::kTagProperty,
+                            Expression::Kind::kEdgeProperty,
+                            Expression::Kind::kDstProperty,
+                            Expression::Kind::kSrcProperty,
+                            Expression::Kind::kEdgeSrc,
+                            Expression::Kind::kEdgeType,
+                            Expression::Kind::kEdgeRank,
+                            Expression::Kind::kEdgeDst});
+    }
+
+    static std::vector<const Expression*> findAllStorage(const Expression* expr) {
+        return findAnyKindInAll(expr,
+                                {Expression::Kind::kSymProperty,
+                                 Expression::Kind::kTagProperty,
+                                 Expression::Kind::kEdgeProperty,
+                                 Expression::Kind::kDstProperty,
+                                 Expression::Kind::kSrcProperty,
+                                 Expression::Kind::kEdgeSrc,
+                                 Expression::Kind::kEdgeType,
+                                 Expression::Kind::kEdgeRank,
+                                 Expression::Kind::kEdgeDst});
+    }
+
+    static std::vector<const Expression*> findAllInputVariableProp(const Expression* expr) {
+        return findAnyKindInAll(expr,
+                                {Expression::Kind::kInputProperty, Expression::Kind::kVarProperty});
+    }
+
+    static bool hasStorage(const Expression* expr) {
+        return findStorage(expr) != nullptr;
+    }
+
+    static bool isStorage(const Expression* expr) {
+        return isKindOf(expr,
+                        {Expression::Kind::kSymProperty,
+                         Expression::Kind::kTagProperty,
+                         Expression::Kind::kEdgeProperty,
+                         Expression::Kind::kDstProperty,
+                         Expression::Kind::kSrcProperty,
+                         Expression::Kind::kEdgeSrc,
+                         Expression::Kind::kEdgeType,
+                         Expression::Kind::kEdgeRank,
+                         Expression::Kind::kEdgeDst});
+    }
+
+    static bool isConstExpr(const Expression* expr) {
+        return !hasAnyKind(expr,
+                           {Expression::Kind::kInputProperty,
+                            Expression::Kind::kVarProperty,
+                            Expression::Kind::kVar,
+                            Expression::Kind::kVersionedVar,
+
+                            Expression::Kind::kSymProperty,
+                            Expression::Kind::kTagProperty,
+                            Expression::Kind::kEdgeProperty,
+                            Expression::Kind::kDstProperty,
+                            Expression::Kind::kSrcProperty,
+                            Expression::Kind::kEdgeSrc,
+                            Expression::Kind::kEdgeType,
+                            Expression::Kind::kEdgeRank,
+                            Expression::Kind::kEdgeDst});
+    }
+
+    // clone expression
+    static std::unique_ptr<Expression> clone(const Expression* expr) {
+        // TODO(shylock) optimize
+        if (expr == nullptr) {
+            return nullptr;
+        }
+        return CHECK_NOTNULL(Expression::decode(expr->encode()));
+    }
+
+    // determine the detail about symbol property expression
+    template <typename To,
+              typename = std::enable_if_t<std::is_same<To, EdgePropertyExpression>::value ||
+                                          std::is_same<To, TagPropertyExpression>::value>>
+    static void transAllSymbolPropertyExpr(Expression* expr) {
+        traverse(expr, [](Expression* current) -> bool {
+            switch (current->kind()) {
+                case Expression::Kind::kDstProperty:
+                case Expression::Kind::kSrcProperty:
+                case Expression::Kind::kSymProperty:
+                case Expression::Kind::kTagProperty:
+                case Expression::Kind::kEdgeProperty:
+                case Expression::Kind::kEdgeSrc:
+                case Expression::Kind::kEdgeType:
+                case Expression::Kind::kEdgeRank:
+                case Expression::Kind::kEdgeDst:
+                case Expression::Kind::kInputProperty:
+                case Expression::Kind::kVarProperty:
+                case Expression::Kind::kUUID:
+                case Expression::Kind::kVar:
+                case Expression::Kind::kVersionedVar:
+                case Expression::Kind::kConstant: {
+                    return true;
+                }
+                case Expression::Kind::kAdd:
+                case Expression::Kind::kMinus:
+                case Expression::Kind::kMultiply:
+                case Expression::Kind::kDivision:
+                case Expression::Kind::kMod:
+                case Expression::Kind::kRelEQ:
+                case Expression::Kind::kRelNE:
+                case Expression::Kind::kRelLT:
+                case Expression::Kind::kRelLE:
+                case Expression::Kind::kRelGT:
+                case Expression::Kind::kRelGE:
+                case Expression::Kind::kLogicalAnd:
+                case Expression::Kind::kLogicalOr:
+                case Expression::Kind::kLogicalXor: {
+                    auto* biExpr = static_cast<BinaryExpression*>(current);
+                    if (biExpr->left()->kind() == Expression::Kind::kSymProperty) {
+                        auto* symbolExpr = static_cast<SymbolPropertyExpression*>(biExpr->left());
+                        biExpr->setLeft(transSymbolPropertyExpression<To>(symbolExpr));
+                    }
+                    if (biExpr->right()->kind() == Expression::Kind::kSymProperty) {
+                        auto* symbolExpr = static_cast<SymbolPropertyExpression*>(biExpr->right());
+                        biExpr->setRight(transSymbolPropertyExpression<To>(symbolExpr));
+                    }
+                    return true;
+                }
+                case Expression::Kind::kRelIn:
+                case Expression::Kind::kUnaryIncr:
+                case Expression::Kind::kUnaryDecr:
+                case Expression::Kind::kUnaryPlus:
+                case Expression::Kind::kUnaryNegate:
+                case Expression::Kind::kUnaryNot: {
+                    auto* unaryExpr = static_cast<UnaryExpression*>(current);
+                    if (unaryExpr->operand()->kind() == Expression::Kind::kSymProperty) {
+                        auto* symbolExpr =
+                            static_cast<SymbolPropertyExpression*>(unaryExpr->operand());
+                        unaryExpr->setOperand(transSymbolPropertyExpression<To>(symbolExpr));
+                    }
+                    return true;
+                }
+                case Expression::Kind::kTypeCasting: {
+                    auto* typeCastingExpr = static_cast<TypeCastingExpression*>(current);
+                    if (typeCastingExpr->operand()->kind() == Expression::Kind::kSymProperty) {
+                        auto* symbolExpr =
+                            static_cast<SymbolPropertyExpression*>(typeCastingExpr->operand());
+                        typeCastingExpr->setOperand(transSymbolPropertyExpression<To>(symbolExpr));
+                    }
+                    return true;
+                }
+                case Expression::Kind::kFunctionCall: {
+                    auto* funcExpr = static_cast<FunctionCallExpression*>(current);
+                    for (auto& arg : funcExpr->args()->args()) {
+                        if (arg->kind() == Expression::Kind::kSymProperty) {
+                            auto* symbolExpr = static_cast<SymbolPropertyExpression*>(arg.get());
+                            arg.reset(transSymbolPropertyExpression<To>(symbolExpr));
+                        }
+                    }
+                    return true;
+                }
+            }   // switch
+            DLOG(FATAL) << "Impossible expression kind " << static_cast<int>(current->kind());
+            return false;
+        });   // traverse
+    }
+
+    template <typename To,
+              typename = std::enable_if_t<std::is_same<To, EdgePropertyExpression>::value ||
+                                          std::is_same<To, TagPropertyExpression>::value>>
+    static To* transSymbolPropertyExpression(SymbolPropertyExpression* expr) {
+        return new To(new std::string(std::move(*expr->sym())),
+                      new std::string(std::move(*expr->prop())));
+    }
+
+private:
+    // keep const or non-const with T
+    template <typename T, typename To>
+    using keep_const_t = std::conditional_t<std::is_const<T>::value,
+                                            const std::remove_const_t<To>,
+                                            std::remove_const_t<To>>;
+};
+
+}   // namespace graph
+}   // namespace nebula
+
+#endif  // _UTIL_EXPRESSION_UTILS_H_
diff --git a/src/util/test/CMakeLists.txt b/src/util/test/CMakeLists.txt
index b6934dd1adf8a007c1bf5c26bf07cda80ffd89ab..ead3eb1bde62b50717e25790e252fcc0c99f7c9f 100644
--- a/src/util/test/CMakeLists.txt
+++ b/src/util/test/CMakeLists.txt
@@ -1,16 +1,20 @@
 nebula_add_test(
-    NAME
-        util_test
+    NAME utils_test
     SOURCES
+        ExpressionUtilsTest.cpp
         IdGeneratorTest.cpp
         ObjectPoolTest.cpp
         ScopedTimerTest.cpp
     OBJECTS
         $<TARGET_OBJECTS:common_base_obj>
         $<TARGET_OBJECTS:common_concurrent_obj>
+        $<TARGET_OBJECTS:common_datatypes_obj>
+        $<TARGET_OBJECTS:common_expression_obj>
+        $<TARGET_OBJECTS:common_function_manager_obj>
         $<TARGET_OBJECTS:common_time_obj>
         $<TARGET_OBJECTS:idgenerator_obj>
     LIBRARIES
         gtest
         gtest_main
+        ${THRIFT_LIBRARIES}
 )
diff --git a/src/util/test/ExpressionUtilsTest.cpp b/src/util/test/ExpressionUtilsTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b8d2da9da7596e8bec248703b030a1fd86a1ffde
--- /dev/null
+++ b/src/util/test/ExpressionUtilsTest.cpp
@@ -0,0 +1,208 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include <gtest/gtest.h>
+#include "common/expression/ArithmeticExpression.h"
+#include "common/expression/ConstantExpression.h"
+#include "common/expression/TypeCastingExpression.h"
+#include "util/ExpressionUtils.h"
+
+namespace nebula {
+namespace graph {
+
+class ExpressionUtilsTest : public ::testing::Test {};
+
+TEST_F(ExpressionUtilsTest, CheckComponent) {
+    {
+        // single node
+        const auto root = std::make_unique<ConstantExpression>();
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kConstant}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kConstant}));
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kConstant, Expression::Kind::kAdd}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kConstant, Expression::Kind::kAdd}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kAdd}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kDivision, Expression::Kind::kAdd}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kAdd}));
+
+        // find
+        const Expression *found =
+            ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kConstant});
+        ASSERT_EQ(found, root.get());
+
+        found = ExpressionUtils::findAnyKind(
+            root.get(),
+            {Expression::Kind::kConstant, Expression::Kind::kAdd, Expression::Kind::kEdgeProperty});
+        ASSERT_EQ(found, root.get());
+
+        found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kEdgeDst});
+        ASSERT_EQ(found, nullptr);
+
+        found = ExpressionUtils::findAnyKind(
+            root.get(), {Expression::Kind::kEdgeRank, Expression::Kind::kInputProperty});
+        ASSERT_EQ(found, nullptr);
+
+        // find all
+        const auto willFoundAll = std::vector<const Expression *>{root.get()};
+        std::vector<const Expression *> founds =
+            ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant});
+        ASSERT_EQ(founds, willFoundAll);
+
+        founds = ExpressionUtils::findAnyKindInAll(
+            root.get(),
+            {Expression::Kind::kAdd, Expression::Kind::kConstant, Expression::Kind::kEdgeDst});
+        ASSERT_EQ(founds, willFoundAll);
+
+        founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kSrcProperty});
+        ASSERT_TRUE(founds.empty());
+
+        founds = ExpressionUtils::findAnyKindInAll(root.get(),
+                                                   {Expression::Kind::kUnaryNegate,
+                                                    Expression::Kind::kEdgeDst,
+                                                    Expression::Kind::kEdgeDst});
+        ASSERT_TRUE(founds.empty());
+    }
+
+    {
+        // list like
+        const auto root = std::make_unique<TypeCastingExpression>(
+            Value::Type::BOOL,
+            new TypeCastingExpression(
+                Value::Type::BOOL,
+                new TypeCastingExpression(Value::Type::BOOL, new ConstantExpression())));
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kTypeCasting}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kConstant}));
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kAdd}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kDivision, Expression::Kind::kAdd}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kAdd}));
+
+        // found
+        const Expression *found =
+            ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kTypeCasting});
+        ASSERT_EQ(found, root.get());
+
+        found = ExpressionUtils::findAnyKind(root.get(),
+                                             {Expression::Kind::kFunctionCall,
+                                              Expression::Kind::kTypeCasting,
+                                              Expression::Kind::kLogicalAnd});
+        ASSERT_EQ(found, root.get());
+
+        found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kDivision});
+        ASSERT_EQ(found, nullptr);
+
+        found = ExpressionUtils::findAnyKind(root.get(),
+                                             {Expression::Kind::kLogicalXor,
+                                              Expression::Kind::kRelGE,
+                                              Expression::Kind::kEdgeProperty});
+        ASSERT_EQ(found, nullptr);
+
+        // found all
+        std::vector<const Expression *> founds =
+            ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant});
+        ASSERT_EQ(founds.size(), 1);
+
+        founds = ExpressionUtils::findAnyKindInAll(
+            root.get(), {Expression::Kind::kFunctionCall, Expression::Kind::kTypeCasting});
+        ASSERT_EQ(founds.size(), 3);
+
+        founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kAdd});
+        ASSERT_TRUE(founds.empty());
+
+        founds = ExpressionUtils::findAnyKindInAll(
+            root.get(), {Expression::Kind::kRelLE, Expression::Kind::kDstProperty});
+        ASSERT_TRUE(founds.empty());
+    }
+
+    {
+        // tree like
+        const auto root = std::make_unique<ArithmeticExpression>(
+            Expression::Kind::kAdd,
+            new ArithmeticExpression(Expression::Kind::kDivision,
+                                     new ConstantExpression(3),
+                                     new ArithmeticExpression(Expression::Kind::kMinus,
+                                                              new ConstantExpression(4),
+                                                              new ConstantExpression(2))),
+            new ArithmeticExpression(Expression::Kind::kMod,
+                                     new ArithmeticExpression(Expression::Kind::kMultiply,
+                                                              new ConstantExpression(3),
+                                                              new ConstantExpression(10)),
+                                     new ConstantExpression(2)));
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kAdd}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kMinus}));
+
+        ASSERT_TRUE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kTypeCasting, Expression::Kind::kAdd}));
+        ASSERT_TRUE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kSymProperty, Expression::Kind::kDivision}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(root.get(), {Expression::Kind::kConstant}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(root.get(), {Expression::Kind::kFunctionCall}));
+
+        ASSERT_FALSE(ExpressionUtils::isKindOf(
+            root.get(), {Expression::Kind::kDivision, Expression::Kind::kEdgeProperty}));
+        ASSERT_FALSE(ExpressionUtils::hasAnyKind(
+            root.get(), {Expression::Kind::kDstProperty, Expression::Kind::kLogicalAnd}));
+
+        // found
+        const Expression *found =
+            ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kAdd});
+        ASSERT_EQ(found, root.get());
+
+        found = ExpressionUtils::findAnyKind(root.get(),
+                                             {Expression::Kind::kFunctionCall,
+                                              Expression::Kind::kRelLE,
+                                              Expression::Kind::kMultiply});
+        ASSERT_NE(found, nullptr);
+
+        found = ExpressionUtils::findAnyKind(root.get(), {Expression::Kind::kInputProperty});
+        ASSERT_EQ(found, nullptr);
+
+        found = ExpressionUtils::findAnyKind(root.get(),
+                                             {Expression::Kind::kLogicalXor,
+                                              Expression::Kind::kEdgeRank,
+                                              Expression::Kind::kUnaryNot});
+        ASSERT_EQ(found, nullptr);
+
+        // found all
+        std::vector<const Expression *> founds =
+            ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kConstant});
+        ASSERT_EQ(founds.size(), 6);
+
+        founds = ExpressionUtils::findAnyKindInAll(
+            root.get(), {Expression::Kind::kDivision, Expression::Kind::kMinus});
+        ASSERT_EQ(founds.size(), 2);
+
+        founds = ExpressionUtils::findAnyKindInAll(root.get(), {Expression::Kind::kEdgeDst});
+        ASSERT_TRUE(founds.empty());
+
+        founds = ExpressionUtils::findAnyKindInAll(
+            root.get(), {Expression::Kind::kLogicalAnd, Expression::Kind::kUnaryNegate});
+        ASSERT_TRUE(founds.empty());
+    }
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/CMakeLists.txt b/src/validator/CMakeLists.txt
index 2d141df336342d349a68149eff136eb8fdb5f77d..daa47c3df699f96c5c38816b02236274aa5aff58 100644
--- a/src/validator/CMakeLists.txt
+++ b/src/validator/CMakeLists.txt
@@ -17,6 +17,8 @@ nebula_add_library(
     AdminValidator.cpp
     MaintainValidator.cpp
     MutateValidator.cpp
+    FetchEdgesValidator.cpp
+    FetchVerticesValidator.cpp
     LimitValidator.cpp
     OrderByValidator.cpp
     YieldValidator.cpp
diff --git a/src/validator/FetchEdgesValidator.cpp b/src/validator/FetchEdgesValidator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..caefb9f89c3dc24973637ec3881e2c3a7829b595
--- /dev/null
+++ b/src/validator/FetchEdgesValidator.cpp
@@ -0,0 +1,245 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "validator/FetchEdgesValidator.h"
+#include "planner/Query.h"
+#include "util/ExpressionUtils.h"
+#include "util/SchemaUtil.h"
+
+namespace nebula {
+namespace graph {
+
+/*static*/ const std::unordered_set<std::string> FetchEdgesValidator::reservedProperties{
+    kSrc,
+    kType,
+    kRank,
+    kDst,
+};
+
+Status FetchEdgesValidator::validateImpl() {
+    NG_RETURN_IF_ERROR(check());
+    NG_RETURN_IF_ERROR(prepareEdges());
+    NG_RETURN_IF_ERROR(prepareProperties());
+    return Status::OK();
+}
+
+Status FetchEdgesValidator::toPlan() {
+    // Start [-> some input] -> GetEdges [-> Project] [-> Dedup] [-> next stage] -> End
+    auto *plan = qctx_->plan();
+    auto *getEdgesNode = GetEdges::make(plan,
+                                        nullptr,
+                                        spaceId_,
+                                        std::move(edges_),
+                                        std::move(src_),
+                                        edgeType_,
+                                        std::move(ranking_),
+                                        std::move(dst_),
+                                        std::move(props_),
+                                        std::move(exprs_),
+                                        dedup_,
+                                        limit_,
+                                        std::move(orderBy_),
+                                        std::move(filter_));
+    getEdgesNode->setInputVar(inputVar_);
+    // the pipe will set the input variable
+    PlanNode *current = getEdgesNode;
+
+    if (withProject_) {
+        auto *projectNode = Project::make(plan, current, newYield_->yields());
+        projectNode->setInputVar(current->varName());
+        projectNode->setColNames(colNames_);
+        current = projectNode;
+    }
+    // Project select the properties then dedup
+    if (dedup_) {
+        auto *dedupNode = Dedup::make(plan, current);
+        dedupNode->setInputVar(current->varName());
+        dedupNode->setColNames(colNames_);
+        current = dedupNode;
+
+        // the framework will add data collect to collect the result
+        // if the result is required
+    }
+    root_ = current;
+    tail_ = getEdgesNode;
+    return Status::OK();
+}
+
+Status FetchEdgesValidator::check() {
+    auto *sentence = static_cast<FetchEdgesSentence *>(sentence_);
+    spaceId_ = vctx_->whichSpace().id;
+    edgeTypeName_ = *sentence->edge();
+    auto edgeStatus = qctx_->schemaMng()->toEdgeType(spaceId_, edgeTypeName_);
+    NG_RETURN_IF_ERROR(edgeStatus);
+    edgeType_ = edgeStatus.value();
+    schema_ = qctx_->schemaMng()->getEdgeSchema(spaceId_, edgeType_);
+    if (schema_ == nullptr) {
+        LOG(ERROR) << "No schema found for " << sentence->edge();
+        return Status::Error("No schema found for `%s'", sentence->edge()->c_str());
+    }
+
+    return Status::OK();
+}
+
+Status FetchEdgesValidator::prepareEdges() {
+    auto *sentence = static_cast<FetchEdgesSentence *>(sentence_);
+    // from ref, eval in execute
+    if (sentence->isRef()) {
+        src_ = sentence->ref()->srcid();
+        auto result = checkRef(src_, Value::Type::STRING);
+        NG_RETURN_IF_ERROR(result);
+        inputVar_ = std::move(result).value();
+        ranking_ = sentence->ref()->rank();
+        result = checkRef(ranking_, Value::Type::INT);
+        NG_RETURN_IF_ERROR(result);
+        if (inputVar_ != result.value()) {
+            return Status::Error("Can't refer to different variable as key at same time.");
+        }
+        dst_ = sentence->ref()->dstid();
+        result = checkRef(dst_, Value::Type::STRING);
+        NG_RETURN_IF_ERROR(result);
+        if (inputVar_ != result.value()) {
+            return Status::Error("Can't refer to different variable as key at same time.");
+        }
+        return Status::OK();
+    }
+
+    // from constant, eval now
+    QueryExpressionContext dummy(nullptr);
+    auto keysPointer = sentence->keys();
+    if (keysPointer != nullptr) {
+        auto keys = keysPointer->keys();
+        // row: _src, _type, _ranking, _dst
+        edges_.reserve(keys.size());
+        for (const auto &key : keys) {
+            DCHECK(ExpressionUtils::isConstExpr(key->srcid()));
+            // TODO(shylock) Add new value type EDGE_ID to semantic and simplify this
+            auto src = key->srcid()->eval(dummy);
+            if (!src.isStr()) {   // string as vid
+                return Status::NotSupported("src is not a vertex id");
+            }
+            auto ranking = key->rank();
+            DCHECK(ExpressionUtils::isConstExpr(key->dstid()));
+            auto dst = key->dstid()->eval(dummy);
+            if (!src.isStr()) {
+                return Status::NotSupported("dst is not a vertex id");
+            }
+            edges_.emplace_back(nebula::Row(
+                {std::move(src).getStr(), edgeType_, ranking, std::move(dst).getStr()}));
+        }
+    }
+    return Status::OK();
+}
+
+Status FetchEdgesValidator::prepareProperties() {
+    auto *sentence = static_cast<FetchEdgesSentence *>(sentence_);
+    auto *yield = sentence->yieldClause();
+    storage::cpp2::EdgeProp prop;
+    prop.set_type(edgeType_);
+    // empty for all properties
+    if (yield != nullptr) {
+        withProject_ = true;
+        // insert the reserved properties expression be compatible with 1.0
+        auto *newYieldColumns = new YieldColumns();
+        newYieldColumns->addColumn(
+            new YieldColumn(new EdgeSrcIdExpression(new std::string(edgeTypeName_))));
+        newYieldColumns->addColumn(
+            new YieldColumn(new EdgeDstIdExpression(new std::string(edgeTypeName_))));
+        newYieldColumns->addColumn(
+            new YieldColumn(new EdgeRankExpression(new std::string(edgeTypeName_))));
+        for (auto col : yield->columns()) {
+            newYieldColumns->addColumn(col->clone().release());
+        }
+        newYield_ = qctx_->objPool()->add(new YieldClause(newYieldColumns, yield->isDistinct()));
+
+        std::vector<std::string> propsName;
+        propsName.reserve(newYield_->columns().size());
+        dedup_ = newYield_->isDistinct();
+        for (auto col : newYield_->columns()) {
+            if (col->expr()->kind() == Expression::Kind::kSymProperty) {
+                auto symbolExpr = static_cast<SymbolPropertyExpression *>(col->expr());
+                col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>(
+                    symbolExpr));
+            } else {
+                ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr());
+            }
+            const auto *invalidExpr = findInvalidYieldExpression(col->expr());
+            if (invalidExpr != nullptr) {
+                return Status::Error("Invalid newYield_ expression `%s'.",
+                                     col->expr()->toString().c_str());
+            }
+            // The properties from storage directly push down only
+            // The other will be computed in Project Executor
+            const auto storageExprs = ExpressionUtils::findAllStorage(col->expr());
+            for (const auto &storageExpr : storageExprs) {
+                const auto *expr = static_cast<const SymbolPropertyExpression *>(storageExpr);
+                if (*expr->sym() != edgeTypeName_) {
+                    return Status::Error("Mismatched edge type name");
+                }
+                // Check is prop name in schema
+                if (schema_->getFieldIndex(*expr->prop()) < 0 &&
+                    reservedProperties.find(*expr->prop()) == reservedProperties.end()) {
+                    LOG(ERROR) << "Unknown column `" << *expr->prop() << "' in edge `"
+                                << edgeTypeName_ << "'.";
+                    return Status::Error("Unknown column `%s' in edge `%s'",
+                                            expr->prop()->c_str(),
+                                            edgeTypeName_.c_str());
+                }
+                propsName.emplace_back(*expr->prop());
+            }
+            // TODO(shylock) think about the push-down expr
+        }
+        prop.set_props(std::move(propsName));
+
+        // outpus
+        colNames_ = deduceColNames(newYield_->yields());
+        outputs_.reserve(colNames_.size());
+        for (std::size_t i = 0; i < colNames_.size(); ++i) {
+            auto typeResult = deduceExprType(newYield_->columns()[i]->expr());
+            NG_RETURN_IF_ERROR(typeResult);
+            outputs_.emplace_back(colNames_[i], typeResult.value());
+        }
+    } else {
+        // no yield
+        std::vector<std::string> propNames;   // filter the type
+        propNames.reserve(3 + schema_->getNumFields());
+        outputs_.reserve(3 + schema_->getNumFields());
+        colNames_.reserve(3 + schema_->getNumFields());
+        // insert the reserved properties be compatible with 1.0
+        propNames.emplace_back(kSrc);
+        outputs_.emplace_back(edgeTypeName_ + "." + kSrc, Value::Type::STRING);
+        colNames_.emplace_back(edgeTypeName_ + "." + kSrc);
+        propNames.emplace_back(kDst);
+        outputs_.emplace_back(edgeTypeName_ + "." + kDst, Value::Type::STRING);
+        colNames_.emplace_back(edgeTypeName_ + "." + kDst);
+        propNames.emplace_back(kRank);
+        outputs_.emplace_back(edgeTypeName_ + "." + kRank, Value::Type::INT);
+        colNames_.emplace_back(edgeTypeName_ + "." + kRank);
+
+        for (std::size_t i = 0; i < schema_->getNumFields(); ++i) {
+            propNames.emplace_back(schema_->getFieldName(i));
+            outputs_.emplace_back(schema_->getFieldName(i),
+                                  SchemaUtil::propTypeToValueType(schema_->getFieldType(i)));
+            colNames_.emplace_back(schema_->getFieldName(i));
+        }
+        prop.set_props(std::move(propNames));
+    }
+
+    props_.emplace_back(std::move(prop));
+    return Status::OK();
+}
+
+/*static*/
+const Expression *FetchEdgesValidator::findInvalidYieldExpression(const Expression *root) {
+    return ExpressionUtils::findAnyKind(root,
+                                        {Expression::Kind::kInputProperty,
+                                         Expression::Kind::kVarProperty,
+                                         Expression::Kind::kSrcProperty,
+                                         Expression::Kind::kDstProperty});
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/FetchEdgesValidator.h b/src/validator/FetchEdgesValidator.h
new file mode 100644
index 0000000000000000000000000000000000000000..da6f54c147ee45b77dbe76c6cf673d0a297e50b2
--- /dev/null
+++ b/src/validator/FetchEdgesValidator.h
@@ -0,0 +1,67 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef _VALIDATOR_FETCH_EDGES_VALIDATOR_H_
+#define _VALIDATOR_FETCH_EDGES_VALIDATOR_H_
+
+#include "common/base/Base.h"
+#include "common/interface/gen-cpp2/storage_types.h"
+#include "parser/TraverseSentences.h"
+#include "validator/Validator.h"
+
+namespace nebula {
+namespace graph {
+
+class FetchEdgesValidator final : public Validator {
+public:
+    FetchEdgesValidator(Sentence* sentence, QueryContext* context)
+        : Validator(sentence, context) {}
+
+private:
+    Status validateImpl() override;
+
+    Status toPlan() override;
+
+    Status check();
+
+    Status prepareEdges();
+
+    Status prepareProperties();
+
+    static const Expression* findInvalidYieldExpression(const Expression* root);
+
+    static const std::unordered_set<std::string> reservedProperties;
+
+private:
+    GraphSpaceID spaceId_;
+    std::vector<nebula::Row> edges_;
+    Expression* src_{nullptr};
+    Expression* ranking_{nullptr};
+    Expression* dst_{nullptr};
+    std::string edgeTypeName_;
+    EdgeType edgeType_{0};
+    std::shared_ptr<const meta::SchemaProviderIf> schema_;
+    std::vector<storage::cpp2::EdgeProp> props_;
+    std::vector<storage::cpp2::Expr>     exprs_;
+    bool dedup_{false};
+    int64_t limit_{std::numeric_limits<int64_t>::max()};
+    std::vector<storage::cpp2::OrderBy> orderBy_{};
+    std::string filter_{""};
+    // valid when yield expression not require storage
+    // So expression like these will be evaluate in Project Executor
+    bool withProject_{false};
+    // outputs
+    std::vector<std::string> colNames_;
+    // new yield to inject reserved properties for compatible with 1.0
+    YieldClause* newYield_{nullptr};
+    // input
+    std::string inputVar_;
+};
+
+}   // namespace graph
+}   // namespace nebula
+
+#endif  // _VALIDATOR_FETCH_EDGES_VALIDATOR_H_
diff --git a/src/validator/FetchVerticesValidator.cpp b/src/validator/FetchVerticesValidator.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f4ee57b56e2f8b60851257c5a73fd6fb1dfdace9
--- /dev/null
+++ b/src/validator/FetchVerticesValidator.cpp
@@ -0,0 +1,223 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+#include "validator/FetchVerticesValidator.h"
+#include "planner/Query.h"
+#include "util/ExpressionUtils.h"
+#include "util/SchemaUtil.h"
+
+namespace nebula {
+namespace graph {
+
+Status FetchVerticesValidator::validateImpl() {
+    NG_RETURN_IF_ERROR(check());
+    NG_RETURN_IF_ERROR(prepareVertices());
+    NG_RETURN_IF_ERROR(prepareProperties());
+    return Status::OK();
+}
+
+Status FetchVerticesValidator::toPlan() {
+    // Start [-> some input] -> GetVertices [-> Project] [-> Dedup] [-> next stage] -> End
+    auto *sentence = static_cast<FetchVerticesSentence*>(sentence_);
+    auto *plan = qctx_->plan();
+    auto *getVerticesNode = GetVertices::make(plan,
+                                              nullptr,
+                                              spaceId_,
+                                              std::move(vertices_),
+                                              std::move(src_),
+                                              std::move(props_),
+                                              std::move(exprs_),
+                                              dedup_,
+                                              std::move(orderBy_),
+                                              limit_,
+                                              std::move(filter_));
+    getVerticesNode->setInputVar(inputVar_);
+    // pipe will set the input variable
+    PlanNode *current = getVerticesNode;
+
+    if (withProject_) {
+        auto *projectNode = Project::make(plan, current, sentence->yieldClause()->yields());
+        projectNode->setInputVar(current->varName());
+        projectNode->setColNames(colNames_);
+        current = projectNode;
+    }
+    // Project select properties then dedup
+    if (dedup_) {
+        auto *dedupNode = Dedup::make(plan, current);
+        dedupNode->setInputVar(current->varName());
+        dedupNode->setColNames(colNames_);
+        current = dedupNode;
+
+        // the framework will add data collect to collect the result
+        // if the result is required
+    }
+    root_ = current;
+    tail_ = getVerticesNode;
+    return Status::OK();
+}
+
+Status FetchVerticesValidator::check() {
+    auto *sentence = static_cast<FetchVerticesSentence*>(sentence_);
+    spaceId_ = vctx_->whichSpace().id;
+
+    tagName_ = *sentence->tag();
+    if (!sentence->isAllTagProps()) {
+        tagName_ = *(sentence->tag());
+        auto tagStatus = qctx_->schemaMng()->toTagID(spaceId_, tagName_);
+        NG_RETURN_IF_ERROR(tagStatus);
+
+        tagId_ = tagStatus.value();
+        schema_ = qctx_->schemaMng()->getTagSchema(spaceId_, tagId_.value());
+        if (schema_ == nullptr) {
+            LOG(ERROR) << "No schema found for " << tagName_;
+            return Status::Error("No schema found for `%s'", tagName_.c_str());
+        }
+    }
+    return Status::OK();
+}
+
+Status FetchVerticesValidator::prepareVertices() {
+    auto *sentence = static_cast<FetchVerticesSentence*>(sentence_);
+    // from ref, eval when execute
+    if (sentence->isRef()) {
+        src_ = sentence->ref();
+        auto result = checkRef(src_, Value::Type::STRING);
+        NG_RETURN_IF_ERROR(result);
+        inputVar_ = std::move(result).value();
+        return Status::OK();
+    }
+
+    // from constant, eval now
+    // TODO(shylock) add eval() method for expression
+    QueryExpressionContext dummy(nullptr);
+    auto vids = sentence->vidList();
+    vertices_.reserve(vids.size());
+    for (const auto vid : vids) {
+        // TODO(shylock) Add a new value type VID to semantic this
+        DCHECK(ExpressionUtils::isConstExpr(vid));
+        auto v = vid->eval(dummy);
+        if (!v.isStr()) {   // string as vid
+            return Status::NotSupported("Not a vertex id");
+        }
+        vertices_.emplace_back(nebula::Row({std::move(v).getStr()}));
+    }
+    return Status::OK();
+}
+
+// TODO(shylock) select _vid property instead of return always.
+Status FetchVerticesValidator::prepareProperties() {
+    auto *sentence = static_cast<FetchVerticesSentence*>(sentence_);
+    auto *yield = sentence->yieldClause();
+    if (yield == nullptr) {
+        // empty for all tag and properties
+        props_.clear();
+        if (!sentence->isAllTagProps()) {
+            // for one tag all properties
+            storage::cpp2::VertexProp prop;
+            prop.set_tag(tagId_.value());
+            // empty for all
+            props_.emplace_back(std::move(prop));
+            outputs_.emplace_back(kVid, Value::Type::STRING);
+            colNames_.emplace_back(kVid);
+            for (std::size_t i = 0; i < schema_->getNumFields(); ++i) {
+                outputs_.emplace_back(schema_->getFieldName(i),
+                                      SchemaUtil::propTypeToValueType(schema_->getFieldType(i)));
+                colNames_.emplace_back(schema_->getFieldName(i));
+            }
+        } else {
+            // all schema properties
+            const auto allTagsResult = qctx_->schemaMng()->getAllVerTagSchema(spaceId_);
+            NG_RETURN_IF_ERROR(allTagsResult);
+            const auto allTags = std::move(allTagsResult).value();
+            std::vector<std::pair<TagID, std::shared_ptr<const meta::NebulaSchemaProvider>>>
+                allTagsSchema;
+            allTagsSchema.reserve(allTags.size());
+            for (const auto &tag : allTags) {
+                allTagsSchema.emplace_back(tag.first, tag.second.back());
+            }
+            std::sort(allTagsSchema.begin(), allTagsSchema.end(), [](const auto &a, const auto &b) {
+                return a.first < b.first;
+            });
+            outputs_.emplace_back(kVid, Value::Type::STRING);
+            colNames_.emplace_back(kVid);
+            for (const auto &tagSchema : allTagsSchema) {
+                for (std::size_t i = 0; i < tagSchema.second->getNumFields(); ++i) {
+                    outputs_.emplace_back(
+                        tagSchema.second->getFieldName(i),
+                        SchemaUtil::propTypeToValueType(tagSchema.second->getFieldType(i)));
+                    colNames_.emplace_back(tagSchema.second->getFieldName(i));
+                }
+            }
+        }
+    } else {
+        CHECK(!sentence->isAllTagProps()) << "Not supported yield for *.";
+        withProject_ = true;
+        dedup_ = yield->isDistinct();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId_.value());
+        std::vector<std::string> propsName;
+        propsName.reserve(yield->columns().size());
+        for (auto col : yield->columns()) {
+            if (col->expr()->kind() == Expression::Kind::kSymProperty) {
+                auto symbolExpr = static_cast<SymbolPropertyExpression *>(col->expr());
+                col->setExpr(ExpressionUtils::transSymbolPropertyExpression<TagPropertyExpression>(
+                    symbolExpr));
+            } else {
+                ExpressionUtils::transAllSymbolPropertyExpr<TagPropertyExpression>(col->expr());
+            }
+            const auto *invalidExpr = findInvalidYieldExpression(col->expr());
+            if (invalidExpr != nullptr) {
+                return Status::Error("Invalid yield expression `%s'.",
+                                     col->expr()->toString().c_str());
+            }
+            // The properties from storage directly push down only
+            // The other will be computed in Project Executor
+            const auto storageExprs = ExpressionUtils::findAllStorage(col->expr());
+            for (const auto &storageExpr : storageExprs) {
+                const auto *expr = static_cast<const SymbolPropertyExpression *>(storageExpr);
+                if (*expr->sym() != tagName_) {
+                    return Status::Error("Mismatched tag name");
+                }
+                // Check is prop name in schema
+                if (schema_->getFieldIndex(*expr->prop()) < 0) {
+                    LOG(ERROR) << "Unknown column `" << *expr->prop() << "' in tag `"
+                                << tagName_ << "'.";
+                    return Status::Error("Unknown column `%s' in tag `%s'.",
+                                            expr->prop()->c_str(),
+                                            tagName_.c_str());
+                }
+                propsName.emplace_back(*expr->prop());
+            }
+            // TODO(shylock) think about the push-down expr
+        }
+        prop.set_props(std::move(propsName));
+        props_.emplace_back(std::move(prop));
+
+        // outputs
+        colNames_ = deduceColNames(yield->yields());
+        outputs_.reserve(colNames_.size());
+        for (std::size_t i = 0; i < colNames_.size(); ++i) {
+            auto typeResult = deduceExprType(yield->columns()[i]->expr());
+            NG_RETURN_IF_ERROR(typeResult);
+            outputs_.emplace_back(colNames_[i], typeResult.value());
+        }
+    }
+
+    return Status::OK();
+}
+
+/*static*/
+const Expression *FetchVerticesValidator::findInvalidYieldExpression(const Expression *root) {
+    return ExpressionUtils::findAnyKind(root,
+                                        {Expression::Kind::kInputProperty,
+                                         Expression::Kind::kVarProperty,
+                                         Expression::Kind::kEdgeSrc,
+                                         Expression::Kind::kEdgeType,
+                                         Expression::Kind::kEdgeRank,
+                                         Expression::Kind::kEdgeDst});
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/FetchVerticesValidator.h b/src/validator/FetchVerticesValidator.h
new file mode 100644
index 0000000000000000000000000000000000000000..e5860102e0a2a6684b5f39049d7c0f347c8b1af5
--- /dev/null
+++ b/src/validator/FetchVerticesValidator.h
@@ -0,0 +1,62 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_
+#define _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_
+
+#include "common/base/Base.h"
+#include "common/interface/gen-cpp2/storage_types.h"
+#include "parser/TraverseSentences.h"
+#include "validator/Validator.h"
+
+namespace nebula {
+namespace graph {
+
+class FetchVerticesValidator final : public Validator {
+public:
+    FetchVerticesValidator(Sentence* sentence, QueryContext* context)
+        : Validator(sentence, context) {}
+
+private:
+    Status validateImpl() override;
+
+    Status toPlan() override;
+
+    Status check();
+
+    Status prepareVertices();
+
+    Status prepareProperties();
+
+    static const Expression* findInvalidYieldExpression(const Expression* root);
+
+private:
+    GraphSpaceID spaceId_{0};
+    std::vector<nebula::Row> vertices_;
+    Expression* src_{nullptr};
+    std::string tagName_;
+    // none if not specified tag
+    folly::Optional<TagID> tagId_;
+    std::shared_ptr<const meta::SchemaProviderIf> schema_;
+    std::vector<storage::cpp2::VertexProp> props_;
+    std::vector<storage::cpp2::Expr>       exprs_;
+    bool dedup_{false};
+    std::vector<storage::cpp2::OrderBy> orderBy_{};
+    int64_t limit_{std::numeric_limits<int64_t>::max()};
+    std::string filter_{};
+    // valid when yield expression not require storage
+    // So expression like these will be evaluate in Project Executor
+    bool withProject_{false};
+    // outputs
+    std::vector<std::string> colNames_;
+    // input
+    std::string inputVar_;
+};
+
+}   // namespace graph
+}   // namespace nebula
+
+#endif  // _VALIDATOR_FETCH_VERTICES_VALIDATOR_H_
diff --git a/src/validator/GoValidator.cpp b/src/validator/GoValidator.cpp
index fb4f9ae7ef9acffb8da89d12284dc3bc730c90d0..aee047db0b7523520e50117ae93e6bd8216db743 100644
--- a/src/validator/GoValidator.cpp
+++ b/src/validator/GoValidator.cpp
@@ -6,6 +6,8 @@
 
 #include "validator/GoValidator.h"
 
+#include "util/ExpressionUtils.h"
+
 #include "common/base/Base.h"
 #include "common/expression/VariableExpression.h"
 #include "common/interface/gen-cpp2/storage_types.h"
@@ -123,13 +125,21 @@ Status GoValidator::validateOver(const OverClause* over) {
     return Status::OK();
 }
 
-Status GoValidator::validateWhere(const WhereClause* where) {
+Status GoValidator::validateWhere(WhereClause* where) {
     if (where == nullptr) {
         return Status::OK();
     }
 
     filter_ = where->filter();
 
+    if (filter_->kind() == Expression::Kind::kSymProperty) {
+        auto symbolExpr = static_cast<SymbolPropertyExpression*>(filter_);
+        where->setFilter(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>(
+            symbolExpr));
+    } else {
+        ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(filter_);
+    }
+
     auto typeStatus = deduceExprType(filter_);
     if (!typeStatus.ok()) {
         return typeStatus.status();
@@ -150,7 +160,7 @@ Status GoValidator::validateWhere(const WhereClause* where) {
     return Status::OK();
 }
 
-Status GoValidator::validateYield(const YieldClause* yield) {
+Status GoValidator::validateYield(YieldClause* yield) {
     if (yield == nullptr) {
         return Status::Error("Yield clause nullptr.");
     }
@@ -158,6 +168,14 @@ Status GoValidator::validateYield(const YieldClause* yield) {
     distinct_ = yield->isDistinct();
     auto cols = yield->columns();
     for (auto col : cols) {
+        if (col->expr()->kind() == Expression::Kind::kSymProperty) {
+            auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr());
+            col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>(
+                symbolExpr));
+        } else {
+            ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr());
+        }
+
         if (!col->getAggFunName().empty()) {
             return Status::Error(
                 "`%s', not support aggregate function in go sentence.",
diff --git a/src/validator/GoValidator.h b/src/validator/GoValidator.h
index 9006ad6b1c5ef2c7995a77c63f9386798f92602c..3dd44d9aac4c8119bad3c7e5ceb6c09ba0f80d69 100644
--- a/src/validator/GoValidator.h
+++ b/src/validator/GoValidator.h
@@ -29,9 +29,9 @@ private:
 
     Status validateOver(const OverClause* over);
 
-    Status validateWhere(const WhereClause* where);
+    Status validateWhere(WhereClause* where);
 
-    Status validateYield(const YieldClause* yield);
+    Status validateYield(YieldClause* yield);
 
     void extractPropExprs(const Expression* expr);
 
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index b11f3551ba38b9d0600bfd28cde04210f7e0b799..17b7d22c2ec6d77ca1637eaaa81d484620c20f7d 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -9,6 +9,7 @@
 #include "parser/Sentence.h"
 #include "planner/Query.h"
 #include "util/SchemaUtil.h"
+#include "util/ExpressionUtils.h"
 #include "validator/AdminValidator.h"
 #include "validator/AssignmentValidator.h"
 #include "validator/ExplainValidator.h"
@@ -19,6 +20,8 @@
 #include "validator/MutateValidator.h"
 #include "validator/OrderByValidator.h"
 #include "validator/PipeValidator.h"
+#include "validator/FetchVerticesValidator.h"
+#include "validator/FetchEdgesValidator.h"
 #include "validator/ReportError.h"
 #include "validator/SequentialValidator.h"
 #include "validator/SetValidator.h"
@@ -97,6 +100,10 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon
             return std::make_unique<InsertVerticesValidator>(sentence, context);
         case Sentence::Kind::kInsertEdges:
             return std::make_unique<InsertEdgesValidator>(sentence, context);
+        case Sentence::Kind::kFetchVertices:
+            return std::make_unique<FetchVerticesValidator>(sentence, context);
+        case Sentence::Kind::kFetchEdges:
+            return std::make_unique<FetchEdgesValidator>(sentence, context);
         case Sentence::Kind::kCreateSnapshot:
             return std::make_unique<CreateSnapshotValidator>(sentence, context);
         case Sentence::Kind::kDropSnapshot:
@@ -123,6 +130,8 @@ Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
         case PlanNode::Kind::kLoop:
         case PlanNode::Kind::kMultiOutputs:
         case PlanNode::Kind::kSwitchSpace:
+        case PlanNode::Kind::kGetEdges:
+        case PlanNode::Kind::kGetVertices:
         case PlanNode::Kind::kCreateSpace:
         case PlanNode::Kind::kCreateTag:
         case PlanNode::Kind::kCreateEdge:
@@ -660,5 +669,36 @@ bool Validator::evaluableExpr(const Expression* expr) const {
     return false;
 }
 
-}   // namespace graph
-}   // namespace nebula
+StatusOr<std::string> Validator::checkRef(const Expression *ref, Value::Type type) const {
+    if (ref->kind() == Expression::Kind::kInputProperty) {
+        const auto* symExpr = static_cast<const SymbolPropertyExpression*>(ref);
+        ColDef col(*symExpr->prop(), type);
+        const auto find = std::find(inputs_.begin(), inputs_.end(), col);
+        if (find == inputs_.end()) {
+            return Status::Error("No input property %s", symExpr->prop()->c_str());
+        }
+        return std::string();
+    } else if (ref->kind() == Expression::Kind::kVarProperty) {
+        const auto* symExpr = static_cast<const SymbolPropertyExpression*>(ref);
+        ColDef col(*symExpr->prop(), type);
+        const auto &varName = *symExpr->sym();
+        const auto &var = vctx_->getVar(varName);
+        if (var.empty()) {
+            return Status::Error("No variable %s", varName.c_str());
+        }
+        const auto find = std::find(var.begin(), var.end(), col);
+        if (find == var.end()) {
+            return Status::Error("No property %s in variable %s",
+                                 symExpr->prop()->c_str(),
+                                 varName.c_str());
+        }
+        return varName;
+    } else {
+        // it's guranteed by parser
+        DLOG(FATAL) << "Unexpected expression " << ref->kind();
+        return Status::Error("Unexpected expression.");
+    }
+}
+
+}  // namespace graph
+}  // namespace nebula
diff --git a/src/validator/Validator.h b/src/validator/Validator.h
index 0eae4243e7b9556bd11c87f959e05057f475527a..4dd81a0210c674767e36c9cbac9a177134e3b021 100644
--- a/src/validator/Validator.h
+++ b/src/validator/Validator.h
@@ -88,6 +88,10 @@ protected:
 
     static Status appendPlan(PlanNode* plan, PlanNode* appended);
 
+    // Check the variable or input property reference
+    // return the input variable
+    StatusOr<std::string> checkRef(const Expression *ref, const Value::Type type) const;
+
 protected:
     SpaceDescription                space_;
     Sentence*                       sentence_{nullptr};
diff --git a/src/validator/YieldValidator.cpp b/src/validator/YieldValidator.cpp
index d68bac18e8fea063f0bfd22b59cc73900cd52057..acb338060dd4ca17e426b2b71001f6af00a0e224 100644
--- a/src/validator/YieldValidator.cpp
+++ b/src/validator/YieldValidator.cpp
@@ -5,7 +5,7 @@
  */
 
 #include "validator/YieldValidator.h"
-
+#include "util/ExpressionUtils.h"
 #include "common/expression/Expression.h"
 #include "context/QueryContext.h"
 #include "parser/Clauses.h"
@@ -20,6 +20,7 @@ YieldValidator::YieldValidator(Sentence *sentence, QueryContext *qctx)
 
 Status YieldValidator::validateImpl() {
     auto yield = static_cast<YieldSentence *>(sentence_);
+    rebuildYield(yield->yield());
     NG_RETURN_IF_ERROR(validateYieldAndBuildOutputs(yield->yield()));
     NG_RETURN_IF_ERROR(validateWhere(yield->where()));
 
@@ -261,5 +262,19 @@ Status YieldValidator::toPlan() {
     return Status::OK();
 }
 
+void YieldValidator::rebuildYield(YieldClause* yield) {
+    // TODO(shylock) keep same with previous so transfer to EdgePropertyExpression
+    auto columns = yield->columns();
+    for (auto& col : columns) {
+        if (col->expr()->kind() == Expression::Kind::kSymProperty) {
+            auto symbolExpr = static_cast<SymbolPropertyExpression*>(col->expr());
+            col->setExpr(ExpressionUtils::transSymbolPropertyExpression<EdgePropertyExpression>(
+                symbolExpr));
+        } else {
+            ExpressionUtils::transAllSymbolPropertyExpr<EdgePropertyExpression>(col->expr());
+        }
+    }
+}
+
 }   // namespace graph
 }   // namespace nebula
diff --git a/src/validator/YieldValidator.h b/src/validator/YieldValidator.h
index 605dee94c56bc7fa980f4c7853d5757b34b3af5a..eadcbdf84bd1b570920f7a932fd2e333eded7551 100644
--- a/src/validator/YieldValidator.h
+++ b/src/validator/YieldValidator.h
@@ -42,6 +42,7 @@ private:
     YieldColumns *getYieldColumns(YieldColumns *yieldColumns,
                                   ObjectPool *objPool,
                                   size_t numColumns);
+    void rebuildYield(YieldClause* yield);
 
     bool hasAggFun_{false};
 };
diff --git a/src/validator/test/CMakeLists.txt b/src/validator/test/CMakeLists.txt
index f007458e8babff6bc3c1930c2762a803df8709ba..c05ef4ea7fe0ceb6b107e6b6017424b97ab96f2d 100644
--- a/src/validator/test/CMakeLists.txt
+++ b/src/validator/test/CMakeLists.txt
@@ -49,6 +49,8 @@ set(VALIDATOR_TEST_LIBS
 nebula_add_test(
     NAME validator_test
     SOURCES
+        FetchVerticesTest.cpp
+        FetchEdgesTest.cpp
         MockSchemaManagerTest.cpp
         ValidatorTestBase.cpp
         QueryValidatorTest.cpp
diff --git a/src/validator/test/FetchEdgesTest.cpp b/src/validator/test/FetchEdgesTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f573acab3aee3d06e324df2fbffe0f11aab1ec7e
--- /dev/null
+++ b/src/validator/test/FetchEdgesTest.cpp
@@ -0,0 +1,509 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "planner/Query.h"
+#include "validator/FetchEdgesValidator.h"
+#include "validator/test/ValidatorTestBase.h"
+
+namespace nebula {
+namespace graph {
+
+class FetchEdgesValidatorTest : public ValidatorTestBase {};
+
+TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\""));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
+        ASSERT_TRUE(edgeTypeResult.ok());
+        auto edgeType = edgeTypeResult.value();
+        std::vector<nebula::Row> edges{nebula::Row({
+            "1",
+            edgeType,
+            0,
+            "2",
+        })};
+        storage::cpp2::EdgeProp prop;
+        prop.set_type(edgeType);
+        prop.set_props({kSrc, kDst, kRank, "start", "end", "likeness"});
+        std::vector<storage::cpp2::EdgeProp> props;
+        props.emplace_back(std::move(prop));
+        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+                                  start,
+                                  1,
+                                  std::move(edges),
+                                  nullptr,
+                                  edgeType,
+                                  nullptr,
+                                  nullptr,
+                                  std::move(props),
+                                  {});
+        expectedQueryCtx_->plan()->setRoot(ge);
+        auto result = Eq(plan->root(), ge);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, like.end"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
+        ASSERT_TRUE(edgeTypeResult.ok());
+        auto edgeType = edgeTypeResult.value();
+        std::vector<nebula::Row> edges{nebula::Row({
+            "1",
+            edgeType,
+            0,
+            "2",
+        })};
+        storage::cpp2::EdgeProp prop;
+        prop.set_type(edgeType);
+        prop.set_props({kSrc, kDst, kRank, "start", "end"});
+        std::vector<storage::cpp2::EdgeProp> props;
+        props.emplace_back(std::move(prop));
+        std::vector<storage::cpp2::Expr> exprs;
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("start")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("end")).encode());
+        exprs.emplace_back(std::move(expr1));
+        exprs.emplace_back(std::move(expr2));
+        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+                                  start,
+                                  1,
+                                  std::move(edges),
+                                  nullptr,
+                                  edgeType,
+                                  nullptr,
+                                  nullptr,
+                                  std::move(props),
+                                  std::move(exprs));
+
+        // Project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("start"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("end"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get());
+        project->setColNames({std::string("like.") + kSrc,
+                              std::string("like.") + kDst,
+                              std::string("like.") + kRank,
+                              "like.start",
+                              "like.end"});
+        expectedQueryCtx_->plan()->setRoot(project);
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD const expression
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, 1 + 1, like.end"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+
+        // GetEdges
+        auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
+        ASSERT_TRUE(edgeTypeResult.ok());
+        auto edgeType = edgeTypeResult.value();
+        std::vector<nebula::Row> edges{nebula::Row({
+            "1",
+            edgeType,
+            0,
+            "2",
+        })};
+        storage::cpp2::EdgeProp prop;
+        prop.set_type(edgeType);
+        prop.set_props({kSrc, kDst, kRank, "start", "end"});
+        std::vector<storage::cpp2::EdgeProp> props;
+        props.emplace_back(std::move(prop));
+        std::vector<storage::cpp2::Expr> exprs;
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("start")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("end")).encode());
+        exprs.emplace_back(std::move(expr1));
+        exprs.emplace_back(std::move(expr2));
+        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+                                  start,
+                                  1,
+                                  std::move(edges),
+                                  nullptr,
+                                  edgeType,
+                                  nullptr,
+                                  nullptr,
+                                  std::move(props),
+                                  std::move(exprs));
+
+        // Project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("start"))));
+        yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression(
+            Expression::Kind::kAdd, new ConstantExpression(1), new ConstantExpression(1))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("end"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get());
+        project->setColNames({std::string("like.") + kSrc,
+                              std::string("like.") + kDst,
+                              std::string("like.") + kRank,
+                              "like.start",
+                              "(1+1)",
+                              "like.end"});
+        expectedQueryCtx_->plan()->setRoot(project);
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD combine properties
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start > like.end"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
+        ASSERT_TRUE(edgeTypeResult.ok());
+        auto edgeType = edgeTypeResult.value();
+        std::vector<nebula::Row> edges{nebula::Row({
+            "1",
+            edgeType,
+            0,
+            "2",
+        })};
+        storage::cpp2::EdgeProp prop;
+        prop.set_type(edgeType);
+        std::vector<std::string> propsName;
+        prop.set_props({kSrc, kDst, kRank, "start", "end"});
+        std::vector<storage::cpp2::EdgeProp> props;
+        props.emplace_back(std::move(prop));
+        std::vector<storage::cpp2::Expr> exprs;
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            RelationalExpression(
+                Expression::Kind::kRelGT,
+                new EdgePropertyExpression(new std::string("like"), new std::string("start")),
+                new EdgePropertyExpression(new std::string("like"), new std::string("end")))
+                .encode());
+        exprs.emplace_back(std::move(expr1));
+        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+                                  start,
+                                  1,
+                                  std::move(edges),
+                                  nullptr,
+                                  edgeType,
+                                  nullptr,
+                                  nullptr,
+                                  std::move(props),
+                                  std::move(exprs));
+
+        // project, TODO(shylock) it's could push-down to storage if it supported
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new RelationalExpression(
+            Expression::Kind::kRelGT,
+            new EdgePropertyExpression(new std::string("like"), new std::string("start")),
+            new EdgePropertyExpression(new std::string("like"), new std::string("end")))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get());
+        project->setColNames({std::string("like.") + kSrc,
+                              std::string("like.") + kDst,
+                              std::string("like.") + kRank,
+                              "(like.start>like.end)"});
+
+        expectedQueryCtx_->plan()->setRoot(project);
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD distinct
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD distinct like.start, like.end"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
+        ASSERT_TRUE(edgeTypeResult.ok());
+        auto edgeType = edgeTypeResult.value();
+        std::vector<nebula::Row> edges{nebula::Row({
+            "1",
+            edgeType,
+            0,
+            "2",
+        })};
+        storage::cpp2::EdgeProp prop;
+        prop.set_type(edgeType);
+        prop.set_props({kSrc, kDst, kRank, "start", "end"});
+        std::vector<storage::cpp2::EdgeProp> props;
+        props.emplace_back(std::move(prop));
+        std::vector<storage::cpp2::Expr> exprs;
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("start")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            EdgePropertyExpression(new std::string("like"), new std::string("end")).encode());
+        exprs.emplace_back(std::move(expr1));
+        exprs.emplace_back(std::move(expr2));
+        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+                                  start,
+                                  1,
+                                  std::move(edges),
+                                  nullptr,
+                                  edgeType,
+                                  nullptr,
+                                  nullptr,
+                                  std::move(props),
+                                  std::move(exprs));
+
+        std::vector<std::string> colNames{std::string("like.") + kSrc,
+                                          std::string("like.") + kDst,
+                                          std::string("like.") + kRank,
+                                          "like.start",
+                                          "like.end"};
+
+        // project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(new EdgeSrcIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeDstIdExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(new EdgeRankExpression(new std::string("like"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("start"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new EdgePropertyExpression(new std::string("like"), new std::string("end"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), ge, yieldColumns.get());
+        project->setColNames({std::string("like.") + kSrc,
+                              std::string("like.") + kDst,
+                              std::string("like.") + kRank,
+                              "like.start",
+                              "like.end"});
+        // dedup
+        auto *dedup = Dedup::make(expectedQueryCtx_->plan(), project);
+        dedup->setColNames(colNames);
+
+        // data collect
+        auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(),
+                                              dedup,
+                                              DataCollect::CollectKind::kRowBasedMove,
+                                              {dedup->varName()});
+        dataCollect->setColNames(colNames);
+
+        expectedQueryCtx_->plan()->setRoot(dataCollect);
+        auto result = Eq(plan->root(), dataCollect);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+}
+
+TEST_F(FetchEdgesValidatorTest, FetchEdgesInputOutput) {
+    // var
+    {
+        const std::string query = "$a = FETCH PROP ON like \"1\"->\"2\" "
+                                  "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;"
+                                  "FETCH PROP ON like $a.src->$a.dst@$a.rank";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+    // pipe
+    {
+        const std::string query = "FETCH PROP ON like \"1\"->\"2\" "
+                                  "YIELD like._src AS src, like._dst AS dst, like._rank AS rank"
+                                  " | FETCH PROP ON like $-.src->$-.dst@$-.rank";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+
+    // with project
+    // var
+    {
+        const std::string query =
+            "$a = FETCH PROP ON like \"1\"->\"2\" "
+            "YIELD like._src AS src, like._dst AS dst, like._rank + 1 AS rank;"
+            "FETCH PROP ON like $a.src->$a.dst@$a.rank "
+            "YIELD like._src + 1";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+    // pipe
+    {
+        const std::string query = "FETCH PROP ON like \"1\"->\"2\" "
+                                  "YIELD like._src AS src, like._dst AS dst, like._rank + 1 AS rank"
+                                  " | FETCH PROP ON like $-.src->$-.dst@$-.rank "
+                                  "YIELD like._src + 1";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetEdges,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+}
+
+TEST_F(FetchEdgesValidatorTest, FetchEdgesPropFailed) {
+    // mismatched tag
+    {
+        auto result = GQLParser().parse("FETCH PROP ON edge1 \"1\"->\"2\" YIELD edge2.prop2");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // notexist edge
+    {
+        auto result = GQLParser().parse("FETCH PROP ON not_exist_edge \"1\"->\"2\" "
+                                        "YIELD not_exist_edge.prop1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // notexist edge property
+    {
+        auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" "
+                                        "YIELD like.not_exist_prop");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // invalid yield expression
+    {
+        auto result = GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" "
+                                        "YIELD like._src AS src;"
+                                        "FETCH PROP ON like \"1\"->\"2\" YIELD $a.src + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" "
+                                        "YIELD like._src AS src | "
+                                        "FETCH PROP ON like \"1\"->\"2\" YIELD $-.src + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" "
+                                        "YIELD $^.like.start + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" "
+                                        "YIELD $$.like.start + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+}
+
+TEST_F(FetchEdgesValidatorTest, FetchEdgesInputFailed) {
+    // mismatched variable
+    {
+        auto result =
+            GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" "
+                              "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;"
+                              "FETCH PROP ON like $b.src->$b.dst@$b.rank");
+        ASSERT_TRUE(result.ok()) << result.status();
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // mismatched variable property
+    {
+        auto result =
+            GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" "
+                              "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;"
+                              "FETCH PROP ON like $b.src->$b.dst@$b.not_exist_property");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // mismatched input property
+    {
+        auto result =
+            GQLParser().parse("FETCH PROP ON like \"1\"->\"2\" "
+                              "YIELD like._src AS src, like._dst AS dst, like._rank AS rank | "
+                              "FETCH PROP ON like $-.src->$-.dst@$-.not_exist_property");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // refer to different variables
+    {
+        auto result =
+            GQLParser().parse("$a = FETCH PROP ON like \"1\"->\"2\" "
+                              "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;"
+                              "$b = FETCH PROP ON like \"1\"->\"2\" "
+                              "YIELD like._src AS src, like._dst AS dst, like._rank AS rank;"
+                              "FETCH PROP ON like $a.src->$b.dst@$b.rank");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/test/FetchVerticesTest.cpp b/src/validator/test/FetchVerticesTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..83042c6db6698f8ce1fb05aef66d8b37db696a4e
--- /dev/null
+++ b/src/validator/test/FetchVerticesTest.cpp
@@ -0,0 +1,409 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "planner/Query.h"
+#include "validator/FetchVerticesValidator.h"
+#include "validator/test/ValidatorTestBase.h"
+
+namespace nebula {
+namespace graph {
+
+class FetchVerticesValidatorTest : public ValidatorTestBase {};
+
+TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\""));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto tagIdResult = schemaMng_->toTagID(1, "person");
+        ASSERT_TRUE(tagIdResult.ok());
+        auto tagId = tagIdResult.value();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId);
+        auto *gv = GetVertices::make(expectedQueryCtx_->plan(),
+                                     start,
+                                     1,
+                                     std::vector<Row>{Row({"1"})},
+                                     nullptr,
+                                     std::vector<storage::cpp2::VertexProp>{std::move(prop)},
+                                     {});
+        auto result = Eq(plan->root(), gv);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name, person.age"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto tagIdResult = schemaMng_->toTagID(1, "person");
+        ASSERT_TRUE(tagIdResult.ok());
+        auto tagId = tagIdResult.value();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId);
+        prop.set_props(std::vector<std::string>{"name", "age"});
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("name")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
+        auto *gv =
+            GetVertices::make(expectedQueryCtx_->plan(),
+                              start,
+                              1,
+                              std::vector<Row>{Row({"1"})},
+                              nullptr,
+                              std::vector<storage::cpp2::VertexProp>{std::move(prop)},
+                              std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)});
+
+        // project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("name"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("age"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get());
+        project->setColNames({"person.name", "person.age"});
+
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD const expression
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name, 1 > 1, person.age"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+
+        // get vertices
+        auto tagIdResult = schemaMng_->toTagID(1, "person");
+        ASSERT_TRUE(tagIdResult.ok());
+        auto tagId = tagIdResult.value();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId);
+        prop.set_props(std::vector<std::string>{"name", "age"});
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("name")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
+        auto *gv =
+            GetVertices::make(expectedQueryCtx_->plan(),
+                              start,
+                              1,
+                              std::vector<Row>{Row({"1"})},
+                              nullptr,
+                              std::vector<storage::cpp2::VertexProp>{std::move(prop)},
+                              std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)});
+
+        // project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("name"))));
+        yieldColumns->addColumn(new YieldColumn(new RelationalExpression(
+            Expression::Kind::kRelGT, new ConstantExpression(1), new ConstantExpression(1))));
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("age"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get());
+        project->setColNames({"person.name", "(1>1)", "person.age"});
+
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD combine properties
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD person.name + person.age"));
+        auto *plan = qCtx_->plan();
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto tagIdResult = schemaMng_->toTagID(1, "person");
+        ASSERT_TRUE(tagIdResult.ok());
+        auto tagId = tagIdResult.value();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId);
+        prop.set_props(std::vector<std::string>{"name", "age"});
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            ArithmeticExpression(
+                Expression::Kind::kAdd,
+                new TagPropertyExpression(new std::string("person"), new std::string("name")),
+                new TagPropertyExpression(new std::string("person"), new std::string("age")))
+                .encode());
+
+        auto *gv = GetVertices::make(expectedQueryCtx_->plan(),
+                                     start,
+                                     1,
+                                     std::vector<Row>{Row({"1"})},
+                                     nullptr,
+                                     std::vector<storage::cpp2::VertexProp>{std::move(prop)},
+                                     std::vector<storage::cpp2::Expr>{std::move(expr1)});
+
+        // project, TODO(shylock) could push down to storage is it supported
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(new ArithmeticExpression(
+            Expression::Kind::kAdd,
+            new TagPropertyExpression(new std::string("person"), new std::string("name")),
+            new TagPropertyExpression(new std::string("person"), new std::string("age")))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get());
+        project->setColNames({"(person.name+person.age)"});
+
+        auto result = Eq(plan->root(), project);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // With YIELD distinct
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\" YIELD distinct person.name, person.age"));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+        auto tagIdResult = schemaMng_->toTagID(1, "person");
+        ASSERT_TRUE(tagIdResult.ok());
+        auto tagId = tagIdResult.value();
+        storage::cpp2::VertexProp prop;
+        prop.set_tag(tagId);
+        prop.set_props(std::vector<std::string>{"name", "age"});
+        storage::cpp2::Expr expr1;
+        expr1.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("name")).encode());
+        storage::cpp2::Expr expr2;
+        expr2.set_expr(
+            TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
+        auto *gv =
+            GetVertices::make(expectedQueryCtx_->plan(),
+                              start,
+                              1,
+                              std::vector<Row>{Row({"1"})},
+                              nullptr,
+                              std::vector<storage::cpp2::VertexProp>{std::move(prop)},
+                              std::vector<storage::cpp2::Expr>{std::move(expr1), std::move(expr2)});
+
+        std::vector<std::string> colNames{"person.name", "person.age"};
+
+        // project
+        auto yieldColumns = std::make_unique<YieldColumns>();
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("name"))));
+        yieldColumns->addColumn(new YieldColumn(
+            new TagPropertyExpression(new std::string("person"), new std::string("age"))));
+        auto *project = Project::make(expectedQueryCtx_->plan(), gv, yieldColumns.get());
+        project->setColNames({"person.name", "person.age"});
+
+        // dedup
+        auto *dedup = Dedup::make(expectedQueryCtx_->plan(), project);
+        dedup->setColNames(colNames);
+
+        // data collect
+        auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(),
+                                              dedup,
+                                              DataCollect::CollectKind::kRowBasedMove,
+                                              {dedup->varName()});
+        dataCollect->setColNames(colNames);
+
+        auto result = Eq(plan->root(), dataCollect);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+    // ON *
+    {
+        ASSERT_TRUE(toPlan("FETCH PROP ON * \"1\""));
+
+        auto *start = StartNode::make(expectedQueryCtx_->plan());
+
+        auto *plan = qCtx_->plan();
+
+        auto *gv = GetVertices::make(
+            expectedQueryCtx_->plan(), start, 1, std::vector<Row>{Row({"1"})}, nullptr, {}, {});
+        auto result = Eq(plan->root(), gv);
+        ASSERT_TRUE(result.ok()) << result;
+    }
+}
+
+TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) {
+    // pipe
+    {
+        const std::string query = "FETCH PROP ON person \"1\" YIELD person.name AS name"
+                                  " | FETCH PROP ON person $-.name";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+    // Variable
+    {
+        const std::string query = "$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                                  "FETCH PROP ON person $a.name";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+
+    // with project
+    // pipe
+    {
+        const std::string query = "FETCH PROP ON person \"1\" YIELD person.name + 1 AS name"
+                                  " | FETCH PROP ON person $-.name YIELD person.name + 1";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+    // Variable
+    {
+        const std::string query = "$a = FETCH PROP ON person \"1\" YIELD person.name + 1 AS name;"
+                                  "FETCH PROP ON person $a.name YIELD person.name + 1 ";
+        EXPECT_TRUE(checkResult(query,
+                                {
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kProject,
+                                    PlanNode::Kind::kGetVertices,
+                                    PlanNode::Kind::kStart,
+                                }));
+    }
+}
+
+TEST_F(FetchVerticesValidatorTest, FetchVerticesPropFailed) {
+    // mismatched tag
+    {
+        auto result = GQLParser().parse("FETCH PROP ON tag1 \"1\" YIELD tag2.prop2");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // not exist tag
+    {
+        auto result =
+            GQLParser().parse("FETCH PROP ON not_exist_tag \"1\" YIELD not_exist_tag.prop1");
+        ASSERT_TRUE(result.ok()) << result.status();
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // not exist property
+    {
+        auto result =
+            GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.not_exist_property");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // invalid yield expression
+    {
+        auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                                        " FETCH PROP ON person \"1\" YIELD $a.name + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    // invalid yield expression
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.name AS name | "
+                                        " FETCH PROP ON person \"1\" YIELD $-.name + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._src + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._type");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._rank + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person._dst + 1");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+}
+
+TEST_F(FetchVerticesValidatorTest, FetchVerticesInputFailed) {
+    // mismatched varirable
+    {
+        auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                                        "FETCH PROP ON person $b.name");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // mismatched varirable property
+    {
+        auto result = GQLParser().parse("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                                        "FETCH PROP ON person $a.not_exist_property");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+
+    // mismatched input property
+    {
+        auto result = GQLParser().parse("FETCH PROP ON person \"1\" YIELD person.name AS name | "
+                                        "FETCH PROP ON person $-.not_exist_property");
+        ASSERT_TRUE(result.ok());
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        ASSERT_FALSE(validateResult.ok());
+    }
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/test/MockSchemaManager.cpp b/src/validator/test/MockSchemaManager.cpp
index 25f233cc5184c5e612c92fdbc637d97e2eb69aad..511bf782259b1dd0b2c7595051a3cd1d89e30c65 100644
--- a/src/validator/test/MockSchemaManager.cpp
+++ b/src/validator/test/MockSchemaManager.cpp
@@ -9,6 +9,11 @@
 namespace nebula {
 namespace graph {
 
+// space: test_space
+// tag:
+//     person(name string, age int8)
+// edge:
+//     like(start timestamp, end timestamp, likeness int64)
 void MockSchemaManager::init() {
     spaceNameIds_.emplace("test_space", 1);
     tagNameIds_.emplace("person", 2);
diff --git a/src/validator/test/MockSchemaManager.h b/src/validator/test/MockSchemaManager.h
index 7ef370f136edecb7d7794c3da3f5083ba46e7951..b9f914432da26bef20fb679deecb487573b4ce7a 100644
--- a/src/validator/test/MockSchemaManager.h
+++ b/src/validator/test/MockSchemaManager.h
@@ -19,6 +19,12 @@ public:
     MockSchemaManager() = default;
     ~MockSchemaManager() = default;
 
+    static std::unique_ptr<MockSchemaManager> makeUnique() {
+        auto instance = std::make_unique<MockSchemaManager>();
+        instance->init();
+        return instance;
+    }
+
     void init();
 
     std::shared_ptr<const nebula::meta::NebulaSchemaProvider>
diff --git a/src/validator/test/ValidatorTestBase.cpp b/src/validator/test/ValidatorTestBase.cpp
index 80acd68cd123f26e8ef0489234f762b83b1d39a2..606fed112cc7a199e9154ffc2692dbd254002ab8 100644
--- a/src/validator/test/ValidatorTestBase.cpp
+++ b/src/validator/test/ValidatorTestBase.cpp
@@ -4,11 +4,244 @@
  * attached with Common Clause Condition 1.0, found in the LICENSES directory.
  */
 
+#include "common/base/Base.h"
+#include "context/QueryContext.h"
+#include "context/ValidateContext.h"
+#include "parser/GQLParser.h"
+#include "planner/Admin.h"
+#include "planner/ExecutionPlan.h"
+#include "planner/Maintain.h"
+#include "planner/Mutate.h"
+#include "planner/PlanNode.h"
+#include "planner/Query.h"
+#include "validator/ASTValidator.h"
 #include "validator/test/ValidatorTestBase.h"
 
 namespace nebula {
 namespace graph {
 
+// Compare the node itself's field in this function
+/*static*/ Status ValidatorTestBase::EqSelf(const PlanNode *l, const PlanNode *r) {
+    if (l != nullptr && r != nullptr) {
+        // continue
+    } else if (l == nullptr && r == nullptr) {
+        return Status::OK();
+    } else {
+        return Status::Error("%s.this != %s.this", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+    }
+    if (l->kind() != r->kind()) {
+        return Status::Error(
+            "%s.kind_ != %s.kind_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+    }
+    if (l->colNamesRef() != r->colNamesRef()) {
+        return Status::Error(
+            "%s.colNames_ != %s.colNames_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+    }
+    switch (l->kind()) {
+        case PlanNode::Kind::kUnknown:
+            return Status::Error("Unknown node");
+        case PlanNode::Kind::kStart:
+        case PlanNode::Kind::kDedup:
+            return Status::OK();
+        case PlanNode::Kind::kDeleteEdges:
+        case PlanNode::Kind::kDeleteVertices:
+        case PlanNode::Kind::kIndexScan:
+        case PlanNode::Kind::kGetNeighbors:
+        case PlanNode::Kind::kFilter:
+        case PlanNode::Kind::kSort:
+        case PlanNode::Kind::kLimit:
+        case PlanNode::Kind::kAggregate:
+        case PlanNode::Kind::kSwitchSpace:
+        case PlanNode::Kind::kMultiOutputs:
+        case PlanNode::Kind::kCreateSpace:
+        case PlanNode::Kind::kCreateTag:
+        case PlanNode::Kind::kCreateEdge:
+        case PlanNode::Kind::kDescSpace:
+        case PlanNode::Kind::kDescTag:
+        case PlanNode::Kind::kDescEdge:
+        case PlanNode::Kind::kInsertVertices:
+        case PlanNode::Kind::kInsertEdges:
+        case PlanNode::Kind::kUnion:
+        case PlanNode::Kind::kIntersect:
+        case PlanNode::Kind::kMinus:
+        case PlanNode::Kind::kSelect:
+        case PlanNode::Kind::kLoop:
+        case PlanNode::Kind::kAlterEdge:
+        case PlanNode::Kind::kAlterTag:
+        case PlanNode::Kind::kShowCreateSpace:
+        case PlanNode::Kind::kShowCreateTag:
+        case PlanNode::Kind::kShowCreateEdge:
+        case PlanNode::Kind::kDropSpace:
+        case PlanNode::Kind::kDropTag:
+        case PlanNode::Kind::kDropEdge:
+        case PlanNode::Kind::kShowSpaces:
+        case PlanNode::Kind::kShowTags:
+        case PlanNode::Kind::kShowEdges:
+        case PlanNode::Kind::kCreateSnapshot:
+        case PlanNode::Kind::kDropSnapshot:
+        case PlanNode::Kind::kShowSnapshots:
+        case PlanNode::Kind::kDataJoin:
+            LOG(FATAL) << "Unimplemented";
+        case PlanNode::Kind::kDataCollect: {
+            const auto *lDC = static_cast<const DataCollect*>(l);
+            const auto *rDC = static_cast<const DataCollect*>(r);
+            if (lDC->collectKind() != rDC->collectKind()) {
+                return Status::Error(
+                    "%s.collectKind_ != %s.collectKind_",
+                    l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            return Status::OK();
+        }
+        case PlanNode::Kind::kGetVertices: {
+            const auto *lGV = static_cast<const GetVertices *>(l);
+            const auto *rGV = static_cast<const GetVertices *>(r);
+            // vertices
+            if (lGV->vertices() != rGV->vertices()) {
+                return Status::Error(
+                    "%s.vertices_ != %s.vertices_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // src
+            if (lGV->src() != nullptr && rGV->src() != nullptr) {
+                if (*lGV->src() != *rGV->src()) {
+                    return Status::Error(
+                        "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+                }
+            } else if (lGV->src() != rGV->src()) {
+                    return Status::Error(
+                        "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // props
+            if (lGV->props() != rGV->props()) {
+                return Status::Error(
+                    "%s.props_ != %s.props_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            return Status::OK();
+        }
+        case PlanNode::Kind::kGetEdges: {
+            const auto *lGE = static_cast<const GetEdges *>(l);
+            const auto *rGE = static_cast<const GetEdges *>(r);
+            // vertices
+            if (lGE->edges() != rGE->edges()) {
+                return Status::Error(
+                    "%s.vertices_ != %s.vertices_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // src
+            if (lGE->src() != nullptr && rGE->src() != nullptr) {
+                if (*lGE->src() != *rGE->src()) {
+                    return Status::Error(
+                        "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+                }
+            } else if (lGE->src() != rGE->src()) {
+                    return Status::Error(
+                        "%s.src_ != %s.src_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // dst
+            if (lGE->dst() != nullptr && rGE->dst() != nullptr) {
+                if (*lGE->dst() != *rGE->dst()) {
+                    return Status::Error(
+                        "%s.dst_ != %s.dst_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+                }
+            } else if (lGE->dst() != rGE->dst()) {
+                    return Status::Error(
+                        "%s.dst_ != %s.dst_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // ranking
+            if (lGE->ranking() != nullptr && rGE->ranking() != nullptr) {
+                if (*lGE->ranking() != *lGE->ranking()) {
+                    return Status::Error(
+                        "%s.ranking_ != %s.ranking_",
+                        l->nodeLabel().c_str(), r->nodeLabel().c_str());
+                }
+            } else if (lGE->ranking() != rGE->ranking()) {
+                    return Status::Error(
+                        "%s.ranking_ != %s.ranking_",
+                        l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            // props
+            if (lGE->props() != rGE->props()) {
+                return Status::Error(
+                    "%s.props_ != %s.props_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            return Status::OK();
+        }
+        case PlanNode::Kind::kProject: {
+            const auto *lp = static_cast<const Project *>(l);
+            const auto *rp = static_cast<const Project *>(r);
+            if (lp->columns() == nullptr && rp->columns() == nullptr) {
+            } else if (lp->columns() != nullptr && rp->columns() != nullptr) {
+                auto lCols = lp->columns()->columns();
+                auto rCols = rp->columns()->columns();
+                if (lCols.size() != rCols.size()) {
+                    return Status::Error("%s.columns_ != %s.columns_",
+                                         l->nodeLabel().c_str(),
+                                         r->nodeLabel().c_str());
+                }
+                for (std::size_t i = 0; i < lCols.size(); ++i) {
+                    if (*lCols[i] != *rCols[i]) {
+                        return Status::Error("%s.columns_ != %s.columns_",
+                                             l->nodeLabel().c_str(),
+                                             r->nodeLabel().c_str());
+                    }
+                }
+            } else {
+                return Status::Error(
+                    "%s.columns_ != %s.columns_", l->nodeLabel().c_str(), r->nodeLabel().c_str());
+            }
+            return Status::OK();
+        }
+    }
+    LOG(FATAL) << "Unknow plan node kind " << static_cast<int>(l->kind());
+}
+
+// only traversal the plan by inputs, only `select` or `loop` make ring
+// So it's a DAG
+// There are some node visited multiple times but it doesn't matter
+// For Example:
+//      A
+//    |  |
+//   V   V
+//   B   C
+//   |  |
+//   V V
+//    D
+//    |
+//   V
+//   E
+// this will traversal sub-tree [D->E] twice but not matter the Equal result
+// TODO(shylock) maybe need check the toplogy of `Select` and `Loop`
+/*static*/ Status ValidatorTestBase::Eq(const PlanNode *l, const PlanNode *r) {
+    auto result = EqSelf(l, r);
+    if (!result.ok()) {
+        return result;
+    }
+
+    const auto *lSingle = dynamic_cast<const SingleDependencyNode *>(l);
+    const auto *rSingle = dynamic_cast<const SingleDependencyNode *>(r);
+    if (lSingle != nullptr) {
+        const auto *lDep = lSingle->dep();
+        const auto *rDep = CHECK_NOTNULL(rSingle)->dep();
+        return Eq(lDep, rDep);
+    }
+
+    const auto *lBi = dynamic_cast<const BiInputNode *>(l);
+    const auto *rBi = dynamic_cast<const BiInputNode *>(r);
+    if (lBi != nullptr) {
+        const auto *llInput = lBi->left();
+        const auto *rlInput = CHECK_NOTNULL(rBi)->left();
+        result = Eq(llInput, rlInput);
+        if (!result.ok()) {
+            return result;
+        }
+
+        const auto *lrInput = lBi->right();
+        const auto *rrInput = rBi->right();
+        result = Eq(lrInput, rrInput);
+        return result;
+    }
+
+    return result;
+}
+
 std::ostream& operator<<(std::ostream& os, const std::vector<PlanNode::Kind>& plan) {
     std::vector<const char*> kinds(plan.size());
     std::transform(plan.cbegin(), plan.cend(), kinds.begin(), PlanNode::toString);
diff --git a/src/validator/test/ValidatorTestBase.h b/src/validator/test/ValidatorTestBase.h
index 3e93991d8dd55f8f27fab39328af2d8c635badb3..fa09d1c54f112a8b59e9c32d171520f8fce3ac41 100644
--- a/src/validator/test/ValidatorTestBase.h
+++ b/src/validator/test/ValidatorTestBase.h
@@ -4,10 +4,10 @@
  * attached with Common Clause Condition 1.0, found in the LICENSES directory.
  */
 
-#ifndef VALIDATOR_TEST_VALIDATORTESTBASE_H_
-#define VALIDATOR_TEST_VALIDATORTESTBASE_H_
+#ifndef _VALIDATOR_TEST_VALIDATOR_TEST_BASE_H_
+#define _VALIDATOR_TEST_VALIDATOR_TEST_BASE_H_
 
-#include <vector>
+#include "common/base/Base.h"
 
 #include <gtest/gtest.h>
 
@@ -30,15 +30,40 @@ protected:
     void SetUp() override {
         session_ = Session::create(0);
         session_->setSpace("test_space", 1);
-        schemaMng_ = std::make_unique<MockSchemaManager>();
-        schemaMng_->init();
+        schemaMng_ = CHECK_NOTNULL(MockSchemaManager::makeUnique());
+        qCtx_ = buildContext();
+        expectedQueryCtx_ = buildContext();
     }
 
     void TearDown() override {
-        session_.reset();
-        schemaMng_.reset();
     }
 
+    // some utils
+    inline ::testing::AssertionResult toPlan(const std::string &query) {
+        auto result = GQLParser().parse(query);
+        if (!result.ok()) {
+            return ::testing::AssertionFailure() << result.status().toString();
+        }
+        sentences_ = std::move(result).value();
+        qCtx_ = buildContext();
+        ASTValidator validator(sentences_.get(), qCtx_.get());
+        auto validateResult = validator.validate();
+        if (!validateResult.ok()) {
+            return ::testing::AssertionFailure() << validateResult.toString();
+        }
+        return ::testing::AssertionSuccess();
+    }
+
+    StatusOr<ExecutionPlan*> validate(const std::string& query) {
+        auto result = GQLParser().parse(query);
+        if (!result.ok()) return std::move(result).status();
+        auto sentences = std::move(result).value();
+        ASTValidator validator(sentences.get(), qCtx_.get());
+        NG_RETURN_IF_ERROR(validator.validate());
+        return qCtx_->plan();
+    }
+
+
     std::unique_ptr<QueryContext> buildContext() {
         auto rctx = std::make_unique<RequestContext<cpp2::ExecutionResponse>>();
         rctx->setSession(session_);
@@ -49,6 +74,10 @@ protected:
         return qctx;
     }
 
+    static Status EqSelf(const PlanNode* l, const PlanNode* r);
+
+    static Status Eq(const PlanNode *l, const PlanNode *r);
+
     ::testing::AssertionResult checkResult(const std::string& query,
                                            const std::vector<PlanNode::Kind>& expected = {},
                                            const std::vector<std::string> &rootColumns = {}) {
@@ -196,6 +225,10 @@ protected:
 protected:
     std::shared_ptr<Session>              session_;
     std::unique_ptr<MockSchemaManager>    schemaMng_;
+    std::unique_ptr<QueryContext>         qCtx_;
+    std::unique_ptr<Sentence>             sentences_;
+    // used to hold the expected query plan
+    std::unique_ptr<QueryContext>         expectedQueryCtx_;
 };
 
 std::ostream& operator<<(std::ostream& os, const std::vector<PlanNode::Kind>& plan);