From 56856e76a80785da0ec96c3a32f9bf6feb02c740 Mon Sep 17 00:00:00 2001 From: jimingquan Date: Tue, 7 Jun 2022 11:23:42 +0800 Subject: [PATCH] Shortest path (#4071) * shortestpath * frame * fix * fix * fix * almost finish * fix * fix * add tck * refactor shortest path * conjunct path * conjunct path * create left & right path * fix error * fix compile error * fix createPath error * concurrent execute * rebase * add shortestpath plan * fix error * add test case * same change * add comment * fix spell error * batch process * batch shortest path * add singleShortestPath file * add batchshortest path * new batch process * fix error * rebase * format * fix batchShortestPath 's single problem * fix define error * fix test case * Synchronous interface is replaced by asynchronous interface in batch shortest path * Synchronous interface is replaced by asynchronous interface in single shortest path * del testcase Co-authored-by: jackwener <30525741+jackwener@users.noreply.github.com> Co-authored-by: Sophie <84560950+Sophie-Xie@users.noreply.github.com> --- src/graph/context/Iterator.h | 1 + src/graph/context/ast/CypherAstContext.h | 11 +- src/graph/executor/CMakeLists.txt | 4 + src/graph/executor/Executor.cpp | 4 + src/graph/executor/algo/BatchShortestPath.cpp | 541 ++++++++++++++++++ src/graph/executor/algo/BatchShortestPath.h | 72 +++ src/graph/executor/algo/ShortestPathBase.cpp | 224 ++++++++ src/graph/executor/algo/ShortestPathBase.h | 98 ++++ .../executor/algo/ShortestPathExecutor.cpp | 63 ++ .../executor/algo/ShortestPathExecutor.h | 29 + .../executor/algo/SingleShortestPath.cpp | 364 ++++++++++++ src/graph/executor/algo/SingleShortestPath.h | 60 ++ src/graph/executor/query/TraverseExecutor.cpp | 4 +- src/graph/planner/CMakeLists.txt | 1 + .../planner/match/MatchClausePlanner.cpp | 58 +- src/graph/planner/match/MatchClausePlanner.h | 7 - src/graph/planner/match/MatchPathPlanner.cpp | 135 +---- src/graph/planner/match/MatchPathPlanner.h | 12 - src/graph/planner/match/MatchSolver.cpp | 61 ++ src/graph/planner/match/MatchSolver.h | 3 + .../planner/match/ShortestPathPlanner.cpp | 123 ++++ src/graph/planner/match/ShortestPathPlanner.h | 26 + src/graph/planner/plan/Algo.cpp | 39 ++ src/graph/planner/plan/Algo.h | 83 +++ src/graph/planner/plan/PlanNode.cpp | 2 + src/graph/planner/plan/PlanNode.h | 1 + src/graph/planner/plan/Query.cpp | 6 +- src/graph/util/PlannerUtil.cpp | 1 - src/graph/util/SchemaUtil.cpp | 45 ++ src/graph/util/SchemaUtil.h | 7 + src/graph/validator/MatchValidator.cpp | 29 +- src/parser/MatchPath.h | 11 + src/parser/parser.yy | 12 +- src/parser/scanner.lex | 2 + .../features/match/AllShortestPaths.feature | 477 +++++++++++++++ .../features/match/SingleShorestPath.feature | 392 +++++++++++++ 36 files changed, 2809 insertions(+), 199 deletions(-) create mode 100644 src/graph/executor/algo/BatchShortestPath.cpp create mode 100644 src/graph/executor/algo/BatchShortestPath.h create mode 100644 src/graph/executor/algo/ShortestPathBase.cpp create mode 100644 src/graph/executor/algo/ShortestPathBase.h create mode 100644 src/graph/executor/algo/ShortestPathExecutor.cpp create mode 100644 src/graph/executor/algo/ShortestPathExecutor.h create mode 100644 src/graph/executor/algo/SingleShortestPath.cpp create mode 100644 src/graph/executor/algo/SingleShortestPath.h create mode 100644 src/graph/planner/match/ShortestPathPlanner.cpp create mode 100644 src/graph/planner/match/ShortestPathPlanner.h create mode 100644 tests/tck/features/match/AllShortestPaths.feature create mode 100644 tests/tck/features/match/SingleShorestPath.feature diff --git a/src/graph/context/Iterator.h b/src/graph/context/Iterator.h index bde9341ea45..9d9ec1a553f 100644 --- a/src/graph/context/Iterator.h +++ b/src/graph/context/Iterator.h @@ -499,6 +499,7 @@ class SequentialIter : public Iterator { friend class DataCollectExecutor; friend class AppendVerticesExecutor; friend class TraverseExecutor; + friend class ShortestPathExecutor; void doReset(size_t pos) override; diff --git a/src/graph/context/ast/CypherAstContext.h b/src/graph/context/ast/CypherAstContext.h index feacef056cd..3a73cee3fd0 100644 --- a/src/graph/context/ast/CypherAstContext.h +++ b/src/graph/context/ast/CypherAstContext.h @@ -24,6 +24,8 @@ enum class CypherClauseKind : uint8_t { kOrderBy, kPagination, kYield, + kShortestPath, + kAllShortestPaths, }; enum class PatternKind : uint8_t { @@ -80,6 +82,9 @@ struct Path final { std::vector compareVariables; // "(v)-[:like]->()" in (v)-[:like]->() std::string collectVariable; + + enum PathType : int8_t { kDefault, kAllShortest, kSingleShortest }; + PathType pathType{PathType::kDefault}; }; struct CypherClauseContextBase : AstContext { @@ -210,8 +215,8 @@ struct NodeContext final : PatternContext { QueryContext* qctx; WhereClauseContext* bindWhereClause; GraphSpaceID spaceId; - NodeInfo* info{nullptr}; - std::unordered_set* nodeAliasesAvailable; + NodeInfo* info; + std::unordered_set* nodeAliasesAvailable{nullptr}; // Output fields ScanInfo scanInfo; @@ -227,7 +232,7 @@ struct EdgeContext final : PatternContext { QueryContext* qctx; WhereClauseContext* bindWhereClause; GraphSpaceID spaceId; - EdgeInfo* info{nullptr}; + EdgeInfo* info; // Output fields ScanInfo scanInfo; diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index 43c4ca6ba7d..963c7c29769 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -43,8 +43,12 @@ nebula_add_library( algo/BFSShortestPathExecutor.cpp algo/MultiShortestPathExecutor.cpp algo/ProduceAllPathsExecutor.cpp + algo/ShortestPathExecutor.cpp algo/CartesianProductExecutor.cpp algo/SubgraphExecutor.cpp + algo/ShortestPathBase.cpp + algo/SingleShortestPath.cpp + algo/BatchShortestPath.cpp admin/AddHostsExecutor.cpp admin/DropHostsExecutor.cpp admin/SwitchSpaceExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index 5a7428aa06a..6e1a5ae6ddf 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -48,6 +48,7 @@ #include "graph/executor/algo/CartesianProductExecutor.h" #include "graph/executor/algo/MultiShortestPathExecutor.h" #include "graph/executor/algo/ProduceAllPathsExecutor.h" +#include "graph/executor/algo/ShortestPathExecutor.h" #include "graph/executor/algo/SubgraphExecutor.h" #include "graph/executor/logic/ArgumentExecutor.h" #include "graph/executor/logic/LoopExecutor.h" @@ -545,6 +546,9 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kAlterSpace: { return pool->add(new AlterSpaceExecutor(node, qctx)); } + case PlanNode::Kind::kShortestPath: { + return pool->add(new ShortestPathExecutor(node, qctx)); + } case PlanNode::Kind::kUnknown: { LOG(FATAL) << "Unknown plan node kind " << static_cast(node->kind()); break; diff --git a/src/graph/executor/algo/BatchShortestPath.cpp b/src/graph/executor/algo/BatchShortestPath.cpp new file mode 100644 index 00000000000..cf7c8591ac0 --- /dev/null +++ b/src/graph/executor/algo/BatchShortestPath.cpp @@ -0,0 +1,541 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#include "graph/executor/algo/BatchShortestPath.h" + +#include + +#include "graph/service/GraphFlags.h" +#include "sys/sysinfo.h" + +using nebula::storage::StorageClient; +DECLARE_uint32(num_path_thread); +namespace nebula { +namespace graph { +folly::Future BatchShortestPath::execute(const std::unordered_set& startVids, + const std::unordered_set& endVids, + DataSet* result) { + size_t rowSize = init(startVids, endVids); + std::vector> futures; + futures.reserve(rowSize); + for (size_t rowNum = 0; rowNum < rowSize; ++rowNum) { + resultDs_[rowNum].colNames = pathNode_->colNames(); + futures.emplace_back(shortestPath(rowNum, 1)); + } + return folly::collect(futures) + .via(qctx_->rctx()->runner()) + .thenValue([this, result](auto&& resps) { + for (auto& resp : resps) { + NG_RETURN_IF_ERROR(resp); + } + result->colNames = pathNode_->colNames(); + for (auto& ds : resultDs_) { + result->append(std::move(ds)); + } + return Status::OK(); + }); +} + +size_t BatchShortestPath::init(const std::unordered_set& startVids, + const std::unordered_set& endVids) { + size_t rowSize = splitTask(startVids, endVids); + + leftVids_.reserve(rowSize); + rightVids_.reserve(rowSize); + allLeftPathMaps_.resize(rowSize); + allRightPathMaps_.resize(rowSize); + currentLeftPathMaps_.reserve(rowSize); + currentRightPathMaps_.reserve(rowSize); + preRightPathMaps_.reserve(rowSize); + + terminationMaps_.reserve(rowSize); + resultDs_.resize(rowSize); + + for (auto& _startVids : batchStartVids_) { + DataSet startDs; + PathMap leftPathMap; + for (auto& startVid : _startVids) { + startDs.rows.emplace_back(Row({startVid})); + std::vector dummy; + leftPathMap[startVid].emplace(startVid, std::move(dummy)); + } + for (auto& _endVids : batchEndVids_) { + DataSet endDs; + PathMap preRightPathMap; + PathMap rightPathMap; + for (auto& endVid : _endVids) { + endDs.rows.emplace_back(Row({endVid})); + std::vector dummy; + rightPathMap[endVid].emplace(endVid, dummy); + preRightPathMap[endVid].emplace(endVid, std::move(dummy)); + } + + // set originRightpath + currentLeftPathMaps_.emplace_back(leftPathMap); + preRightPathMaps_.emplace_back(std::move(preRightPathMap)); + currentRightPathMaps_.emplace_back(std::move(rightPathMap)); + + // set vid for getNeightbor + leftVids_.emplace_back(startDs); + rightVids_.emplace_back(std::move(endDs)); + + // set terminateMap + std::unordered_multimap> terminationMap; + for (auto& _startVid : _startVids) { + for (auto& _endVid : _endVids) { + if (_startVid != _endVid) { + terminationMap.emplace(_startVid, std::make_pair(_endVid, true)); + } + } + } + terminationMaps_.emplace_back(std::move(terminationMap)); + } + } + return rowSize; +} + +folly::Future BatchShortestPath::shortestPath(size_t rowNum, size_t stepNum) { + std::vector> futures; + futures.emplace_back(getNeighbors(rowNum, stepNum, false)); + futures.emplace_back(getNeighbors(rowNum, stepNum, true)); + return folly::collect(futures) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum, stepNum](auto&& resps) { + for (auto& resp : resps) { + if (!resp.ok()) { + return folly::makeFuture(std::move(resp)); + } + } + return handleResponse(rowNum, stepNum); + }); +} + +folly::Future BatchShortestPath::getNeighbors(size_t rowNum, size_t stepNum, bool reverse) { + StorageClient* storageClient = qctx_->getStorageClient(); + time::Duration getNbrTime; + storage::StorageClient::CommonRequestParam param(pathNode_->space(), + qctx_->rctx()->session()->id(), + qctx_->plan()->id(), + qctx_->plan()->isProfileEnabled()); + auto& inputRows = reverse ? rightVids_[rowNum].rows : leftVids_[rowNum].rows; + return storageClient + ->getNeighbors(param, + {nebula::kVid}, + std::move(inputRows), + {}, + pathNode_->edgeDirection(), + nullptr, + pathNode_->vertexProps(), + reverse ? pathNode_->reverseEdgeProps() : pathNode_->edgeProps(), + nullptr, + false, + false, + {}, + -1, + nullptr) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum, reverse, stepNum, getNbrTime](auto&& resp) { + addStats(resp, stats_, stepNum, getNbrTime.elapsedInUSec(), reverse); + return buildPath(rowNum, std::move(resp), reverse); + }); +} + +Status BatchShortestPath::buildPath(size_t rowNum, RpcResponse&& resps, bool reverse) { + auto result = handleCompleteness(resps, FLAGS_accept_partial_success); + NG_RETURN_IF_ERROR(result); + auto& responses = std::move(resps).responses(); + List list; + for (auto& resp : responses) { + auto dataset = resp.get_vertices(); + if (dataset == nullptr) { + LOG(INFO) << "Empty dataset in response"; + continue; + } + list.values.emplace_back(std::move(*dataset)); + } + auto listVal = std::make_shared(std::move(list)); + auto iter = std::make_unique(listVal); + return doBuildPath(rowNum, iter.get(), reverse); +} + +Status BatchShortestPath::doBuildPath(size_t rowNum, GetNeighborsIter* iter, bool reverse) { + auto& historyPathMap = reverse ? allRightPathMaps_[rowNum] : allLeftPathMaps_[rowNum]; + auto& currentPathMap = reverse ? currentRightPathMaps_[rowNum] : currentLeftPathMaps_[rowNum]; + + for (; iter->valid(); iter->next()) { + auto edgeVal = iter->getEdge(); + if (UNLIKELY(!edgeVal.isEdge())) { + continue; + } + auto& edge = edgeVal.getEdge(); + auto src = edge.src; + auto dst = edge.dst; + if (src == dst) { + continue; + } + auto vertex = iter->getVertex(); + CustomPath customPath; + customPath.emplace_back(std::move(vertex)); + customPath.emplace_back(std::move(edge)); + + auto findSrcFromHistory = historyPathMap.find(src); + if (findSrcFromHistory == historyPathMap.end()) { + // first step + auto findDstFromCurrent = currentPathMap.find(dst); + if (findDstFromCurrent == currentPathMap.end()) { + std::vector tmp({std::move(customPath)}); + currentPathMap[dst].emplace(src, std::move(tmp)); + } else { + auto findSrc = findDstFromCurrent->second.find(src); + if (findSrc == findDstFromCurrent->second.end()) { + std::vector tmp({std::move(customPath)}); + findDstFromCurrent->second.emplace(src, std::move(tmp)); + } else { + // same , different edge type or rank + findSrc->second.emplace_back(std::move(customPath)); + } + } + } else { + // not first step + auto& srcPathMap = findSrcFromHistory->second; + auto findDstFromHistory = historyPathMap.find(dst); + if (findDstFromHistory == historyPathMap.end()) { + // dst not in history + auto findDstFromCurrent = currentPathMap.find(dst); + if (findDstFromCurrent == currentPathMap.end()) { + // dst not in current, new edge + for (const auto& srcPath : srcPathMap) { + currentPathMap[dst].emplace(srcPath.first, createPaths(srcPath.second, customPath)); + } + } else { + // dst in current + for (const auto& srcPath : srcPathMap) { + auto newPaths = createPaths(srcPath.second, customPath); + auto findSrc = findDstFromCurrent->second.find(srcPath.first); + if (findSrc == findDstFromCurrent->second.end()) { + findDstFromCurrent->second.emplace(srcPath.first, std::move(newPaths)); + } else { + findSrc->second.insert(findSrc->second.begin(), + std::make_move_iterator(newPaths.begin()), + std::make_move_iterator(newPaths.end())); + } + } + } + } else { + // dst in history + auto& dstPathMap = findDstFromHistory->second; + for (const auto& srcPath : srcPathMap) { + if (dstPathMap.find(srcPath.first) != dstPathMap.end()) { + // loop : a->b->c->a or a->b->c->b + // filter out path that with duplicate vertex or have already been found before + continue; + } + auto findDstFromCurrent = currentPathMap.find(dst); + if (findDstFromCurrent == currentPathMap.end()) { + currentPathMap[dst].emplace(srcPath.first, createPaths(srcPath.second, customPath)); + } else { + auto newPaths = createPaths(srcPath.second, customPath); + auto findSrc = findDstFromCurrent->second.find(srcPath.first); + if (findSrc == findDstFromCurrent->second.end()) { + findDstFromCurrent->second.emplace(srcPath.first, std::move(newPaths)); + } else { + findSrc->second.insert(findSrc->second.begin(), + std::make_move_iterator(newPaths.begin()), + std::make_move_iterator(newPaths.end())); + } + } + } + } + } + } + // set nextVid + setNextStepVid(currentPathMap, rowNum, reverse); + return Status::OK(); +} + +folly::Future BatchShortestPath::handleResponse(size_t rowNum, size_t stepNum) { + return folly::makeFuture(Status::OK()) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum](auto&& status) { + // odd step + UNUSED(status); + return conjunctPath(rowNum, true); + }) + .thenValue([this, rowNum, stepNum](auto&& terminate) { + // even Step + if (terminate || stepNum * 2 > maxStep_) { + return folly::makeFuture(true); + } + return conjunctPath(rowNum, false); + }) + .thenValue([this, rowNum, stepNum](auto&& result) { + if (result || stepNum * 2 >= maxStep_) { + return folly::makeFuture(Status::OK()); + } + auto& leftVids = leftVids_[rowNum].rows; + auto& rightVids = rightVids_[rowNum].rows; + if (leftVids.empty() || rightVids.empty()) { + return folly::makeFuture(Status::OK()); + } + // update allPathMap + auto& leftPathMap = currentLeftPathMaps_[rowNum]; + auto& rightPathMap = currentRightPathMaps_[rowNum]; + preRightPathMaps_[rowNum] = rightPathMap; + for (auto& iter : leftPathMap) { + allLeftPathMaps_[rowNum][iter.first].insert(std::make_move_iterator(iter.second.begin()), + std::make_move_iterator(iter.second.end())); + } + for (auto& iter : rightPathMap) { + allRightPathMaps_[rowNum][iter.first].insert(std::make_move_iterator(iter.second.begin()), + std::make_move_iterator(iter.second.end())); + } + leftPathMap.clear(); + rightPathMap.clear(); + return shortestPath(rowNum, stepNum + 1); + }); +} + +folly::Future> BatchShortestPath::getMeetVids(size_t rowNum, + bool oddStep, + std::vector& meetVids) { + if (!oddStep) { + return getMeetVidsProps(meetVids); + } + std::vector vertices; + vertices.reserve(meetVids.size()); + for (auto& meetVid : meetVids) { + for (auto& dstPaths : currentRightPathMaps_[rowNum]) { + bool flag = false; + for (auto& srcPaths : dstPaths.second) { + for (auto& path : srcPaths.second) { + auto& vertex = path.values[path.size() - 2]; + auto& vid = vertex.getVertex().vid; + if (vid == meetVid) { + vertices.emplace_back(vertex); + flag = true; + break; + } + } + if (flag) { + break; + } + } + if (flag) { + break; + } + } + } + return folly::makeFuture>(std::move(vertices)); +} + +folly::Future BatchShortestPath::conjunctPath(size_t rowNum, bool oddStep) { + auto& _leftPathMaps = currentLeftPathMaps_[rowNum]; + auto& _rightPathMaps = oddStep ? preRightPathMaps_[rowNum] : currentRightPathMaps_[rowNum]; + + std::vector meetVids; + meetVids.reserve(_leftPathMaps.size()); + for (const auto& leftPathMap : _leftPathMaps) { + auto findCommonVid = _rightPathMaps.find(leftPathMap.first); + if (findCommonVid != _rightPathMaps.end()) { + meetVids.emplace_back(findCommonVid->first); + } + } + if (meetVids.empty()) { + return folly::makeFuture(false); + } + + auto future = getMeetVids(rowNum, oddStep, meetVids); + return future.via(qctx_->rctx()->runner()).thenValue([this, rowNum, oddStep](auto&& vertices) { + if (vertices.empty()) { + return false; + } + std::unordered_map verticesMap; + for (auto& vertex : vertices) { + verticesMap[vertex.getVertex().vid] = std::move(vertex); + } + auto& terminationMap = terminationMaps_[rowNum]; + auto& leftPathMaps = currentLeftPathMaps_[rowNum]; + auto& rightPathMaps = oddStep ? preRightPathMaps_[rowNum] : currentRightPathMaps_[rowNum]; + for (const auto& leftPathMap : leftPathMaps) { + auto findCommonVid = rightPathMaps.find(leftPathMap.first); + if (findCommonVid == rightPathMaps.end()) { + continue; + } + auto findCommonVertex = verticesMap.find(findCommonVid->first); + if (findCommonVertex == verticesMap.end()) { + continue; + } + auto& rightPaths = findCommonVid->second; + for (const auto& srcPaths : leftPathMap.second) { + auto range = terminationMap.equal_range(srcPaths.first); + if (range.first == range.second) { + continue; + } + for (const auto& dstPaths : rightPaths) { + for (auto found = range.first; found != range.second; ++found) { + if (found->second.first == dstPaths.first) { + if (singleShortest_ && !found->second.second) { + break; + } + doConjunctPath(srcPaths.second, dstPaths.second, findCommonVertex->second, rowNum); + found->second.second = false; + } + } + } + } + } + // update terminationMap + for (auto iter = terminationMap.begin(); iter != terminationMap.end();) { + if (!iter->second.second) { + iter = terminationMap.erase(iter); + } else { + ++iter; + } + } + if (terminationMap.empty()) { + return true; + } + return false; + }); +} + +void BatchShortestPath::doConjunctPath(const std::vector& leftPaths, + const std::vector& rightPaths, + const Value& commonVertex, + size_t rowNum) { + auto& resultDs = resultDs_[rowNum]; + if (rightPaths.empty()) { + for (const auto& leftPath : leftPaths) { + auto forwardPath = leftPath.values; + auto src = forwardPath.front(); + forwardPath.erase(forwardPath.begin()); + Row row; + row.emplace_back(std::move(src)); + row.emplace_back(List(std::move(forwardPath))); + row.emplace_back(commonVertex); + resultDs.rows.emplace_back(std::move(row)); + if (singleShortest_) { + return; + } + } + return; + } + for (const auto& leftPath : leftPaths) { + for (const auto& rightPath : rightPaths) { + auto forwardPath = leftPath.values; + auto backwardPath = rightPath.values; + auto src = forwardPath.front(); + forwardPath.erase(forwardPath.begin()); + forwardPath.emplace_back(commonVertex); + std::reverse(backwardPath.begin(), backwardPath.end()); + forwardPath.insert(forwardPath.end(), + std::make_move_iterator(backwardPath.begin()), + std::make_move_iterator(backwardPath.end())); + auto dst = forwardPath.back(); + forwardPath.pop_back(); + Row row; + row.emplace_back(std::move(src)); + row.emplace_back(List(std::move(forwardPath))); + row.emplace_back(std::move(dst)); + resultDs.rows.emplace_back(std::move(row)); + if (singleShortest_) { + return; + } + } + } +} + +// [a, a->b, b, b->c ] insert [c, c->d] result is [a, a->b, b, b->c, c, c->d] +std::vector BatchShortestPath::createPaths(const std::vector& paths, + const CustomPath& path) { + std::vector newPaths; + newPaths.reserve(paths.size()); + for (const auto& p : paths) { + auto customPath = p; + customPath.values.insert(customPath.values.end(), path.values.begin(), path.values.end()); + newPaths.emplace_back(std::move(customPath)); + } + return newPaths; +} + +void BatchShortestPath::setNextStepVid(const PathMap& paths, size_t rowNum, bool reverse) { + std::vector nextStepVids; + nextStepVids.reserve(paths.size()); + for (const auto& path : paths) { + Row row; + row.values.emplace_back(path.first); + nextStepVids.emplace_back(std::move(row)); + } + if (reverse) { + rightVids_[rowNum].rows.swap(nextStepVids); + } else { + leftVids_[rowNum].rows.swap(nextStepVids); + } +} + +size_t BatchShortestPath::splitTask(const std::unordered_set& startVids, + const std::unordered_set& endVids) { + size_t threadNum = FLAGS_num_path_thread; + size_t startVidsSize = startVids.size(); + size_t endVidsSize = endVids.size(); + size_t maxSlices = startVidsSize < threadNum ? startVidsSize : threadNum; + int minValue = INT_MAX; + size_t startSlices = 1; + size_t endSlices = 1; + for (size_t i = maxSlices; i >= 1; --i) { + auto j = threadNum / i; + auto val = std::abs(static_cast(startVidsSize / i) - static_cast(endVidsSize / j)); + if (val < minValue) { + minValue = val; + startSlices = i; + endSlices = j; + } + } + // split tasks + { + auto batchSize = startVidsSize / startSlices; + size_t i = 0; + size_t slices = 1; + size_t count = 1; + std::vector tmp; + tmp.reserve(batchSize); + for (auto& startVid : startVids) { + tmp.emplace_back(startVid); + if ((++i == batchSize && slices != startSlices) || count == startVidsSize) { + batchStartVids_.emplace_back(std::move(tmp)); + tmp.reserve(batchSize); + i = 0; + ++slices; + } + ++count; + } + } + { + auto batchSize = endVidsSize / endSlices; + size_t i = 0; + size_t slices = 1; + size_t count = 1; + std::vector tmp; + tmp.reserve(batchSize); + for (auto& endVid : endVids) { + tmp.emplace_back(endVid); + if ((++i == batchSize && slices != endSlices) || count == endVidsSize) { + batchEndVids_.emplace_back(std::move(tmp)); + tmp.reserve(batchSize); + i = 0; + ++slices; + } + ++count; + } + } + std::stringstream ss; + ss << "{\n" + << "startVids' size : " << startVidsSize << " endVids's size : " << endVidsSize; + ss << " thread num : " << threadNum; + ss << " start blocks : " << startSlices << " end blocks : " << endSlices << "\n}"; + stats_->emplace(folly::sformat("split task "), ss.str()); + return startSlices * endSlices; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/algo/BatchShortestPath.h b/src/graph/executor/algo/BatchShortestPath.h new file mode 100644 index 00000000000..43e2af3b9c8 --- /dev/null +++ b/src/graph/executor/algo/BatchShortestPath.h @@ -0,0 +1,72 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#ifndef GRAPH_EXECUTOR_ALGO_BATCHSHORTESTPATH_H_ +#define GRAPH_EXECUTOR_ALGO_BATCHSHORTESTPATH_H_ + +#include "graph/executor/algo/ShortestPathBase.h" +#include "graph/planner/plan/Algo.h" + +namespace nebula { +namespace graph { +class BatchShortestPath final : public ShortestPathBase { + public: + BatchShortestPath(const ShortestPath* node, + QueryContext* qctx, + std::unordered_map* stats) + : ShortestPathBase(node, qctx, stats) {} + + folly::Future execute(const std::unordered_set& startVids, + const std::unordered_set& endVids, + DataSet* result) override; + + using CustomPath = Row; + using PathMap = std::unordered_map>>; + + private: + size_t splitTask(const std::unordered_set& startVids, + const std::unordered_set& endVids); + + size_t init(const std::unordered_set& startVids, const std::unordered_set& endVids); + + folly::Future getNeighbors(size_t rowNum, size_t stepNum, bool reverse); + + folly::Future shortestPath(size_t rowNum, size_t stepNum); + + folly::Future handleResponse(size_t rowNum, size_t stepNum); + + Status buildPath(size_t rowNum, RpcResponse&& resp, bool reverse); + + Status doBuildPath(size_t rowNum, GetNeighborsIter* iter, bool reverse); + + folly::Future conjunctPath(size_t rowNum, bool oddStep); + + folly::Future> getMeetVids(size_t rowNum, + bool oddStep, + std::vector& meetVids); + + void doConjunctPath(const std::vector& leftPaths, + const std::vector& rightPaths, + const Value& commonVertex, + size_t rowNum); + + std::vector createPaths(const std::vector& paths, const CustomPath& path); + + void setNextStepVid(const PathMap& paths, size_t rowNum, bool reverse); + + private: + std::vector> batchStartVids_; + std::vector> batchEndVids_; + std::vector>> terminationMaps_; + std::vector allLeftPathMaps_; + std::vector allRightPathMaps_; + + std::vector currentLeftPathMaps_; + std::vector currentRightPathMaps_; + std::vector preRightPathMaps_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_ALGO_BATCHSHORTESTPATH_H_ diff --git a/src/graph/executor/algo/ShortestPathBase.cpp b/src/graph/executor/algo/ShortestPathBase.cpp new file mode 100644 index 00000000000..876e52d352b --- /dev/null +++ b/src/graph/executor/algo/ShortestPathBase.cpp @@ -0,0 +1,224 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#include "graph/executor/algo/ShortestPathBase.h" + +#include "graph/service/GraphFlags.h" +#include "graph/util/SchemaUtil.h" +#include "graph/util/Utils.h" + +using apache::thrift::optional_field_ref; +using nebula::storage::StorageClient; + +namespace nebula { +namespace graph { +folly::Future> ShortestPathBase::getMeetVidsProps( + const std::vector& meetVids) { + nebula::DataSet vertices({kVid}); + vertices.rows.reserve(meetVids.size()); + for (auto& vid : meetVids) { + vertices.emplace_back(Row({vid})); + } + + time::Duration getPropsTime; + StorageClient* storageClient = qctx_->getStorageClient(); + StorageClient::CommonRequestParam param(pathNode_->space(), + qctx_->rctx()->session()->id(), + qctx_->plan()->id(), + qctx_->plan()->isProfileEnabled()); + return DCHECK_NOTNULL(storageClient) + ->getProps(param, + std::move(vertices), + pathNode_->vertexProps(), + nullptr, + nullptr, + false, + {}, + -1, + nullptr) + .via(qctx_->rctx()->runner()) + .thenValue([this, getPropsTime](PropRpcResponse&& resp) { + addStats(resp, stats_, getPropsTime.elapsedInUSec()); + return handlePropResp(std::move(resp)); + }); +} + +std::vector ShortestPathBase::handlePropResp(PropRpcResponse&& resps) { + std::vector vertices; + auto result = handleCompleteness(resps, FLAGS_accept_partial_success); + if (!result.ok()) { + LOG(WARNING) << "GetProp partial fail"; + return vertices; + } + nebula::DataSet v; + for (auto& resp : resps.responses()) { + if (resp.props_ref().has_value()) { + if (UNLIKELY(!v.append(std::move(*resp.props_ref())))) { + // it's impossible according to the interface + LOG(WARNING) << "Heterogeneous props dataset"; + } + } else { + LOG(WARNING) << "GetProp partial success"; + } + } + auto val = std::make_shared(std::move(v)); + auto iter = std::make_unique(val); + vertices.reserve(iter->size()); + for (; iter->valid(); iter->next()) { + vertices.emplace_back(iter->getVertex()); + } + return vertices; +} + +std::string ShortestPathBase::getStorageDetail( + optional_field_ref&> ref) const { + if (ref.has_value()) { + auto content = util::join(*ref, [](auto& iter) -> std::string { + return folly::sformat("{}:{}(us)", iter.first, iter.second); + }); + return "{" + content + "}"; + } + return ""; +} + +Status ShortestPathBase::handleErrorCode(nebula::cpp2::ErrorCode code, PartitionID partId) const { + switch (code) { + case nebula::cpp2::ErrorCode::E_KEY_NOT_FOUND: + return Status::Error("Storage Error: Vertex or edge not found."); + case nebula::cpp2::ErrorCode::E_DATA_TYPE_MISMATCH: { + std::string error = + "Storage Error: The data type does not meet the requirements. " + "Use the correct type of data."; + return Status::Error(std::move(error)); + } + case nebula::cpp2::ErrorCode::E_INVALID_VID: { + std::string error = + "Storage Error: The VID must be a 64-bit integer" + " or a string fitting space vertex id length limit."; + return Status::Error(std::move(error)); + } + case nebula::cpp2::ErrorCode::E_INVALID_FIELD_VALUE: { + std::string error = + "Storage Error: Invalid field value: " + "may be the filed is not NULL " + "or without default value or wrong schema."; + return Status::Error(std::move(error)); + } + case nebula::cpp2::ErrorCode::E_LEADER_CHANGED: + return Status::Error( + folly::sformat("Storage Error: Not the leader of {}. Please retry later.", partId)); + case nebula::cpp2::ErrorCode::E_INVALID_FILTER: + return Status::Error("Storage Error: Invalid filter."); + case nebula::cpp2::ErrorCode::E_INVALID_UPDATER: + return Status::Error("Storage Error: Invalid Update col or yield col."); + case nebula::cpp2::ErrorCode::E_INVALID_SPACEVIDLEN: + return Status::Error("Storage Error: Invalid space vid len."); + case nebula::cpp2::ErrorCode::E_SPACE_NOT_FOUND: + return Status::Error("Storage Error: Space not found."); + case nebula::cpp2::ErrorCode::E_PART_NOT_FOUND: + return Status::Error(folly::sformat("Storage Error: Part {} not found.", partId)); + case nebula::cpp2::ErrorCode::E_TAG_NOT_FOUND: + return Status::Error("Storage Error: Tag not found."); + case nebula::cpp2::ErrorCode::E_TAG_PROP_NOT_FOUND: + return Status::Error("Storage Error: Tag prop not found."); + case nebula::cpp2::ErrorCode::E_EDGE_NOT_FOUND: + return Status::Error("Storage Error: Edge not found."); + case nebula::cpp2::ErrorCode::E_EDGE_PROP_NOT_FOUND: + return Status::Error("Storage Error: Edge prop not found."); + case nebula::cpp2::ErrorCode::E_INDEX_NOT_FOUND: + return Status::Error("Storage Error: Index not found."); + case nebula::cpp2::ErrorCode::E_INVALID_DATA: + return Status::Error("Storage Error: Invalid data, may be wrong value type."); + case nebula::cpp2::ErrorCode::E_NOT_NULLABLE: + return Status::Error("Storage Error: The not null field cannot be null."); + case nebula::cpp2::ErrorCode::E_FIELD_UNSET: + return Status::Error( + "Storage Error: " + "The not null field doesn't have a default value."); + case nebula::cpp2::ErrorCode::E_OUT_OF_RANGE: + return Status::Error("Storage Error: Out of range value."); + case nebula::cpp2::ErrorCode::E_DATA_CONFLICT_ERROR: + return Status::Error( + "Storage Error: More than one request trying to " + "add/update/delete one edge/vertex at the same time."); + case nebula::cpp2::ErrorCode::E_FILTER_OUT: + return Status::OK(); + case nebula::cpp2::ErrorCode::E_RAFT_TERM_OUT_OF_DATE: + return Status::Error(folly::sformat( + "Storage Error: Term of part {} is out of date. Please retry later.", partId)); + case nebula::cpp2::ErrorCode::E_RAFT_WAL_FAIL: + return Status::Error("Storage Error: Write wal failed. Probably disk is almost full."); + case nebula::cpp2::ErrorCode::E_RAFT_WRITE_BLOCKED: + return Status::Error( + "Storage Error: Write is blocked when creating snapshot. Please retry later."); + case nebula::cpp2::ErrorCode::E_RAFT_BUFFER_OVERFLOW: + return Status::Error(folly::sformat( + "Storage Error: Part {} raft buffer is full. Please retry later.", partId)); + case nebula::cpp2::ErrorCode::E_RAFT_ATOMIC_OP_FAILED: + return Status::Error("Storage Error: Atomic operation failed."); + default: + auto status = Status::Error("Storage Error: part: %d, error: %s(%d).", + partId, + apache::thrift::util::enumNameSafe(code).c_str(), + static_cast(code)); + LOG(ERROR) << status; + return status; + } + return Status::OK(); +} + +void ShortestPathBase::addStats(RpcResponse& resp, + std::unordered_map* stats, + size_t stepNum, + int64_t timeInUSec, + bool reverse) const { + auto& hostLatency = resp.hostLatency(); + std::stringstream ss; + ss << "{\n"; + for (size_t i = 0; i < hostLatency.size(); ++i) { + size_t size = 0u; + auto& result = resp.responses()[i]; + if (result.vertices_ref().has_value()) { + size = (*result.vertices_ref()).size(); + } + auto& info = hostLatency[i]; + ss << "{" << folly::sformat("{} exec/total/vertices: ", std::get<0>(info).toString()) + << folly::sformat("{}(us)/{}(us)/{},", std::get<1>(info), std::get<2>(info), size) << "\n" + << folly::sformat("total_rpc_time: {}(us)", timeInUSec) << "\n"; + auto detail = getStorageDetail(result.result.latency_detail_us_ref()); + if (!detail.empty()) { + ss << folly::sformat("storage_detail: {}", detail); + } + ss << "\n}"; + } + ss << "\n}"; + if (reverse) { + stats->emplace(folly::sformat("reverse step {}", stepNum), ss.str()); + } else { + stats->emplace(folly::sformat("step {}", stepNum), ss.str()); + } +} + +void ShortestPathBase::addStats(PropRpcResponse& resp, + std::unordered_map* stats, + int64_t timeInUSec) const { + auto& hostLatency = resp.hostLatency(); + std::stringstream ss; + ss << "{\n"; + for (size_t i = 0; i < hostLatency.size(); ++i) { + auto& info = hostLatency[i]; + ss << "{" << folly::sformat("{} exec/total: ", std::get<0>(info).toString()) + << folly::sformat("{}(us)/{}(us),", std::get<1>(info), std::get<2>(info)) << "\n" + << folly::sformat("total_rpc_time: {}(us)", timeInUSec) << "\n"; + auto detail = getStorageDetail(resp.responses()[i].result_ref()->latency_detail_us_ref()); + if (!detail.empty()) { + ss << folly::sformat("storage_detail: {}", detail); + } + ss << "\n}"; + } + ss << "\n}"; + stats->emplace(folly::sformat("get_prop "), ss.str()); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/algo/ShortestPathBase.h b/src/graph/executor/algo/ShortestPathBase.h new file mode 100644 index 00000000000..7f80336260f --- /dev/null +++ b/src/graph/executor/algo/ShortestPathBase.h @@ -0,0 +1,98 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#ifndef GRAPH_EXECUTOR_ALGO_SHORTESTPATHBASE_H_ +#define GRAPH_EXECUTOR_ALGO_SHORTESTPATHBASE_H_ + +#include "graph/planner/plan/Algo.h" + +using nebula::storage::StorageRpcResponse; +using nebula::storage::cpp2::GetNeighborsResponse; +using RpcResponse = StorageRpcResponse; +using PropRpcResponse = StorageRpcResponse; +namespace nebula { +namespace graph { +class ShortestPathBase { + public: + ShortestPathBase(const ShortestPath* node, + QueryContext* qctx, + std::unordered_map* stats) + : pathNode_(node), qctx_(qctx), stats_(stats) { + singleShortest_ = node->singleShortest(); + maxStep_ = node->stepRange()->max(); + } + + virtual ~ShortestPathBase() {} + + virtual folly::Future execute(const std::unordered_set& startVids, + const std::unordered_set& endVids, + DataSet* result) = 0; + + using DstVid = Value; + // start vid of the path + using StartVid = Value; + // end vid of the path + using EndVid = Value; + // save the starting vertex and the corresponding edge. eg [vertex(a), edge(a->b)] + using CustomStep = Row; + + protected: + folly::Future> getMeetVidsProps(const std::vector& meetVids); + + std::vector handlePropResp(PropRpcResponse&& resps); + + Status handleErrorCode(nebula::cpp2::ErrorCode code, PartitionID partId) const; + + void addStats(RpcResponse& resp, + std::unordered_map* stats, + size_t stepNum, + int64_t timeInUSec, + bool reverse) const; + + void addStats(PropRpcResponse& resp, + std::unordered_map* stats, + int64_t timeInUSec) const; + + template + StatusOr handleCompleteness(const storage::StorageRpcResponse& rpcResp, + bool isPartialSuccessAccepted) const { + auto completeness = rpcResp.completeness(); + if (completeness != 100) { + const auto& failedCodes = rpcResp.failedParts(); + for (auto it = failedCodes.begin(); it != failedCodes.end(); it++) { + LOG(ERROR) << " failed, error " << apache::thrift::util::enumNameSafe(it->second) + << ", part " << it->first; + } + // cannot execute at all, or partial success is not accepted + if (completeness == 0 || !isPartialSuccessAccepted) { + if (failedCodes.size() > 0) { + return handleErrorCode(failedCodes.begin()->second, failedCodes.begin()->first); + } + return Status::Error("Request to storage failed, without failedCodes."); + } + // partial success is accepted + qctx_->setPartialSuccess(); + return Result::State::kPartialSuccess; + } + return Result::State::kSuccess; + } + + std::string getStorageDetail( + apache::thrift::optional_field_ref&> ref) const; + + protected: + const ShortestPath* pathNode_{nullptr}; + QueryContext* qctx_{nullptr}; + std::unordered_map* stats_{nullptr}; + size_t maxStep_; + bool singleShortest_{true}; + + std::vector resultDs_; + std::vector leftVids_; + std::vector rightVids_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_QUERY_ShortestPathBase_H_ diff --git a/src/graph/executor/algo/ShortestPathExecutor.cpp b/src/graph/executor/algo/ShortestPathExecutor.cpp new file mode 100644 index 00000000000..904ab6ad110 --- /dev/null +++ b/src/graph/executor/algo/ShortestPathExecutor.cpp @@ -0,0 +1,63 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#include "graph/executor/algo/ShortestPathExecutor.h" + +#include "graph/executor/algo/BatchShortestPath.h" +#include "graph/executor/algo/SingleShortestPath.h" +#include "graph/service/GraphFlags.h" +#include "graph/util/SchemaUtil.h" +#include "sys/sysinfo.h" + +using nebula::storage::StorageClient; + +DEFINE_uint32(num_path_thread, 0, "number of concurrent threads when do shortest path"); +namespace nebula { +namespace graph { +folly::Future ShortestPathExecutor::execute() { + SCOPED_TIMER(&execTime_); + if (FLAGS_num_path_thread == 0) { + FLAGS_num_path_thread = get_nprocs_conf(); + } + DataSet result; + result.colNames = pathNode_->colNames(); + + std::unordered_set startVids; + std::unordered_set endVids; + size_t rowSize = checkInput(startVids, endVids); + std::unique_ptr pathPtr = nullptr; + if (rowSize <= FLAGS_num_path_thread) { + pathPtr = std::make_unique(pathNode_, qctx_, &otherStats_); + } else { + pathPtr = std::make_unique(pathNode_, qctx_, &otherStats_); + } + auto status = pathPtr->execute(startVids, endVids, &result).get(); + NG_RETURN_IF_ERROR(status); + return finish(ResultBuilder().value(Value(std::move(result))).build()); +} + +size_t ShortestPathExecutor::checkInput(std::unordered_set& startVids, + std::unordered_set& endVids) { + auto iter = ectx_->getResult(pathNode_->inputVar()).iter(); + const auto& vidType = *(qctx()->rctx()->session()->space().spaceDesc.vid_type_ref()); + for (; iter->valid(); iter->next()) { + auto start = iter->getColumn(0); + auto end = iter->getColumn(1); + if (!SchemaUtil::isValidVid(start, vidType) || !SchemaUtil::isValidVid(end, vidType)) { + LOG(ERROR) << "Mismatched shortestPath vid type. start type : " << start.type() + << ", end type: " << end.type() + << ", space vid type: " << SchemaUtil::typeToString(vidType); + continue; + } + if (start == end) { + // continue or return error + continue; + } + startVids.emplace(std::move(start)); + endVids.emplace(std::move(end)); + } + return startVids.size() * endVids.size(); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/algo/ShortestPathExecutor.h b/src/graph/executor/algo/ShortestPathExecutor.h new file mode 100644 index 00000000000..f06a2b53f0e --- /dev/null +++ b/src/graph/executor/algo/ShortestPathExecutor.h @@ -0,0 +1,29 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#ifndef GRAPH_EXECUTOR_ALGO_SHORTESTPATHEXECUTOR_H_ +#define GRAPH_EXECUTOR_ALGO_SHORTESTPATHEXECUTOR_H_ + +#include "graph/executor/Executor.h" +#include "graph/planner/plan/Algo.h" +namespace nebula { +namespace graph { +class ShortestPathExecutor final : public Executor { + public: + ShortestPathExecutor(const PlanNode* node, QueryContext* qctx) + : Executor("ShortestPath", node, qctx) { + pathNode_ = asNode(node); + } + + folly::Future execute() override; + + size_t checkInput(std::unordered_set& startVids, std::unordered_set& endVids); + + private: + const ShortestPath* pathNode_{nullptr}; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_ALGO_SHORTESTPATHEXECUTOR_H_ diff --git a/src/graph/executor/algo/SingleShortestPath.cpp b/src/graph/executor/algo/SingleShortestPath.cpp new file mode 100644 index 00000000000..eb862e7967a --- /dev/null +++ b/src/graph/executor/algo/SingleShortestPath.cpp @@ -0,0 +1,364 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#include "graph/executor/algo/SingleShortestPath.h" + +#include "graph/service/GraphFlags.h" +#include "graph/util/SchemaUtil.h" + +using nebula::storage::StorageClient; + +namespace nebula { +namespace graph { +folly::Future SingleShortestPath::execute(const std::unordered_set& startVids, + const std::unordered_set& endVids, + DataSet* result) { + size_t rowSize = startVids.size() * endVids.size(); + init(startVids, endVids, rowSize); + std::vector> futures; + futures.reserve(rowSize); + for (size_t rowNum = 0; rowNum < rowSize; ++rowNum) { + resultDs_[rowNum].colNames = pathNode_->colNames(); + futures.emplace_back(shortestPath(rowNum, 1)); + } + return folly::collect(futures) + .via(qctx_->rctx()->runner()) + .thenValue([this, result](auto&& resps) { + for (auto& resp : resps) { + NG_RETURN_IF_ERROR(resp); + } + result->colNames = pathNode_->colNames(); + for (auto& ds : resultDs_) { + result->append(std::move(ds)); + } + return Status::OK(); + }); +} + +void SingleShortestPath::init(const std::unordered_set& startVids, + const std::unordered_set& endVids, + size_t rowSize) { + leftVids_.reserve(rowSize); + rightVids_.reserve(rowSize); + + leftVisitedVids_.resize(rowSize); + rightVisitedVids_.resize(rowSize); + + allLeftPaths_.resize(rowSize); + allRightPaths_.reserve(rowSize); + resultDs_.resize(rowSize); + for (const auto& startVid : startVids) { + for (const auto& endVid : endVids) { + std::unordered_map> steps; + std::vector dummy; + steps.emplace(endVid, std::move(dummy)); + HalfPath originRightPath({std::move(steps)}); + allRightPaths_.emplace_back(std::move(originRightPath)); + + DataSet startDs, endDs; + startDs.rows.emplace_back(Row({startVid})); + endDs.rows.emplace_back(Row({endVid})); + leftVids_.emplace_back(std::move(startDs)); + rightVids_.emplace_back(std::move(endDs)); + } + } +} + +folly::Future SingleShortestPath::shortestPath(size_t rowNum, size_t stepNum) { + std::vector> futures; + futures.reserve(2); + futures.emplace_back(getNeighbors(rowNum, stepNum, false)); + futures.emplace_back(getNeighbors(rowNum, stepNum, true)); + return folly::collect(futures) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum, stepNum](auto&& resps) { + for (auto& resp : resps) { + if (!resp.ok()) { + return folly::makeFuture(std::move(resp)); + } + } + return handleResponse(rowNum, stepNum); + }); +} + +folly::Future SingleShortestPath::getNeighbors(size_t rowNum, + size_t stepNum, + bool reverse) { + StorageClient* storageClient = qctx_->getStorageClient(); + time::Duration getNbrTime; + storage::StorageClient::CommonRequestParam param(pathNode_->space(), + qctx_->rctx()->session()->id(), + qctx_->plan()->id(), + qctx_->plan()->isProfileEnabled()); + auto& inputRows = reverse ? rightVids_[rowNum].rows : leftVids_[rowNum].rows; + return storageClient + ->getNeighbors(param, + {nebula::kVid}, + std::move(inputRows), + {}, + pathNode_->edgeDirection(), + nullptr, + pathNode_->vertexProps(), + reverse ? pathNode_->reverseEdgeProps() : pathNode_->edgeProps(), + nullptr, + false, + false, + {}, + -1, + nullptr) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum, stepNum, getNbrTime, reverse](auto&& resp) { + addStats(resp, stats_, stepNum, getNbrTime.elapsedInUSec(), reverse); + return buildPath(rowNum, std::move(resp), reverse); + }); +} + +Status SingleShortestPath::buildPath(size_t rowNum, RpcResponse&& resps, bool reverse) { + auto result = handleCompleteness(resps, FLAGS_accept_partial_success); + NG_RETURN_IF_ERROR(result); + auto& responses = std::move(resps).responses(); + List list; + for (auto& resp : responses) { + auto dataset = resp.get_vertices(); + if (dataset == nullptr) { + LOG(INFO) << "Empty dataset in response"; + continue; + } + list.values.emplace_back(std::move(*dataset)); + } + auto listVal = std::make_shared(std::move(list)); + auto iter = std::make_unique(listVal); + return doBuildPath(rowNum, iter.get(), reverse); +} + +Status SingleShortestPath::doBuildPath(size_t rowNum, GetNeighborsIter* iter, bool reverse) { + auto iterSize = iter->size(); + auto& visitedVids = reverse ? rightVisitedVids_[rowNum] : leftVisitedVids_[rowNum]; + visitedVids.reserve(visitedVids.size() + iterSize); + auto& allSteps = reverse ? allRightPaths_[rowNum] : allLeftPaths_[rowNum]; + allSteps.emplace_back(); + auto& currentStep = allSteps.back(); + + std::unordered_set uniqueDst; + uniqueDst.reserve(iterSize); + std::vector nextStepVids; + nextStepVids.reserve(iterSize); + + QueryExpressionContext ctx(qctx_->ectx()); + for (iter->reset(); iter->valid(); iter->next()) { + auto edgeVal = iter->getEdge(); + if (UNLIKELY(!edgeVal.isEdge())) { + continue; + } + auto& edge = edgeVal.getEdge(); + auto dst = edge.dst; + if (visitedVids.find(dst) != visitedVids.end()) { + continue; + } + visitedVids.emplace(edge.src); + if (uniqueDst.emplace(dst).second) { + nextStepVids.emplace_back(Row({dst})); + } + auto vertex = iter->getVertex(); + Row step; + step.emplace_back(std::move(vertex)); + step.emplace_back(std::move(edge)); + auto findDst = currentStep.find(dst); + if (findDst == currentStep.end()) { + std::vector steps({std::move(step)}); + currentStep.emplace(std::move(dst), std::move(steps)); + } else { + auto& steps = findDst->second; + steps.emplace_back(std::move(step)); + } + } + visitedVids.insert(std::make_move_iterator(uniqueDst.begin()), + std::make_move_iterator(uniqueDst.end())); + if (reverse) { + rightVids_[rowNum].rows.swap(nextStepVids); + } else { + leftVids_[rowNum].rows.swap(nextStepVids); + } + return Status::OK(); +} + +folly::Future SingleShortestPath::handleResponse(size_t rowNum, size_t stepNum) { + return folly::makeFuture(Status::OK()) + .via(qctx_->rctx()->runner()) + .thenValue([this, rowNum, stepNum](auto&& status) { + UNUSED(status); + return conjunctPath(rowNum, stepNum); + }) + .thenValue([this, rowNum, stepNum](auto&& result) { + if (result || stepNum * 2 >= maxStep_) { + return folly::makeFuture(Status::OK()); + } + auto& leftVids = leftVids_[rowNum].rows; + auto& rightVids = rightVids_[rowNum].rows; + if (leftVids.empty() || rightVids.empty()) { + return folly::makeFuture(Status::OK()); + } + return shortestPath(rowNum, stepNum + 1); + }); +} + +folly::Future SingleShortestPath::conjunctPath(size_t rowNum, size_t stepNum) { + const auto& leftStep = allLeftPaths_[rowNum].back(); + const auto& prevRightStep = allRightPaths_[rowNum][stepNum - 1]; + std::vector meetVids; + for (const auto& step : leftStep) { + if (prevRightStep.find(step.first) != prevRightStep.end()) { + meetVids.push_back(step.first); + if (singleShortest_) { + break; + } + } + } + if (!meetVids.empty()) { + buildOddPath(rowNum, meetVids); + return folly::makeFuture(true); + } + if (stepNum * 2 > maxStep_) { + return folly::makeFuture(false); + } + + const auto& rightStep = allRightPaths_[rowNum].back(); + for (const auto& step : leftStep) { + if (rightStep.find(step.first) != rightStep.end()) { + meetVids.push_back(step.first); + if (singleShortest_) { + break; + } + } + } + if (meetVids.empty()) { + return folly::makeFuture(false); + } + return buildEvenPath(rowNum, meetVids); +} + +void SingleShortestPath::buildOddPath(size_t rowNum, const std::vector& meetVids) { + for (auto& meetVid : meetVids) { + auto leftPaths = createLeftPath(rowNum, meetVid); + auto rightPaths = createRightPath(rowNum, meetVid, true); + for (auto& leftPath : leftPaths) { + for (auto& rightPath : rightPaths) { + Row path = leftPath; + auto& steps = path.values.back().mutableList().values; + steps.insert(steps.end(), rightPath.values.begin(), rightPath.values.end() - 1); + path.emplace_back(rightPath.values.back()); + resultDs_[rowNum].rows.emplace_back(std::move(path)); + if (singleShortest_) { + return; + } + } + } + } +} + +folly::Future SingleShortestPath::buildEvenPath(size_t rowNum, + const std::vector& meetVids) { + auto future = getMeetVidsProps(meetVids); + return future.via(qctx_->rctx()->runner()).thenValue([this, rowNum](auto&& vertices) { + if (vertices.empty()) { + return false; + } + for (auto& meetVertex : vertices) { + auto meetVid = meetVertex.getVertex().vid; + auto leftPaths = createLeftPath(rowNum, meetVid); + auto rightPaths = createRightPath(rowNum, meetVid, false); + for (auto& leftPath : leftPaths) { + for (auto& rightPath : rightPaths) { + Row path = leftPath; + auto& steps = path.values.back().mutableList().values; + steps.emplace_back(meetVertex); + steps.insert(steps.end(), rightPath.values.begin(), rightPath.values.end() - 1); + path.emplace_back(rightPath.values.back()); + resultDs_[rowNum].rows.emplace_back(std::move(path)); + if (singleShortest_) { + return true; + } + } + } + } + return true; + }); +} + +std::vector SingleShortestPath::createLeftPath(size_t rowNum, const Value& meetVid) { + auto& allSteps = allLeftPaths_[rowNum]; + auto& lastSteps = allSteps.back(); + auto findMeetVid = lastSteps.find(meetVid); + std::vector leftPaths(findMeetVid->second); + for (auto stepIter = allSteps.rbegin() + 1; stepIter != allSteps.rend(); ++stepIter) { + std::vector temp; + for (auto& leftPath : leftPaths) { + Value id = leftPath.values.front().getVertex().vid; + auto findId = stepIter->find(id); + for (auto& step : findId->second) { + auto newPath = leftPath; + newPath.values.insert(newPath.values.begin(), step.values.begin(), step.values.end()); + temp.emplace_back(std::move(newPath)); + } + } + leftPaths.swap(temp); + } + std::vector result; + result.reserve(leftPaths.size()); + for (auto& path : leftPaths) { + Row row; + auto src = path.values.front(); + path.values.erase(path.values.begin()); + row.emplace_back(std::move(src)); + row.emplace_back(std::move(path)); + result.emplace_back(std::move(row)); + } + return result; +} + +std::vector SingleShortestPath::createRightPath(size_t rowNum, + const Value& meetVid, + bool oddStep) { + auto& allSteps = allRightPaths_[rowNum]; + std::vector rightPaths; + auto& lastSteps = allSteps.back(); + if (oddStep) { + for (auto& steps : lastSteps) { + bool flag = false; + for (auto& step : steps.second) { + auto& vertex = step.values.front(); + if (vertex.getVertex().vid == meetVid) { + rightPaths.emplace_back(Row({vertex})); + flag = true; + break; + } + } + if (flag) { + break; + } + } + } else { + auto findMeetVid = lastSteps.find(meetVid); + rightPaths = findMeetVid->second; + } + for (auto stepIter = allSteps.rbegin() + 1; stepIter != allSteps.rend() - 1; ++stepIter) { + std::vector temp; + for (auto& rightPath : rightPaths) { + Value id = rightPath.values.front().getVertex().vid; + auto findId = stepIter->find(id); + for (auto& step : findId->second) { + auto newPath = rightPath; + newPath.values.insert(newPath.values.begin(), step.values.begin(), step.values.end()); + temp.emplace_back(std::move(newPath)); + } + } + rightPaths.swap(temp); + } + for (auto& path : rightPaths) { + std::reverse(path.values.begin(), path.values.end()); + } + return rightPaths; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/algo/SingleShortestPath.h b/src/graph/executor/algo/SingleShortestPath.h new file mode 100644 index 00000000000..f09b847abb3 --- /dev/null +++ b/src/graph/executor/algo/SingleShortestPath.h @@ -0,0 +1,60 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#ifndef GRAPH_EXECUTOR_ALGO_SINGLE_SHORTESTPATH_H_ +#define GRAPH_EXECUTOR_ALGO_SINGLE_SHORTESTPATH_H_ + +#include "graph/executor/algo/ShortestPathBase.h" +#include "graph/planner/plan/Algo.h" + +namespace nebula { +namespace graph { +class SingleShortestPath final : public ShortestPathBase { + public: + SingleShortestPath(const ShortestPath* node, + QueryContext* qctx, + std::unordered_map* stats) + : ShortestPathBase(node, qctx, stats) {} + + folly::Future execute(const std::unordered_set& startVids, + const std::unordered_set& endVids, + DataSet* result) override; + + using HalfPath = std::vector>>; + + private: + void init(const std::unordered_set& startVids, + const std::unordered_set& endVids, + size_t rowSize); + + folly::Future shortestPath(size_t rowNum, size_t stepNum); + + folly::Future getNeighbors(size_t rowNum, size_t stepNum, bool reverse); + + Status buildPath(size_t rowNum, RpcResponse&& resps, bool reverse); + + Status doBuildPath(size_t rowNum, GetNeighborsIter* iter, bool reverse); + + folly::Future handleResponse(size_t rowNum, size_t stepNum); + + folly::Future conjunctPath(size_t rowNum, size_t stepNum); + + folly::Future buildEvenPath(size_t rowNum, const std::vector& meetVids); + + void buildOddPath(size_t rowNum, const std::vector& meetVids); + + std::vector createRightPath(size_t rowNum, const Value& meetVid, bool evenStep); + + std::vector createLeftPath(size_t rowNum, const Value& meetVid); + + private: + std::vector> leftVisitedVids_; + std::vector> rightVisitedVids_; + std::vector allLeftPaths_; + std::vector allRightPaths_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_QUERY_SINGLE_SHORTESTPATH_H_ diff --git a/src/graph/executor/query/TraverseExecutor.cpp b/src/graph/executor/query/TraverseExecutor.cpp index f7288df00d8..2879272bbd6 100644 --- a/src/graph/executor/query/TraverseExecutor.cpp +++ b/src/graph/executor/query/TraverseExecutor.cpp @@ -52,8 +52,8 @@ Status TraverseExecutor::buildRequestDataSet() { for (; iter->valid(); iter->next()) { auto vid = src->eval(ctx(iter)); if (!SchemaUtil::isValidVid(vid, vidType)) { - LOG(WARNING) << "Mismatched vid type: " << vid.type() - << ", space vid type: " << SchemaUtil::typeToString(vidType); + LOG(ERROR) << "Mismatched vid type: " << vid.type() + << ", space vid type: " << SchemaUtil::typeToString(vidType); continue; } // Need copy here, Argument executor may depends on this variable. diff --git a/src/graph/planner/CMakeLists.txt b/src/graph/planner/CMakeLists.txt index a83a22c084a..e8b0340130d 100644 --- a/src/graph/planner/CMakeLists.txt +++ b/src/graph/planner/CMakeLists.txt @@ -25,6 +25,7 @@ nebula_add_library( match/ScanSeek.cpp match/ArgumentFinder.cpp match/MatchPathPlanner.cpp + match/ShortestPathPlanner.cpp ngql/PathPlanner.cpp ngql/GoPlanner.cpp ngql/SubgraphPlanner.cpp diff --git a/src/graph/planner/match/MatchClausePlanner.cpp b/src/graph/planner/match/MatchClausePlanner.cpp index f1b8b9c5bc4..544ee69e1f1 100644 --- a/src/graph/planner/match/MatchClausePlanner.cpp +++ b/src/graph/planner/match/MatchClausePlanner.cpp @@ -5,19 +5,11 @@ #include "graph/planner/match/MatchClausePlanner.h" -#include "MatchPathPlanner.h" #include "graph/context/ast/CypherAstContext.h" #include "graph/planner/match/MatchPathPlanner.h" -#include "graph/planner/match/MatchSolver.h" #include "graph/planner/match/SegmentsConnector.h" -#include "graph/planner/match/StartVidFinder.h" -#include "graph/planner/match/WhereClausePlanner.h" -#include "graph/planner/plan/Algo.h" -#include "graph/planner/plan/Logic.h" +#include "graph/planner/match/ShortestPathPlanner.h" #include "graph/planner/plan/Query.h" -#include "graph/util/ExpressionUtils.h" -#include "graph/util/SchemaUtil.h" -#include "graph/visitor/RewriteVisitor.h" namespace nebula { namespace graph { @@ -35,34 +27,34 @@ StatusOr MatchClausePlanner::transform(CypherClauseContextBase* clauseC auto& pathInfos = matchClauseCtx->paths; for (auto iter = pathInfos.begin(); iter < pathInfos.end(); ++iter) { auto& nodeInfos = iter->nodeInfos; - MatchPathPlanner matchPathPlanner; - auto result = matchPathPlanner.transform(matchClauseCtx->qctx, - matchClauseCtx->space.id, - matchClauseCtx->where.get(), - matchClauseCtx->aliasesAvailable, - nodeAliasesSeen, - *iter); - NG_RETURN_IF_ERROR(result); - NG_RETURN_IF_ERROR(connectPathPlan( - nodeInfos, matchClauseCtx, std::move(result).value(), nodeAliasesSeen, matchClausePlan)); + SubPlan pathPlan; + if (iter->pathType == Path::PathType::kDefault) { + MatchPathPlanner matchPathPlanner; + auto result = matchPathPlanner.transform(matchClauseCtx->qctx, + matchClauseCtx->space.id, + matchClauseCtx->where.get(), + matchClauseCtx->aliasesAvailable, + nodeAliasesSeen, + *iter); + NG_RETURN_IF_ERROR(result); + pathPlan = std::move(result).value(); + } else { + ShortestPathPlanner shortestPathPlanner; + auto result = shortestPathPlanner.transform(matchClauseCtx->qctx, + matchClauseCtx->space.id, + matchClauseCtx->where.get(), + matchClauseCtx->aliasesAvailable, + nodeAliasesSeen, + *iter); + NG_RETURN_IF_ERROR(result); + pathPlan = std::move(result).value(); + } + NG_RETURN_IF_ERROR( + connectPathPlan(nodeInfos, matchClauseCtx, pathPlan, nodeAliasesSeen, matchClausePlan)); } return matchClausePlan; } -Status MatchClausePlanner::appendFilterPlan(MatchClauseContext* matchClauseCtx, SubPlan& subplan) { - if (matchClauseCtx->where == nullptr) { - return Status::OK(); - } - - matchClauseCtx->where->inputColNames = subplan.root->colNames(); - auto wherePlan = std::make_unique()->transform(matchClauseCtx->where.get()); - NG_RETURN_IF_ERROR(wherePlan); - auto plan = std::move(wherePlan).value(); - subplan = SegmentsConnector::addInput(plan, subplan, true); - VLOG(1) << subplan; - return Status::OK(); -} - Status MatchClausePlanner::connectPathPlan(const std::vector& nodeInfos, MatchClauseContext* matchClauseCtx, const SubPlan& subplan, diff --git a/src/graph/planner/match/MatchClausePlanner.h b/src/graph/planner/match/MatchClausePlanner.h index e6066be0b74..af295d24292 100644 --- a/src/graph/planner/match/MatchClausePlanner.h +++ b/src/graph/planner/match/MatchClausePlanner.h @@ -18,18 +18,11 @@ class MatchClausePlanner final : public CypherClausePlanner { StatusOr transform(CypherClauseContextBase* clauseCtx) override; private: - PlanNode* joinLeftAndRightExpandPart(QueryContext* qctx, PlanNode* left, PlanNode* right); - - Status appendFilterPlan(MatchClauseContext* matchClauseCtx, SubPlan& subplan); - Status connectPathPlan(const std::vector& nodeInfos, MatchClauseContext* matchClauseCtx, const SubPlan& subplan, std::unordered_set& nodeAliasesSeen, SubPlan& matchClausePlan); - - private: - Expression* initialExpr_{nullptr}; }; } // namespace graph } // namespace nebula diff --git a/src/graph/planner/match/MatchPathPlanner.cpp b/src/graph/planner/match/MatchPathPlanner.cpp index ecca87f9448..7751ad6e7f4 100644 --- a/src/graph/planner/match/MatchPathPlanner.cpp +++ b/src/graph/planner/match/MatchPathPlanner.cpp @@ -56,59 +56,6 @@ static Expression* genEdgeFilter(const EdgeInfo& edge) { return edge.filter; } -static StatusOr>> genVertexProps(const NodeInfo& node, - QueryContext* qctx, - GraphSpaceID spaceId) { - // TODO - UNUSED(node); - return SchemaUtil::getAllVertexProp(qctx, spaceId, true); -} - -static std::unique_ptr> genEdgeProps(const EdgeInfo& edge, - bool reversely, - QueryContext* qctx, - GraphSpaceID spaceId) { - auto edgeProps = std::make_unique>(); - for (auto edgeType : edge.edgeTypes) { - auto edgeSchema = qctx->schemaMng()->getEdgeSchema(spaceId, edgeType); - - switch (edge.direction) { - case Direction::OUT_EDGE: { - if (reversely) { - edgeType = -edgeType; - } - break; - } - case Direction::IN_EDGE: { - if (!reversely) { - edgeType = -edgeType; - } - break; - } - case Direction::BOTH: { - EdgeProp edgeProp; - edgeProp.type_ref() = -edgeType; - std::vector props{kSrc, kType, kRank, kDst}; - for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { - props.emplace_back(edgeSchema->getFieldName(i)); - } - edgeProp.props_ref() = std::move(props); - edgeProps->emplace_back(std::move(edgeProp)); - break; - } - } - EdgeProp edgeProp; - edgeProp.type_ref() = edgeType; - std::vector props{kSrc, kType, kRank, kDst}; - for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { - props.emplace_back(edgeSchema->getFieldName(i)); - } - edgeProp.props_ref() = std::move(props); - edgeProps->emplace_back(std::move(edgeProp)); - } - return edgeProps; -} - static Expression* nodeId(ObjectPool* pool, const NodeInfo& node) { return AttributeExpression::make( pool, InputPropertyExpression::make(pool, node.alias), ConstantExpression::make(pool, kVid)); @@ -149,7 +96,8 @@ StatusOr MatchPathPlanner::transform( startIndex, subplan, nodeAliasesSeenInPattern)); - NG_RETURN_IF_ERROR(projectColumnsBySymbols(qctx, path, subplan)); + + MatchSolver::buildProjectColumns(qctx, path, subplan); return subplan; } @@ -314,10 +262,10 @@ Status MatchPathPlanner::leftExpandFromNode( auto& edge = edgeInfos[i - 1]; auto traverse = Traverse::make(qctx, subplan.root, spaceId); traverse->setSrc(nextTraverseStart); - auto vertexProps = genVertexProps(node, qctx, spaceId); + auto vertexProps = SchemaUtil::getAllVertexProp(qctx, spaceId, true); NG_RETURN_IF_ERROR(vertexProps); traverse->setVertexProps(std::move(vertexProps).value()); - traverse->setEdgeProps(genEdgeProps(edge, reversely, qctx, spaceId)); + traverse->setEdgeProps(SchemaUtil::getEdgeProps(edge, reversely, qctx, spaceId)); traverse->setVertexFilter(genVertexFilter(node)); traverse->setEdgeFilter(genEdgeFilter(edge)); traverse->setEdgeDirection(edge.direction); @@ -345,7 +293,6 @@ Status MatchPathPlanner::leftExpandFromNode( } } - VLOG(1) << subplan; auto& node = nodeInfos.front(); if (!node.anonymous) { nodeAliasesSeenInPattern.emplace(node.alias); @@ -361,7 +308,6 @@ Status MatchPathPlanner::leftExpandFromNode( appendV->setColNames(genAppendVColNames(subplan.root->colNames(), node, !edgeInfos.empty())); subplan.root = appendV; - VLOG(1) << subplan; return Status::OK(); } @@ -386,10 +332,10 @@ Status MatchPathPlanner::rightExpandFromNode( auto& edge = edgeInfos[i]; auto traverse = Traverse::make(qctx, subplan.root, spaceId); traverse->setSrc(nextTraverseStart); - auto vertexProps = genVertexProps(node, qctx, spaceId); + auto vertexProps = SchemaUtil::getAllVertexProp(qctx, spaceId, true); NG_RETURN_IF_ERROR(vertexProps); traverse->setVertexProps(std::move(vertexProps).value()); - traverse->setEdgeProps(genEdgeProps(edge, reversely, qctx, spaceId)); + traverse->setEdgeProps(SchemaUtil::getEdgeProps(edge, reversely, qctx, spaceId)); traverse->setVertexFilter(genVertexFilter(node)); traverse->setEdgeFilter(genEdgeFilter(edge)); traverse->setEdgeDirection(edge.direction); @@ -410,7 +356,6 @@ Status MatchPathPlanner::rightExpandFromNode( } } - VLOG(1) << subplan; auto& node = nodeInfos.back(); if (!node.anonymous) { nodeAliasesSeenInPattern.emplace(node.alias); @@ -426,7 +371,6 @@ Status MatchPathPlanner::rightExpandFromNode( appendV->setColNames(genAppendVColNames(subplan.root->colNames(), node, !edgeInfos.empty())); subplan.root = appendV; - VLOG(1) << subplan; return Status::OK(); } @@ -441,72 +385,5 @@ Status MatchPathPlanner::expandFromEdge(const std::vector& nodeInfos, nodeInfos, edgeInfos, qctx, spaceId, startIndex, subplan, nodeAliasesSeenInPattern); } -Status MatchPathPlanner::projectColumnsBySymbols(QueryContext* qctx, Path& path, SubPlan& plan) { - auto columns = qctx->objPool()->makeAndAdd(); - std::vector colNames; - auto& nodeInfos = path.nodeInfos; - auto& edgeInfos = path.edgeInfos; - - auto addNode = [this, columns, &colNames, qctx](auto& nodeInfo) { - if (!nodeInfo.alias.empty() && !nodeInfo.anonymous) { - columns->addColumn(buildVertexColumn(qctx->objPool(), nodeInfo.alias)); - colNames.emplace_back(nodeInfo.alias); - } - }; - - auto addEdge = [this, columns, &colNames, qctx](auto& edgeInfo) { - if (!edgeInfo.alias.empty() && !edgeInfo.anonymous) { - columns->addColumn(buildEdgeColumn(qctx->objPool(), edgeInfo)); - colNames.emplace_back(edgeInfo.alias); - } - }; - - for (size_t i = 0; i < edgeInfos.size(); i++) { - addNode(nodeInfos[i]); - addEdge(edgeInfos[i]); - } - - // last vertex - DCHECK(!nodeInfos.empty()); - addNode(nodeInfos.back()); - - if (!path.anonymous) { - DCHECK(!path.alias.empty()); - columns->addColumn(buildPathColumn(DCHECK_NOTNULL(path.pathBuild), path.alias)); - colNames.emplace_back(path.alias); - } - - auto project = Project::make(qctx, plan.root, columns); - project->setColNames(std::move(colNames)); - - plan.root = project; - VLOG(1) << plan; - return Status::OK(); -} - -YieldColumn* MatchPathPlanner::buildVertexColumn(ObjectPool* pool, const std::string& alias) const { - return new YieldColumn(InputPropertyExpression::make(pool, alias), alias); -} - -YieldColumn* MatchPathPlanner::buildEdgeColumn(ObjectPool* pool, EdgeInfo& edge) const { - Expression* expr = nullptr; - if (edge.range == nullptr) { - expr = SubscriptExpression::make( - pool, InputPropertyExpression::make(pool, edge.alias), ConstantExpression::make(pool, 0)); - } else { - auto* args = ArgumentList::make(pool); - args->addArgument(VariableExpression::make(pool, "e")); - auto* filter = FunctionCallExpression::make(pool, "is_edge", args); - expr = ListComprehensionExpression::make( - pool, "e", InputPropertyExpression::make(pool, edge.alias), filter); - } - return new YieldColumn(expr, edge.alias); -} - -YieldColumn* MatchPathPlanner::buildPathColumn(Expression* pathBuild, - const std::string& alias) const { - return new YieldColumn(pathBuild, alias); -} - } // namespace graph } // namespace nebula diff --git a/src/graph/planner/match/MatchPathPlanner.h b/src/graph/planner/match/MatchPathPlanner.h index 51bcf9efe2c..7a812f8b6e5 100644 --- a/src/graph/planner/match/MatchPathPlanner.h +++ b/src/graph/planner/match/MatchPathPlanner.h @@ -49,8 +49,6 @@ class MatchPathPlanner final { SubPlan& subplan, std::unordered_set& nodeAliasesSeenInPattern); - PlanNode* joinLeftAndRightExpandPart(QueryContext* qctx, PlanNode* left, PlanNode* right); - Status leftExpandFromNode(const std::vector& nodeInfos, const std::vector& edgeInfos, QueryContext* qctx, @@ -76,16 +74,6 @@ class MatchPathPlanner final { SubPlan& subplan, std::unordered_set& nodeAliasesSeenInPattern); - // Project all named alias. - // TODO: Might not neccessary - Status projectColumnsBySymbols(QueryContext* qctx, Path& path, SubPlan& plan); - - YieldColumn* buildVertexColumn(ObjectPool* pool, const std::string& alias) const; - - YieldColumn* buildEdgeColumn(ObjectPool* pool, EdgeInfo& edge) const; - - YieldColumn* buildPathColumn(Expression* pathBuild, const std::string& alias) const; - private: Expression* initialExpr_{nullptr}; }; diff --git a/src/graph/planner/match/MatchSolver.cpp b/src/graph/planner/match/MatchSolver.cpp index 5630679e4b0..16d33439df5 100644 --- a/src/graph/planner/match/MatchSolver.cpp +++ b/src/graph/planner/match/MatchSolver.cpp @@ -321,5 +321,66 @@ Status MatchSolver::appendFetchVertexPlan(const Expression* nodeFilter, return Status::OK(); } +static YieldColumn* buildVertexColumn(ObjectPool* pool, const std::string& alias) { + return new YieldColumn(InputPropertyExpression::make(pool, alias), alias); +} + +static YieldColumn* buildEdgeColumn(ObjectPool* pool, const EdgeInfo& edge) { + Expression* expr = nullptr; + if (edge.range == nullptr) { + expr = SubscriptExpression::make( + pool, InputPropertyExpression::make(pool, edge.alias), ConstantExpression::make(pool, 0)); + } else { + auto* args = ArgumentList::make(pool); + args->addArgument(VariableExpression::make(pool, "e")); + auto* filter = FunctionCallExpression::make(pool, "is_edge", args); + expr = ListComprehensionExpression::make( + pool, "e", InputPropertyExpression::make(pool, edge.alias), filter); + } + return new YieldColumn(expr, edge.alias); +} + +// static +void MatchSolver::buildProjectColumns(QueryContext* qctx, Path& path, SubPlan& plan) { + auto columns = qctx->objPool()->makeAndAdd(); + std::vector colNames; + auto& nodeInfos = path.nodeInfos; + auto& edgeInfos = path.edgeInfos; + + auto addNode = [columns, &colNames, qctx](auto& nodeInfo) { + if (!nodeInfo.alias.empty() && !nodeInfo.anonymous) { + columns->addColumn(buildVertexColumn(qctx->objPool(), nodeInfo.alias)); + colNames.emplace_back(nodeInfo.alias); + } + }; + + auto addEdge = [columns, &colNames, qctx](auto& edgeInfo) { + if (!edgeInfo.alias.empty() && !edgeInfo.anonymous) { + columns->addColumn(buildEdgeColumn(qctx->objPool(), edgeInfo)); + colNames.emplace_back(edgeInfo.alias); + } + }; + + for (size_t i = 0; i < edgeInfos.size(); i++) { + addNode(nodeInfos[i]); + addEdge(edgeInfos[i]); + } + + // last vertex + DCHECK(!nodeInfos.empty()); + addNode(nodeInfos.back()); + + if (!path.anonymous) { + DCHECK(!path.alias.empty()); + columns->addColumn(new YieldColumn(DCHECK_NOTNULL(path.pathBuild), path.alias)); + colNames.emplace_back(path.alias); + } + + auto project = Project::make(qctx, plan.root, columns); + project->setColNames(std::move(colNames)); + + plan.root = project; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/planner/match/MatchSolver.h b/src/graph/planner/match/MatchSolver.h index 502a9114f88..4db5cf86b23 100644 --- a/src/graph/planner/match/MatchSolver.h +++ b/src/graph/planner/match/MatchSolver.h @@ -76,6 +76,9 @@ class MatchSolver final { Expression** initialExpr, std::string inputVar, SubPlan& plan); + + // Build yield columns for match & shortestPath statement + static void buildProjectColumns(QueryContext* qctx, Path& path, SubPlan& plan); }; } // namespace graph diff --git a/src/graph/planner/match/ShortestPathPlanner.cpp b/src/graph/planner/match/ShortestPathPlanner.cpp new file mode 100644 index 00000000000..5eef293a7ca --- /dev/null +++ b/src/graph/planner/match/ShortestPathPlanner.cpp @@ -0,0 +1,123 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#include "graph/planner/match/ShortestPathPlanner.h" + +#include "graph/planner/match/MatchSolver.h" +#include "graph/planner/match/SegmentsConnector.h" +#include "graph/planner/match/StartVidFinder.h" +#include "graph/planner/plan/Algo.h" +#include "graph/planner/plan/Logic.h" +#include "graph/planner/plan/Query.h" +#include "graph/util/SchemaUtil.h" + +namespace nebula { +namespace graph { +// The plan looks like this: +// +--------+---------+ +---------+--------+ +// | Start | | Start | +// +--------+---------+ +---------+--------+ +// | | +// +--------+---------+ +---------+--------+ +// |indexScan/Argument| |indexScan/Argument| +// +--------+---------+ +---------+--------+ +// | | +// +--------+---------+ +---------+--------+ +// | Project | | Project | +// +--------+---------+ +---------+--------+ +// | | +// +------------+---------------+ +// | +// +--------+---------+ +// |BiCartesianProduct| +// +--------+---------+ +// | +// +--------+---------+ +// | ShortestPath | +// +--------+---------+ +// | +// +--------+---------+ +// | Project | +// +--------+---------+ + +StatusOr ShortestPathPlanner::transform( + QueryContext* qctx, + GraphSpaceID spaceId, + WhereClauseContext* bindWhereClause, + const std::unordered_map& aliasesAvailable, + std::unordered_set nodeAliasesSeen, + Path& path) { + std::unordered_set allNodeAliasesAvailable; + allNodeAliasesAvailable.merge(nodeAliasesSeen); + std::for_each( + aliasesAvailable.begin(), aliasesAvailable.end(), [&allNodeAliasesAvailable](auto& kv) { + if (kv.second == AliasType::kNode) { + allNodeAliasesAvailable.emplace(kv.first); + } + }); + + SubPlan subplan; + bool singleShortest = path.pathType == Path::PathType::kSingleShortest; + auto& nodeInfos = path.nodeInfos; + auto& edge = path.edgeInfos.front(); + std::vector colNames; + colNames.emplace_back(nodeInfos.front().alias); + colNames.emplace_back(edge.alias); + colNames.emplace_back(nodeInfos.back().alias); + + auto& startVidFinders = StartVidFinder::finders(); + std::vector plans; + + for (auto& nodeInfo : nodeInfos) { + bool foundIndex = false; + for (auto& finder : startVidFinders) { + auto nodeCtx = NodeContext(qctx, bindWhereClause, spaceId, &nodeInfo); + nodeCtx.nodeAliasesAvailable = &allNodeAliasesAvailable; + auto nodeFinder = finder(); + if (nodeFinder->match(&nodeCtx)) { + auto status = nodeFinder->transform(&nodeCtx); + NG_RETURN_IF_ERROR(status); + auto plan = status.value(); + auto start = StartNode::make(qctx); + plan.tail->setDep(0, start); + plan.tail = start; + + auto initExpr = nodeCtx.initialExpr->clone(); + auto columns = qctx->objPool()->makeAndAdd(); + columns->addColumn(new YieldColumn(initExpr, nodeInfo.alias)); + plan.root = Project::make(qctx, plan.root, columns); + + plans.emplace_back(std::move(plan)); + foundIndex = true; + break; + } + } + if (!foundIndex) { + return Status::SemanticError("Can't find index from shortestPath pattern"); + } + } + auto& leftPlan = plans.front(); + auto& rightPlan = plans.back(); + + auto cp = BiCartesianProduct::make(qctx, leftPlan.root, rightPlan.root); + + auto shortestPath = ShortestPath::make(qctx, cp, spaceId, singleShortest); + auto vertexProp = SchemaUtil::getAllVertexProp(qctx, spaceId, true); + NG_RETURN_IF_ERROR(vertexProp); + shortestPath->setVertexProps(std::move(vertexProp).value()); + shortestPath->setEdgeProps(SchemaUtil::getEdgeProps(edge, false, qctx, spaceId)); + shortestPath->setReverseEdgeProps(SchemaUtil::getEdgeProps(edge, true, qctx, spaceId)); + shortestPath->setEdgeDirection(edge.direction); + shortestPath->setStepRange(edge.range); + shortestPath->setColNames(std::move(colNames)); + + subplan.root = shortestPath; + subplan.tail = leftPlan.tail; + + MatchSolver::buildProjectColumns(qctx, path, subplan); + return subplan; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/planner/match/ShortestPathPlanner.h b/src/graph/planner/match/ShortestPathPlanner.h new file mode 100644 index 00000000000..718bbb12781 --- /dev/null +++ b/src/graph/planner/match/ShortestPathPlanner.h @@ -0,0 +1,26 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#ifndef GRAPH_PLANNER_MATCH_SHORTESTPATHPLANNER_H_ +#define GRAPH_PLANNER_MATCH_SHORTESTPATHPLANNER_H_ + +#include "graph/context/ast/CypherAstContext.h" +#include "graph/planner/Planner.h" + +namespace nebula { +namespace graph { +class ShortestPathPlanner final { + public: + ShortestPathPlanner() = default; + + StatusOr transform(QueryContext* qctx, + GraphSpaceID spaceId, + WhereClauseContext* bindWhereClause, + const std::unordered_map& aliasesAvailable, + std::unordered_set nodeAliasesSeen, + Path& path); +}; +} // namespace graph +} // namespace nebula +#endif // GRAPH_PLANNER_MATCH_SHORTESTPATHPLANNER_H_ diff --git a/src/graph/planner/plan/Algo.cpp b/src/graph/planner/plan/Algo.cpp index f6625c68fe3..14aafa974e3 100644 --- a/src/graph/planner/plan/Algo.cpp +++ b/src/graph/planner/plan/Algo.cpp @@ -34,6 +34,45 @@ std::unique_ptr ProduceAllPaths::explain() const { return desc; } +std::unique_ptr ShortestPath::explain() const { + auto desc = SingleInputNode::explain(); + addDescription("singleShortest", folly::toJson(util::toJson(singleShortest_)), desc.get()); + addDescription("steps", range_ != nullptr ? range_->toString() : "", desc.get()); + addDescription("edgeDirection", apache::thrift::util::enumNameSafe(edgeDirection_), desc.get()); + addDescription( + "vertexProps", vertexProps_ ? folly::toJson(util::toJson(*vertexProps_)) : "", desc.get()); + addDescription( + "edgeProps", edgeProps_ ? folly::toJson(util::toJson(*edgeProps_)) : "", desc.get()); + return desc; +} + +PlanNode* ShortestPath::clone() const { + auto* path = ShortestPath::make(qctx_, nullptr, space_, singleShortest_); + path->cloneMembers(*this); + return path; +} + +void ShortestPath::cloneMembers(const ShortestPath& path) { + SingleInputNode::cloneMembers(path); + setStepRange(path.range_); + setEdgeDirection(path.edgeDirection_); + if (path.vertexProps_) { + auto vertexProps = *path.vertexProps_; + auto vertexPropsPtr = std::make_unique(vertexProps); + setVertexProps(std::move(vertexPropsPtr)); + } + if (path.edgeProps_) { + auto edgeProps = *path.edgeProps_; + auto edgePropsPtr = std::make_unique(std::move(edgeProps)); + setEdgeProps(std::move(edgePropsPtr)); + } + if (path.reverseEdgeProps_) { + auto edgeProps = *path.reverseEdgeProps_; + auto edgePropsPtr = std::make_unique(std::move(edgeProps)); + setEdgeProps(std::move(edgePropsPtr)); + } +} + std::unique_ptr CartesianProduct::explain() const { auto desc = SingleDependencyNode::explain(); for (size_t i = 0; i < inputVars_.size(); ++i) { diff --git a/src/graph/planner/plan/Algo.h b/src/graph/planner/plan/Algo.h index 242195f3889..6b00d6f3e0e 100644 --- a/src/graph/planner/plan/Algo.h +++ b/src/graph/planner/plan/Algo.h @@ -145,6 +145,89 @@ class ProduceAllPaths final : public BinaryInputNode { std::string rightVidVar_; }; +using VertexProp = nebula::storage::cpp2::VertexProp; +using EdgeProp = nebula::storage::cpp2::EdgeProp; +using Direction = nebula::storage::cpp2::EdgeDirection; + +class ShortestPath final : public SingleInputNode { + public: + static ShortestPath* make(QueryContext* qctx, + PlanNode* node, + GraphSpaceID space, + bool singleShortest) { + return qctx->objPool()->add(new ShortestPath(qctx, node, space, singleShortest)); + } + + PlanNode* clone() const override; + + std::unique_ptr explain() const override; + + MatchStepRange* stepRange() const { + return range_; + } + + storage::cpp2::EdgeDirection edgeDirection() const { + return edgeDirection_; + } + + const std::vector* edgeProps() const { + return edgeProps_.get(); + } + + const std::vector* reverseEdgeProps() const { + return reverseEdgeProps_.get(); + } + + const std::vector* vertexProps() const { + return vertexProps_.get(); + } + + GraphSpaceID space() const { + return space_; + } + + bool singleShortest() const { + return singleShortest_; + } + + void setStepRange(MatchStepRange* range) { + range_ = range; + } + + void setEdgeDirection(Direction direction) { + edgeDirection_ = direction; + } + + void setVertexProps(std::unique_ptr> vertexProps) { + vertexProps_ = std::move(vertexProps); + } + + void setEdgeProps(std::unique_ptr> edgeProps) { + edgeProps_ = std::move(edgeProps); + } + + void setReverseEdgeProps(std::unique_ptr> reverseEdgeProps) { + reverseEdgeProps_ = std::move(reverseEdgeProps); + } + + private: + ShortestPath(QueryContext* qctx, PlanNode* node, GraphSpaceID space, bool singleShortest) + : SingleInputNode(qctx, Kind::kShortestPath, node), + space_(space), + singleShortest_(singleShortest) {} + + void cloneMembers(const ShortestPath&); + + private: + GraphSpaceID space_; + bool singleShortest_{false}; + MatchStepRange* range_{nullptr}; + std::unique_ptr> edgeProps_; + std::unique_ptr> reverseEdgeProps_; + std::unique_ptr> vertexProps_; + storage::cpp2::EdgeDirection edgeDirection_{Direction::OUT_EDGE}; +}; + class CartesianProduct final : public SingleDependencyNode { public: static CartesianProduct* make(QueryContext* qctx, PlanNode* input) { diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index fb95a041055..f2484b79f52 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -293,6 +293,8 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "BiInnerJoin"; case Kind::kBiCartesianProduct: return "BiCartesianProduct"; + case Kind::kShortestPath: + return "ShortestPath"; case Kind::kArgument: return "Argument"; case Kind::kRollUpApply: diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index 35ecbb497eb..f68469976ff 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -29,6 +29,7 @@ class PlanNode { kGetEdges, kTraverse, kAppendVertices, + kShortestPath, // ------------------ // TODO(yee): refactor in logical plan diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index c601fdef1d2..b3b6403a086 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -687,9 +687,9 @@ void InnerJoin::cloneMembers(const InnerJoin& l) { std::unique_ptr Assign::explain() const { auto desc = SingleDependencyNode::explain(); - for (size_t i = 0; i < items_.size(); ++i) { - addDescription("varName", items_[i].first, desc.get()); - addDescription("value", items_[i].second->toString(), desc.get()); + for (const auto& item : items_) { + addDescription("varName", item.first, desc.get()); + addDescription("value", item.second->toString(), desc.get()); } return desc; } diff --git a/src/graph/util/PlannerUtil.cpp b/src/graph/util/PlannerUtil.cpp index 6102cc17924..45b2e8a1fdf 100644 --- a/src/graph/util/PlannerUtil.cpp +++ b/src/graph/util/PlannerUtil.cpp @@ -12,7 +12,6 @@ namespace nebula { namespace graph { - // static void PlannerUtil::buildConstantInput(QueryContext* qctx, Starts& starts, std::string& vidsVar) { vidsVar = qctx->vctx()->anonVarGen()->getVar(); diff --git a/src/graph/util/SchemaUtil.cpp b/src/graph/util/SchemaUtil.cpp index 4a6217e26d4..7180e2e2dda 100644 --- a/src/graph/util/SchemaUtil.cpp +++ b/src/graph/util/SchemaUtil.cpp @@ -10,6 +10,7 @@ #include "common/base/Status.h" #include "graph/context/QueryContext.h" #include "graph/context/QueryExpressionContext.h" +#include "graph/context/ast/CypherAstContext.h" namespace nebula { namespace graph { @@ -378,5 +379,49 @@ StatusOr>> SchemaUtil::getE } return edgeProps; } + +std::unique_ptr> SchemaUtil::getEdgeProps( + const EdgeInfo &edge, bool reversely, QueryContext *qctx, GraphSpaceID spaceId) { + auto edgeProps = std::make_unique>(); + for (auto edgeType : edge.edgeTypes) { + auto edgeSchema = qctx->schemaMng()->getEdgeSchema(spaceId, edgeType); + + switch (edge.direction) { + case Direction::OUT_EDGE: { + if (reversely) { + edgeType = -edgeType; + } + break; + } + case Direction::IN_EDGE: { + if (!reversely) { + edgeType = -edgeType; + } + break; + } + case Direction::BOTH: { + EdgeProp edgeProp; + edgeProp.type_ref() = -edgeType; + std::vector props{kSrc, kType, kRank, kDst}; + for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { + props.emplace_back(edgeSchema->getFieldName(i)); + } + edgeProp.props_ref() = std::move(props); + edgeProps->emplace_back(std::move(edgeProp)); + break; + } + } + EdgeProp edgeProp; + edgeProp.type_ref() = edgeType; + std::vector props{kSrc, kType, kRank, kDst}; + for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { + props.emplace_back(edgeSchema->getFieldName(i)); + } + edgeProp.props_ref() = std::move(props); + edgeProps->emplace_back(std::move(edgeProp)); + } + return edgeProps; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/util/SchemaUtil.h b/src/graph/util/SchemaUtil.h index 424cca2daec..0c1a5bd56d9 100644 --- a/src/graph/util/SchemaUtil.h +++ b/src/graph/util/SchemaUtil.h @@ -17,6 +17,7 @@ namespace nebula { namespace graph { class QueryContext; struct SpaceInfo; +struct EdgeInfo; class SchemaUtil final { public: using VertexProp = nebula::storage::cpp2::VertexProp; @@ -81,6 +82,12 @@ class SchemaUtil final { const SpaceInfo& space, const std::vector& edgeTypes, bool withProp); + + // Retrieves prop from EdgeInfo. + static std::unique_ptr> getEdgeProps(const EdgeInfo& edge, + bool reversely, + QueryContext* qctx, + GraphSpaceID spaceId); }; } // namespace graph diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index 7ded59e3e0c..8bbea0b1144 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -139,6 +139,23 @@ Status MatchValidator::validatePath(const MatchPath *path, Path &pathInfo) { Status MatchValidator::buildPathExpr(const MatchPath *path, Path &pathInfo, std::unordered_map &aliasesGenerated) { + auto &nodeInfos = pathInfo.nodeInfos; + auto &edgeInfos = pathInfo.edgeInfos; + + auto pathType = path->pathType(); + if (pathType != MatchPath::PathType::kDefault) { + if (nodeInfos.size() != 2 || edgeInfos.size() != 1) { + return Status::SemanticError( + "`shortestPath(...)' only support pattern like (start)-[edge*..hop]-(end)"); + } + auto min = edgeInfos.front().range->min(); + if (min != 0 && min != 1) { + return Status::SemanticError( + "`shortestPath(...)' does not support a minimal length different from 0 or 1"); + } + pathInfo.pathType = static_cast(pathType); + } + auto *pathAlias = path->alias(); if (pathAlias == nullptr) { return Status::OK(); @@ -146,10 +163,6 @@ Status MatchValidator::buildPathExpr(const MatchPath *path, if (!aliasesGenerated.emplace(*pathAlias, AliasType::kPath).second) { return Status::SemanticError("`%s': Redefined alias", pathAlias->c_str()); } - - auto &nodeInfos = pathInfo.nodeInfos; - auto &edgeInfos = pathInfo.edgeInfos; - auto *pool = qctx_->objPool(); auto pathBuild = PathBuildExpression::make(pool); for (size_t i = 0; i < edgeInfos.size(); ++i) { @@ -557,8 +570,8 @@ Status MatchValidator::validateWith(const WithClause *with, } // Check validity of unwind clause. -// Check column must has alias, check can't contains aggregate expression, check alias in expression -// must be available, check defined alias don't defined before. +// Check column must has alias, check can't contains aggregate expression, check alias in +// expression must be available, check defined alias don't defined before. Status MatchValidator::validateUnwind(const UnwindClause *unwindClause, UnwindClauseContext &unwindCtx) { if (unwindClause->alias().empty()) { @@ -625,8 +638,8 @@ StatusOr MatchValidator::makeEdgeSubFilter(MapExpression *map) con return root; } -// Convert map attributes of vertex defined in pattern to filter expression and keep origin -// semantic. e.g. convert match (v{a:1, b:2}) to v.a == 1 && v.b == 2 +// Convert map attributes of vertex defined in pattern to filter expression and keep origin semantic +// e.g. convert match (v{a:1, b:2}) to v.a == 1 && v.b == 2 StatusOr MatchValidator::makeNodeSubFilter(MapExpression *map, const std::string &label) const { auto *pool = qctx_->objPool(); diff --git a/src/parser/MatchPath.h b/src/parser/MatchPath.h index 01d05cd2340..5908da54b05 100644 --- a/src/parser/MatchPath.h +++ b/src/parser/MatchPath.h @@ -301,6 +301,16 @@ class MatchPath final { return edges_[i].get(); } + enum PathType : int8_t { kDefault, kAllShortest, kSingleShortest }; + + PathType pathType() const { + return pathType_; + } + + void setPathType(PathType type) { + pathType_ = type; + } + std::string toString() const; MatchPath clone() const { @@ -318,6 +328,7 @@ class MatchPath final { std::unique_ptr alias_; std::vector> nodes_; std::vector> edges_; + PathType pathType_{PathType::kDefault}; }; } // namespace nebula diff --git a/src/parser/parser.yy b/src/parser/parser.yy index a92e4c0f1d3..05e96dfa713 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -189,7 +189,7 @@ using namespace nebula; %token KW_ORDER KW_ASC KW_LIMIT KW_SAMPLE KW_OFFSET KW_ASCENDING KW_DESCENDING %token KW_DISTINCT KW_ALL KW_OF %token KW_BALANCE KW_LEADER KW_RESET KW_PLAN -%token KW_SHORTEST KW_PATH KW_NOLOOP +%token KW_SHORTEST KW_PATH KW_NOLOOP KW_SHORTESTPATH KW_ALLSHORTESTPATHS %token KW_IS KW_NULL KW_DEFAULT %token KW_SNAPSHOT KW_SNAPSHOTS KW_LOOKUP %token KW_JOBS KW_JOB KW_RECOVER KW_FLUSH KW_COMPACT KW_REBUILD KW_SUBMIT KW_STATS KW_STATUS @@ -517,6 +517,8 @@ unreserved_keyword | KW_NONE { $$ = new std::string("none"); } | KW_REDUCE { $$ = new std::string("reduce"); } | KW_SHORTEST { $$ = new std::string("shortest"); } + | KW_SHORTESTPATH { $$ = new std::string("shortestpath"); } + | KW_ALLSHORTESTPATHS { $$ = new std::string("allshortestpaths"); } | KW_NOLOOP { $$ = new std::string("noloop"); } | KW_CONTAINS { $$ = new std::string("contains"); } | KW_STARTS { $$ = new std::string("starts"); } @@ -1762,6 +1764,14 @@ match_path_pattern $$ = $1; $$->add($2, $3); } + | KW_SHORTESTPATH L_PAREN match_path_pattern R_PAREN { + $$ = $3; + $$->setPathType(MatchPath::PathType::kSingleShortest); + } + | KW_ALLSHORTESTPATHS L_PAREN match_path_pattern R_PAREN { + $$ = $3; + $$->setPathType(MatchPath::PathType::kAllShortest); + } ; match_path diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index b0e13b4b978..ddcbadf0e40 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -244,6 +244,8 @@ LABEL_FULL_WIDTH {CN_EN_FULL_WIDTH}{CN_EN_NUM_FULL_WIDTH}* "STORAGE" { return TokenType::KW_STORAGE; } "SHORTEST" { return TokenType::KW_SHORTEST; } "NOLOOP" { return TokenType::KW_NOLOOP; } +"SHORTESTPATH" { return TokenType::KW_SHORTESTPATH; } +"AllSHORTESTPATHS" { return TokenType::KW_ALLSHORTESTPATHS; } "OUT" { return TokenType::KW_OUT; } "BOTH" { return TokenType::KW_BOTH; } "SUBGRAPH" { return TokenType::KW_SUBGRAPH; } diff --git a/tests/tck/features/match/AllShortestPaths.feature b/tests/tck/features/match/AllShortestPaths.feature new file mode 100644 index 00000000000..71ea1225b07 --- /dev/null +++ b/tests/tck/features/match/AllShortestPaths.feature @@ -0,0 +1,477 @@ +# Copyright (c) 2022 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: allShortestPaths + + Background: + Given a graph with space named "nba" + + Scenario: allShortestPaths1 + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 95}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN a, e, b + """ + Then the result should be, in any order, with relax comparison: + | a | e | b | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | [[:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]] | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | [[:teammate "Tim Duncan"->"Tony Parker" @0 {end_year: 2016, start_year: 2001}]] | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | [[:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]] | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | [[:like "Tim Duncan"->"Tony Parker" @0 {likeness: 95}]] | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN e, p + """ + Then the result should be, in any order, with relax comparison: + | e | p | + | [[:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]] | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | [[:teammate "Tim Duncan"->"Tony Parker" @0 {end_year: 2016, start_year: 2001}]] | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | [[:like "Tony Parker"->"Tim Duncan" @0 {likeness: 95}]] | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 95}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | [[:like "Tim Duncan"->"Tony Parker" @0 {likeness: 95}]] | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tiago Splitter"})-[e:like*..5]->(b:player{name:"LaMarcus Aldridge"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 90}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tiago Splitter"})-[e*..5]->(b:player{name:"LaMarcus Aldridge"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2015}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tiago Splitter"})-[e*..5]->(b:player{name:"LaMarcus Aldridge"}) ) + WHERE length(p) > 2 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Tiago Splitter"})-[e*..1]->(b:player{name:"Tim Duncan"}) ) RETURN nodes(p), relationships(p) + """ + Then the result should be, in any order, with relax comparison: + | nodes(p) | relationships(p) | + | [("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}), ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})] | [[:like "Tiago Splitter"->"Tim Duncan" @0 {likeness: 80}]] | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{age:30})-[e*..5]->(b:player{name:"LeBron James"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{age:30})-[e*..5]->(b:team) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:like@0 {likeness: 90}]->("James Harden" :player{age: 29, name: "James Harden"})-[:serve@0 {end_year: 2019, start_year: 2012}]->("Rockets" :team{name: "Rockets"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:like@0 {likeness: 90}]->("Paul George" :player{age: 28, name: "Paul George"})-[:serve@0 {end_year: 2017, start_year: 2010}]->("Pacers" :team{name: "Pacers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2014, start_year: 2010}]->("Heat" :team{name: "Heat"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"})-[:serve@0 {end_year: 2016, start_year: 2003}]->("Heat" :team{name: "Heat"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"})-[:serve@1 {end_year: 2019, start_year: 2018}]->("Heat" :team{name: "Heat"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2016, start_year: 2015}]->("Kings" :team{name: "Kings"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Lakers" :team{name: "Lakers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Knicks" :team{name: "Knicks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2011, start_year: 2005}]->("Hornets" :team{name: "Hornets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2015, start_year: 2014}]->("Mavericks" :team{name: "Mavericks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Bulls" :team{name: "Bulls"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:serve@0 {end_year: 2003, start_year: 1996}]->("Bucks" :team{name: "Bucks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2011, start_year: 2003}]->("Nuggets" :team{name: "Nuggets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:serve@0 {end_year: 2012, start_year: 2007}]->("Celtics" :team{name: "Celtics"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Pelicans" :team{name: "Pelicans"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2021, start_year: 2017}]->("Rockets" :team{name: "Rockets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2010, start_year: 2003}]->("Cavaliers" :team{name: "Cavaliers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@1 {end_year: 2018, start_year: 2014}]->("Cavaliers" :team{name: "Cavaliers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Cavaliers" :team{name: "Cavaliers"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{age:30})-[e*..5]->(b:team) ) + WHERE length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player{name:"Yao Ming"})-[e:serve*1..3]-(b:team) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2013, start_year: 2012}]->("Lakers" :team{name: "Lakers"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2018}]-("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Knicks" :team{name: "Knicks"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Hornets" :team{name: "Hornets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2021, start_year: 2017}]-("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2011, start_year: 2005}]->("Hornets" :team{name: "Hornets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2021, start_year: 2017}]-("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Clippers" :team{name: "Clippers"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2018}]-("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2011, start_year: 2003}]->("Nuggets" :team{name: "Nuggets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2012}]-("James Harden" :player{age: 29, name: "James Harden"})-[:serve@0 {end_year: 2012, start_year: 2009}]->("Thunders" :team{name: "Thunders"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2018}]-("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Thunders" :team{name: "Thunders"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2004, start_year: 2000}]->("Magic" :team{name: "Magic"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2012, start_year: 2004}]->("Magic" :team{name: "Magic"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Hawks" :team{name: "Hawks"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2000, start_year: 1997}]->("Raptors" :team{name: "Raptors"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Wizards" :team{name: "Wizards"})> | + + Scenario: allShortestPaths2 + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' and id(b) in ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 95}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 80}]-("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)-[e*..5]->(b) ) + WHERE id(a) == 'Tim Duncan' and id(b) IN ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e*..5]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:teammate@0 {end_year: 2018, start_year: 2002}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:serve@0 {end_year: 2018, start_year: 1999}]->("Spurs" :team{name: "Spurs"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2010}]->("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 80}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Lakers" :team{name: "Lakers"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2010}]->("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 80}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Lakers" :team{name: "Lakers"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2002}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2004, start_year: 1996}]->("Lakers" :team{name: "Lakers"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e:like*..4]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e:like*..4]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['xxx', 'zzz'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + + Scenario: allShortestPaths3 + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team{name:"Spurs"}) + MATCH p = allShortestPaths( (a)-[e:serve*..3]-(b) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team{name:"Spurs"}), p = allShortestPaths( (a)-[e:serve*..3]-(b) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team) + MATCH p = allShortestPaths( (a)-[e:serve*..3]-(b) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2008, start_year: 2001}]->("Grizzlies" :team{name: "Grizzlies"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 2014}]-("Kyle Anderson" :player{age: 25, name: "Kyle Anderson"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Grizzlies" :team{name: "Grizzlies"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:serve@0 {end_year: 2013, start_year: 2006}]->("Grizzlies" :team{name: "Grizzlies"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2014, start_year: 2008}]->("Lakers" :team{name: "Lakers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2016, start_year: 2014}]->("Bulls" :team{name: "Bulls"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2013, start_year: 2012}]->("Bulls" :team{name: "Bulls"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2013, start_year: 2012}]->("Bulls" :team{name: "Bulls"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 1999}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2015}]-("David West" :player{age: 38, name: "David West"})-[:serve@0 {end_year: 2011, start_year: 2003}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2012, start_year: 2008}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2012, start_year: 2010}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@1 {end_year: 2017, start_year: 2016}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2012, start_year: 2010}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@1 {end_year: 2017, start_year: 2016}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:serve@0 {end_year: 2017, start_year: 2013}]->("Kings" :team{name: "Kings"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2016, start_year: 2015}]->("Kings" :team{name: "Kings"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2016, start_year: 2015}]->("Kings" :team{name: "Kings"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Jazz" :team{name: "Jazz"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2008, start_year: 2005}]->("Suns" :team{name: "Suns"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2010}]-("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Hawks" :team{name: "Hawks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2005, start_year: 2003}]->("Hawks" :team{name: "Hawks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Hawks" :team{name: "Hawks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Hawks" :team{name: "Hawks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2015}]-("David West" :player{age: 38, name: "David West"})-[:serve@0 {end_year: 2018, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2009, start_year: 2007}]->("Warriors" :team{name: "Warriors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2009, start_year: 2007}]->("Warriors" :team{name: "Warriors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2020, start_year: 2019}]->("Bucks" :team{name: "Bucks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2010}]-("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:serve@0 {end_year: 2017, start_year: 2017}]->("76ers" :team{name: "76ers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2017, start_year: 2015}]-("Jonathon Simmons" :player{age: 29, name: "Jonathon Simmons"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("76ers" :team{name: "76ers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2018, start_year: 2018}]->("76ers" :team{name: "76ers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2018, start_year: 2018}]->("76ers" :team{name: "76ers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2019, start_year: 2017}]->("Celtics" :team{name: "Celtics"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2015}]-("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:serve@0 {end_year: 2015, start_year: 2006}]->("Trail Blazers" :team{name: "Trail Blazers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2013, start_year: 2013}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2010, start_year: 2004}]->("Rockets" :team{name: "Rockets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2017, start_year: 2015}]-("Jonathon Simmons" :player{age: 29, name: "Jonathon Simmons"})-[:serve@0 {end_year: 2019, start_year: 2017}]->("Magic" :team{name: "Magic"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2013, start_year: 2013}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2004, start_year: 2000}]->("Magic" :team{name: "Magic"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2015}]-("David West" :player{age: 38, name: "David West"})-[:serve@0 {end_year: 2015, start_year: 2011}]->("Pacers" :team{name: "Pacers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2011}]-("Cory Joseph" :player{age: 27, name: "Cory Joseph"})-[:serve@0 {end_year: 2019, start_year: 2017}]->("Pacers" :team{name: "Pacers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Pistons" :team{name: "Pistons"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 2010}]-("Danny Green" :player{age: 31, name: "Danny Green"})-[:serve@0 {end_year: 2010, start_year: 2009}]->("Cavaliers" :team{name: "Cavaliers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 2010}]-("Danny Green" :player{age: 31, name: "Danny Green"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Raptors" :team{name: "Raptors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2011}]-("Cory Joseph" :player{age: 27, name: "Cory Joseph"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Raptors" :team{name: "Raptors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2013, start_year: 2013}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2000, start_year: 1997}]->("Raptors" :team{name: "Raptors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Raptors" :team{name: "Raptors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2010, start_year: 2009}]->("Raptors" :team{name: "Raptors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@1 {end_year: 2019, start_year: 2018}]-("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:serve@0 {end_year: 2010, start_year: 2009}]->("Raptors" :team{name: "Raptors"})> | + When executing query: + """ + MATCH (b:team), (a:player{age:30}) + MATCH p = allShortestPaths( (a)-[e*..5]->(b) ) + WHERE length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH (b:team) + MATCH p = allShortestPaths( (a:player)-[e*..5]->(b) ) + WHERE a.player.age == 30 AND length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH (b:team) + MATCH p = allShortestPaths( (a:player)-[e*..5]->(b) ) + WHERE id(a) IN ['DeAndre Jordan', 'Kevin Durant', 'Russell Westbrook', 'Blake Griffin'] AND length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + + Scenario: allShortestPaths4 + When executing query: + """ + MATCH p = allShortestPaths( (a:player)-[e:serve*..3]-(b:team) ) + WHERE a.player.name == 'Tim Duncan' AND b.team.name == 'Spurs' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player)-[e:serve*..3]-(b:team) ) + WHERE a.player.age > 45 AND b.team.name == 'Spurs' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})-[:serve@0 {end_year: 2007, start_year: 2000}]->("Magic" :team{name: "Magic"})<-[:serve@0 {end_year: 2004, start_year: 2000}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})-[:serve@0 {end_year: 2007, start_year: 2000}]->("Magic" :team{name: "Magic"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Jonathon Simmons" :player{age: 29, name: "Jonathon Simmons"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Spurs" :team{name: "Spurs"})> | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})-[:serve@0 {end_year: 2000, start_year: 1994}]->("Pistons" :team{name: "Pistons"})<-[:serve@0 {end_year: 2017, start_year: 2015}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2015, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})-[:serve@0 {end_year: 2012, start_year: 2007}]->("Suns" :team{name: "Suns"})<-[:serve@0 {end_year: 2008, start_year: 2005}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2016, start_year: 2012}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 1996, start_year: 1992}]->("Magic" :team{name: "Magic"})<-[:serve@0 {end_year: 2004, start_year: 2000}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 1996, start_year: 1992}]->("Magic" :team{name: "Magic"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Jonathon Simmons" :player{age: 29, name: "Jonathon Simmons"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2009, start_year: 2008}]->("Suns" :team{name: "Suns"})<-[:serve@0 {end_year: 2008, start_year: 2005}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2016, start_year: 2012}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2004, start_year: 1996}]->("Lakers" :team{name: "Lakers"})<-[:serve@0 {end_year: 2014, start_year: 2008}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2011, start_year: 2010}]->("Celtics" :team{name: "Celtics"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2015, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2010, start_year: 2009}]->("Cavaliers" :team{name: "Cavaliers"})<-[:serve@0 {end_year: 2010, start_year: 2009}]-("Danny Green" :player{age: 31, name: "Danny Green"})-[:serve@0 {end_year: 2018, start_year: 2010}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH p = allShortestPaths( (a:player)-[e:like*..3]-(b:player) ) + WHERE a.player.age > 45 AND b.player.age < 30 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("James Harden" :player{age: 29, name: "James Harden"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("Kyle Anderson" :player{age: 25, name: "Kyle Anderson"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 75}]-("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})<-[:like@0 {likeness: 80}]-("Damian Lillard" :player{age: 28, name: "Damian Lillard"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})> | + When executing query: + """ + MATCH (a:player) + MATCH p = allShortestPaths( (a)<-[e:like*..2]-(b:player{name:"Yao Ming"}) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 80}]-("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Rudy Gay" :player{age: 32, name: "Rudy Gay"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("JaVale McGee" :player{age: 31, name: "JaVale McGee"})<-[:like@0 {likeness: 100}]-("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + When executing query: + """ + MATCH (a:player) + MATCH p = allShortestPaths( (a)-[e:like*..2]->(b:player{name:"Tony Parker"}) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:like@0 {likeness: 70}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Damian Lillard" :player{age: 28, name: "Damian Lillard"})-[:like@0 {likeness: 80}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:like@0 {likeness: 50}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 70}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 83}]->("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:like@0 {likeness: 50}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:like@0 {likeness: 80}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})-[:like@0 {likeness: 90}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH (a:player) + MATCH p = allShortestPaths( (a)-[e:like*..2]->(b) ) + WHERE id(b) IN ['xxx', 'zzz', 'yyy', 'Tim Duncan', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:like@0 {likeness: 70}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Damian Lillard" :player{age: 28, name: "Damian Lillard"})-[:like@0 {likeness: 80}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:like@0 {likeness: 55}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 70}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})-[:like@0 {likeness: 90}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + + Scenario: allShortestPaths5 + When executing query: + """ + MATCH p= allShortestPaths((a:player {name:"Tim Duncan"})-[*..15]-(b:player {age:33})) + WITH nodes(p) AS pathNodes + UNWIND pathNodes AS node + RETURN DISTINCT node + """ + Then the result should be, in any order, with relax comparison: + | node | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | + | ("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"}) | + | ("Chris Paul" :player{age: 33, name: "Chris Paul"}) | + | ("Dwight Howard" :player{age: 33, name: "Dwight Howard"}) | + | ("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"}) | + | ("Lakers" :team{name: "Lakers"}) | + | ("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"}) | + | ("Hornets" :team{name: "Hornets"}) | + | ("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"}) | + | ("Kings" :team{name: "Kings"}) | + | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + | ("Boris Diaw" :player{age: 36, name: "Boris Diaw"}) | + | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | + | ("Celtics" :team{name: "Celtics"}) | + | ("Hawks" :team{name: "Hawks"}) | + | ("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}) | + | ("Magic" :team{name: "Magic"}) | + | ("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"}) | + | ("Bulls" :team{name: "Bulls"}) | + + Scenario: allShortestPaths Can Not find index + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' OR id(b) in ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. + When executing query: + """ + MATCH p = allShortestPaths( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' + RETURN p + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. diff --git a/tests/tck/features/match/SingleShorestPath.feature b/tests/tck/features/match/SingleShorestPath.feature new file mode 100644 index 00000000000..48939394857 --- /dev/null +++ b/tests/tck/features/match/SingleShorestPath.feature @@ -0,0 +1,392 @@ +# Copyright (c) 2022 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: single shortestPath + + Background: + Given a graph with space named "nba" + + Scenario: single shortestPath1 + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN a, e, b + """ + Then the result should be, in any order, with relax comparison: + | a | e | b | + | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | [[:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]] | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tim Duncan"})-[e*..5]-(b:player{name:"Tony Parker"}) ) RETURN e, p + """ + Then the result should be, in any order, with relax comparison: + | e | p | + | [[:teammate "Tony Parker"->"Tim Duncan" @0 {end_year: 2016, start_year: 2001}]] | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tiago Splitter"})-[e:like*..5]->(b:player{name:"LaMarcus Aldridge"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 90}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tiago Splitter"})-[e*..5]->(b:player{name:"LaMarcus Aldridge"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2015}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tiago Splitter"})-[e*..5]->(b:player{name:"LaMarcus Aldridge"}) ) + WHERE length(p) > 2 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Tiago Splitter"})-[e*..1]->(b:player{name:"Tim Duncan"}) ) RETURN nodes(p), relationships(p) + """ + Then the result should be, in any order, with relax comparison: + | nodes(p) | relationships(p) | + | [("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"}), ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})] | [[:like "Tiago Splitter"->"Tim Duncan" @0 {likeness: 80}]] | + When executing query: + """ + MATCH p = shortestPath( (a:player{age:30})-[e*..5]->(b:player{name:"LeBron James"}) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{age:30})-[e*..5]->(b:team) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:like@0 {likeness: 90}]->("James Harden" :player{age: 29, name: "James Harden"})-[:serve@0 {end_year: 2019, start_year: 2012}]->("Rockets" :team{name: "Rockets"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:like@0 {likeness: 90}]->("Paul George" :player{age: 28, name: "Paul George"})-[:serve@0 {end_year: 2017, start_year: 2010}]->("Pacers" :team{name: "Pacers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2014, start_year: 2010}]->("Heat" :team{name: "Heat"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2016, start_year: 2015}]->("Kings" :team{name: "Kings"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Lakers" :team{name: "Lakers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Knicks" :team{name: "Knicks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2011, start_year: 2005}]->("Hornets" :team{name: "Hornets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2015, start_year: 2014}]->("Mavericks" :team{name: "Mavericks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Dwyane Wade" :player{age: 37, name: "Dwyane Wade"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Bulls" :team{name: "Bulls"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:serve@0 {end_year: 2003, start_year: 1996}]->("Bucks" :team{name: "Bucks"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2011, start_year: 2003}]->("Nuggets" :team{name: "Nuggets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:serve@0 {end_year: 2012, start_year: 2007}]->("Celtics" :team{name: "Celtics"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:like@0 {likeness: 100}]->("Ray Allen" :player{age: 43, name: "Ray Allen"})-[:like@0 {likeness: 9}]->("Rajon Rondo" :player{age: 33, name: "Rajon Rondo"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Pelicans" :team{name: "Pelicans"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2021, start_year: 2017}]->("Rockets" :team{name: "Rockets"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:like@0 {likeness: -1}]->("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:like@0 {likeness: 90}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2010, start_year: 2003}]->("Cavaliers" :team{name: "Cavaliers"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{age:30})-[e*..5]->(b:team) ) + WHERE length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player{name:"Yao Ming"})-[e:serve*1..3]-(b:team) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2013, start_year: 2012}]->("Lakers" :team{name: "Lakers"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2018}]-("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Knicks" :team{name: "Knicks"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2018, start_year: 2017}]->("Hornets" :team{name: "Hornets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2021, start_year: 2017}]-("Chris Paul" :player{age: 33, name: "Chris Paul"})-[:serve@0 {end_year: 2017, start_year: 2011}]->("Clippers" :team{name: "Clippers"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2018}]-("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"})-[:serve@0 {end_year: 2011, start_year: 2003}]->("Nuggets" :team{name: "Nuggets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2019, start_year: 2012}]-("James Harden" :player{age: 29, name: "James Harden"})-[:serve@0 {end_year: 2012, start_year: 2009}]->("Thunders" :team{name: "Thunders"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2004, start_year: 2000}]->("Magic" :team{name: "Magic"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Hawks" :team{name: "Hawks"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2010, start_year: 2004}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2000, start_year: 1997}]->("Raptors" :team{name: "Raptors"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:serve@0 {end_year: 2011, start_year: 2002}]->("Rockets" :team{name: "Rockets"})<-[:serve@0 {end_year: 2016, start_year: 2013}]-("Dwight Howard" :player{age: 33, name: "Dwight Howard"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Wizards" :team{name: "Wizards"})> | + + Scenario: single shortestPaths2 + When executing query: + """ + MATCH p = shortestPath( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' and id(b) in ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:teammate@0 {end_year: 2016, start_year: 2001}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 80}]-("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 = shortestPath( (a)-[e*..5]->(b) ) + WHERE id(a) == 'Tim Duncan' and id(b) IN ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | + When executing query: + """ + MATCH p = shortestPath( (a)-[e*..5]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:teammate@0 {end_year: 2018, start_year: 2002}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:serve@0 {end_year: 2018, start_year: 1999}]->("Spurs" :team{name: "Spurs"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:teammate@0 {end_year: 2016, start_year: 2001}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2010}]->("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 80}]->("LeBron James" :player{age: 34, name: "LeBron James"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Lakers" :team{name: "Lakers"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:teammate@0 {end_year: 2016, start_year: 2002}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2013, start_year: 2013}]->("Spurs" :team{name: "Spurs"})> | + | <("Yao Ming" :player{age: 38, name: "Yao Ming"})-[:like@0 {likeness: 90}]->("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:serve@0 {end_year: 2004, start_year: 1996}]->("Lakers" :team{name: "Lakers"})> | + When executing query: + """ + MATCH p = shortestPath( (a)-[e:like*..4]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['Tony Parker', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | + When executing query: + """ + MATCH p = shortestPath( (a)-[e:like*..4]->(b) ) + WHERE id(b) IN ['Manu Ginobili', 'Spurs', 'Lakers'] and id(a) in ['xxx', 'zzz'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + + Scenario: single shortestPaths3 + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team{name:"Spurs"}) + MATCH p = shortestPath( (a)-[e:serve*..3]-(b) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team{name:"Spurs"}), p = shortestPath( (a)-[e:serve*..3]-(b) ) RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team) + MATCH p = shortestPath( (a)-[e:serve*..3]-(b) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2008, start_year: 2001}]->("Grizzlies" :team{name: "Grizzlies"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2014, start_year: 2008}]->("Lakers" :team{name: "Lakers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2016, start_year: 2014}]->("Bulls" :team{name: "Bulls"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 1999}]-("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Hornets" :team{name: "Hornets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2017}]-("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:serve@0 {end_year: 2017, start_year: 2013}]->("Kings" :team{name: "Kings"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2017, start_year: 2016}]->("Jazz" :team{name: "Jazz"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2012}]-("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:serve@0 {end_year: 2008, start_year: 2005}]->("Suns" :team{name: "Suns"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2010}]-("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Hawks" :team{name: "Hawks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2015}]-("David West" :player{age: 38, name: "David West"})-[:serve@0 {end_year: 2018, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2016}]-("Paul Gasol" :player{age: 38, name: "Paul Gasol"})-[:serve@0 {end_year: 2020, start_year: 2019}]->("Bucks" :team{name: "Bucks"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2010}]-("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:serve@0 {end_year: 2017, start_year: 2017}]->("76ers" :team{name: "76ers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2019, start_year: 2017}]->("Celtics" :team{name: "Celtics"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2019, start_year: 2015}]-("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:serve@0 {end_year: 2015, start_year: 2006}]->("Trail Blazers" :team{name: "Trail Blazers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2013, start_year: 2013}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})-[:serve@0 {end_year: 2010, start_year: 2004}]->("Rockets" :team{name: "Rockets"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2017, start_year: 2015}]-("Jonathon Simmons" :player{age: 29, name: "Jonathon Simmons"})-[:serve@0 {end_year: 2019, start_year: 2017}]->("Magic" :team{name: "Magic"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2016, start_year: 2015}]-("David West" :player{age: 38, name: "David West"})-[:serve@0 {end_year: 2015, start_year: 2011}]->("Pacers" :team{name: "Pacers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2015, start_year: 2013}]-("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:serve@0 {end_year: 2017, start_year: 2015}]->("Pistons" :team{name: "Pistons"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 2010}]-("Danny Green" :player{age: 31, name: "Danny Green"})-[:serve@0 {end_year: 2010, start_year: 2009}]->("Cavaliers" :team{name: "Cavaliers"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})<-[:serve@0 {end_year: 2018, start_year: 2010}]-("Danny Green" :player{age: 31, name: "Danny Green"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Raptors" :team{name: "Raptors"})> | + When executing query: + """ + MATCH (b:team), (a:player{age:30}) + MATCH p = shortestPath( (a)-[e*..5]->(b) ) + WHERE length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH (b:team) + MATCH p = shortestPath( (a:player)-[e*..5]->(b) ) + WHERE a.player.age == 30 AND length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + When executing query: + """ + MATCH (b:team) + MATCH p = shortestPath( (a:player)-[e*..5]->(b) ) + WHERE id(a) IN ['DeAndre Jordan', 'Kevin Durant', 'Russell Westbrook', 'Blake Griffin'] AND length(p) == 1 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2019}]->("Knicks" :team{name: "Knicks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Mavericks" :team{name: "Mavericks"})> | + | <("DeAndre Jordan" :player{age: 30, name: "DeAndre Jordan"})-[:serve@0 {end_year: 2018, start_year: 2008}]->("Clippers" :team{name: "Clippers"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2016, start_year: 2007}]->("Thunders" :team{name: "Thunders"})> | + | <("Kevin Durant" :player{age: 30, name: "Kevin Durant"})-[:serve@0 {end_year: 2019, start_year: 2016}]->("Warriors" :team{name: "Warriors"})> | + | <("Russell Westbrook" :player{age: 30, name: "Russell Westbrook"})-[:serve@0 {end_year: 2019, start_year: 2008}]->("Thunders" :team{name: "Thunders"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2018, start_year: 2009}]->("Clippers" :team{name: "Clippers"})> | + | <("Blake Griffin" :player{age: 30, name: "Blake Griffin"})-[:serve@0 {end_year: 2019, start_year: 2018}]->("Pistons" :team{name: "Pistons"})> | + + Scenario: single shortestPaths4 + When executing query: + """ + MATCH p = shortestPath( (a:player)-[e:serve*..3]-(b:team) ) + WHERE a.player.name == 'Tim Duncan' AND b.team.name == 'Spurs' + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:serve@0 {end_year: 2016, start_year: 1997}]->("Spurs" :team{name: "Spurs"})> | + When executing query: + """ + MATCH p = shortestPath( (a:player)-[e:serve*..3]-(b:team) ) + WHERE a.player.age > 45 AND b.team.name == 'Spurs' + RETURN length(p) + """ + Then the result should be, in any order, with relax comparison: + | length(p) | + | 3 | + | 3 | + When executing query: + """ + MATCH p = shortestPath( (a:player)-[e:like*..3]-(b:player) ) + WHERE a.player.age > 45 AND b.player.age < 30 + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("James Harden" :player{age: 29, name: "James Harden"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("Kyle Anderson" :player{age: 25, name: "Kyle Anderson"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 75}]-("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})<-[:like@0 {likeness: 80}]-("Damian Lillard" :player{age: 28, name: "Damian Lillard"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 99}]-("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})> | + When executing query: + """ + MATCH (a:player) + MATCH p = shortestPath( (a)<-[e:like*..2]-(b:player{name:"Yao Ming"}) ) + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Kobe Bryant" :player{age: 40, name: "Kobe Bryant"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})<-[:like@0 {likeness: 80}]-("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Rudy Gay" :player{age: 32, name: "Rudy Gay"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Grant Hill" :player{age: 46, name: "Grant Hill"})<-[:like@0 {likeness: 90}]-("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("JaVale McGee" :player{age: 31, name: "JaVale McGee"})<-[:like@0 {likeness: 100}]-("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + | <("Tracy McGrady" :player{age: 39, name: "Tracy McGrady"})<-[:like@0 {likeness: 90}]-("Yao Ming" :player{age: 38, name: "Yao Ming"})> | + When executing query: + """ + MATCH (a:player) + MATCH p = shortestPath( (a)-[e:like*..2]->(b:player{name:"Tony Parker"}) ) + RETURN max(length(p)) AS maxL + """ + Then the result should be, in any order, with relax comparison: + | maxL | + | 2 | + When executing query: + """ + MATCH (a:player) + MATCH p = shortestPath( (a)-[e:like*..2]->(b) ) + WHERE id(b) IN ['xxx', 'zzz', 'yyy', 'Tim Duncan', 'Yao Ming'] + RETURN p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Rudy Gay" :player{age: 32, name: "Rudy Gay"})-[:like@0 {likeness: 70}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Damian Lillard" :player{age: 28, name: "Damian Lillard"})-[:like@0 {likeness: 80}]->("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Marco Belinelli" :player{age: 32, name: "Marco Belinelli"})-[:like@0 {likeness: 55}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("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: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Aron Baynes" :player{age: 32, name: "Aron Baynes"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tiago Splitter" :player{age: 34, name: "Tiago Splitter"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Shaquille O'Neal" :player{age: 47, name: "Shaquille O'Neal"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Danny Green" :player{age: 31, name: "Danny Green"})-[:like@0 {likeness: 70}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Boris Diaw" :player{age: 36, name: "Boris Diaw"})-[:like@0 {likeness: 80}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Dejounte Murray" :player{age: 29, name: "Dejounte Murray"})-[:like@0 {likeness: 99}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})-[:like@0 {likeness: 90}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("Tony Parker" :player{age: 36, name: "Tony Parker"})-[:like@0 {likeness: 95}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + | <("LaMarcus Aldridge" :player{age: 33, name: "LaMarcus Aldridge"})-[:like@0 {likeness: 75}]->("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})> | + + Scenario: single shortestPaths5 + When executing query: + """ + MATCH (a:player{name:"Tim Duncan"}), (b:team{name:"Lakers"}) + OPTIONAL MATCH p = shortestPath( (a)-[:like*]-(b) ) + RETURN + CASE p is NULL + WHEN true THEN -1 + ELSE length(p) + END AS shortestPathLength + """ + Then the result should be, in any order, with relax comparison: + | shortestPathLength | + | -1 | + + Scenario: single shortestPaths Can Not find index + When executing query: + """ + MATCH p = shortestPath( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' OR id(b) in ['Spurs', 'Tony Parker', 'Yao Ming'] + RETURN p + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down. + When executing query: + """ + MATCH p = shortestPath( (a)-[e*..5]-(b) ) + WHERE id(a) == 'Tim Duncan' + RETURN p + """ + Then a ExecutionError should be raised at runtime: Scan vertices or edges need to specify a limit number, or limit number can not push down.