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")'