diff --git a/ci/test.sh b/ci/test.sh
index 8bcd54413eb575bdd9a439709644b11eb304b9a0..88b9003e4b084869f9ccc7f9820bce3c0b1dd277 100755
--- a/ci/test.sh
+++ b/ci/test.sh
@@ -91,12 +91,14 @@ function run_ctest() {
 
 function run_test() {
     export PYTHONPATH=$PROJ_DIR:$PYTHONPATH
+    pushd $PROJ_DIR/tests
     pytest -n 8 --build_dir=$BUILD_DIR \
         --dist=loadfile \
         --debug_log=false \
         ${@:1}
 
     # $BUILD_DIR/tests/ntr --debug_log=false ${@:1} $PROJ_DIR/tests/job/*
+    popd
 }
 
 function test_in_cluster() {
diff --git a/src/context/ast/QueryAstContext.h b/src/context/ast/QueryAstContext.h
index 82db5166a93768f5b7595cb8cd1eb24642b75533..f0ced1840644606bed4fec4447378458ff0ca260 100644
--- a/src/context/ast/QueryAstContext.h
+++ b/src/context/ast/QueryAstContext.h
@@ -59,6 +59,7 @@ enum class AliasType : int8_t{
 struct ScanInfo {
     Expression                             *filter{nullptr};
     int32_t                                 schemaId{0};
+    const std::string                      *schemaName{nullptr};
 };
 
 struct CypherClauseContextBase : AstContext {
diff --git a/src/executor/query/IndexScanExecutor.cpp b/src/executor/query/IndexScanExecutor.cpp
index 698175763d24c0d3207219161c3c514edfa2728e..7a8a58993956db721a7a2243279b40a204778f2e 100644
--- a/src/executor/query/IndexScanExecutor.cpp
+++ b/src/executor/query/IndexScanExecutor.cpp
@@ -38,6 +38,7 @@ folly::Future<Status> IndexScanExecutor::indexScan() {
         });
 }
 
+// TODO(shylock) merge the handler with GetProp
 template <typename Resp>
 Status IndexScanExecutor::handleResp(storage::StorageRpcResponse<Resp> &&rpcResp) {
     auto completeness = handleCompleteness(rpcResp, false);
@@ -58,6 +59,10 @@ Status IndexScanExecutor::handleResp(storage::StorageRpcResponse<Resp> &&rpcResp
             state = Result::State::kPartialSuccess;
         }
     }
+    if (!node()->colNamesRef().empty()) {
+        DCHECK_EQ(node()->colNamesRef().size(), v.colNames.size());
+        v.colNames = node()->colNamesRef();
+    }
     // TODO(yee): Unify the response structure of IndexScan and GetProps and change the following
     // iterator to PropIter type
     VLOG(2) << "Dataset produced by IndexScan: \n" << v << "\n";
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 9070f266c32b6e990845359e7bdc1930554b0478..1b9219efd1d68f735ce41e39be7da801af74b936 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -157,12 +157,11 @@ public:
         return false;
     }
 
-    void setOutputVar(std::string var) {
+    void setOutputVar(const std::string &var) {
         DCHECK_EQ(1, outputVars_.size());
         auto* outputVarPtr = qctx_->symTable()->getVar(var);
         DCHECK(outputVarPtr != nullptr);
         auto oldVar = outputVars_[0]->name;
-        outputVarPtr->colNames = outputVars_[0]->colNames;
         outputVars_[0] = outputVarPtr;
         qctx_->symTable()->updateWrittenBy(oldVar, var, this);
     }
diff --git a/src/planner/match/Expand.cpp b/src/planner/match/Expand.cpp
index d4d7f7d720cf89674dbdc1e977d2b226c500212f..35932ffb01983aaad50c33e9188a62d58504ae1a 100644
--- a/src/planner/match/Expand.cpp
+++ b/src/planner/match/Expand.cpp
@@ -25,22 +25,35 @@ static std::unique_ptr<std::vector<VertexProp>> genVertexProps() {
     return std::make_unique<std::vector<VertexProp>>();
 }
 
-static std::unique_ptr<std::vector<EdgeProp>> genEdgeProps(const EdgeInfo &edge) {
-    auto edgeProps = std::make_unique<std::vector<EdgeProp>>();
+std::unique_ptr<std::vector<storage::cpp2::EdgeProp>> Expand::genEdgeProps(const EdgeInfo &edge) {
     if (edge.edgeTypes.empty()) {
-        return edgeProps;
+        return std::make_unique<std::vector<storage::cpp2::EdgeProp>>(
+            buildAllEdgeProp().value());
     }
 
+    auto edgeProps = std::make_unique<std::vector<EdgeProp>>();
     for (auto edgeType : edge.edgeTypes) {
+        auto edgeSchema = matchCtx_->qctx->schemaMng()->getEdgeSchema(
+            matchCtx_->space.id, edgeType);
         if (edge.direction == Direction::IN_EDGE) {
             edgeType = -edgeType;
         } else if (edge.direction == Direction::BOTH) {
             EdgeProp edgeProp;
             edgeProp.set_type(-edgeType);
+            std::vector<std::string> props{kSrc, kType, kRank, kDst};
+            for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) {
+                props.emplace_back(edgeSchema->getFieldName(i));
+            }
+            edgeProp.set_props(std::move(props));
             edgeProps->emplace_back(std::move(edgeProp));
         }
         EdgeProp edgeProp;
         edgeProp.set_type(edgeType);
+        std::vector<std::string> props{kSrc, kType, kRank, kDst};
+        for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) {
+            props.emplace_back(edgeSchema->getFieldName(i));
+        }
+        edgeProp.set_props(std::move(props));
         edgeProps->emplace_back(std::move(edgeProp));
     }
     return edgeProps;
@@ -210,5 +223,29 @@ Status Expand::filterDatasetByPathLength(const EdgeInfo& edge,
     return Status::OK();
 }
 
+StatusOr<std::vector<storage::cpp2::EdgeProp>> Expand::buildAllEdgeProp() {
+    // list all edge properties
+    std::map<TagID, std::shared_ptr<const meta::SchemaProviderIf>> edgesSchema;
+    const auto allEdgesResult = matchCtx_->qctx->schemaMng()->getAllVerEdgeSchema(
+        matchCtx_->space.id);
+    NG_RETURN_IF_ERROR(allEdgesResult);
+    const auto allEdges = std::move(allEdgesResult).value();
+    for (const auto &edge : allEdges) {
+        edgesSchema.emplace(edge.first, edge.second.back());
+    }
+    std::vector<storage::cpp2::EdgeProp> eProps;
+    for (const auto &edgeSchema : edgesSchema) {
+        storage::cpp2::EdgeProp eProp;
+        eProp.set_type(edgeSchema.first);
+        std::vector<std::string> props{kSrc, kType, kRank, kDst};
+        for (std::size_t i = 0; i < edgeSchema.second->getNumFields(); ++i) {
+            props.emplace_back(edgeSchema.second->getFieldName(i));
+        }
+        eProp.set_props(std::move(props));
+        eProps.emplace_back(std::move(eProp));
+    }
+    return eProps;
+}
+
 }  // namespace graph
 }  // namespace nebula
diff --git a/src/planner/match/Expand.h b/src/planner/match/Expand.h
index 956ea4a71322178917b07ca9edcd1c4d508c7cba..09e255c591cff29f87b7da8d4d9fede5d32ba41d 100644
--- a/src/planner/match/Expand.h
+++ b/src/planner/match/Expand.h
@@ -56,6 +56,10 @@ public:
     }
 
 private:
+    std::unique_ptr<std::vector<storage::cpp2::EdgeProp>> genEdgeProps(const EdgeInfo &edge);
+
+    StatusOr<std::vector<storage::cpp2::EdgeProp>> buildAllEdgeProp();
+
     MatchClauseContext* matchCtx_;
     Expression**        initialExpr_;
 };
diff --git a/src/planner/match/PropIndexSeek.cpp b/src/planner/match/PropIndexSeek.cpp
index 7100d79e3eb9afb2b7a315b4ce115d0ac43952dd..2cb25c964fab59a5d31f318301e30343f5b7fcd7 100644
--- a/src/planner/match/PropIndexSeek.cpp
+++ b/src/planner/match/PropIndexSeek.cpp
@@ -47,6 +47,7 @@ bool PropIndexSeek::matchNode(NodeContext* nodeCtx) {
 
     nodeCtx->scanInfo.filter = filter;
     nodeCtx->scanInfo.schemaId = node.tid;
+    nodeCtx->scanInfo.schemaName = node.label;
 
     return true;
 }
@@ -60,6 +61,7 @@ StatusOr<SubPlan> PropIndexSeek::transformNode(NodeContext* nodeCtx) {
     auto contexts = std::make_unique<std::vector<IQC>>();
     contexts->emplace_back(std::move(iqctx));
     auto columns = std::make_unique<std::vector<std::string>>();
+    columns->emplace_back(kVid);
     auto scan = IndexScan::make(matchClauseCtx->qctx,
                                 nullptr,
                                 matchClauseCtx->space.id,
@@ -67,6 +69,7 @@ StatusOr<SubPlan> PropIndexSeek::transformNode(NodeContext* nodeCtx) {
                                 std::move(columns),
                                 false,
                                 nodeCtx->scanInfo.schemaId);
+    scan->setColNames({kVid});
     plan.tail = scan;
     plan.root = scan;
 
@@ -74,5 +77,6 @@ StatusOr<SubPlan> PropIndexSeek::transformNode(NodeContext* nodeCtx) {
     nodeCtx->initialExpr = ExpressionUtils::newVarPropExpr(kVid);
     return plan;
 }
+
 }  // namespace graph
 }  // namespace nebula
diff --git a/src/validator/AssignmentValidator.cpp b/src/validator/AssignmentValidator.cpp
index 9b7f66e04710d61a77fc4d92450c1a38e3720a4b..457025b38df47249bfcc49d07680a2e8f9fb6374 100644
--- a/src/validator/AssignmentValidator.cpp
+++ b/src/validator/AssignmentValidator.cpp
@@ -25,7 +25,10 @@ Status AssignmentValidator::validateImpl() {
 
 Status AssignmentValidator::toPlan() {
     root_ = validator_->root();
-    qctx_->symTable()->newVariable(var_);
+    auto *var = qctx_->symTable()->newVariable(var_);
+    for (const auto &outputCol : validator_->outputCols()) {
+        var->colNames.emplace_back(outputCol.name);
+    }
     root_->setOutputVar(var_);
     tail_ = validator_->tail();
     return Status::OK();
diff --git a/src/validator/FindPathValidator.cpp b/src/validator/FindPathValidator.cpp
index c81371b4143d1f6db6bb0c14360ab8818d76b33d..03b9453bfd9f4a2a0d5600eebef39ab8e2038884 100644
--- a/src/validator/FindPathValidator.cpp
+++ b/src/validator/FindPathValidator.cpp
@@ -107,9 +107,11 @@ PlanNode* FindPathValidator::bfs(PlanNode* dep,
     project->setColNames(deduceColNames(columns));
 
     auto* dedup = Dedup::make(qctx_, project);
-    dedup->setColNames(project->colNames());
+    auto* startVidsVarPtr = qctx_->symTable()->getVar(startVidsVar);
+    startVidsVarPtr->colNames = project->colNames();
     dedup->setOutputVar(startVidsVar);
 
+
     DataSet ds;
     ds.colNames = {"_vid", "edge"};
     Row row;
@@ -204,7 +206,8 @@ PlanNode* FindPathValidator::buildAllPairFirstDataSet(PlanNode* dep,
 
     auto* project = Project::make(qctx_, dep, columns);
     project->setInputVar(inputVar);
-    project->setColNames({kVid, "path"});
+    auto* outputVarPtr = qctx_->symTable()->getVar(outputVar);
+    outputVarPtr->colNames = {kVid, "path"};
     project->setOutputVar(outputVar);
     return project;
 }
@@ -273,7 +276,8 @@ PlanNode* FindPathValidator::allPaths(PlanNode* dep,
     project->setColNames(deduceColNames(columns));
 
     auto* dedup = Dedup::make(qctx_, project);
-    dedup->setColNames(project->colNames());
+    auto* startVidsVarPtr = qctx_->symTable()->getVar(startVidsVar);
+    startVidsVarPtr->colNames = project->colNames();
     dedup->setOutputVar(startVidsVar);
 
     return dedup;
@@ -350,7 +354,8 @@ PlanNode* FindPathValidator::buildMultiPairFirstDataSet(PlanNode* dep,
 
     auto* project = Project::make(qctx_, dep, columns);
     project->setInputVar(inputVar);
-    project->setColNames({kDst, kSrc, "cost", "paths"});
+    auto* outputVarPtr = qctx_->symTable()->getVar(outputVar);
+    outputVarPtr->colNames = {kDst, kSrc, "cost", "paths"};
     project->setOutputVar(outputVar);
     return project;
 }
@@ -428,7 +433,8 @@ PlanNode* FindPathValidator::multiPairShortestPath(PlanNode* dep,
     project->setColNames(deduceColNames(columns));
 
     auto* dedup = Dedup::make(qctx_, project);
-    dedup->setColNames(project->colNames());
+    auto* startVidsVarPtr = qctx_->symTable()->getVar(startVidsVar);
+    startVidsVarPtr->colNames = project->colNames();
     dedup->setOutputVar(startVidsVar);
 
     return dedup;
diff --git a/src/validator/GetSubgraphValidator.cpp b/src/validator/GetSubgraphValidator.cpp
index 1822ee3cdbb49311d4127f49448bde91ecbd6d99..ea176d82f4710b88ed60b66184aa1706f78367f0 100644
--- a/src/validator/GetSubgraphValidator.cpp
+++ b/src/validator/GetSubgraphValidator.cpp
@@ -5,6 +5,7 @@
  */
 
 #include "validator/GetSubgraphValidator.h"
+#include <memory>
 
 #include "common/expression/UnaryExpression.h"
 #include "common/expression/VariableExpression.h"
@@ -148,26 +149,79 @@ Expression* GetSubgraphValidator::buildFilterCondition(int64_t step) {
     return result;
 }
 
-GetNeighbors::EdgeProps GetSubgraphValidator::buildEdgeProps() {
-    GetNeighbors::EdgeProps edgeProps = std::make_unique<std::vector<storage::cpp2::EdgeProp>>();
+StatusOr<GetNeighbors::EdgeProps> GetSubgraphValidator::buildEdgeProps() {
     if (edgeTypes_.empty()) {
-        return edgeProps;
+        auto allEdgePropResult = buildAllEdgeProp();
+        NG_RETURN_IF_ERROR(allEdgePropResult);
+        return std::make_unique<std::vector<storage::cpp2::EdgeProp>>(
+            std::move(allEdgePropResult).value());
     }
-    edgeProps->reserve(edgeTypes_.size());
-    for (auto& e : edgeTypes_) {
-        storage::cpp2::EdgeProp ep;
-        ep.set_type(e);
-        edgeProps->emplace_back(std::move(ep));
+    auto edgePropResult = fillEdgeProp(edgeTypes_);
+    NG_RETURN_IF_ERROR(edgePropResult);
+    return std::make_unique<std::vector<storage::cpp2::EdgeProp>>(
+        std::move(edgePropResult).value());
+}
+
+StatusOr<std::vector<storage::cpp2::EdgeProp>> GetSubgraphValidator::fillEdgeProp(
+    const std::unordered_set<EdgeType>& edges) {
+    // list all edge properties
+    std::vector<storage::cpp2::EdgeProp> eProps;
+    for (const auto edge : edges) {
+        auto edgeSchema = qctx()->schemaMng()->getEdgeSchema(space_.id, std::abs(edge));
+        if (edgeSchema == nullptr) {
+            return Status::SemanticError("Not exist edge `%d' in space `%d'.", edge, space_.id);
+        }
+        storage::cpp2::EdgeProp eProp;
+        eProp.set_type(edge);
+        std::vector<std::string> props{kSrc, kType, kRank, kDst};
+        for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) {
+            props.emplace_back(edgeSchema->getFieldName(i));
+        }
+        eProp.set_props(std::move(props));
+        eProps.emplace_back(std::move(eProp));
+    }
+    return eProps;
+}
+
+StatusOr<std::vector<storage::cpp2::EdgeProp>> GetSubgraphValidator::buildAllEdgeProp() {
+    // list all edge properties
+    std::map<TagID, std::shared_ptr<const meta::SchemaProviderIf>> edgesSchema;
+    const auto allEdgesResult = qctx()->schemaMng()->getAllVerEdgeSchema(space_.id);
+    NG_RETURN_IF_ERROR(allEdgesResult);
+    const auto allEdges = std::move(allEdgesResult).value();
+    for (const auto& edge : allEdges) {
+        edgesSchema.emplace(edge.first, edge.second.back());
     }
-    return edgeProps;
+    std::vector<storage::cpp2::EdgeProp> eProps;
+    for (const auto& edgeSchema : edgesSchema) {
+        storage::cpp2::EdgeProp eProp;
+        storage::cpp2::EdgeProp rEProp;
+        eProp.set_type(edgeSchema.first);
+        rEProp.set_type(-edgeSchema.first);
+        std::vector<std::string> props{kSrc, kType, kRank, kDst};
+        for (std::size_t i = 0; i < edgeSchema.second->getNumFields(); ++i) {
+            props.emplace_back(edgeSchema.second->getFieldName(i));
+        }
+        eProp.set_props(props);
+        rEProp.set_props(std::move(props));
+        eProps.emplace_back(std::move(eProp));
+        eProps.emplace_back(std::move(rEProp));
+    }
+    return eProps;
 }
 
 Status GetSubgraphValidator::zeroStep(PlanNode* depend, const std::string& inputVar) {
     auto& space = vctx_->whichSpace();
     std::vector<storage::cpp2::Expr> exprs;
-    std::vector<storage::cpp2::VertexProp> vertexProps;
-    auto* getVertex = GetVertices::make(
-        qctx_, depend, space.id, from_.src, std::move(vertexProps), std::move(exprs), true);
+    auto vertexPropsResult = buildVertexProp();
+    NG_RETURN_IF_ERROR(vertexPropsResult);
+    auto* getVertex = GetVertices::make(qctx_,
+                                        depend,
+                                        space.id,
+                                        from_.src,
+                                        std::move(vertexPropsResult).value(),
+                                        std::move(exprs),
+                                        true);
     getVertex->setInputVar(inputVar);
 
     auto var = vctx_->anonVarGen()->getVar();
@@ -228,20 +282,25 @@ Status GetSubgraphValidator::toPlan() {
         return zeroStep(collectRunTimeStartVids, startVidsVar);
     }
 
-    auto vertexProps = std::make_unique<std::vector<storage::cpp2::VertexProp>>();
+    auto vertexPropsResult = buildVertexProp();
+    NG_RETURN_IF_ERROR(vertexPropsResult);
     auto* gn = GetNeighbors::make(qctx_, bodyStart, space.id);
     gn->setSrc(from_.src);
-    gn->setVertexProps(std::move(vertexProps));
-    gn->setEdgeProps(buildEdgeProps());
+    gn->setVertexProps(std::make_unique<std::vector<storage::cpp2::VertexProp>>(
+        std::move(vertexPropsResult).value()));
+    auto edgePropsResult = buildEdgeProps();
+    NG_RETURN_IF_ERROR(edgePropsResult);
+    gn->setEdgeProps(
+        std::make_unique<std::vector<storage::cpp2::EdgeProp>>(*edgePropsResult.value()));
     gn->setEdgeDirection(storage::cpp2::EdgeDirection::BOTH);
     gn->setInputVar(startVidsVar);
 
     auto* projectVids = projectDstVidsFromGN(gn, startVidsVar);
 
     auto var = vctx_->anonVarGen()->getVar();
-    auto* column = new YieldColumn(
-        new VariablePropertyExpression(new std::string(var), new std::string(kVid)),
-        new std::string(kVid));
+    auto* column =
+        new YieldColumn(new VariablePropertyExpression(new std::string(var), new std::string(kVid)),
+                        new std::string(kVid));
     qctx_->objPool()->add(column);
     column->setAggFunction(new std::string("COLLECT_SET"));
     auto fun = column->getAggFunName();
@@ -258,12 +317,17 @@ Status GetSubgraphValidator::toPlan() {
     auto* condition = buildNStepLoopCondition(steps_.steps);
     auto* loop = Loop::make(qctx_, collectRunTimeStartVids, collect, condition);
 
-    vertexProps = std::make_unique<std::vector<storage::cpp2::VertexProp>>();
+    vertexPropsResult = buildVertexProp();
+    NG_RETURN_IF_ERROR(vertexPropsResult);
     auto edgeProps = std::make_unique<std::vector<storage::cpp2::EdgeProp>>();
     auto* gn1 = GetNeighbors::make(qctx_, loop, space.id);
     gn1->setSrc(from_.src);
-    gn1->setVertexProps(std::move(vertexProps));
-    gn1->setEdgeProps(std::move(edgeProps));
+    gn1->setVertexProps(std::make_unique<std::vector<storage::cpp2::VertexProp>>(
+        std::move(vertexPropsResult).value()));
+    auto allEdgePropResult = buildAllEdgeProp();
+    NG_RETURN_IF_ERROR(allEdgePropResult);
+    gn1->setEdgeProps(std::make_unique<std::vector<storage::cpp2::EdgeProp>>(
+        std::move(allEdgePropResult).value()));
     gn1->setEdgeDirection(storage::cpp2::EdgeDirection::BOTH);
     gn1->setInputVar(projectVids->outputVar());
 
@@ -282,5 +346,28 @@ Status GetSubgraphValidator::toPlan() {
     return Status::OK();
 }
 
-}  // namespace graph
-}  // namespace nebula
+StatusOr<std::vector<storage::cpp2::VertexProp>> GetSubgraphValidator::buildVertexProp() {
+    // list all tag properties
+    std::map<TagID, std::shared_ptr<const meta::SchemaProviderIf>> tagsSchema;
+    const auto allTagsResult = qctx()->schemaMng()->getAllVerTagSchema(space_.id);
+    NG_RETURN_IF_ERROR(allTagsResult);
+    const auto allTags = std::move(allTagsResult).value();
+    for (const auto& tag : allTags) {
+        tagsSchema.emplace(tag.first, tag.second.back());
+    }
+    std::vector<storage::cpp2::VertexProp> vProps;
+    for (const auto& tagSchema : tagsSchema) {
+        storage::cpp2::VertexProp vProp;
+        vProp.set_tag(tagSchema.first);
+        std::vector<std::string> props;
+        for (std::size_t i = 0; i < tagSchema.second->getNumFields(); ++i) {
+            props.emplace_back(tagSchema.second->getFieldName(i));
+        }
+        vProp.set_props(std::move(props));
+        vProps.emplace_back(std::move(vProp));
+    }
+    return vProps;
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/validator/GetSubgraphValidator.h b/src/validator/GetSubgraphValidator.h
index 39b76970026175a77a3e450f68597cf23187c264..4cd87dd10148ac138fedb8773b3807d400806728 100644
--- a/src/validator/GetSubgraphValidator.h
+++ b/src/validator/GetSubgraphValidator.h
@@ -31,10 +31,17 @@ private:
 
     Expression* buildFilterCondition(int64_t step);
 
-    GetNeighbors::EdgeProps buildEdgeProps();
+    StatusOr<GetNeighbors::EdgeProps> buildEdgeProps();
 
     Status zeroStep(PlanNode* depend, const std::string& inputVar);
 
+    StatusOr<std::vector<storage::cpp2::VertexProp>> buildVertexProp();
+
+    StatusOr<std::vector<storage::cpp2::EdgeProp>> fillEdgeProp(
+        const std::unordered_set<EdgeType> &edges);
+
+    StatusOr<std::vector<storage::cpp2::EdgeProp>> buildAllEdgeProp();
+
 private:
     std::unordered_set<EdgeType>                edgeTypes_;
     std::string                                 collectVar_;
diff --git a/src/validator/IndexScanValidator.cpp b/src/validator/IndexScanValidator.cpp
index 496a15118192d257f3478ec984b8e0f953693df0..45c400ec8a49cc5d247149d3ed21372a59c03e80 100644
--- a/src/validator/IndexScanValidator.cpp
+++ b/src/validator/IndexScanValidator.cpp
@@ -14,6 +14,12 @@ DECLARE_uint32(ft_request_retry_times);
 namespace nebula {
 namespace graph {
 
+/*static*/ constexpr char IndexScanValidator::kSrcVID[];
+/*static*/ constexpr char IndexScanValidator::kDstVID[];
+/*static*/ constexpr char IndexScanValidator::kRanking[];
+
+/*static*/ constexpr char IndexScanValidator::kVertexID[];
+
 Status IndexScanValidator::validateImpl() {
     NG_RETURN_IF_ERROR(prepareFrom());
     NG_RETURN_IF_ERROR(prepareYield());
@@ -22,12 +28,18 @@ Status IndexScanValidator::validateImpl() {
 }
 
 Status IndexScanValidator::toPlan() {
-    return genSingleNodePlan<IndexScan>(spaceId_,
-                                        std::move(contexts_),
-                                        std::move(returnCols_),
-                                        isEdge_,
-                                        schemaId_,
-                                        isEmptyResultSet_);
+    auto *is = IndexScan::make(qctx_,
+                               nullptr,
+                               spaceId_,
+                               std::move(contexts_),
+                               std::move(returnCols_),
+                               isEdge_,
+                               schemaId_,
+                               isEmptyResultSet_);
+    is->setColNames(std::move(colNames_));
+    root_ = is;
+    tail_ = is;
+    return Status::OK();
 }
 
 Status IndexScanValidator::prepareFrom() {
@@ -46,6 +58,18 @@ Status IndexScanValidator::prepareFrom() {
 Status IndexScanValidator::prepareYield() {
     auto *sentence = static_cast<const LookupSentence *>(sentence_);
     returnCols_ = std::make_unique<std::vector<std::string>>();
+    // always return
+    if (isEdge_) {
+        returnCols_->emplace_back(kSrc);
+        colNames_.emplace_back(kSrcVID);
+        returnCols_->emplace_back(kDst);
+        colNames_.emplace_back(kDstVID);
+        returnCols_->emplace_back(kRank);
+        colNames_.emplace_back(kRanking);
+    } else {
+        returnCols_->emplace_back(kVid);
+        colNames_.emplace_back(kVertexID);
+    }
     if (sentence->yieldClause() == nullptr) {
         return Status::OK();
     }
@@ -85,6 +109,7 @@ Status IndexScanValidator::prepareYield() {
                                          colName.c_str(), from_.c_str());
         }
         returnCols_->emplace_back(colName);
+        colNames_.emplace_back(from_ + "." + colName);
     }
     return Status::OK();
 }
diff --git a/src/validator/IndexScanValidator.h b/src/validator/IndexScanValidator.h
index 839544e1a8076f783b8959f62711de8dd3bc7a2e..c26e0531b13a9ec62cfe916f065a0cf574696bbb 100644
--- a/src/validator/IndexScanValidator.h
+++ b/src/validator/IndexScanValidator.h
@@ -53,6 +53,12 @@ private:
     const nebula::plugin::HttpClient& randomFTClient() const;
 
 private:
+    static constexpr char kSrcVID[] = "SrcVID";
+    static constexpr char kDstVID[] = "DstVID";
+    static constexpr char kRanking[] = "Ranking";
+
+    static constexpr char kVertexID[] = "VertexID";
+
     GraphSpaceID                      spaceId_{0};
     IndexScan::IndexQueryCtx          contexts_{};
     IndexScan::IndexReturnCols        returnCols_{};
@@ -62,6 +68,7 @@ private:
     bool                              textSearchReady_{false};
     std::string                       from_;
     std::vector<nebula::plugin::HttpClient> esClients_;
+    std::vector<std::string>          colNames_;
 };
 
 }   // namespace graph
diff --git a/src/validator/test/MockSchemaManager.h b/src/validator/test/MockSchemaManager.h
index 23ae09843df1a9d3c27fb7edbeb99fbdd221b9c8..ddede73b5e8f108cf5dc880c403b7358a42d3889 100644
--- a/src/validator/test/MockSchemaManager.h
+++ b/src/validator/test/MockSchemaManager.h
@@ -80,9 +80,14 @@ public:
 
     // get all version of all edges
     StatusOr<meta::EdgeSchemas> getAllVerEdgeSchema(GraphSpaceID space) override {
-        UNUSED(space);
-        DLOG(FATAL) << "Unimplemented";
-        return Status::Error("Unimplemented");
+        meta::EdgeSchemas allVerEdgeSchemas;
+        const auto& edgeSchemas = edgeSchemas_[space];
+        for (const auto &edgeSchema : edgeSchemas) {
+            allVerEdgeSchemas.emplace(edgeSchema.first,
+                                     std::vector<std::shared_ptr<const meta::NebulaSchemaProvider>>
+                                        {edgeSchema.second});
+        }
+        return allVerEdgeSchemas;
     }
 
     StatusOr<std::vector<nebula::meta::cpp2::FTClient> > getFTClients() override;
diff --git a/tests/maintain/test_index.py b/tests/maintain/test_index.py
index 8161048ada6a0d1fed51e75d53720cb9618baba0..28086601463e6e6925b5a8d0749c78eab45834df 100644
--- a/tests/maintain/test_index.py
+++ b/tests/maintain/test_index.py
@@ -291,18 +291,18 @@ class TestIndex(NebulaTestSuite):
         if self.find_result(resp0, [['single_edge_index', 'FINISHED']]):
             resp = self.client.execute('LOOKUP ON edge_1 WHERE edge_1.col2 == 22 YIELD edge_1.col2')
             self.check_resp_succeeded(resp)
-            expect = [['102', 0, '103', 22]]
+            expect = [['102', '103', 0, 22]]
             self.check_out_of_order_result(resp, expect)
 
         if self.find_result(resp0, [['multi_edge_index', 'FINISHED']]):
             resp = self.client.execute('LOOKUP ON edge_1 WHERE edge_1.col3 > 43.4 YIELD edge_1.col1')
             self.check_resp_succeeded(resp)
-            expect = [['102', 0, '103', 'Yellow'], ['101', 0, '102', 'Red']]
+            expect = [['102', '103', 0, 'Yellow'], ['101', '102', 0, 'Red']]
             self.check_out_of_order_result(resp, expect)
 
             resp = self.client.execute('LOOKUP ON edge_1 WHERE edge_1.col2 > 45 AND edge_1.col3 < 44.3 YIELD edge_1.col1')
             self.check_resp_succeeded(resp)
-            expect = [['103', 0, '101', 'Blue']]
+            expect = [['103', '101', 0, 'Blue']]
             self.check_out_of_order_result(resp, expect)
 
         # Describe Edge Index