diff --git a/src/planner/planners/MatchSolver.cpp b/src/planner/planners/MatchSolver.cpp
index f0a322f09ad3d0d3ec26ab709c3738a32787e3c7..6f0ee3b5675ecbdeda2938723c906b49bf5bcb24 100644
--- a/src/planner/planners/MatchSolver.cpp
+++ b/src/planner/planners/MatchSolver.cpp
@@ -20,21 +20,24 @@ Status MatchSolver::buildReturn(MatchAstContext* mctx, SubPlan& subPlan) {
     for (auto *col : mctx->yieldColumns->columns()) {
         auto kind = col->expr()->kind();
         YieldColumn *newColumn = nullptr;
-        if (kind == Expression::Kind::kLabel) {
-            auto *label = static_cast<const LabelExpression*>(col->expr());
-            newColumn = new YieldColumn(rewrite(label));
-        } else if (kind == Expression::Kind::kLabelAttribute) {
-            auto *la = static_cast<const LabelAttributeExpression*>(col->expr());
-            newColumn = new YieldColumn(rewrite(la));
-        } else {
-            auto newExpr = col->expr()->clone();
-            auto rewriter = [] (const Expression *expr) {
-                if (expr->kind() == Expression::Kind::kLabel) {
-                    return rewrite(static_cast<const LabelExpression*>(expr));
+        auto rewriter = [mctx] (const Expression *expr) {
+            if (expr->kind() == Expression::Kind::kLabel) {
+                auto* labelExpr = static_cast<const LabelExpression*>(expr);
+                auto alias = mctx->aliases.find(*labelExpr->name());
+                DCHECK(alias != mctx->aliases.end());
+                if (alias->second == MatchValidator::AliasType::kPath) {
+                    return mctx->pathBuild->clone().release();
                 } else {
-                    return rewrite(static_cast<const LabelAttributeExpression*>(expr));
+                    return rewrite(labelExpr);
                 }
-            };
+            } else {
+                return rewrite(static_cast<const LabelAttributeExpression*>(expr));
+            }
+        };
+        if (kind == Expression::Kind::kLabel || kind == Expression::Kind::kLabelAttribute) {
+            newColumn = new YieldColumn(rewriter(col->expr()));
+        } else {
+            auto newExpr = col->expr()->clone();
             RewriteMatchLabelVisitor visitor(std::move(rewriter));
             newExpr->accept(&visitor);
             newColumn = new YieldColumn(newExpr.release());
diff --git a/src/validator/MatchValidator.cpp b/src/validator/MatchValidator.cpp
index 72709da06bb50ff51fde0b4461ddcd411a20a8a0..d96872867ff634dbdeeffb6e8dd23bbe04db7953 100644
--- a/src/validator/MatchValidator.cpp
+++ b/src/validator/MatchValidator.cpp
@@ -5,8 +5,9 @@
  */
 
 #include "validator/MatchValidator.h"
-#include "visitor/RewriteMatchLabelVisitor.h"
+
 #include "util/ExpressionUtils.h"
+#include "visitor/RewriteMatchLabelVisitor.h"
 
 namespace nebula {
 namespace graph {
@@ -46,19 +47,50 @@ Status MatchValidator::validateImpl() {
         NG_RETURN_IF_ERROR(validateFilter(matchClause->where()->filter()));
     }
     NG_RETURN_IF_ERROR(validateReturn(sentence->ret()));
-    return analyzeStartPoint();
+    return Status::OK();
 }
 
 
 Status MatchValidator::validatePath(const MatchPath *path) {
-    auto *sm = qctx_->schemaMng();
-    auto steps = path->steps();
+    NG_RETURN_IF_ERROR(buildNodeInfo(path));
+    NG_RETURN_IF_ERROR(buildEdgeInfo(path));
+    NG_RETURN_IF_ERROR(buildPathExpr(path));
+    return Status::OK();
+}
+
+
+Status MatchValidator::buildPathExpr(const MatchPath *path) {
+    auto* pathAlias = path->alias();
+    if (pathAlias == nullptr) {
+        return Status::OK();
+    }
+    if (!matchCtx_->aliases.emplace(*pathAlias, kPath).second) {
+        return Status::SemanticError("`%s': Redefined alias", pathAlias->c_str());
+    }
 
     auto& nodeInfos = matchCtx_->nodeInfos;
     auto& edgeInfos = matchCtx_->edgeInfos;
 
+    auto pathBuild = std::make_unique<PathBuildExpression>();
+    for (size_t i = 0; i < edgeInfos.size(); ++i) {
+        pathBuild->add(std::make_unique<VariablePropertyExpression>(
+            new std::string(), new std::string(*nodeInfos[i].alias)));
+        pathBuild->add(std::make_unique<VariablePropertyExpression>(
+            new std::string(), new std::string(*edgeInfos[i].alias)));
+    }
+    pathBuild->add(std::make_unique<VariablePropertyExpression>(
+        new std::string(), new std::string(*nodeInfos.back().alias)));
+    matchCtx_->pathBuild = std::move(pathBuild);
+    return Status::OK();
+}
+
+
+Status MatchValidator::buildNodeInfo(const MatchPath *path) {
+    auto *sm = qctx_->schemaMng();
+    auto steps = path->steps();
+    auto& nodeInfos = matchCtx_->nodeInfos;
     nodeInfos.resize(steps + 1);
-    edgeInfos.resize(steps);
+
     for (auto i = 0u; i <= steps; i++) {
         auto *node = path->node(i);
         auto *label = node->label();
@@ -68,7 +100,7 @@ Status MatchValidator::validatePath(const MatchPath *path) {
         if (label != nullptr) {
             auto tid = sm->toTagID(space_.id, *label);
             if (!tid.ok()) {
-                return Status::Error("`%s': Unknown tag", label->c_str());
+                return Status::SemanticError("`%s': Unknown tag", label->c_str());
             }
             nodeInfos[i].tid = tid.value();
         }
@@ -77,7 +109,7 @@ Status MatchValidator::validatePath(const MatchPath *path) {
             alias = saveObject(new std::string(vctx_->anonVarGen()->getVar()));
         }
         if (!matchCtx_->aliases.emplace(*alias, kNode).second) {
-            return Status::Error("`%s': Redefined alias", alias->c_str());
+            return Status::SemanticError("`%s': Redefined alias", alias->c_str());
         }
         Expression *filter = nullptr;
         if (props != nullptr) {
@@ -92,6 +124,15 @@ Status MatchValidator::validatePath(const MatchPath *path) {
         nodeInfos[i].filter = filter;
     }
 
+    return Status::OK();
+}
+
+Status MatchValidator::buildEdgeInfo(const MatchPath *path) {
+    auto *sm = qctx_->schemaMng();
+    auto steps = path->steps();
+    auto& edgeInfos = matchCtx_->edgeInfos;
+    edgeInfos.resize(steps);
+
     for (auto i = 0u; i < steps; i++) {
         auto *edge = path->edge(i);
         auto &types = edge->types();
@@ -306,10 +347,6 @@ Status MatchValidator::validateAliases(const std::vector<const Expression*> &exp
     return Status::OK();
 }
 
-Status MatchValidator::analyzeStartPoint() {
-    return Status::OK();
-}
-
 
 StatusOr<Expression*>
 MatchValidator::makeSubFilter(const std::string &alias,
diff --git a/src/validator/MatchValidator.h b/src/validator/MatchValidator.h
index 4d5f78a6d4c92ec18acfe694bde594c4db05cddb..fd8268f5fc6e88ee8e670ededa1243d3e707406e 100644
--- a/src/validator/MatchValidator.h
+++ b/src/validator/MatchValidator.h
@@ -68,8 +68,6 @@ private:
 
     Status validateAliases(const std::vector<const Expression*> &exprs) const;
 
-    Status analyzeStartPoint();
-
     StatusOr<Expression*> makeSubFilter(const std::string &alias,
                                         const MapExpression *map) const;
 
@@ -78,6 +76,12 @@ private:
         return qctx_->objPool()->add(obj);
     }
 
+    Status buildNodeInfo(const MatchPath *path);
+
+    Status buildEdgeInfo(const MatchPath *path);
+
+    Status buildPathExpr(const MatchPath *path);
+
 private:
     std::unique_ptr<MatchAstContext>            matchCtx_;
 };
@@ -86,6 +90,7 @@ struct MatchAstContext final : AstContext {
     std::vector<MatchValidator::NodeInfo>                       nodeInfos;
     std::vector<MatchValidator::EdgeInfo>                       edgeInfos;
     std::unordered_map<std::string, MatchValidator::AliasType>  aliases;
+    std::unique_ptr<PathBuildExpression>                        pathBuild;
     std::unique_ptr<Expression>                                 filter;
     const YieldColumns                                         *yieldColumns;
     MatchValidator::ScanInfo                                    scanInfo;
diff --git a/tests/common/nebula_test_suite.py b/tests/common/nebula_test_suite.py
index cd96734f8311cce8d629e6d0156a005a4722d3c2..1e5b83a7fa3ae64e01b9655f797a5b19bb69a38a 100644
--- a/tests/common/nebula_test_suite.py
+++ b/tests/common/nebula_test_suite.py
@@ -34,6 +34,11 @@ T_NULL_UNKNOWN_PROP.set_nVal(CommonTtypes.NullType.UNKNOWN_PROP)
 T_NULL_UNKNOWN_DIV_BY_ZERO = CommonTtypes.Value()
 T_NULL_UNKNOWN_DIV_BY_ZERO.set_nVal(CommonTtypes.NullType.DIV_BY_ZERO)
 
+class PathVal:
+    items = []
+
+    def __init__(self, items):
+        self.items = items
 
 class NebulaTestSuite(object):
     @classmethod
@@ -353,6 +358,26 @@ class NebulaTestSuite(object):
             ok = (expect[i] == result)
             assert ok, "different column name, expect: {} vs. result: {}".format(expect[i], result)
 
+    @classmethod
+    def to_path_value(self, col):
+        path = CommonTtypes.Path()
+        path.steps = []
+        for col, j in zip(col.items, range(len(col.items))):
+            if j == 0:
+                path.src = col.get_vVal()
+            elif (j % 2) == 1:
+                edge = col[0].get_eVal()
+                step = CommonTtypes.Step()
+                step.name = edge.name
+                step.ranking = edge.ranking
+                step.type = col[1]
+                step.props = edge.props
+                path.steps.append(step)
+            else:
+                print("step: %d", len(path.steps))
+                path.steps[-1].dst = col.get_vVal()
+        return path
+
     @classmethod
     def to_value(self, col):
         value = CommonTtypes.Value()
@@ -389,6 +414,8 @@ class NebulaTestSuite(object):
                     return ok, temp
                 set_val.values.add(temp)
             value.set_uVal(set_val)
+        elif isinstance(col, PathVal):
+            value.set_pVal(self.to_path_value(col))
         else:
             return False, 'Wrong val type'
         return True, value
diff --git a/tests/query/v2/test_match.py b/tests/query/v2/test_match.py
index e53742f01bd63e500d2ef827605e51e4d9755e90..e87781b0995baaf9cf40c6e483ca665a525fcedc 100644
--- a/tests/query/v2/test_match.py
+++ b/tests/query/v2/test_match.py
@@ -7,7 +7,7 @@
 
 import pytest
 
-from tests.common.nebula_test_suite import NebulaTestSuite
+from tests.common.nebula_test_suite import NebulaTestSuite, PathVal
 
 
 @pytest.mark.usefixtures('set_vertices_and_edges')
@@ -572,6 +572,117 @@ class TestMatch(NebulaTestSuite):
                   ['Tony Parker', ['player']]]
         self.check_out_of_order_result(resp, result)
 
+    def test_return_path(self):
+        VERTICES, EDGES = self.VERTEXS, self.EDGES
+
+        stmt = 'MATCH p = (n:player{name:"Tony Parker"}) return p,n'
+        resp = self.execute_query(stmt)
+        self.check_resp_succeeded(resp);
+        columns_name = ['p', 'n']
+        self.check_column_names(resp, columns_name)
+        result = [
+            [PathVal([VERTICES["Tony Parker"]]), VERTICES["Tony Parker"]]
+            ]
+        self.check_out_of_order_result(resp, result)
+
+        stmt = 'MATCH p = (n:player{name:"LeBron James"})-[:like]->(m) return p, n.name, m.name'
+        resp = self.execute_query(stmt)
+        self.check_resp_succeeded(resp)
+        columns_name = ['p', 'n.name', 'm.name']
+        self.check_column_names(resp, columns_name)
+        result = [
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["LeBron James"+"Ray Allen"+"like"+"0"], 1),
+                    VERTICES["Ray Allen"]]),
+                "LeBron James", "Ray Allen"]
+            ]
+        self.check_out_of_order_result(resp, result)
+
+        stmt = 'MATCH p = (n:player{name:"LeBron James"})<-[:like]-(m) return p, n.name, m.name'
+        resp = self.execute_query(stmt)
+        self.check_resp_succeeded(resp)
+        columns_name = ['p', 'n.name', 'm.name']
+        self.check_column_names(resp, columns_name)
+        result = [
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Dejounte Murray"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Dejounte Murray"]]),
+                "LeBron James", "Dejounte Murray"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Carmelo Anthony"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Carmelo Anthony"]]),
+                "LeBron James", "Carmelo Anthony"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Kyrie Irving"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Kyrie Irving"]]),
+                "LeBron James", "Kyrie Irving"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Dwyane Wade"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Dwyane Wade"]]),
+                "LeBron James", "Dwyane Wade"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Danny Green"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Danny Green"]]),
+                "LeBron James", "Danny Green"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Chris Paul"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Chris Paul"]]),
+                "LeBron James", "Chris Paul"],
+            ]
+        self.check_out_of_order_result(resp, result)
+
+        stmt = 'MATCH p = (n:player{name:"LeBron James"})-[:like]-(m) return p, n.name, m.name'
+        resp = self.execute_query(stmt)
+        self.check_resp_succeeded(resp)
+        columns_name = ['p', 'n.name', 'm.name']
+        self.check_column_names(resp, columns_name)
+        result = [
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["LeBron James"+"Ray Allen"+"like"+"0"], 1),
+                    VERTICES["Ray Allen"]]),
+                "LeBron James", "Ray Allen"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Dejounte Murray"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Dejounte Murray"]]),
+                "LeBron James", "Dejounte Murray"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Carmelo Anthony"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Carmelo Anthony"]]),
+                "LeBron James", "Carmelo Anthony"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Kyrie Irving"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Kyrie Irving"]]),
+                "LeBron James", "Kyrie Irving"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Dwyane Wade"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Dwyane Wade"]]),
+                "LeBron James", "Dwyane Wade"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Danny Green"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Danny Green"]]),
+                "LeBron James", "Danny Green"],
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["Chris Paul"+"LeBron James"+"like"+"0"], -1),
+                    VERTICES["Chris Paul"]]),
+                "LeBron James", "Chris Paul"],
+            ]
+        self.check_out_of_order_result(resp, result)
+
+        stmt = 'MATCH p = (n:player{name:"LeBron James"})-[:like]->(m)-[:like]->(k) return p, n.name, m.name, k.name'
+        resp = self.execute_query(stmt)
+        self.check_resp_succeeded(resp)
+        columns_name = ['p', 'n.name', 'm.name', 'k.name']
+        self.check_column_names(resp, columns_name)
+        result = [
+            [PathVal([VERTICES["LeBron James"],
+                    (EDGES["LeBron James"+"Ray Allen"+"like"+"0"], 1),
+                    VERTICES["Ray Allen"],
+                    (EDGES["Ray Allen"+"Rajon Rondo"+"like"+"0"], 1),
+                    VERTICES["Rajon Rondo"]]),
+                "LeBron James", "Ray Allen", "Rajon Rondo"]
+            ]
+        self.check_out_of_order_result(resp, result)
+
     def test_failures(self):
         # No RETURN
         stmt = 'MATCH (v:player{name:"abc")'