diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 4b884fba8e20e7db8c7da14af295bff761a9e37d..40a0dddb46365174e2759f2f5421e0b7a98e8486 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -25,17 +25,7 @@ jobs:
       - name: Cpplint
         run: |
           ln -snf $PWD/.linters/cpp/hooks/pre-commit.sh $PWD/.linters/cpp/pre-commit.sh
-          .linters/cpp/pre-commit.sh ${{ steps.diff.outputs.all }}
-      - name: Pylint
-        if: false
-        uses: docker://github/super-linter:v3
-        env:
-          VALIDATE_ALL_CODEBASE: false
-          DEFAULT_BRANCH: master
-          DEFAULT_WORKSPACE: ${{ github.workspace }}/tests
-          PYTHON_PYLINT_CONFIG_FILE: ${{ github.workspace }}/tests/.pylintrc
-          VALIDATE_PYTHON_FLAKE8: false
-          GITHUB_TOKEN: ${{ github.token }}
+          .linters/cpp/pre-commit.sh $(git --no-pager diff --diff-filter=d --name-only HEAD^ HEAD)
 
   build:
     name: build
diff --git a/src/scheduler/Scheduler.cpp b/src/scheduler/Scheduler.cpp
index 5b207907a9f37bfd7e9f5eccbec2b2ff8d3173ee..8f60314c7b08741c6fd4845f1500f2ffc5468d20 100644
--- a/src/scheduler/Scheduler.cpp
+++ b/src/scheduler/Scheduler.cpp
@@ -19,6 +19,9 @@ namespace graph {
 
 Scheduler::Task::Task(const Executor *e) : planId(DCHECK_NOTNULL(e)->node()->id()) {}
 
+Scheduler::MultiOutputsData::MultiOutputsData(int32_t outputs)
+    : promise(std::make_unique<folly::SharedPromise<Status>>()), numOutputs(outputs) {}
+
 Scheduler::Scheduler(QueryContext *qctx) : qctx_(DCHECK_NOTNULL(qctx)) {}
 
 folly::Future<Status> Scheduler::schedule() {
diff --git a/src/scheduler/Scheduler.h b/src/scheduler/Scheduler.h
index ca7825ff8c3d6aa2eabba72e5b9e5bf6be9950b5..0a88ed1c752b525686bad4390eaf9371e955142c 100644
--- a/src/scheduler/Scheduler.h
+++ b/src/scheduler/Scheduler.h
@@ -28,7 +28,7 @@ class LoopExecutor;
 
 class Scheduler final : private cpp::NonCopyable, private cpp::NonMovable {
 public:
-    // For check whether a task is a Scheduler::Task by std::is_base_of<>::value in thread pool
+    // check whether a task is a Scheduler::Task by std::is_base_of<>::value in thread pool
     struct Task {
         int64_t planId;
         explicit Task(const Executor *e);
@@ -41,7 +41,7 @@ public:
 
 private:
     // Enable thread pool check the query plan id of each callback registered in future. The functor
-    // is only the proxy of the invocable function `fn`.
+    // is only the proxy of the invocable function `fn'.
     template <typename F>
     struct ExecTask : Task {
         using Extract = folly::futures::detail::Extract<F>;
@@ -68,17 +68,15 @@ private:
     folly::Future<Status> iterate(LoopExecutor *loop);
     folly::Future<Status> execute(Executor *executor);
 
-    QueryContext *qctx_{nullptr};
-
     struct MultiOutputsData {
         folly::SpinLock lock;
         std::unique_ptr<folly::SharedPromise<Status>> promise;
         int32_t numOutputs;
 
-        explicit MultiOutputsData(int32_t outputs)
-            : promise(std::make_unique<folly::SharedPromise<Status>>()), numOutputs(outputs) {}
+        explicit MultiOutputsData(int32_t outputs);
     };
 
+    QueryContext *qctx_{nullptr};
     std::unordered_map<std::string, MultiOutputsData> multiOutputPromiseMap_;
 };
 
diff --git a/src/service/QueryInstance.cpp b/src/service/QueryInstance.cpp
index c0d19c0dcc59cf65dc5cc975a7d30e2909b379b7..08fbc7aff4dc240c3542d270eed961a1e66d2fe7 100644
--- a/src/service/QueryInstance.cpp
+++ b/src/service/QueryInstance.cpp
@@ -13,6 +13,7 @@
 #include "planner/ExecutionPlan.h"
 #include "planner/PlanNode.h"
 #include "scheduler/Scheduler.h"
+#include "validator/Validator.h"
 
 namespace nebula {
 namespace graph {
@@ -46,10 +47,9 @@ Status QueryInstance::validateAndOptimize() {
     VLOG(1) << "Parsing query: " << rctx->query();
     auto result = GQLParser().parse(rctx->query());
     NG_RETURN_IF_ERROR(result);
-    sentences_ = std::move(result).value();
+    sentence_ = std::move(result).value();
 
-    validator_ = std::make_unique<ASTValidator>(sentences_.get(), qctx());
-    NG_RETURN_IF_ERROR(validator_->validate());
+    NG_RETURN_IF_ERROR(Validator::validate(sentence_.get(), qctx()));
 
     // TODO: optional optimize for plan.
 
@@ -57,11 +57,11 @@ Status QueryInstance::validateAndOptimize() {
 }
 
 bool QueryInstance::explainOrContinue() {
-    if (sentences_->kind() != Sentence::Kind::kExplain) {
+    if (sentence_->kind() != Sentence::Kind::kExplain) {
         return true;
     }
     qctx_->fillPlanDescription();
-    return static_cast<const ExplainSentence *>(sentences_.get())->isProfile();
+    return static_cast<const ExplainSentence *>(sentence_.get())->isProfile();
 }
 
 void QueryInstance::onFinish() {
diff --git a/src/service/QueryInstance.h b/src/service/QueryInstance.h
index 615c5950317d6a714b0e18d4ef1defc442c54511..8c813fddf5db7240a619390deca34cd83f051b7c 100644
--- a/src/service/QueryInstance.h
+++ b/src/service/QueryInstance.h
@@ -13,7 +13,6 @@
 #include "context/QueryContext.h"
 #include "parser/GQLParser.h"
 #include "scheduler/Scheduler.h"
-#include "validator/ASTValidator.h"
 
 /**
  * QueryInstance coordinates the execution process,
@@ -57,9 +56,8 @@ private:
     // return true if continue to execute
     bool explainOrContinue();
 
-    std::unique_ptr<Sentence>                   sentences_;
+    std::unique_ptr<Sentence>                   sentence_;
     std::unique_ptr<QueryContext>               qctx_;
-    std::unique_ptr<ASTValidator>               validator_;
     std::unique_ptr<Scheduler>                  scheduler_;
 };
 
diff --git a/src/validator/ASTValidator.cpp b/src/validator/ASTValidator.cpp
deleted file mode 100644
index 0b343aaed019927c702b7c925825bf9142bd413a..0000000000000000000000000000000000000000
--- a/src/validator/ASTValidator.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/* 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/ASTValidator.h"
-
-#include "parser/Sentence.h"
-#include "planner/ExecutionPlan.h"
-#include "validator/Validator.h"
-
-namespace nebula {
-namespace graph {
-
-Status ASTValidator::validate() {
-    // Check if space chosen from session. if chosen, add it to context.
-    auto session = qctx_->rctx()->session();
-    if (session->space() > -1) {
-        qctx_->vctx()->switchToSpace(session->spaceName(), session->space());
-    }
-
-    auto validator = Validator::makeValidator(sentences_, qctx_);
-    NG_RETURN_IF_ERROR(validator->validate());
-
-    auto root = validator->root();
-    if (!root) {
-        return Status::Error("Get null plan from sequantial validator.");
-    }
-
-    qctx_->plan()->setRoot(root);
-    return Status::OK();
-}
-
-}  // namespace graph
-}  // namespace nebula
diff --git a/src/validator/ASTValidator.h b/src/validator/ASTValidator.h
deleted file mode 100644
index dadbdf7190895ca914ee96ea4867cf9384ab4b2a..0000000000000000000000000000000000000000
--- a/src/validator/ASTValidator.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/* 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_ASTVALIDATOR_H_
-#define VALIDATOR_ASTVALIDATOR_H_
-
-#include "common/base/Base.h"
-#include "parser/Sentence.h"
-#include "context/QueryContext.h"
-
-namespace nebula {
-
-class Sentence;
-
-namespace graph {
-
-class ExecutionPlan;
-
-class ASTValidator final {
-public:
-    ASTValidator(Sentence* sentences, QueryContext* qctx)
-        : sentences_(DCHECK_NOTNULL(sentences)), qctx_(DCHECK_NOTNULL(qctx)) {}
-
-    Status validate();
-
-private:
-    Sentence*                           sentences_{nullptr};
-    QueryContext*                       qctx_{nullptr};
-};
-}  // namespace graph
-}  // namespace nebula
-#endif
diff --git a/src/validator/CMakeLists.txt b/src/validator/CMakeLists.txt
index ce27a56e2bcff00bf5b66df3f403d9c1cb51da08..cf8c317ded842302c83ed23527386b1f1505c56f 100644
--- a/src/validator/CMakeLists.txt
+++ b/src/validator/CMakeLists.txt
@@ -7,7 +7,6 @@ nebula_add_library(
     validator_obj OBJECT
     Validator.cpp
     AssignmentValidator.cpp
-    ASTValidator.cpp
     GoValidator.cpp
     PipeValidator.cpp
     SequentialValidator.cpp
diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp
index f5346911bffd36d9274a6aa183c394fac3ab3dfe..203fcae53ea4f54c594f3d3397675d004ab8f42e 100644
--- a/src/validator/Validator.cpp
+++ b/src/validator/Validator.cpp
@@ -187,6 +187,28 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon
     return std::make_unique<ReportError>(sentence, context);
 }
 
+// static
+Status Validator::validate(Sentence* sentence, QueryContext* qctx) {
+    DCHECK(sentence != nullptr);
+    DCHECK(qctx != nullptr);
+
+    // Check if space chosen from session. if chosen, add it to context.
+    auto session = qctx->rctx()->session();
+    if (session->space() > -1) {
+        qctx->vctx()->switchToSpace(session->spaceName(), session->space());
+    }
+
+    auto validator = makeValidator(sentence, qctx);
+    NG_RETURN_IF_ERROR(validator->validate());
+
+    auto root = validator->root();
+    if (!root) {
+        return Status::Error("Get null plan from sequential validator");
+    }
+    qctx->plan()->setRoot(root);
+    return Status::OK();
+}
+
 Status Validator::appendPlan(PlanNode* node, PlanNode* appended) {
     switch (DCHECK_NOTNULL(node)->kind()) {
         case PlanNode::Kind::kShowHosts:
diff --git a/src/validator/Validator.h b/src/validator/Validator.h
index a0e65a3d00c3840272f8fbf9294583653f1a546d..fc780aa14dcfe4eaa43a481f0dd09afecf3e5fb6 100644
--- a/src/validator/Validator.h
+++ b/src/validator/Validator.h
@@ -78,10 +78,12 @@ public:
     static std::unique_ptr<Validator> makeValidator(Sentence* sentence,
                                                     QueryContext* context);
 
-    MUST_USE_RESULT Status appendPlan(PlanNode* tail);
+    static Status validate(Sentence* sentence, QueryContext* qctx);
 
     Status validate();
 
+    MUST_USE_RESULT Status appendPlan(PlanNode* tail);
+
     void setInputVarName(std::string name) {
         inputVarName_ = std::move(name);
     }
diff --git a/src/validator/test/ACLValidatorTest.cpp b/src/validator/test/ACLValidatorTest.cpp
index 338e4d147500502d826903918c108148ec1c2d9c..f737cad07adb2e09da5ab629c31d2ee6251f2fef 100644
--- a/src/validator/test/ACLValidatorTest.cpp
+++ b/src/validator/test/ACLValidatorTest.cpp
@@ -22,8 +22,7 @@ TEST_F(ACLValidatorTest, Simple) {
     constexpr char space[] = "test_space";
     // create user
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER %s", user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan(folly::stringPrintf("CREATE USER %s", user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kCreateUser,
@@ -39,8 +38,8 @@ TEST_F(ACLValidatorTest, Simple) {
         ASSERT_EQ(*createUser->password(), "");
     }
     {  // if not exists
-        ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER IF NOT EXISTS %s", user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan =
+            toPlan(folly::stringPrintf("CREATE USER IF NOT EXISTS %s", user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kCreateUser,
@@ -56,10 +55,8 @@ TEST_F(ACLValidatorTest, Simple) {
         ASSERT_EQ(*createUser->password(), "");
     }
     {  // with password
-        ASSERT_TRUE(toPlan(folly::stringPrintf("CREATE USER %s WITH PASSWORD \"%s\"",
-                                     user,
-                                     password)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan =
+            toPlan(folly::stringPrintf("CREATE USER %s WITH PASSWORD \"%s\"", user, password));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kCreateUser,
@@ -77,9 +74,7 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // drop user
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("DROP USER %s",
-                                    user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan(folly::stringPrintf("DROP USER %s", user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kDropUser,
@@ -94,9 +89,7 @@ TEST_F(ACLValidatorTest, Simple) {
         ASSERT_EQ(*dropUser->username(), user);
     }
     {  // if exits
-        ASSERT_TRUE(toPlan(folly::stringPrintf("DROP USER IF EXISTS %s",
-                                    user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan(folly::stringPrintf("DROP USER IF EXISTS %s", user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kDropUser,
@@ -113,9 +106,8 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // update user
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("ALTER USER %s WITH PASSWORD \"%s\"",
-                                    user, password)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan =
+            toPlan(folly::stringPrintf("ALTER USER %s WITH PASSWORD \"%s\"", user, password));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kUpdateUser,
@@ -132,8 +124,7 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // show users
     {
-        ASSERT_TRUE(toPlan("SHOW USERS"));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan("SHOW USERS");
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kListUsers,
@@ -144,9 +135,8 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // change password
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("CHANGE PASSWORD %s FROM \"%s\" TO \"%s\"",
-                                    user, password, newPassword)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan(folly::stringPrintf(
+            "CHANGE PASSWORD %s FROM \"%s\" TO \"%s\"", user, password, newPassword));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kChangePassword,
@@ -164,9 +154,8 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // grant role
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("GRANT ROLE %s ON %s TO %s",
-                                     roleTypeName, space, user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan =
+            toPlan(folly::stringPrintf("GRANT ROLE %s ON %s TO %s", roleTypeName, space, user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kGrantRole,
@@ -184,9 +173,8 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // revoke role
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("REVOKE ROLE %s ON %s FROM %s",
-                                    roleTypeName, space, user)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan =
+            toPlan(folly::stringPrintf("REVOKE ROLE %s ON %s FROM %s", roleTypeName, space, user));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kRevokeRole,
@@ -204,9 +192,7 @@ TEST_F(ACLValidatorTest, Simple) {
 
     // show roles in space
     {
-        ASSERT_TRUE(toPlan(folly::stringPrintf("SHOW ROLES IN %s",
-                                    space)));
-        const ExecutionPlan *plan = qCtx_->plan();
+        const ExecutionPlan *plan = toPlan(folly::stringPrintf("SHOW ROLES IN %s", space));
 
         std::vector<PlanNode::Kind> expectedTop {
             PlanNode::Kind::kListRoles,
diff --git a/src/validator/test/FetchEdgesTest.cpp b/src/validator/test/FetchEdgesTest.cpp
index da4285e119f3b7bcbf5dae38420993b6ecfb2683..362882b4804e3a11e32a260e600e192c40987d88 100644
--- a/src/validator/test/FetchEdgesTest.cpp
+++ b/src/validator/test/FetchEdgesTest.cpp
@@ -14,20 +14,20 @@ namespace graph {
 class FetchEdgesValidatorTest : public ValidatorTestBase {};
 
 TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
-    auto src = std::make_unique<VariablePropertyExpression>(
-        new std::string(qCtx_->vctx()->anonVarGen()->getVar()), new std::string(kSrc));
-    auto type = std::make_unique<VariablePropertyExpression>(
-        new std::string(qCtx_->vctx()->anonVarGen()->getVar()), new std::string(kType));
-    auto rank = std::make_unique<VariablePropertyExpression>(
-        new std::string(qCtx_->vctx()->anonVarGen()->getVar()), new std::string(kRank));
-    auto dst = std::make_unique<VariablePropertyExpression>(
-        new std::string(qCtx_->vctx()->anonVarGen()->getVar()), new std::string(kDst));
+    auto src = std::make_unique<VariablePropertyExpression>(new std::string("_VAR1_"),
+                                                            new std::string(kSrc));
+    auto type = std::make_unique<VariablePropertyExpression>(new std::string("_VAR2_"),
+                                                             new std::string(kType));
+    auto rank = std::make_unique<VariablePropertyExpression>(new std::string("_VAR3_"),
+                                                             new std::string(kRank));
+    auto dst = std::make_unique<VariablePropertyExpression>(new std::string("_VAR4_"),
+                                                            new std::string(kDst));
     {
-        ASSERT_TRUE(toPlan("FETCH PROP ON like \"1\"->\"2\""));
+        auto plan = toPlan("FETCH PROP ON like \"1\"->\"2\"");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
         ASSERT_TRUE(edgeTypeResult.ok());
         auto edgeType = edgeTypeResult.value();
@@ -36,7 +36,7 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
         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(),
+        auto *ge = GetEdges::make(&expectedPlan,
                                   start,
                                   1,
                                   src.get(),
@@ -51,17 +51,17 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
                          "like.start",
                          "like.end",
                          "like.likeness"});
-        expectedQueryCtx_->plan()->setRoot(ge);
+        expectedPlan.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 plan = toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, like.end");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
         ASSERT_TRUE(edgeTypeResult.ok());
         auto edgeType = edgeTypeResult.value();
@@ -79,7 +79,7 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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(),
+        auto *ge = GetEdges::make(&expectedPlan,
                                   start,
                                   1,
                                   src.get(),
@@ -103,23 +103,22 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, 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);
+        expectedPlan.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 plan = toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start, 1 + 1, like.end");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
-
-        auto *plan = qCtx_->plan();
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
         // GetEdges
         auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
@@ -139,7 +138,7 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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(),
+        auto *ge = GetEdges::make(&expectedPlan,
                                   start,
                                   1,
                                   src.get(),
@@ -166,24 +165,24 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, 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);
+        expectedPlan.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 plan = toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD like.start > like.end");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
         ASSERT_TRUE(edgeTypeResult.ok());
         auto edgeType = edgeTypeResult.value();
@@ -202,7 +201,7 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
                 new EdgePropertyExpression(new std::string("like"), new std::string("end")))
                 .encode());
         exprs.emplace_back(std::move(expr1));
-        auto *ge = GetEdges::make(expectedQueryCtx_->plan(),
+        auto *ge = GetEdges::make(&expectedPlan,
                                   start,
                                   1,
                                   src.get(),
@@ -225,23 +224,23 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, 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);
+        expectedPlan.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 plan = toPlan("FETCH PROP ON like \"1\"->\"2\" YIELD distinct like.start, like.end");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto edgeTypeResult = schemaMng_->toEdgeType(1, "like");
         ASSERT_TRUE(edgeTypeResult.ok());
         auto edgeType = edgeTypeResult.value();
@@ -259,7 +258,7 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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(),
+        auto *ge = GetEdges::make(&expectedPlan,
                                   start,
                                   1,
                                   src.get(),
@@ -285,24 +284,22 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, 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);
+        auto *dedup = Dedup::make(&expectedPlan, project);
         dedup->setColNames(colNames);
 
         // data collect
-        auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(),
-                                              dedup,
-                                              DataCollect::CollectKind::kRowBasedMove,
-                                              {dedup->varName()});
+        auto *dataCollect = DataCollect::make(
+            &expectedPlan, dedup, DataCollect::CollectKind::kRowBasedMove, {dedup->varName()});
         dataCollect->setColNames(colNames);
 
-        expectedQueryCtx_->plan()->setRoot(dataCollect);
+        expectedPlan.setRoot(dataCollect);
         auto result = Eq(plan->root(), dataCollect);
         ASSERT_TRUE(result.ok()) << result;
     }
@@ -372,132 +369,49 @@ TEST_F(FetchEdgesValidatorTest, FetchEdgesInputOutput) {
 
 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON edge1 \"1\"->\"2\" YIELD edge2.prop2"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON not_exist_edge \"1\"->\"2\" YIELD not_exist_edge.prop1"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" YIELD like.not_exist_prop"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("$a = FETCH PROP ON like \"1\"->\"2\" "
+                          "YIELD like._src AS src;"
+                          "FETCH PROP ON like \"1\"->\"2\" YIELD $a.src + 1"));
+
+    ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" "
+                          "YIELD like._src AS src | "
+                          "FETCH PROP ON like \"1\"->\"2\" YIELD $-.src + 1"));
+
+    ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" YIELD $^.like.start + 1"));
+    ASSERT_FALSE(validate("FETCH PROP ON like \"1\"->\"2\" YIELD $$.like.start + 1"));
 }
 
 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());
-    }
+    ASSERT_FALSE(validate("$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"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("$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"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("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"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("$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"));
 }
 
 }   // namespace graph
diff --git a/src/validator/test/FetchVerticesTest.cpp b/src/validator/test/FetchVerticesTest.cpp
index 1cfd96a09aef28b49c1b2d0033737a3cfd34aa9c..bfabf2fc69fe5a7a7cd91abed9814ed0de63824c 100644
--- a/src/validator/test/FetchVerticesTest.cpp
+++ b/src/validator/test/FetchVerticesTest.cpp
@@ -4,7 +4,9 @@
  * attached with Common Clause Condition 1.0, found in the LICENSES directory.
  */
 
+#include "planner/Logic.h"
 #include "planner/Query.h"
+#include "util/ObjectPool.h"
 #include "validator/FetchVerticesValidator.h"
 #include "validator/test/ValidatorTestBase.h"
 
@@ -15,35 +17,36 @@ class FetchVerticesValidatorTest : public ValidatorTestBase {};
 
 TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
     auto src = std::make_unique<VariablePropertyExpression>(
-        new std::string(qCtx_->vctx()->anonVarGen()->getVar()), new std::string(kVid));
+        new std::string("_VARNAME_"), new std::string(kVid));
     {
-        ASSERT_TRUE(toPlan("FETCH PROP ON person \"1\""));
+        auto plan = toPlan("FETCH PROP ON person \"1\"");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        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(),
+        auto *gv = GetVertices::make(&expectedPlan,
                                      start,
                                      1,
                                      src.get(),
                                      std::vector<storage::cpp2::VertexProp>{std::move(prop)},
                                      {});
         gv->setColNames({kVid, "person.name", "person.age"});
+        expectedPlan.setRoot(gv);
         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 plan = toPlan("FETCH PROP ON person \"1\" YIELD person.name, person.age");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto tagIdResult = schemaMng_->toTagID(1, "person");
         ASSERT_TRUE(tagIdResult.ok());
         auto tagId = tagIdResult.value();
@@ -57,7 +60,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
         expr2.set_expr(
             TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
         auto *gv =
-            GetVertices::make(expectedQueryCtx_->plan(),
+            GetVertices::make(&expectedPlan,
                               start,
                               1,
                               src.get(),
@@ -73,19 +76,19 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, gv, yieldColumns.get());
         project->setColNames({kVid, "person.name", "person.age"});
 
+        expectedPlan.setRoot(project);
         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 plan = toPlan("FETCH PROP ON person \"1\" YIELD person.name, 1 > 1, person.age");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
-
-        auto *plan = qCtx_->plan();
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
         // get vertices
         auto tagIdResult = schemaMng_->toTagID(1, "person");
@@ -101,7 +104,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
         expr2.set_expr(
             TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
         auto *gv =
-            GetVertices::make(expectedQueryCtx_->plan(),
+            GetVertices::make(&expectedPlan,
                               start,
                               1,
                               src.get(),
@@ -119,18 +122,20 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, gv, yieldColumns.get());
         project->setColNames({kVid, "person.name", "(1>1)", "person.age"});
 
+        expectedPlan.setRoot(project);
+
         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 plan = toPlan("FETCH PROP ON person \"1\" YIELD person.name + person.age");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
         auto tagIdResult = schemaMng_->toTagID(1, "person");
         ASSERT_TRUE(tagIdResult.ok());
@@ -146,7 +151,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
                 new TagPropertyExpression(new std::string("person"), new std::string("age")))
                 .encode());
 
-        auto *gv = GetVertices::make(expectedQueryCtx_->plan(),
+        auto *gv = GetVertices::make(&expectedPlan,
                                      start,
                                      1,
                                      src.get(),
@@ -162,19 +167,21 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, gv, yieldColumns.get());
         project->setColNames({kVid, "(person.name+person.age)"});
 
+        expectedPlan.setRoot(project);
+
         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 plan = toPlan("FETCH PROP ON person \"1\" YIELD distinct person.name, person.age");
 
-        auto *start = StartNode::make(expectedQueryCtx_->plan());
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
-        auto *plan = qCtx_->plan();
         auto tagIdResult = schemaMng_->toTagID(1, "person");
         ASSERT_TRUE(tagIdResult.ok());
         auto tagId = tagIdResult.value();
@@ -188,7 +195,7 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
         expr2.set_expr(
             TagPropertyExpression(new std::string("person"), new std::string("age")).encode());
         auto *gv =
-            GetVertices::make(expectedQueryCtx_->plan(),
+            GetVertices::make(&expectedPlan,
                               start,
                               1,
                               src.get(),
@@ -206,34 +213,34 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesProp) {
             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());
+        auto *project = Project::make(&expectedPlan, gv, yieldColumns.get());
         project->setColNames(colNames);
 
         // dedup
-        auto *dedup = Dedup::make(expectedQueryCtx_->plan(), project);
+        auto *dedup = Dedup::make(&expectedPlan, project);
         dedup->setColNames(colNames);
 
         // data collect
-        auto *dataCollect = DataCollect::make(expectedQueryCtx_->plan(),
-                                              dedup,
-                                              DataCollect::CollectKind::kRowBasedMove,
-                                              {dedup->varName()});
+        auto *dataCollect = DataCollect::make(
+            &expectedPlan, dedup, DataCollect::CollectKind::kRowBasedMove, {dedup->varName()});
         dataCollect->setColNames(colNames);
 
+        expectedPlan.setRoot(dataCollect);
+
         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 = toPlan("FETCH PROP ON * \"1\"");
 
-        auto *plan = qCtx_->plan();
+        ExecutionPlan expectedPlan(pool_.get());
+        auto *start = StartNode::make(&expectedPlan);
 
         auto *gv = GetVertices::make(
-            expectedQueryCtx_->plan(), start, 1, src.get(), {}, {});
+            &expectedPlan, start, 1, src.get(), {}, {});
         gv->setColNames({kVid, "person.name", "person.age"});
+        expectedPlan.setRoot(gv);
         auto result = Eq(plan->root(), gv);
         ASSERT_TRUE(result.ok()) << result;
     }
@@ -296,140 +303,40 @@ TEST_F(FetchVerticesValidatorTest, FetchVerticesInputOutput) {
 
 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON tag1 \"1\" YIELD tag2.prop2"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON not_exist_tag \"1\" YIELD not_exist_tag.prop1"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person.not_exist_property"));
 
     // 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");
-        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.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());
-    }
-    {
-        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());
-    }
+    ASSERT_FALSE(validate("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                          " FETCH PROP ON person \"1\" YIELD $a.name + 1"));
+
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD $^.person.name"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD $$.person.name"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person.name AS name | "
+                          " FETCH PROP ON person \"1\" YIELD $-.name + 1"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person._src + 1"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person._type"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person._rank + 1"));
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person._dst + 1"));
 }
 
 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());
-    }
+    ASSERT_FALSE(validate("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                          "FETCH PROP ON person $b.name"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("$a = FETCH PROP ON person \"1\" YIELD person.name AS name;"
+                          "FETCH PROP ON person $a.not_exist_property"));
 
     // 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());
-    }
+    ASSERT_FALSE(validate("FETCH PROP ON person \"1\" YIELD person.name AS name | "
+                          "FETCH PROP ON person $-.not_exist_property"));
 }
 
 }   // namespace graph
diff --git a/src/validator/test/ValidatorTestBase.cpp b/src/validator/test/ValidatorTestBase.cpp
index 8290bf4a57dfb13281599b01579147d9d4f0ddc4..9275d1a33f723a1c8b272132b0db46937ce665f3 100644
--- a/src/validator/test/ValidatorTestBase.cpp
+++ b/src/validator/test/ValidatorTestBase.cpp
@@ -4,6 +4,8 @@
  * attached with Common Clause Condition 1.0, found in the LICENSES directory.
  */
 
+#include "validator/test/ValidatorTestBase.h"
+
 #include "common/base/Base.h"
 #include "context/QueryContext.h"
 #include "context/ValidateContext.h"
@@ -14,14 +16,116 @@
 #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 {
 
+// static
+void ValidatorTestBase::bfsTraverse(const PlanNode *root, std::vector<PlanNode::Kind> &result) {
+    std::queue<const PlanNode *> queue;
+    std::unordered_set<int64_t> visited;
+    queue.emplace(root);
+
+    while (!queue.empty()) {
+        auto node = queue.front();
+        VLOG(1) << "node kind: " << node->kind();
+        queue.pop();
+        if (visited.find(node->id()) != visited.end()) {
+            continue;
+        }
+        visited.emplace(node->id());
+        result.emplace_back(node->kind());
+
+        switch (node->kind()) {
+            case PlanNode::Kind::kUnknown:
+                ASSERT_TRUE(false) << "Unknown Plan Node.";
+            case PlanNode::Kind::kStart: {
+                break;
+            }
+            case PlanNode::Kind::kCreateUser:
+            case PlanNode::Kind::kDropUser:
+            case PlanNode::Kind::kUpdateUser:
+            case PlanNode::Kind::kGrantRole:
+            case PlanNode::Kind::kRevokeRole:
+            case PlanNode::Kind::kChangePassword:
+            case PlanNode::Kind::kListUserRoles:
+            case PlanNode::Kind::kListUsers:
+            case PlanNode::Kind::kListRoles:
+            case PlanNode::Kind::kShowHosts:
+            case PlanNode::Kind::kGetNeighbors:
+            case PlanNode::Kind::kGetVertices:
+            case PlanNode::Kind::kGetEdges:
+            case PlanNode::Kind::kIndexScan:
+            case PlanNode::Kind::kFilter:
+            case PlanNode::Kind::kProject:
+            case PlanNode::Kind::kSort:
+            case PlanNode::Kind::kLimit:
+            case PlanNode::Kind::kAggregate:
+            case PlanNode::Kind::kSwitchSpace:
+            case PlanNode::Kind::kMultiOutputs:
+            case PlanNode::Kind::kDedup:
+            case PlanNode::Kind::kDataCollect:
+            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::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:
+            case PlanNode::Kind::kDeleteVertices:
+            case PlanNode::Kind::kDeleteEdges:
+            case PlanNode::Kind::kUpdateVertex:
+            case PlanNode::Kind::kUpdateEdge: {
+                auto *current = static_cast<const SingleDependencyNode *>(node);
+                queue.emplace(current->dep());
+                break;
+            }
+            case PlanNode::Kind::kUnion:
+            case PlanNode::Kind::kIntersect:
+            case PlanNode::Kind::kMinus: {
+                auto *current = static_cast<const BiInputNode *>(node);
+                queue.emplace(current->left());
+                queue.emplace(current->right());
+                break;
+            }
+            case PlanNode::Kind::kSelect: {
+                auto *current = static_cast<const Select *>(node);
+                queue.emplace(current->dep());
+                queue.emplace(current->then());
+                if (current->otherwise() != nullptr) {
+                    queue.emplace(current->otherwise());
+                }
+                break;
+            }
+            case PlanNode::Kind::kLoop: {
+                auto *current = static_cast<const Loop *>(node);
+                queue.emplace(current->dep());
+                queue.emplace(current->body());
+                break;
+            }
+            default:
+                LOG(FATAL) << "Unknown PlanNode: " << static_cast<int64_t>(node->kind());
+        }
+    }
+}
+
 // Compare the node itself's field in this function
-/*static*/ Status ValidatorTestBase::EqSelf(const PlanNode *l, const PlanNode *r) {
+// static
+Status ValidatorTestBase::EqSelf(const PlanNode *l, const PlanNode *r) {
     if (l != nullptr && r != nullptr) {
         // continue
     } else if (l == nullptr && r == nullptr) {
@@ -213,4 +317,3 @@ int main(int argc, char** argv) {
 
     return RUN_ALL_TESTS();
 }
-
diff --git a/src/validator/test/ValidatorTestBase.h b/src/validator/test/ValidatorTestBase.h
index 737324d5f3141440c06071d8d002784de0d31565..592a4b161ac2ea1ea1159ca7c3421980ce1c17cf 100644
--- a/src/validator/test/ValidatorTestBase.h
+++ b/src/validator/test/ValidatorTestBase.h
@@ -19,7 +19,8 @@
 #include "planner/Logic.h"
 #include "planner/PlanNode.h"
 #include "planner/Query.h"
-#include "validator/ASTValidator.h"
+#include "util/ObjectPool.h"
+#include "validator/Validator.h"
 #include "validator/test/MockSchemaManager.h"
 
 namespace nebula {
@@ -31,43 +32,31 @@ protected:
         session_ = Session::create(0);
         session_->setSpace("test_space", 1);
         schemaMng_ = CHECK_NOTNULL(MockSchemaManager::makeUnique());
-        qCtx_ = buildContext();
-        expectedQueryCtx_ = buildContext();
+        pool_ = std::make_unique<ObjectPool>();
     }
 
-    void TearDown() override {
+    ExecutionPlan* toPlan(const std::string& query) {
+        auto planStatus = validate(query);
+        EXPECT_TRUE(planStatus);
+        return std::move(planStatus).value();
     }
 
-    // some utils
-    inline ::testing::AssertionResult toPlan(const std::string &query) {
+    StatusOr<ExecutionPlan*> validate(const std::string& query) {
+        VLOG(1) << "query: " << 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 std::move(result).status();
         }
-        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();
+        auto sentences = pool_->add(std::move(result).value().release());
+        auto qctx = buildContext();
+        NG_RETURN_IF_ERROR(Validator::validate(sentences, qctx));
+        return qctx->plan();
     }
 
-
-    std::unique_ptr<QueryContext> buildContext() {
+    QueryContext* buildContext() {
         auto rctx = std::make_unique<RequestContext<cpp2::ExecutionResponse>>();
         rctx->setSession(session_);
-        auto qctx = std::make_unique<QueryContext>();
+        auto qctx = pool_->add(new QueryContext());
         qctx->setRctx(std::move(rctx));
         qctx->setSchemaManager(schemaMng_.get());
         qctx->setCharsetInfo(CharsetInfo::instance());
@@ -81,26 +70,17 @@ protected:
     ::testing::AssertionResult checkResult(const std::string& query,
                                            const std::vector<PlanNode::Kind>& expected = {},
                                            const std::vector<std::string> &rootColumns = {}) {
-        VLOG(1) << "query: " << query;
-        auto result = GQLParser().parse(query);
-        if (!result.ok()) {
-            return ::testing::AssertionFailure() << result.status();
+        auto planStatus = validate(query);
+        if (!planStatus) {
+            return ::testing::AssertionFailure() << std::move(planStatus).status().toString();
         }
-
-        auto sentences = std::move(result).value();
-        auto context = buildContext();
-        ASTValidator validator(sentences.get(), context.get());
-        auto validateResult = validator.validate();
-        if (!validateResult.ok()) {
-            return ::testing::AssertionFailure() << validateResult;
+        auto plan = std::move(planStatus).value();
+        if (plan == nullptr) {
+            return ::testing::AssertionFailure() << "plan is nullptr";
         }
         if (expected.empty()) {
             return ::testing::AssertionSuccess();
         }
-        auto plan = context->plan();
-        if (plan == nullptr) {
-            return ::testing::AssertionFailure() << "plan is nullptr";
-        }
         auto assertResult = verifyPlan(plan->root(), expected);
         if (!assertResult) {
             return assertResult;
@@ -132,115 +112,13 @@ protected:
                << "\n\tResult: " << result << "\n\tExpected: " << expected;
     }
 
-    static void bfsTraverse(const PlanNode* root, std::vector<PlanNode::Kind>& result) {
-        std::queue<const PlanNode*> queue;
-        std::unordered_set<int64_t> visited;
-        queue.emplace(root);
-
-        while (!queue.empty()) {
-            auto node = queue.front();
-            VLOG(1) << "node kind: " << node->kind();
-            queue.pop();
-            if (visited.find(node->id()) != visited.end()) {
-                continue;
-            }
-            visited.emplace(node->id());
-            result.emplace_back(node->kind());
-
-            switch (node->kind()) {
-                case PlanNode::Kind::kUnknown:
-                    ASSERT_TRUE(false) << "Unknown Plan Node.";
-                case PlanNode::Kind::kStart: {
-                    break;
-                }
-                case PlanNode::Kind::kCreateUser:
-                case PlanNode::Kind::kDropUser:
-                case PlanNode::Kind::kUpdateUser:
-                case PlanNode::Kind::kGrantRole:
-                case PlanNode::Kind::kRevokeRole:
-                case PlanNode::Kind::kChangePassword:
-                case PlanNode::Kind::kListUserRoles:
-                case PlanNode::Kind::kListUsers:
-                case PlanNode::Kind::kListRoles:
-                case PlanNode::Kind::kShowHosts:
-                case PlanNode::Kind::kGetNeighbors:
-                case PlanNode::Kind::kGetVertices:
-                case PlanNode::Kind::kGetEdges:
-                case PlanNode::Kind::kIndexScan:
-                case PlanNode::Kind::kFilter:
-                case PlanNode::Kind::kProject:
-                case PlanNode::Kind::kSort:
-                case PlanNode::Kind::kLimit:
-                case PlanNode::Kind::kAggregate:
-                case PlanNode::Kind::kSwitchSpace:
-                case PlanNode::Kind::kMultiOutputs:
-                case PlanNode::Kind::kDedup:
-                case PlanNode::Kind::kDataCollect:
-                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::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:
-                case PlanNode::Kind::kDeleteVertices:
-                case PlanNode::Kind::kDeleteEdges:
-                case PlanNode::Kind::kUpdateVertex:
-                case PlanNode::Kind::kUpdateEdge: {
-                    auto* current = static_cast<const SingleDependencyNode*>(node);
-                    queue.emplace(current->dep());
-                    break;
-                }
-                case PlanNode::Kind::kUnion:
-                case PlanNode::Kind::kIntersect:
-                case PlanNode::Kind::kMinus: {
-                    auto* current = static_cast<const BiInputNode*>(node);
-                    queue.emplace(current->left());
-                    queue.emplace(current->right());
-                    break;
-                }
-                case PlanNode::Kind::kSelect: {
-                    auto* current = static_cast<const Select*>(node);
-                    queue.emplace(current->dep());
-                    queue.emplace(current->then());
-                    if (current->otherwise() != nullptr) {
-                        queue.emplace(current->otherwise());
-                    }
-                    break;
-                }
-                case PlanNode::Kind::kLoop: {
-                    auto* current = static_cast<const Loop*>(node);
-                    queue.emplace(current->dep());
-                    queue.emplace(current->body());
-                    break;
-                }
-                default:
-                    LOG(FATAL) << "Unknown PlanNode: " << static_cast<int64_t>(node->kind());
-            }
-        }
-    }
+    static void bfsTraverse(const PlanNode* root, std::vector<PlanNode::Kind>& result);
 
 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::unique_ptr<ObjectPool>           pool_;
 };
 
 std::ostream& operator<<(std::ostream& os, const std::vector<PlanNode::Kind>& plan);