diff --git a/src/graph/executor/query/TraverseExecutor.h b/src/graph/executor/query/TraverseExecutor.h index 7fe4e0a3b61..424945eec5c 100644 --- a/src/graph/executor/query/TraverseExecutor.h +++ b/src/graph/executor/query/TraverseExecutor.h @@ -14,7 +14,11 @@ // invoke the getNeighbors interface, according to the number of times specified by the user, // and assemble the result into paths // -// The definition of path is : array of vertex and edges +// Path is an array of vertex and edges physically. +// Its definition is a trail, in which all edges are distinct. It's different to a walk +// which allows duplicated vertices and edges, and a path where all vertices and edges +// are distinct. +// // Eg a->b->c. path is [Vertex(a), [Edge(a->b), Vertex(b), Edge(b->c), Vertex(c)]] // the purpose is to extract the path by pathBuildExpression // `resDs_` : keep result dataSet diff --git a/src/storage/exec/GetNeighborsNode.h b/src/storage/exec/GetNeighborsNode.h index ee9719b7458..6fe2b2ecd07 100644 --- a/src/storage/exec/GetNeighborsNode.h +++ b/src/storage/exec/GetNeighborsNode.h @@ -116,6 +116,9 @@ class GetNeighborsNode : public QueryNode { return nebula::cpp2::ErrorCode::SUCCEEDED; } auto key = upstream_->key(); + if (isDuplicatedSelfReflectiveEdge(key)) { + continue; + } auto reader = upstream_->reader(); auto props = context_->props_; auto columnIdx = context_->columnIdx_; @@ -138,6 +141,24 @@ class GetNeighborsNode : public QueryNode { return nebula::cpp2::ErrorCode::SUCCEEDED; } + bool isDuplicatedSelfReflectiveEdge(const folly::StringPiece& key) { + folly::StringPiece srcID = NebulaKeyUtils::getSrcId(context_->vIdLen(), key); + folly::StringPiece dstID = NebulaKeyUtils::getDstId(context_->vIdLen(), key); + if (srcID == dstID) { + // self-reflective edge + std::string rank = std::to_string(NebulaKeyUtils::getRank(context_->vIdLen(), key)); + auto edgeType = NebulaKeyUtils::getEdgeType(context_->vIdLen(), key); + edgeType = edgeType > 0 ? edgeType : -edgeType; + std::string type = std::to_string(edgeType); + std::string localKey = type + rank + srcID.str(); + if (!visitedSelfReflectiveEdges_.insert(localKey).second) { + return true; + } + } + return false; + } + + std::unordered_set visitedSelfReflectiveEdges_; RuntimeContext* context_; IterateNode* hashJoinNode_; IterateNode* upstream_; diff --git a/tests/tck/features/match/SelfReflectiveEdges.feature b/tests/tck/features/match/SelfReflectiveEdges.feature new file mode 100644 index 00000000000..191ef8238e6 --- /dev/null +++ b/tests/tck/features/match/SelfReflectiveEdges.feature @@ -0,0 +1,40 @@ +# Copyright (c) 2022 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Matches on self-reflective edges + + Scenario: no duplicated self reflective edges + Given a graph with space named "nba" + And having executed: + """ + insert vertex player (name, age) values "Hades":("Hades", 99999); + insert vertex team (name) values "Underworld":("Underworld"); + insert edge like (likeness) values "Hades"->"Hades":(3000); + insert edge teammate (start_year, end_year) values "Hades"->"Hades":(3000, 3000); + insert edge serve (start_year, end_year) values "Hades"->"Underworld":(0, 99999); + """ + When executing query: + """ + MATCH x = (n0)-[e1]->(n1)-[e2]-(n0) WHERE id(n0) == "Hades" and id(n1) == "Hades" return e1, e2 + """ + Then the result should be, in any order: + | e1 | e2 | + | [:teammate "Hades"->"Hades" @0 {end_year: 3000, start_year: 3000}] | [:like "Hades"->"Hades" @0 {likeness: 3000}] | + | [:like "Hades"->"Hades" @0 {likeness: 3000}] | [:teammate "Hades"->"Hades" @0 {end_year: 3000, start_year: 3000}] | + When executing query: + """ + MATCH x = (n0)-[e1]->(n1)<-[e2]-(n0) WHERE id(n0) == "Hades" and id(n1) == "Hades" return e1, e2 + """ + Then the result should be, in any order: + | e1 | e2 | + | [:teammate "Hades"->"Hades" @0 {end_year: 3000, start_year: 3000}] | [:like "Hades"->"Hades" @0 {likeness: 3000}] | + | [:like "Hades"->"Hades" @0 {likeness: 3000}] | [:teammate "Hades"->"Hades" @0 {end_year: 3000, start_year: 3000}] | + When executing query: + """ + DELETE EDGE serve "Hades"->"Underworld"; + DELETE EDGE teammate "Hades"->"Hades"; + DELETE EDGE like "Hades"->"Hades"; + DELETE VERTEX "Underworld"; + DELETE VERTEX "Hades"; + """ + Then the execution should be successful