diff --git a/src/graph/executor/algo/ShortestPathExecutor.cpp b/src/graph/executor/algo/ShortestPathExecutor.cpp index 58d036ffc98..a31e331b148 100644 --- a/src/graph/executor/algo/ShortestPathExecutor.cpp +++ b/src/graph/executor/algo/ShortestPathExecutor.cpp @@ -44,6 +44,7 @@ size_t ShortestPathExecutor::checkInput(HashSet& startVids, HashSet& endVids) { auto iter = ectx_->getResult(pathNode_->inputVar()).iter(); const auto& metaVidType = *(qctx()->rctx()->session()->space().spaceDesc.vid_type_ref()); auto vidType = SchemaUtil::propTypeToValueType(metaVidType.get_type()); + bool isZeroStep = pathNode_->stepRange().min() == 0; for (; iter->valid(); iter->next()) { auto start = iter->getColumn(0); auto end = iter->getColumn(1); @@ -52,8 +53,10 @@ size_t ShortestPathExecutor::checkInput(HashSet& startVids, HashSet& endVids) { << ", end type: " << end.type() << ", space vid type: " << vidType; continue; } - if (start == end) { - // continue or return error + + // When the minimum number of steps is 0, and the starting node and the destination node + // are the same. the shortest path between the two nodes is 0 + if (isZeroStep && start == end) { continue; } startVids.emplace(std::move(start)); diff --git a/src/graph/planner/match/ShortestPathPlanner.cpp b/src/graph/planner/match/ShortestPathPlanner.cpp index d93b7d2c15d..f03c5fce117 100644 --- a/src/graph/planner/match/ShortestPathPlanner.cpp +++ b/src/graph/planner/match/ShortestPathPlanner.cpp @@ -55,6 +55,10 @@ StatusOr ShortestPathPlanner::transform(WhereClauseContext* bindWhereCl SubPlan subplan; bool singleShortest = path_.pathType == Path::PathType::kSingleShortest; auto& nodeInfos = path_.nodeInfos; + if (nodeInfos.front().alias == nodeInfos.back().alias) { + return Status::SemanticError( + "The shortest path algorithm does not work when the start and end nodes are the same"); + } auto& edge = path_.edgeInfos.front(); std::vector colNames; colNames.emplace_back(nodeInfos.front().alias); diff --git a/tests/tck/features/match/AllShortestPaths.feature b/tests/tck/features/match/AllShortestPaths.feature index 563d90a371d..f777312f796 100644 --- a/tests/tck/features/match/AllShortestPaths.feature +++ b/tests/tck/features/match/AllShortestPaths.feature @@ -898,3 +898,73 @@ Feature: allShortestPaths | 11 | Argument | | | | 14 | Project | 13 | | | 13 | Argument | | | + + Scenario: allShortestPaths for same start and end node + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}) + MATCH p = allShortestPaths((a)-[:like*1..3]-(a)) + RETURN p + """ + Then a SemanticError should be raised at runtime: The shortest path algorithm does not work when the start and end nodes are the same + When executing query: + """ + MATCH p = allShortestPaths((a:player{name:'Yao Ming'})-[:like*1..3]-(a)) + RETURN p + """ + Then a SemanticError should be raised at runtime: The shortest path algorithm does not work when the start and end nodes are the same + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}), (b:player{name:'Yao Ming'}) + MATCH p = allShortestPaths((a)-[:like*0..3]-(b)) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH p = allShortestPaths((a)-[:like*0..3]-(b)) + WHERE id(a) == 'Yao Ming' AND id(b) == 'Yao Ming' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}), (b:player{name:'Yao Ming'}) + MATCH p = allShortestPaths((a)-[:like*1..3]-(b)) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}) + MATCH p = allShortestPaths((a)-[:like*1..3]-(b:player{name:'Yao Ming'})) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + When executing query: + """ + MATCH p = allShortestPaths((a:player{name:'Yao Ming'})-[:like*1..3]-(b:player{name:'Yao Ming'})) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + When executing query: + """ + MATCH p = allShortestPaths((a)-[:like*1..3]-(b)) + WHERE id(a) == 'Yao Ming' AND id(b) == 'Yao Ming' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | diff --git a/tests/tck/features/match/SingleShorestPath.feature b/tests/tck/features/match/SingleShorestPath.feature index 78ad83c1662..abf9f8d96ac 100644 --- a/tests/tck/features/match/SingleShorestPath.feature +++ b/tests/tck/features/match/SingleShorestPath.feature @@ -746,3 +746,69 @@ Feature: single shortestPath | <("JaVale McGee":player{age:31,name:"JaVale McGee"})-[:serve@0{end_year:2018,start_year:2016}]->("Warriors":team{name:"Warriors"})<-[:serve@0{end_year:2009,start_year:2007}]-("Marco Belinelli":player{age:32,name:"Marco Belinelli"})-[:like@0{likeness:50}]->("Tony Parker":player{age:36,name:"Tony Parker"})> | | <("LeBron James":player{age:34,name:"LeBron James"})<-[:like@0{likeness:99}]-("Dejounte Murray":player{age:29,name:"Dejounte Murray"})-[:like@0{likeness:99}]->("Tony Parker":player{age:36,name:"Tony Parker"})> | | <("Kings":team{name:"Kings"})<-[:serve@0{end_year:2016,start_year:2015}]-("Marco Belinelli":player{age:32,name:"Marco Belinelli"})-[:like@0{likeness:50}]->("Tony Parker":player{age:36,name:"Tony Parker"})> | + + Scenario: shortestPath for same start and end node + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}) + MATCH p = shortestPath((a)-[:like*1..3]-(a)) + RETURN p + """ + Then a SemanticError should be raised at runtime: The shortest path algorithm does not work when the start and end nodes are the same + When executing query: + """ + MATCH p = shortestPath((a:player{name:'Yao Ming'})-[:like*1..3]-(a)) + RETURN p + """ + Then a SemanticError should be raised at runtime: The shortest path algorithm does not work when the start and end nodes are the same + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}), (b:player{name:'Yao Ming'}) + MATCH p = shortestPath((a)-[:like*0..3]-(b)) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH p = shortestPath((a)-[:like*0..3]-(b)) + WHERE id(a) == 'Yao Ming' AND id(b) == 'Yao Ming' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}), (b:player{name:'Yao Ming'}) + MATCH p = shortestPath((a)-[:like*1..3]-(b)) + RETURN a, b + """ + Then the result should be, in any order, with relax comparison: + | a | b | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | + When executing query: + """ + MATCH (a:player{name:'Yao Ming'}) + MATCH p = shortestPath((a)-[:like*1..3]-(b:player{name:'Yao Ming'})) + RETURN a,b + """ + Then the result should be, in any order, with relax comparison: + | a | b | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | + When executing query: + """ + MATCH p = shortestPath((a:player{name:'Yao Ming'})-[:like*1..3]-(b:player{name:'Yao Ming'})) + RETURN a,b + """ + Then the result should be, in any order, with relax comparison: + | a | b | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | + When executing query: + """ + MATCH p = shortestPath((a)-[:like*1..3]-(b)) + WHERE id(a) == 'Yao Ming' AND id(b) == 'Yao Ming' + RETURN a,b + """ + Then the result should be, in any order, with relax comparison: + | a | b | + | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) |