diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index 40f815b6379..43c4ca6ba7d 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -40,9 +40,8 @@ nebula_add_library( query/TraverseExecutor.cpp query/AppendVerticesExecutor.cpp query/RollUpApplyExecutor.cpp - algo/ConjunctPathExecutor.cpp algo/BFSShortestPathExecutor.cpp - algo/ProduceSemiShortestPathExecutor.cpp + algo/MultiShortestPathExecutor.cpp algo/ProduceAllPathsExecutor.cpp algo/CartesianProductExecutor.cpp algo/SubgraphExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index 33ed32d44c0..37706f21c6a 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -46,9 +46,8 @@ #include "graph/executor/admin/ZoneExecutor.h" #include "graph/executor/algo/BFSShortestPathExecutor.h" #include "graph/executor/algo/CartesianProductExecutor.h" -#include "graph/executor/algo/ConjunctPathExecutor.h" +#include "graph/executor/algo/MultiShortestPathExecutor.h" #include "graph/executor/algo/ProduceAllPathsExecutor.h" -#include "graph/executor/algo/ProduceSemiShortestPathExecutor.h" #include "graph/executor/algo/SubgraphExecutor.h" #include "graph/executor/logic/ArgumentExecutor.h" #include "graph/executor/logic/LoopExecutor.h" @@ -447,11 +446,8 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kBFSShortest: { return pool->add(new BFSShortestPathExecutor(node, qctx)); } - case PlanNode::Kind::kProduceSemiShortestPath: { - return pool->add(new ProduceSemiShortestPathExecutor(node, qctx)); - } - case PlanNode::Kind::kConjunctPath: { - return pool->add(new ConjunctPathExecutor(node, qctx)); + case PlanNode::Kind::kMultiShortestPath: { + return pool->add(new MultiShortestPathExecutor(node, qctx)); } case PlanNode::Kind::kProduceAllPaths: { return pool->add(new ProduceAllPathsExecutor(node, qctx)); diff --git a/src/graph/executor/algo/BFSShortestPathExecutor.cpp b/src/graph/executor/algo/BFSShortestPathExecutor.cpp index 631cf3cee9d..87730bf31f5 100644 --- a/src/graph/executor/algo/BFSShortestPathExecutor.cpp +++ b/src/graph/executor/algo/BFSShortestPathExecutor.cpp @@ -1,52 +1,217 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #include "graph/executor/algo/BFSShortestPathExecutor.h" #include "graph/planner/plan/Algo.h" +DECLARE_int32(num_operator_threads); namespace nebula { namespace graph { folly::Future BFSShortestPathExecutor::execute() { SCOPED_TIMER(&execTime_); - auto* bfs = asNode(node()); - auto iter = ectx_->getResult(bfs->inputVar()).iter(); - VLOG(1) << "current: " << node()->outputVar(); - VLOG(1) << "input: " << bfs->inputVar(); + pathNode_ = asNode(node()); + + if (step_ == 1) { + allRightEdges_.emplace_back(); + auto& currentEdges = allRightEdges_.back(); + auto rIter = ectx_->getResult(pathNode_->rightVidVar()).iter(); + std::unordered_set rightVids; + for (; rIter->valid(); rIter->next()) { + auto& vid = rIter->getColumn(0); + if (rightVids.emplace(vid).second) { + Edge dummy; + currentEdges.emplace(vid, std::move(dummy)); + } + } + } + + std::vector> futures; + auto leftFuture = folly::via(runner(), [this]() { return buildPath(false); }); + auto rightFuture = folly::via(runner(), [this]() { return buildPath(true); }); + futures.emplace_back(std::move(leftFuture)); + futures.emplace_back(std::move(rightFuture)); + + return folly::collect(futures) + .via(runner()) + .thenValue([this](auto&& status) { + UNUSED(status); + return conjunctPath(); + }) + .thenValue([this](auto&& status) { + UNUSED(status); + step_++; + DataSet ds; + ds.colNames = pathNode_->colNames(); + ds.rows.swap(currentDs_.rows); + return finish(ResultBuilder().value(Value(std::move(ds))).build()); + }); +} + +Status BFSShortestPathExecutor::buildPath(bool reverse) { + auto iter = reverse ? ectx_->getResult(pathNode_->rightInputVar()).iter() + : ectx_->getResult(pathNode_->leftInputVar()).iter(); DCHECK(!!iter); + auto& visitedVids = reverse ? rightVisitedVids_ : leftVisitedVids_; + auto& allEdges = reverse ? allRightEdges_ : allLeftEdges_; + allEdges.emplace_back(); + auto& currentEdges = allEdges.back(); + + auto iterSize = iter->size(); + visitedVids.reserve(visitedVids.size() + iterSize); + + std::unordered_set uniqueDst; + uniqueDst.reserve(iterSize); + DataSet nextStepVids; + nextStepVids.colNames = {nebula::kVid}; + if (step_ == 1) { + for (; iter->valid(); iter->next()) { + auto edgeVal = iter->getEdge(); + if (UNLIKELY(!edgeVal.isEdge())) { + continue; + } + auto& edge = edgeVal.getEdge(); + auto dst = edge.dst; + visitedVids.emplace(edge.src); + if (uniqueDst.emplace(dst).second) { + nextStepVids.rows.emplace_back(Row({dst})); + } + currentEdges.emplace(std::move(dst), std::move(edge)); + } + } else { + for (; 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; + } + if (uniqueDst.emplace(dst).second) { + nextStepVids.rows.emplace_back(Row({dst})); + } + currentEdges.emplace(std::move(dst), std::move(edge)); + } + } + // set nextVid + const auto& nextVidVar = reverse ? pathNode_->rightVidVar() : pathNode_->leftVidVar(); + ectx_->setResult(nextVidVar, ResultBuilder().value(std::move(nextStepVids)).build()); + + visitedVids.insert(std::make_move_iterator(uniqueDst.begin()), + std::make_move_iterator(uniqueDst.end())); + return Status::OK(); +} + +folly::Future BFSShortestPathExecutor::conjunctPath() { + const auto& leftEdges = allLeftEdges_.back(); + const auto& preRightEdges = allRightEdges_[step_ - 1]; + std::unordered_set meetVids; + bool oddStep = true; + for (const auto& edge : leftEdges) { + if (preRightEdges.find(edge.first) != preRightEdges.end()) { + meetVids.emplace(edge.first); + } + } + if (meetVids.empty() && step_ * 2 <= pathNode_->steps()) { + const auto& rightEdges = allRightEdges_.back(); + for (const auto& edge : leftEdges) { + if (rightEdges.find(edge.first) != rightEdges.end()) { + meetVids.emplace(edge.first); + oddStep = false; + } + } + } + if (meetVids.empty()) { + return Status::OK(); + } + size_t i = 0; + size_t totalSize = meetVids.size(); + size_t batchSize = totalSize / static_cast(FLAGS_num_operator_threads); + std::vector batchVids; + batchVids.reserve(batchSize); + std::vector> futures; + for (auto& vid : meetVids) { + batchVids.push_back(vid); + if (i == totalSize - 1 || batchVids.size() == batchSize) { + auto future = folly::via(runner(), [this, vids = std::move(batchVids), oddStep]() { + return doConjunct(vids, oddStep); + }); + futures.emplace_back(std::move(future)); + } + i++; + } + return folly::collect(futures).via(runner()).thenValue([this](auto&& resps) { + for (auto& resp : resps) { + currentDs_.append(std::move(resp)); + } + return Status::OK(); + }); +} + +DataSet BFSShortestPathExecutor::doConjunct(const std::vector& meetVids, + bool oddStep) const { DataSet ds; - ds.colNames = node()->colNames(); - std::unordered_multimap interim; - - for (; iter->valid(); iter->next()) { - auto edgeVal = iter->getEdge(); - if (!edgeVal.isEdge()) { - continue; - } - auto& edge = edgeVal.getEdge(); - auto visited = visited_.find(edge.dst) != visited_.end(); - if (visited) { - continue; - } - - // save the starts. - visited_.emplace(edge.src); - VLOG(1) << "dst: " << edge.dst << " edge: " << edge; - interim.emplace(edge.dst, std::move(edgeVal)); - } - for (auto& kv : interim) { - auto dst = std::move(kv.first); - auto edge = std::move(kv.second); - Row row; - row.values.emplace_back(dst); - row.values.emplace_back(std::move(edge)); - ds.rows.emplace_back(std::move(row)); - visited_.emplace(dst); - } - return finish(ResultBuilder().value(Value(std::move(ds))).build()); + auto leftPaths = createPath(meetVids, false, oddStep); + auto rightPaths = createPath(meetVids, true, oddStep); + for (auto& leftPath : leftPaths) { + auto range = rightPaths.equal_range(leftPath.first); + for (auto& rightPath = range.first; rightPath != range.second; ++rightPath) { + Path result = leftPath.second; + result.reverse(); + result.append(rightPath->second); + Row row; + row.emplace_back(std::move(result)); + ds.rows.emplace_back(std::move(row)); + } + } + return ds; +} + +std::unordered_multimap BFSShortestPathExecutor::createPath( + std::vector meetVids, bool reverse, bool oddStep) const { + std::unordered_multimap result; + auto& allEdges = reverse ? allRightEdges_ : allLeftEdges_; + for (auto& meetVid : meetVids) { + Path start; + start.src = Vertex(meetVid, {}); + std::vector interimPaths = {std::move(start)}; + auto iter = (reverse && oddStep) ? allEdges.rbegin() + 1 : allEdges.rbegin(); + auto end = reverse ? allEdges.rend() - 1 : allEdges.rend(); + if (iter == end) { + result.emplace(meetVid, std::move(start)); + return result; + } + for (; iter != end; ++iter) { + std::vector temp; + for (auto& interimPath : interimPaths) { + Value id; + if (interimPath.steps.empty()) { + id = interimPath.src.vid; + } else { + id = interimPath.steps.back().dst.vid; + } + auto range = iter->equal_range(id); + for (auto edgeIter = range.first; edgeIter != range.second; ++edgeIter) { + Path p = interimPath; + auto& edge = edgeIter->second; + p.steps.emplace_back(Step(Vertex(edge.src, {}), -edge.type, edge.name, edge.ranking, {})); + + if (iter == end - 1) { + result.emplace(p.src.vid, std::move(p)); + } else { + temp.emplace_back(std::move(p)); + } + } // edgeIter + } // interimPath + interimPaths = std::move(temp); + } + } + return result; } + } // namespace graph } // namespace nebula diff --git a/src/graph/executor/algo/BFSShortestPathExecutor.h b/src/graph/executor/algo/BFSShortestPathExecutor.h index 68742b54493..4e29645b91a 100644 --- a/src/graph/executor/algo/BFSShortestPathExecutor.h +++ b/src/graph/executor/algo/BFSShortestPathExecutor.h @@ -1,15 +1,45 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #ifndef GRAPH_EXECUTOR_ALGO_BFSSHORTESTPATHEXECUTOR_H_ #define GRAPH_EXECUTOR_ALGO_BFSSHORTESTPATHEXECUTOR_H_ - #include "graph/executor/Executor.h" +// BFSShortestPath has two inputs. GetNeighbors(From) & GetNeighbors(To) +// There are two Main functions +// First : Get the next vid for GetNeighbors to expand +// Second: Extract edges from GetNeighbors to form path, concatenate the path(From) and the path(To) +// into a complete path +// +// +// Functions: +// `buildPath`: extract edges from GetNeighbors put it into allLeftEdges or allRightEdges +// and set the vid that needs to be expanded in the next step +// +// `conjunctPath`: concatenate the path(From) and the path(To) into a complete path +// allLeftEdges needs to match the previous step of the allRightEdges +// then current step of the allRightEdges each time +// Eg. a->b->c->d +// firstStep: allLeftEdges [b>] allRightEdges [], can't find common vid +// secondStep: allLeftEdges [b>, c>] allRightEdges [, ] +// we should use allLeftEdges(secondStep) to match allRightEdges(firstStep) first +// if find common vid, no need to match allRightEdges(secondStep) +// +// Member: +// `allLeftEdges_` : is a array, each element in the array is a hashTable +// hash table +// KEY : the VID of the vertex +// VALUE : edges visited at the current step (the destination is KEY) +// +// `allRightEdges_` : same as allLeftEdges_ +// +// `leftVisitedVids_` : keep already visited vid to avoid repeated visits (left) +// `rightVisitedVids_` : keep already visited vid to avoid repeated visits (right) +// `currentDs_`: keep the paths matched in current step namespace nebula { namespace graph { +class BFSShortestPath; class BFSShortestPathExecutor final : public Executor { public: BFSShortestPathExecutor(const PlanNode* node, QueryContext* qctx) @@ -18,7 +48,24 @@ class BFSShortestPathExecutor final : public Executor { folly::Future execute() override; private: - std::unordered_set visited_; + Status buildPath(bool reverse); + + folly::Future conjunctPath(); + + DataSet doConjunct(const std::vector& meetVids, bool oddStep) const; + + std::unordered_multimap createPath(std::vector meetVids, + bool reverse, + bool oddStep) const; + + private: + const BFSShortestPath* pathNode_{nullptr}; + size_t step_{1}; + std::unordered_set leftVisitedVids_; + std::unordered_set rightVisitedVids_; + std::vector> allLeftEdges_; + std::vector> allRightEdges_; + DataSet currentDs_; }; } // namespace graph } // namespace nebula diff --git a/src/graph/executor/algo/ConjunctPathExecutor.cpp b/src/graph/executor/algo/ConjunctPathExecutor.cpp deleted file mode 100644 index 3d80c4e3fe5..00000000000 --- a/src/graph/executor/algo/ConjunctPathExecutor.cpp +++ /dev/null @@ -1,393 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ -#include "graph/executor/algo/ConjunctPathExecutor.h" - -#include "graph/planner/plan/Algo.h" - -namespace nebula { -namespace graph { -folly::Future ConjunctPathExecutor::execute() { - SCOPED_TIMER(&execTime_); - auto* conjunct = asNode(node()); - switch (conjunct->pathKind()) { - case ConjunctPath::PathKind::kBiBFS: - return bfsShortestPath(); - case ConjunctPath::PathKind::kAllPaths: - return allPaths(); - case ConjunctPath::PathKind::kFloyd: - return floydShortestPath(); - default: - LOG(FATAL) << "Not implement."; - } -} - -folly::Future ConjunctPathExecutor::bfsShortestPath() { - auto* conjunct = asNode(node()); - auto lIter = ectx_->getResult(conjunct->leftInputVar()).iter(); - const auto& rHist = ectx_->getHistory(conjunct->rightInputVar()); - VLOG(1) << "current: " << node()->outputVar(); - VLOG(1) << "left input: " << conjunct->leftInputVar() - << " right input: " << conjunct->rightInputVar(); - DCHECK(!!lIter); - - auto steps = conjunct->steps(); - count_++; - - DataSet ds; - ds.colNames = conjunct->colNames(); - - VLOG(1) << "forward, size: " << forward_.size(); - VLOG(1) << "backward, size: " << backward_.size(); - forward_.emplace_back(); - for (; lIter->valid(); lIter->next()) { - auto& dst = lIter->getColumn(kVid); - auto& edge = lIter->getColumn(kEdgeStr); - VLOG(1) << "dst: " << dst << " edge: " << edge; - if (!edge.isEdge()) { - forward_.back().emplace(Value(dst), nullptr); - } else { - forward_.back().emplace(Value(dst), &edge.getEdge()); - } - } - - bool isLatest = false; - if (rHist.size() >= 2) { - auto previous = rHist[rHist.size() - 2].iter(); - VLOG(1) << "Find odd length path."; - auto rows = findBfsShortestPath(previous.get(), isLatest, forward_.back()); - if (!rows.empty()) { - VLOG(1) << "Meet odd length path."; - ds.rows = std::move(rows); - return finish(ResultBuilder().value(Value(std::move(ds))).build()); - } - } - - if (count_ * 2 <= steps) { - auto latest = rHist.back().iter(); - isLatest = true; - backward_.emplace_back(); - VLOG(1) << "Find even length path."; - auto rows = findBfsShortestPath(latest.get(), isLatest, forward_.back()); - if (!rows.empty()) { - VLOG(1) << "Meet even length path."; - ds.rows = std::move(rows); - } - } - return finish(ResultBuilder().value(Value(std::move(ds))).build()); -} - -std::vector ConjunctPathExecutor::findBfsShortestPath( - Iterator* iter, bool isLatest, std::unordered_multimap& table) { - std::unordered_set meets; - for (; iter->valid(); iter->next()) { - auto& dst = iter->getColumn(kVid); - if (isLatest) { - auto& edge = iter->getColumn(kEdgeStr); - VLOG(1) << "dst: " << dst << " edge: " << edge; - if (!edge.isEdge()) { - backward_.back().emplace(dst, nullptr); - } else { - backward_.back().emplace(dst, &edge.getEdge()); - } - } - if (table.find(dst) != table.end()) { - meets.emplace(dst); - } - } - - std::vector rows; - if (!meets.empty()) { - VLOG(1) << "Build forward, size: " << forward_.size(); - auto forwardPath = buildBfsInterimPath(meets, forward_); - VLOG(1) << "Build backward, size: " << backward_.size(); - auto backwardPath = buildBfsInterimPath(meets, backward_); - for (auto& p : forwardPath) { - auto range = backwardPath.equal_range(p.first); - for (auto& i = range.first; i != range.second; ++i) { - Path result = p.second; - result.reverse(); - VLOG(1) << "Forward path: " << result; - VLOG(1) << "Backward path: " << i->second; - result.append(i->second); - Row row; - row.emplace_back(std::move(result)); - rows.emplace_back(std::move(row)); - } - } - } - return rows; -} - -std::unordered_multimap ConjunctPathExecutor::buildBfsInterimPath( - std::unordered_set& meets, - std::vector>& hists) { - std::unordered_multimap results; - for (auto& v : meets) { - VLOG(1) << "Meet at: " << v; - Path start; - start.src = Vertex(v, {}); - if (hists.empty()) { - // Happens at one step path situation when meet at starts - VLOG(1) << "Start: " << start; - results.emplace(v, std::move(start)); - continue; - } - std::vector interimPaths = {std::move(start)}; - for (auto hist = hists.rbegin(); hist < hists.rend(); ++hist) { - std::vector tmp; - for (auto& interimPath : interimPaths) { - Value id; - if (interimPath.steps.empty()) { - id = interimPath.src.vid; - } else { - id = interimPath.steps.back().dst.vid; - } - auto edges = hist->equal_range(id); - for (auto i = edges.first; i != edges.second; ++i) { - Path p = interimPath; - if (i->second != nullptr) { - auto& edge = *(i->second); - VLOG(1) << "Edge: " << edge; - VLOG(1) << "Interim path: " << interimPath; - p.steps.emplace_back( - Step(Vertex(edge.src, {}), -edge.type, edge.name, edge.ranking, {})); - VLOG(1) << "New semi path: " << p; - } - if (hist == (hists.rend() - 1)) { - VLOG(1) << "emplace result: " << p.src.vid; - results.emplace(p.src.vid, std::move(p)); - } else { - tmp.emplace_back(std::move(p)); - } - } // `edge' - } // `interimPath' - if (hist != (hists.rend() - 1)) { - interimPaths = std::move(tmp); - } - } // `hist' - } // `v' - return results; -} - -folly::Future ConjunctPathExecutor::floydShortestPath() { - auto* conjunct = asNode(node()); - conditionalVar_ = conjunct->conditionalVar(); - auto lIter = ectx_->getResult(conjunct->leftInputVar()).iter(); - const auto& rHist = ectx_->getHistory(conjunct->rightInputVar()); - VLOG(1) << "current: " << node()->outputVar(); - VLOG(1) << "left input: " << conjunct->leftInputVar() - << " right input: " << conjunct->rightInputVar(); - DCHECK(!!lIter); - auto steps = conjunct->steps(); - count_++; - - DataSet ds; - ds.colNames = conjunct->colNames(); - - CostPathsValMap forwardCostPathMap; - for (; lIter->valid(); lIter->next()) { - auto& dst = lIter->getColumn(kDst); - auto& src = lIter->getColumn(kSrc); - auto cost = lIter->getColumn(kCostStr); - - auto& pathList = lIter->getColumn(kPathStr); - if (!pathList.isList()) { - continue; - } - auto& srcPaths = forwardCostPathMap[dst]; - srcPaths.emplace(src, CostPaths(cost, pathList.getList())); - } - - if (rHist.size() >= 2) { - auto previous = rHist[rHist.size() - 2].iter(); - VLOG(1) << "Find odd length path."; - findPath(previous.get(), forwardCostPathMap, ds); - } - - if (count_ * 2 <= steps) { - VLOG(1) << "Find even length path."; - auto latest = rHist.back().iter(); - findPath(latest.get(), forwardCostPathMap, ds); - } - - return finish(ResultBuilder().value(Value(std::move(ds))).build()); -} - -Status ConjunctPathExecutor::conjunctPath(const List& forwardPaths, - const List& backwardPaths, - Value& cost, - DataSet& ds) { - for (auto& i : forwardPaths.values) { - if (!i.isPath()) { - return Status::Error("Forward Path Type Error"); - } - for (auto& j : backwardPaths.values) { - if (!j.isPath()) { - return Status::Error("Backward Path Type Error"); - } - Row row; - auto forward = i.getPath(); - auto backward = j.getPath(); - VLOG(1) << "Forward path:" << forward; - VLOG(1) << "Backward path:" << backward; - backward.reverse(); - VLOG(1) << "Backward reverse path:" << backward; - forward.append(std::move(backward)); - VLOG(1) << "Found path: " << forward; - row.values.emplace_back(std::move(forward)); - row.values.emplace_back(cost); - ds.rows.emplace_back(std::move(row)); - } - } - return Status::OK(); -} - -bool ConjunctPathExecutor::findPath(Iterator* backwardPathIter, - CostPathsValMap& forwardPathTable, - DataSet& ds) { - bool found = false; - for (; backwardPathIter->valid(); backwardPathIter->next()) { - auto& dst = backwardPathIter->getColumn(kDst); - auto& endVid = backwardPathIter->getColumn(kSrc); - auto cost = backwardPathIter->getColumn(kCostStr); - VLOG(1) << "Backward dst: " << dst; - auto& pathList = backwardPathIter->getColumn(kPathStr); - if (!pathList.isList()) { - continue; - } - auto forwardPaths = forwardPathTable.find(dst); - if (forwardPaths == forwardPathTable.end()) { - continue; - } - for (auto& srcPaths : forwardPaths->second) { - auto& startVid = srcPaths.first; - if (startVid == endVid) { - delPathFromConditionalVar(startVid, endVid); - continue; - } - auto totalCost = cost + srcPaths.second.cost_; - if (historyCostMap_.find(startVid) != historyCostMap_.end() && - historyCostMap_[startVid].find(endVid) != historyCostMap_[startVid].end() && - historyCostMap_[startVid][endVid] < totalCost) { - continue; - } - // update history cost - auto& hist = historyCostMap_[startVid]; - hist[endVid] = totalCost; - delPathFromConditionalVar(startVid, endVid); - conjunctPath(srcPaths.second.paths_, pathList.getList(), totalCost, ds); - found = true; - } - } - return found; -} - -void ConjunctPathExecutor::delPathFromConditionalVar(const Value& start, const Value& end) { - auto iter = qctx_->ectx()->getResult(conditionalVar_).iter(); - - while (iter->valid()) { - auto startVid = iter->getColumn(0); - auto endVid = iter->getColumn(4); - if (startVid == endVid || (startVid == start && endVid == end)) { - iter->unstableErase(); - VLOG(1) << "Path src: " << start << " dst: " << end; - VLOG(1) << "Delete CartesianProduct src: " << startVid << " dstVid: " << endVid; - } else { - iter->next(); - } - } -} - -folly::Future ConjunctPathExecutor::allPaths() { - auto* conjunct = asNode(node()); - noLoop_ = conjunct->noLoop(); - auto lIter = ectx_->getResult(conjunct->leftInputVar()).iter(); - const auto& rHist = ectx_->getHistory(conjunct->rightInputVar()); - VLOG(1) << "current: " << node()->outputVar(); - VLOG(1) << "left input: " << conjunct->leftInputVar() - << " right input: " << conjunct->rightInputVar(); - VLOG(1) << "right hist size: " << rHist.size(); - DCHECK(!!lIter); - auto steps = conjunct->steps(); - count_++; - - DataSet ds; - ds.colNames = conjunct->colNames(); - - std::unordered_map table; - for (; lIter->valid(); lIter->next()) { - auto& dst = lIter->getColumn(kVid); - auto& path = lIter->getColumn(kPathStr); - if (path.isList()) { - VLOG(1) << "Forward dst: " << dst; - table.emplace(dst, path.getList()); - } - } - - if (rHist.size() >= 2) { - VLOG(1) << "Find odd length path."; - auto previous = rHist[rHist.size() - 2].iter(); - findAllPaths(previous.get(), table, ds); - } - - if (count_ * 2 <= steps) { - VLOG(1) << "Find even length path."; - auto latest = rHist.back().iter(); - findAllPaths(latest.get(), table, ds); - } - - return finish(ResultBuilder().value(Value(std::move(ds))).build()); -} - -bool ConjunctPathExecutor::findAllPaths(Iterator* backwardPathsIter, - std::unordered_map& forwardPathsTable, - DataSet& ds) { - bool found = false; - for (; backwardPathsIter->valid(); backwardPathsIter->next()) { - auto& dst = backwardPathsIter->getColumn(kVid); - VLOG(1) << "Backward dst: " << dst; - auto& pathList = backwardPathsIter->getColumn(kPathStr); - if (!pathList.isList()) { - continue; - } - for (const auto& path : pathList.getList().values) { - if (!path.isPath()) { - continue; - } - auto forwardPaths = forwardPathsTable.find(dst); - if (forwardPaths == forwardPathsTable.end()) { - continue; - } - - for (const auto& i : forwardPaths->second.values) { - if (!i.isPath()) { - continue; - } - Row row; - auto forward = i.getPath(); - Path backward = path.getPath(); - VLOG(1) << "Forward path:" << forward; - VLOG(1) << "Backward path:" << backward; - backward.reverse(); - VLOG(1) << "Backward reverse path:" << backward; - forward.append(std::move(backward)); - if (forward.hasDuplicateEdges()) { - continue; - } - if (noLoop_ && forward.hasDuplicateVertices()) { - continue; - } - VLOG(1) << "Found path: " << forward; - row.values.emplace_back(std::move(forward)); - ds.rows.emplace_back(std::move(row)); - } // `i' - found = true; - } // `path' - } // `backwardPathsIter' - return found; -} - -} // namespace graph -} // namespace nebula diff --git a/src/graph/executor/algo/ConjunctPathExecutor.h b/src/graph/executor/algo/ConjunctPathExecutor.h deleted file mode 100644 index 4285cd8370b..00000000000 --- a/src/graph/executor/algo/ConjunctPathExecutor.h +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#ifndef GRAPH_EXECUTOR_ALGO_CONJUNCTPATHEXECUTOR_H_ -#define GRAPH_EXECUTOR_ALGO_CONJUNCTPATHEXECUTOR_H_ - -#include "graph/executor/Executor.h" - -namespace nebula { -namespace graph { -class ConjunctPathExecutor final : public Executor { - public: - ConjunctPathExecutor(const PlanNode* node, QueryContext* qctx) - : Executor("ConjunctPathExecutor", node, qctx) {} - - folly::Future execute() override; - - struct CostPaths { - CostPaths(Value& cost, const List& paths) : cost_(cost), paths_(paths) {} - Value cost_; - const List& paths_; - }; - - private: - using CostPathsValMap = std::unordered_map>; - - folly::Future bfsShortestPath(); - - folly::Future allPaths(); - - std::vector findBfsShortestPath(Iterator* iter, - bool isLatest, - std::unordered_multimap& table); - - std::unordered_multimap buildBfsInterimPath( - std::unordered_set& meets, - std::vector>& hist); - - folly::Future floydShortestPath(); - - bool findPath(Iterator* backwardPathIter, CostPathsValMap& forwardPathtable, DataSet& ds); - - Status conjunctPath(const List& forwardPaths, - const List& backwardPaths, - Value& cost, - DataSet& ds); - - bool findAllPaths(Iterator* backwardPathsIter, - std::unordered_map& forwardPathsTable, - DataSet& ds); - void delPathFromConditionalVar(const Value& start, const Value& end); - - private: - std::vector> forward_; - std::vector> backward_; - size_t count_{0}; - // startVid : {endVid, cost} - std::unordered_map> historyCostMap_; - std::string conditionalVar_; - bool noLoop_; -}; -} // namespace graph -} // namespace nebula -#endif // GRAPH_EXECUTOR_ALGO_CONJUNCTPATHEXECUTOR_H_ diff --git a/src/graph/executor/algo/MultiShortestPathExecutor.cpp b/src/graph/executor/algo/MultiShortestPathExecutor.cpp new file mode 100644 index 00000000000..5ab58fa4847 --- /dev/null +++ b/src/graph/executor/algo/MultiShortestPathExecutor.cpp @@ -0,0 +1,221 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. +#include "graph/executor/algo/MultiShortestPathExecutor.h" + +#include "graph/planner/plan/Algo.h" +DECLARE_int32(num_operator_threads); +namespace nebula { +namespace graph { +folly::Future MultiShortestPathExecutor::execute() { + SCOPED_TIMER(&execTime_); + pathNode_ = asNode(node()); + terminationVar_ = pathNode_->terminationVar(); + + if (step_ == 1) { + init(); + } + + std::vector> futures; + auto leftFuture = folly::via(runner(), [this]() { return buildPath(false); }); + auto rightFuture = folly::via(runner(), [this]() { return buildPath(true); }); + futures.emplace_back(std::move(leftFuture)); + futures.emplace_back(std::move(rightFuture)); + + return folly::collect(futures) + .via(runner()) + .thenValue([this](auto&& status) { + // oddStep + UNUSED(status); + return conjunctPath(true); + }) + .thenValue([this](auto&& termination) { + // termination is ture, all paths has found + if (termination || step_ * 2 > pathNode_->steps()) { + return folly::makeFuture(true); + } + // evenStep + return conjunctPath(false); + }) + .thenValue([this](auto&& resp) { + UNUSED(resp); + preLeftPaths_.swap(leftPaths_); + preRightPaths_.swap(rightPaths_); + leftPaths_.clear(); + rightPaths_.clear(); + step_++; + DataSet ds; + ds.colNames = pathNode_->colNames(); + ds.rows.swap(currentDs_.rows); + return finish(ResultBuilder().value(Value(std::move(ds))).build()); + }); +} + +void MultiShortestPathExecutor::init() { + auto lIter = ectx_->getResult(pathNode_->leftVidVar()).iter(); + auto rIter = ectx_->getResult(pathNode_->rightVidVar()).iter(); + std::set rightVids; + for (; rIter->valid(); rIter->next()) { + auto& vid = rIter->getColumn(0); + if (rightVids.emplace(vid).second) { + preRightPaths_[vid].push_back({Path(Vertex(vid, {}), {})}); + } + } + + std::set leftVids; + for (; lIter->valid(); lIter->next()) { + auto& vid = lIter->getColumn(0); + leftVids.emplace(vid); + } + for (const auto& leftVid : leftVids) { + for (const auto& rightVid : rightVids) { + if (leftVid != rightVid) { + terminationMap_.insert({leftVid, {rightVid, true}}); + } + } + } +} + +Status MultiShortestPathExecutor::buildPath(bool reverse) { + auto iter = reverse ? ectx_->getResult(pathNode_->rightInputVar()).iter() + : ectx_->getResult(pathNode_->leftInputVar()).iter(); + DCHECK(!!iter); + auto& currentPaths = reverse ? rightPaths_ : leftPaths_; + if (step_ == 1) { + 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; + } + Path path; + path.src = Vertex(src, {}); + path.steps.emplace_back(Step(Vertex(dst, {}), edge.type, edge.name, edge.ranking, {})); + currentPaths[dst].emplace_back(std::move(path)); + } + } else { + auto& historyPaths = reverse ? preRightPaths_ : preLeftPaths_; + 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; + for (const auto& histPath : historyPaths[src]) { + Path path = histPath; + path.steps.emplace_back(Step(Vertex(dst, {}), edge.type, edge.name, edge.ranking, {})); + if (path.hasDuplicateVertices()) { + continue; + } + currentPaths[dst].emplace_back(std::move(path)); + } + } + } + // set nextVid + const auto& nextVidVar = reverse ? pathNode_->rightVidVar() : pathNode_->leftVidVar(); + setNextStepVid(currentPaths, nextVidVar); + return Status::OK(); +} + +DataSet MultiShortestPathExecutor::doConjunct(Interims::iterator startIter, + Interims::iterator endIter, + bool oddStep) { + auto& rightPaths = oddStep ? preRightPaths_ : rightPaths_; + DataSet ds; + for (; startIter != endIter; ++startIter) { + auto found = rightPaths.find(startIter->first); + if (found == rightPaths.end()) { + continue; + } + for (const auto& lPath : startIter->second) { + const auto& srcVid = lPath.src.vid; + auto range = terminationMap_.equal_range(srcVid); + for (const auto& rPath : found->second) { + const auto& dstVid = rPath.src.vid; + for (auto iter = range.first; iter != range.second; ++iter) { + if (iter->second.first == dstVid) { + auto forwardPath = lPath; + auto backwardPath = rPath; + backwardPath.reverse(); + forwardPath.append(std::move(backwardPath)); + if (forwardPath.hasDuplicateVertices()) { + continue; + } + Row row; + row.values.emplace_back(std::move(forwardPath)); + ds.rows.emplace_back(std::move(row)); + iter->second.second = false; + } + } + } + } + } + return ds; +} + +folly::Future MultiShortestPathExecutor::conjunctPath(bool oddStep) { + size_t batchSize = leftPaths_.size() / static_cast(FLAGS_num_operator_threads); + std::vector> futures; + size_t i = 0; + + auto startIter = leftPaths_.begin(); + for (auto leftIter = leftPaths_.begin(); leftIter != leftPaths_.end(); ++leftIter) { + if (i++ == batchSize) { + auto endIter = leftIter; + endIter++; + auto future = folly::via(runner(), [this, startIter, endIter, oddStep]() { + return doConjunct(startIter, endIter, oddStep); + }); + futures.emplace_back(std::move(future)); + i = 0; + startIter = endIter; + } + } + if (i != 0) { + auto endIter = leftPaths_.end(); + auto future = folly::via(runner(), [this, startIter, endIter, oddStep]() { + return doConjunct(startIter, endIter, oddStep); + }); + futures.emplace_back(std::move(future)); + } + + return folly::collect(futures).via(runner()).thenValue([this](auto&& resps) { + for (auto& resp : resps) { + currentDs_.append(std::move(resp)); + } + + for (auto iter = terminationMap_.begin(); iter != terminationMap_.end();) { + if (!iter->second.second) { + iter = terminationMap_.erase(iter); + } else { + ++iter; + } + } + if (terminationMap_.empty()) { + ectx_->setValue(terminationVar_, true); + return true; + } + return false; + }); +} + +void MultiShortestPathExecutor::setNextStepVid(const Interims& paths, const string& var) { + DataSet ds; + ds.colNames = {nebula::kVid}; + for (const auto& path : paths) { + Row row; + row.values.emplace_back(path.first); + ds.rows.emplace_back(std::move(row)); + } + ectx_->setResult(var, ResultBuilder().value(std::move(ds)).build()); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/algo/MultiShortestPathExecutor.h b/src/graph/executor/algo/MultiShortestPathExecutor.h new file mode 100644 index 00000000000..2ea20286b1c --- /dev/null +++ b/src/graph/executor/algo/MultiShortestPathExecutor.h @@ -0,0 +1,83 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#ifndef GRAPH_EXECUTOR_ALGO_MULTISHORTESTPATHEXECUTOR_H_ +#define GRAPH_EXECUTOR_ALGO_MULTISHORTESTPATHEXECUTOR_H_ + +#include "graph/executor/Executor.h" +// MultiShortestPath has two inputs. GetNeighbors(From) & GetNeighbors(To) +// There are two Main functions +// First : Get the next vid for GetNeighbors to expand +// Second: Extract edges from GetNeighbors to form path, concatenate the path(From) and the path(To) +// into a complete path +// +// Since FromVid & ToVid are expanded at the same time +// the paths(From) need to be spliced ​​with the path(To) of the previous step, +// and then spliced ​​with the current path(To) +// +// Functions: +// `init`: initialize preRightPaths_ & terminationMap_ +// +// `buildPath`: extract edges from GetNeighbors to form path (use previous paths) +// +// `conjunctPath`: concatenate the path(From) and the path(To) into a complete path +// leftPaths needs to match the previous step of the rightPaths +// then current step of the rightPaths each time +// Eg. a->b->c->d +// firstStep: leftPath [b>] rightPath [], can't find common vid +// secondStep: leftPath [b->c>] rightPath [] +// we should use leftPath(secondStep) to match rightPath(firstStep) first +// if find common vid, no need to match rightPath(secondStep) +// +// `setNextStepVid`: set the vid that needs to be expanded in the next step + +// Member: +// `preLeftPaths_` : is hash table (only keep the previous step) +// KEY : the VID of the vertex +// VALUE : all paths(the destination is KEY) +// +// `preRightPaths_` : same as preLeftPaths_ +// `terminationMap_` is hash table, cartesian product of From & To vid, In shortest path scenarios, +// when a pair of paths is found, the pair of data is deleted, and when it is empty, the expansion +// is terminated +// `terminationVar_`: when terminationMap_ is empty, then all paths are found, set it to true and +// the loop will terminate +// `leftPaths_` : same as preLeftPaths_(only keep the current step) +// `rightPaths_` : same as preRightPaths_(only keep the current step) +// `currentDs_`: keep the paths matched in current step +namespace nebula { +namespace graph { +class MultiShortestPath; +class MultiShortestPathExecutor final : public Executor { + public: + MultiShortestPathExecutor(const PlanNode* node, QueryContext* qctx) + : Executor("MultiShortestPath", node, qctx) {} + + folly::Future execute() override; + + private: + // k: dst, v: paths to dst + using Interims = std::unordered_map>; + + void init(); + Status buildPath(bool reverse); + folly::Future conjunctPath(bool oddStep); + DataSet doConjunct(Interims::iterator startIter, Interims::iterator endIter, bool oddStep); + void setNextStepVid(const Interims& paths, const string& var); + + private: + const MultiShortestPath* pathNode_{nullptr}; + size_t step_{1}; + std::string terminationVar_; + std::unordered_multimap> terminationMap_; + Interims leftPaths_; + Interims preLeftPaths_; + Interims preRightPaths_; + Interims rightPaths_; + DataSet currentDs_; +}; + +} // namespace graph +} // namespace nebula +#endif // GRAPH_EXECUTOR_ALGO_MULTISHORTESTPATHEXECUTOR_H_ diff --git a/src/graph/executor/algo/ProduceAllPathsExecutor.cpp b/src/graph/executor/algo/ProduceAllPathsExecutor.cpp index c7a3cd71a72..e851e32caf1 100644 --- a/src/graph/executor/algo/ProduceAllPathsExecutor.cpp +++ b/src/graph/executor/algo/ProduceAllPathsExecutor.cpp @@ -1,88 +1,187 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #include "graph/executor/algo/ProduceAllPathsExecutor.h" - #include "graph/planner/plan/Algo.h" - +DECLARE_int32(num_operator_threads); namespace nebula { namespace graph { folly::Future ProduceAllPathsExecutor::execute() { SCOPED_TIMER(&execTime_); - auto* allPaths = asNode(node()); - noLoop_ = allPaths->noLoop(); - auto iter = ectx_->getResult(allPaths->inputVar()).iter(); - DCHECK(!!iter); + pathNode_ = asNode(node()); + noLoop_ = pathNode_->noLoop(); - DataSet ds; - ds.colNames = node()->colNames(); - Interims interims; - - if (!iter->isGetNeighborsIter()) { - return Status::Error("Only accept GetNeighborsIter."); - } - for (; iter->valid(); iter->next()) { - auto edgeVal = iter->getEdge(); - if (!edgeVal.isEdge()) { - continue; - } - auto& edge = edgeVal.getEdge(); - auto histPaths = historyPaths_.find(edge.src); - if (histPaths == historyPaths_.end()) { - createPaths(edge, interims); - } else { - buildPaths(histPaths->second, edge, interims); + if (step_ == 1) { + auto rIter = ectx_->getResult(pathNode_->rightVidVar()).iter(); + std::unordered_set rightVids; + for (; rIter->valid(); rIter->next()) { + auto& vid = rIter->getColumn(0); + if (rightVids.emplace(vid).second) { + preRightPaths_[vid].push_back({Path(Vertex(vid, {}), {})}); + } } } + std::vector> futures; + auto leftFuture = folly::via(runner(), [this]() { return buildPath(false); }); + auto rightFuture = folly::via(runner(), [this]() { return buildPath(true); }); + futures.emplace_back(std::move(leftFuture)); + futures.emplace_back(std::move(rightFuture)); - for (auto& interim : interims) { - Row row; - auto& dst = interim.first; - auto& paths = interim.second; - row.values.emplace_back(std::move(dst)); - row.values.emplace_back(List(std::move(paths))); - ds.rows.emplace_back(std::move(row)); + return folly::collect(futures) + .via(runner()) + .thenValue([this](auto&& status) { + UNUSED(status); + return conjunctPath(); + }) + .thenValue([this](auto&& status) { + UNUSED(status); + step_++; + DataSet ds; + ds.colNames = pathNode_->colNames(); + ds.rows.swap(currentDs_.rows); + return finish(ResultBuilder().value(Value(std::move(ds))).build()); + }); +} - const auto& dstInDs = ds.rows.back().values.front(); - for (auto& path : ds.rows.back().values.back().getList().values) { - historyPaths_[dstInDs].emplace_back(&path.getPath()); +Status ProduceAllPathsExecutor::buildPath(bool reverse) { + auto iter = reverse ? ectx_->getResult(pathNode_->rightInputVar()).iter() + : ectx_->getResult(pathNode_->leftInputVar()).iter(); + DCHECK(iter); + auto& currentPaths = reverse ? rightPaths_ : leftPaths_; + if (step_ == 1) { + 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 (noLoop_ && src == dst) { + continue; + } + Path path; + path.src = Vertex(src, {}); + path.steps.emplace_back(Step(Vertex(dst, {}), edge.type, edge.name, edge.ranking, {})); + currentPaths[dst].emplace_back(std::move(path)); + } + } else { + auto& historyPaths = reverse ? preRightPaths_ : preLeftPaths_; + 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; + for (const auto& histPath : historyPaths[src]) { + Path path = histPath; + path.steps.emplace_back(Step(Vertex(dst, {}), edge.type, edge.name, edge.ranking, {})); + if (path.hasDuplicateEdges()) { + continue; + } + if (noLoop_ && path.hasDuplicateVertices()) { + continue; + } + currentPaths[dst].emplace_back(std::move(path)); + } } } - count_++; - return finish(ResultBuilder().value(Value(std::move(ds))).build()); + // set nextVid + const auto& nextVidVar = reverse ? pathNode_->rightVidVar() : pathNode_->leftVidVar(); + setNextStepVid(currentPaths, nextVidVar); + return Status::OK(); } -void ProduceAllPathsExecutor::createPaths(const Edge& edge, Interims& interims) { - Path path; - path.src = Vertex(edge.src, {}); - path.steps.emplace_back(Step(Vertex(edge.dst, {}), edge.type, edge.name, edge.ranking, {})); - if (noLoop_ && path.hasDuplicateVertices()) { - return; +DataSet ProduceAllPathsExecutor::doConjunct(Interims::iterator startIter, + Interims::iterator endIter, + bool oddStep) const { + auto& rightPaths = oddStep ? preRightPaths_ : rightPaths_; + DataSet ds; + for (; startIter != endIter; ++startIter) { + auto found = rightPaths.find(startIter->first); + if (found == rightPaths.end()) { + continue; + } + for (const auto& lPath : startIter->second) { + for (const auto& rPath : found->second) { + auto forwardPath = lPath; + auto backwardPath = rPath; + backwardPath.reverse(); + forwardPath.append(std::move(backwardPath)); + if (forwardPath.hasDuplicateEdges()) { + continue; + } + if (noLoop_ && forwardPath.hasDuplicateVertices()) { + continue; + } + Row row; + row.values.emplace_back(std::move(forwardPath)); + ds.rows.emplace_back(std::move(row)); + } + } } - VLOG(1) << "Create path: " << path; - interims[edge.dst].emplace_back(std::move(path)); + return ds; } -void ProduceAllPathsExecutor::buildPaths(const std::vector& history, - const Edge& edge, - Interims& interims) { - for (auto* histPath : history) { - if (histPath->steps.size() < count_) { - continue; - } else { - Path path = *histPath; - path.steps.emplace_back(Step(Vertex(edge.dst, {}), edge.type, edge.name, edge.ranking, {})); - if (path.hasDuplicateEdges()) { - continue; - } - if (noLoop_ && path.hasDuplicateVertices()) { - continue; +folly::Future ProduceAllPathsExecutor::conjunctPath() { + auto batchSize = leftPaths_.size() / static_cast(FLAGS_num_operator_threads); + std::vector> futures; + size_t i = 0; + + auto startIter = leftPaths_.begin(); + for (auto leftIter = leftPaths_.begin(); leftIter != leftPaths_.end(); ++leftIter) { + if (i++ == batchSize) { + auto endIter = leftIter; + endIter++; + auto oddStepFuture = folly::via( + runner(), [this, startIter, endIter]() { return doConjunct(startIter, endIter, true); }); + futures.emplace_back(std::move(oddStepFuture)); + if (step_ * 2 <= pathNode_->steps()) { + auto evenStepFuture = folly::via(runner(), [this, startIter, endIter]() { + return doConjunct(startIter, endIter, false); + }); + futures.emplace_back(std::move(evenStepFuture)); } - VLOG(1) << "Build path: " << path; - interims[edge.dst].emplace_back(std::move(path)); + + i = 0; + startIter = endIter; } } + if (i != 0) { + auto endIter = leftPaths_.end(); + auto oddStepFuture = folly::via( + runner(), [this, startIter, endIter]() { return doConjunct(startIter, endIter, true); }); + futures.emplace_back(std::move(oddStepFuture)); + if (step_ * 2 <= pathNode_->steps()) { + auto evenStepFuture = folly::via( + runner(), [this, startIter, endIter]() { return doConjunct(startIter, endIter, false); }); + futures.emplace_back(std::move(evenStepFuture)); + } + } + + return folly::collect(futures).via(runner()).thenValue([this](auto&& resps) { + for (auto& resp : resps) { + currentDs_.append(std::move(resp)); + } + preLeftPaths_.swap(leftPaths_); + preRightPaths_.swap(rightPaths_); + leftPaths_.clear(); + rightPaths_.clear(); + return Status::OK(); + }); +} + +void ProduceAllPathsExecutor::setNextStepVid(Interims& paths, const string& var) { + DataSet ds; + ds.colNames = {nebula::kVid}; + for (const auto& path : paths) { + Row row; + row.values.emplace_back(path.first); + ds.rows.emplace_back(std::move(row)); + } + ectx_->setResult(var, ResultBuilder().value(std::move(ds)).build()); } } // namespace graph diff --git a/src/graph/executor/algo/ProduceAllPathsExecutor.h b/src/graph/executor/algo/ProduceAllPathsExecutor.h index 986c0da3c13..c62c4bb5e2e 100644 --- a/src/graph/executor/algo/ProduceAllPathsExecutor.h +++ b/src/graph/executor/algo/ProduceAllPathsExecutor.h @@ -1,15 +1,48 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #ifndef GRAPH_EXECUTOR_ALGO_PRODUCEALLPATHSEXECUTOR_H_ #define GRAPH_EXECUTOR_ALGO_PRODUCEALLPATHSEXECUTOR_H_ #include "graph/executor/Executor.h" +// ProduceAllPath has two inputs. GetNeighbors(From) & GetNeighbors(To) +// There are two Main functions +// First : Get the next vid for GetNeighbors to expand +// Second: Extract edges from GetNeighbors to form path, concatenate the path(From) and the path(To) +// into a complete path +// +// Since FromVid & ToVid are expanded at the same time +// the paths(From) need to be spliced ​​with the path(To) of the previous step, +// and then spliced ​​with the current path(To) +// +// Functions: +// `buildPath`: extract edges from GetNeighbors to form path (use previous paths) +// +// `conjunctPath`: concatenate the path(From) and the path(To) into a complete path +// leftPaths needs to match the previous step of the rightPaths +// then current step of the rightPaths each time +// Eg. a->b->c->d +// firstStep: leftPath [b>] rightPath [], can't find common vid +// secondStep: leftPath [b->c>] rightPath [] +// we should use leftPath(secondStep) to match rightPath(firstStep) first +// then rightPath(secondStep) +// +// `setNextStepVid`: set the vid that needs to be expanded in the next step + +// Member: +// `preLeftPaths_` : is hash table (only keep the previous step) +// KEY : the VID of the vertex +// VALUE : all paths(the destination is KEY) +// +// `preRightPaths_` : same as preLeftPaths_ +// `leftPaths_` : same as preLeftPaths_(only keep the current step) +// `rightPaths_` : same as preRightPaths_(only keep the current step) +// `currentDs_`: keep the paths matched in current step namespace nebula { namespace graph { +class ProduceAllPaths; class ProduceAllPathsExecutor final : public Executor { public: ProduceAllPathsExecutor(const PlanNode* node, QueryContext* qctx) @@ -19,18 +52,22 @@ class ProduceAllPathsExecutor final : public Executor { private: // k: dst, v: paths to dst - using HistoryPaths = std::unordered_map>; - - // k: dst, v: paths to dst - using Interims = std::unordered_map>; + using Interims = std::unordered_map>; - void createPaths(const Edge& edge, Interims& interims); + Status buildPath(bool reverse); + folly::Future conjunctPath(); + DataSet doConjunct(Interims::iterator startIter, Interims::iterator endIter, bool oddStep) const; + void setNextStepVid(Interims& paths, const string& var); - void buildPaths(const std::vector& history, const Edge& edge, Interims& interims); - - size_t count_{0}; - HistoryPaths historyPaths_; + private: + const ProduceAllPaths* pathNode_{nullptr}; bool noLoop_{false}; + size_t step_{1}; + Interims preLeftPaths_; + Interims leftPaths_; + Interims preRightPaths_; + Interims rightPaths_; + DataSet currentDs_; }; } // namespace graph } // namespace nebula diff --git a/src/graph/executor/algo/ProduceSemiShortestPathExecutor.cpp b/src/graph/executor/algo/ProduceSemiShortestPathExecutor.cpp deleted file mode 100644 index 067b5ca1935..00000000000 --- a/src/graph/executor/algo/ProduceSemiShortestPathExecutor.cpp +++ /dev/null @@ -1,269 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include "graph/executor/algo/ProduceSemiShortestPathExecutor.h" - -#include "graph/planner/plan/Algo.h" - -namespace nebula { -namespace graph { - -std::vector ProduceSemiShortestPathExecutor::createPaths( - const std::vector& paths, const Edge& edge) { - std::vector newPaths; - newPaths.reserve(paths.size()); - for (auto p : paths) { - Path path = *p; - path.steps.emplace_back(Step(Vertex(edge.dst, {}), edge.type, edge.name, edge.ranking, {})); - newPaths.emplace_back(std::move(path)); - } - return newPaths; -} - -void ProduceSemiShortestPathExecutor::dstInCurrent(const Edge& edge, - CostPathMapType& currentCostPathMap) { - auto& src = edge.src; - auto& dst = edge.dst; - auto weight = 1; // weight = weight_->getWeight(); - auto& srcPaths = historyCostPathMap_[src]; - - for (auto& srcPath : srcPaths) { - if (currentCostPathMap[dst].find(srcPath.first) != currentCostPathMap[dst].end()) { - // startVid in currentCostPathMap[dst] - auto newCost = srcPath.second.cost_ + weight; - auto oldCost = currentCostPathMap[dst][srcPath.first].cost_; - if (newCost > oldCost) { - continue; - } else if (newCost < oldCost) { - // update (dst, startVid)'s path - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - currentCostPathMap[dst][srcPath.first].cost_ = newCost; - currentCostPathMap[dst][srcPath.first].paths_.swap(newPaths); - } else { - // add (dst, startVid)'s path - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - for (auto& p : newPaths) { - currentCostPathMap[dst][srcPath.first].paths_.emplace_back(std::move(p)); - } - } - } else { - // startVid not in currentCostPathMap[dst] - auto newCost = srcPath.second.cost_ + weight; - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - // dst in history - if (historyCostPathMap_.find(dst) != historyCostPathMap_.end()) { - if (historyCostPathMap_[dst].find(srcPath.first) != historyCostPathMap_[dst].end()) { - auto historyCost = historyCostPathMap_[dst][srcPath.first].cost_; - if (newCost > historyCost) { - continue; - } else { - removeSamePath(newPaths, historyCostPathMap_[dst][srcPath.first].paths_); - } - } - } - if (!newPaths.empty()) { - CostPaths temp(newCost, newPaths); - currentCostPathMap[dst].emplace(srcPath.first, std::move(temp)); - } - } - } -} - -void ProduceSemiShortestPathExecutor::dstNotInHistory(const Edge& edge, - CostPathMapType& currentCostPathMap) { - auto& src = edge.src; - auto& dst = edge.dst; - auto weight = 1; // weight = weight_->getWeight(); - auto& srcPaths = historyCostPathMap_[src]; - if (currentCostPathMap.find(dst) == currentCostPathMap.end()) { - // dst not in history and not in current - for (auto& srcPath : srcPaths) { - auto cost = srcPath.second.cost_ + weight; - - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - currentCostPathMap[dst].emplace(srcPath.first, CostPaths(cost, std::move(newPaths))); - } - } else { - // dst in current - dstInCurrent(edge, currentCostPathMap); - } -} - -void ProduceSemiShortestPathExecutor::removeSamePath(std::vector& paths, - std::vector& historyPathsPtr) { - for (auto ptr : historyPathsPtr) { - auto iter = paths.begin(); - while (iter != paths.end()) { - if (*iter == *ptr) { - VLOG(2) << "Erese Path :" << *iter; - iter = paths.erase(iter); - } else { - ++iter; - } - } - } -} - -void ProduceSemiShortestPathExecutor::dstInHistory(const Edge& edge, - CostPathMapType& currentCostPathMap) { - auto& src = edge.src; - auto& dst = edge.dst; - auto weight = 1; // weight = weight_->getWeight(); - auto& srcPaths = historyCostPathMap_[src]; - - if (currentCostPathMap.find(dst) == currentCostPathMap.end()) { - // dst not in current but in history - for (auto& srcPath : srcPaths) { - if (historyCostPathMap_[dst].find(srcPath.first) == historyCostPathMap_[dst].end()) { - // (dst, startVid)'s path not in history - auto newCost = srcPath.second.cost_ + weight; - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - currentCostPathMap[dst].emplace(srcPath.first, CostPaths(newCost, std::move(newPaths))); - } else { - // (dst, startVid)'s path in history, compare cost - auto newCost = srcPath.second.cost_ + weight; - auto historyCost = historyCostPathMap_[dst][srcPath.first].cost_; - if (newCost > historyCost) { - continue; - } else if (newCost < historyCost) { - // update (dst, startVid)'s path - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - currentCostPathMap[dst].emplace(srcPath.first, CostPaths(newCost, std::move(newPaths))); - } else { - std::vector newPaths = createPaths(srcPath.second.paths_, edge); - // if same path in history, remove it - removeSamePath(newPaths, historyCostPathMap_[dst][srcPath.first].paths_); - if (newPaths.empty()) { - continue; - } - currentCostPathMap[dst].emplace(srcPath.first, CostPaths(newCost, std::move(newPaths))); - } - } - } - } else { - // dst in current - dstInCurrent(edge, currentCostPathMap); - } -} - -void ProduceSemiShortestPathExecutor::updateHistory(const Value& dst, - const Value& src, - double cost, - Value& paths) { - const List& pathList = paths.getList(); - std::vector tempPathsPtr; - tempPathsPtr.reserve(pathList.size()); - for (auto& p : pathList.values) { - tempPathsPtr.emplace_back(&p.getPath()); - } - - if (historyCostPathMap_.find(dst) == historyCostPathMap_.end()) { - // insert path to history - std::unordered_map temp = {{src, CostPathsPtr(cost, tempPathsPtr)}}; - historyCostPathMap_.emplace(dst, std::move(temp)); - } else { - if (historyCostPathMap_[dst].find(src) == historyCostPathMap_[dst].end()) { - // startVid not in history ; insert it - historyCostPathMap_[dst].emplace(src, CostPathsPtr(cost, tempPathsPtr)); - } else { - // startVid in history; compare cost - auto historyCost = historyCostPathMap_[dst][src].cost_; - if (cost < historyCost) { - historyCostPathMap_[dst][src].cost_ = cost; - historyCostPathMap_[dst][src].paths_.swap(tempPathsPtr); - } else if (cost == historyCost) { - for (auto p : tempPathsPtr) { - historyCostPathMap_[dst][src].paths_.emplace_back(p); - } - } else { - LOG(FATAL) << "current Cost : " << cost << " history Cost : " << historyCost; - } - } - } -} - -folly::Future ProduceSemiShortestPathExecutor::execute() { - SCOPED_TIMER(&execTime_); - auto* pssp = asNode(node()); - auto iter = ectx_->getResult(pssp->inputVar()).iter(); - VLOG(1) << "current: " << node()->outputVar(); - VLOG(1) << "input: " << pssp->inputVar(); - DCHECK(!!iter); - - CostPathMapType currentCostPathMap; - - for (; iter->valid(); iter->next()) { - auto edgeVal = iter->getEdge(); - if (!edgeVal.isEdge()) { - continue; - } - auto& edge = edgeVal.getEdge(); - auto& src = edge.src; - auto& dst = edge.dst; - auto weight = 1; - - if (historyCostPathMap_.find(src) == historyCostPathMap_.end()) { - // src not in history, now src must be startVid - Path path; - path.src = Vertex(src, {}); - path.steps.emplace_back(Step(Vertex(dst, {}), edge.type, edge.name, edge.ranking, {})); - if (currentCostPathMap.find(dst) != currentCostPathMap.end()) { - if (currentCostPathMap[dst].find(src) == currentCostPathMap[dst].end()) { - CostPaths costPaths(weight, {std::move(path)}); - currentCostPathMap[dst].emplace(src, std::move(costPaths)); - } else { - // same (src, dst), different edge type or rank - auto currentCost = currentCostPathMap[dst][src].cost_; - if (weight == currentCost) { - currentCostPathMap[dst][src].paths_.emplace_back(std::move(path)); - } else if (weight < currentCost) { - std::vector tempPaths = {std::move(path)}; - currentCostPathMap[dst][src].paths_.swap(tempPaths); - } else { - continue; - } - } - } else { - CostPaths costPaths(weight, {std::move(path)}); - currentCostPathMap[dst].emplace(src, std::move(costPaths)); - } - } else { - if (historyCostPathMap_.find(dst) == historyCostPathMap_.end()) { - dstNotInHistory(edge, currentCostPathMap); - } else { - dstInHistory(edge, currentCostPathMap); - } - } - } - - DataSet ds; - ds.colNames = node()->colNames(); - for (auto& dstPath : currentCostPathMap) { - auto& dst = dstPath.first; - for (auto& srcPath : dstPath.second) { - auto& src = srcPath.first; - auto cost = srcPath.second.cost_; - List paths; - paths.values.reserve(srcPath.second.paths_.size()); - for (auto& path : srcPath.second.paths_) { - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back(std::move(dst)); - row.values.emplace_back(std::move(src)); - row.values.emplace_back(std::move(cost)); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - - // update (dst, startVid)'s paths to history - updateHistory(dst, srcPath.first, cost, ds.rows.back().values.back()); - } - } - VLOG(2) << "SemiShortPath : " << ds; - return finish(ResultBuilder().value(Value(std::move(ds))).build()); -} - -} // namespace graph -} // namespace nebula diff --git a/src/graph/executor/algo/ProduceSemiShortestPathExecutor.h b/src/graph/executor/algo/ProduceSemiShortestPathExecutor.h deleted file mode 100644 index e7b93a84cea..00000000000 --- a/src/graph/executor/algo/ProduceSemiShortestPathExecutor.h +++ /dev/null @@ -1,66 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#ifndef GRAPH_EXECUTOR_ALGO_PRODUCESEMISHORTESTPATHEXECUTOR_H_ -#define GRAPH_EXECUTOR_ALGO_PRODUCESEMISHORTESTPATHEXECUTOR_H_ - -#include "graph/executor/Executor.h" - -namespace nebula { -namespace graph { -class ProduceSemiShortestPathExecutor final : public Executor { - public: - ProduceSemiShortestPathExecutor(const PlanNode* node, QueryContext* qctx) - : Executor("ProduceSemiShortestPath", node, qctx) {} - - folly::Future execute() override; - - struct CostPaths { - double cost_; - std::vector paths_; - CostPaths() = default; - CostPaths(double cost, std::vector& paths) : cost_(cost) { - paths_.swap(paths); - } - CostPaths(double cost, std::vector&& paths) : cost_(cost) { - paths_.swap(paths); - } - }; - - struct CostPathsPtr { - CostPathsPtr() = default; - CostPathsPtr(double cost, std::vector& paths) : cost_(cost) { - paths_.swap(paths); - } - double cost_; - std::vector paths_; - }; - - using CostPathMapType = std::unordered_map>; - using CostPathMapPtr = std::unordered_map>; - - private: - void dstNotInHistory(const Edge& edge, CostPathMapType&); - - void dstInHistory(const Edge& edge, CostPathMapType&); - - void dstInCurrent(const Edge& edge, CostPathMapType&); - - void updateHistory(const Value& dst, const Value& src, double cost, Value& paths); - - std::vector createPaths(const std::vector& paths, const Edge& edge); - - void removeSamePath(std::vector& paths, std::vector& historyPaths); - - private: - // dst : {src : } - CostPathMapPtr historyCostPathMap_; - - // std::unique_ptr weight_; -}; - -} // namespace graph -} // namespace nebula -#endif // GRAPH_EXECUTOR_ALGO_PRODUCESEMISHORTESTPATHEXECUTOR_H_ diff --git a/src/graph/executor/query/DataCollectExecutor.cpp b/src/graph/executor/query/DataCollectExecutor.cpp index fc14157221e..86dd92e3343 100644 --- a/src/graph/executor/query/DataCollectExecutor.cpp +++ b/src/graph/executor/query/DataCollectExecutor.cpp @@ -38,14 +38,11 @@ folly::Future DataCollectExecutor::doCollect() { NG_RETURN_IF_ERROR(collectBFSShortest(vars)); break; } + case DataCollect::DCKind::kMultiplePairShortest: case DataCollect::DCKind::kAllPaths: { NG_RETURN_IF_ERROR(collectAllPaths(vars)); break; } - case DataCollect::DCKind::kMultiplePairShortest: { - NG_RETURN_IF_ERROR(collectMultiplePairShortestPath(vars)); - break; - } case DataCollect::DCKind::kPathProp: { NG_RETURN_IF_ERROR(collectPathProp(vars)); break; @@ -212,68 +209,6 @@ Status DataCollectExecutor::collectAllPaths(const std::vector& vars return Status::OK(); } -Status DataCollectExecutor::collectMultiplePairShortestPath(const std::vector& vars) { - DataSet ds; - ds.colNames = std::move(colNames_); - DCHECK(!ds.colNames.empty()); - - // src : {dst : } - std::unordered_map>>> - shortestPath; - - for (auto& var : vars) { - auto& hist = ectx_->getHistory(var); - for (auto& result : hist) { - auto iter = result.iter(); - if (!iter->isSequentialIter()) { - std::stringstream msg; - msg << "Iterator should be kind of SequentialIter, but was: " << iter->kind(); - return Status::Error(msg.str()); - } - auto* seqIter = static_cast(iter.get()); - for (; seqIter->valid(); seqIter->next()) { - auto& pathVal = seqIter->getColumn(kPathStr); - auto cost = seqIter->getColumn(kCostStr); - if (!pathVal.isPath()) { - return Status::Error("Type error `%s', should be PATH", pathVal.typeName().c_str()); - } - auto& path = pathVal.getPath(); - auto& src = path.src.vid; - auto& dst = path.steps.back().dst.vid; - if (shortestPath.find(src) == shortestPath.end() || - shortestPath[src].find(dst) == shortestPath[src].end()) { - auto& dstHist = shortestPath[src]; - std::vector tempPaths = {std::move(path)}; - dstHist.emplace(dst, std::make_pair(cost, std::move(tempPaths))); - } else { - auto oldCost = shortestPath[src][dst].first; - if (cost < oldCost) { - std::vector tempPaths = {std::move(path)}; - shortestPath[src][dst].second.swap(tempPaths); - } else if (cost == oldCost) { - shortestPath[src][dst].second.emplace_back(std::move(path)); - } else { - continue; - } - } - } - } - } - - // collect result - for (auto& srcPath : shortestPath) { - for (auto& dstPath : srcPath.second) { - for (auto& path : dstPath.second.second) { - Row row; - row.values.emplace_back(std::move(path)); - ds.rows.emplace_back(std::move(row)); - } - } - } - result_.setDataSet(std::move(ds)); - return Status::OK(); -} - Status DataCollectExecutor::collectPathProp(const std::vector& vars) { DataSet ds; ds.colNames = colNames_; diff --git a/src/graph/executor/query/DataCollectExecutor.h b/src/graph/executor/query/DataCollectExecutor.h index c229721dec6..b64c8eaa9dc 100644 --- a/src/graph/executor/query/DataCollectExecutor.h +++ b/src/graph/executor/query/DataCollectExecutor.h @@ -48,8 +48,6 @@ class DataCollectExecutor final : public Executor { Status collectAllPaths(const std::vector& vars); - Status collectMultiplePairShortestPath(const std::vector& vars); - Status collectPathProp(const std::vector& vars); std::vector colNames_; diff --git a/src/graph/executor/test/BFSShortestTest.cpp b/src/graph/executor/test/BFSShortestTest.cpp deleted file mode 100644 index 8b87e21e0d7..00000000000 --- a/src/graph/executor/test/BFSShortestTest.cpp +++ /dev/null @@ -1,209 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include - -#include "graph/context/QueryContext.h" -#include "graph/executor/algo/BFSShortestPathExecutor.h" -#include "graph/planner/plan/Algo.h" - -namespace nebula { -namespace graph { -class BFSShortestTest : public testing::Test { - protected: - void SetUp() override { - qctx_ = std::make_unique(); - { - DataSet ds1; - ds1.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - Row row; - // _vid - row.values.emplace_back("1"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto j = 2; j < 4; ++j) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(j)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - firstStepResult_ = std::move(ds1); - - DataSet ds2; - ds2.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - Row row; - // _vid - row.values.emplace_back("2"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto j = 4; j < 6; ++j) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(j)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - { - Row row; - // _vid - row.values.emplace_back("3"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("1"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("4"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - secondStepResult_ = std::move(ds2); - } - { - DataSet ds; - ds.colNames = { - kVid, "_stats", "_tag:tag1:prop1:prop2", "_edge:+edge1:prop1:prop2:_dst:_rank", "_expr"}; - qctx_->symTable()->newVariable("empty_get_neighbors"); - qctx_->ectx()->setResult( - "empty_get_neighbors", - ResultBuilder().value(Value(std::move(ds))).iter(Iterator::Kind::kGetNeighbors).build()); - } - } - - protected: - std::unique_ptr qctx_; - DataSet firstStepResult_; - DataSet secondStepResult_; -}; - -TEST_F(BFSShortestTest, BFSShortest) { - qctx_->symTable()->newVariable("input"); - - auto* bfs = BFSShortestPath::make(qctx_.get(), nullptr); - bfs->setInputVar("input"); - bfs->setColNames({"_vid", "edge"}); - - auto bfsExe = std::make_unique(bfs, qctx_.get()); - // Step 1 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(firstStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = bfsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(bfs->outputVar()); - - DataSet expected; - expected.colNames = {"_vid", "edge"}; - { - Row row; - row.values = {"2", Edge("1", "2", 1, "edge1", 0, {})}; - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"3", Edge("1", "3", 1, "edge1", 0, {})}; - expected.rows.emplace_back(std::move(row)); - } - - std::sort(expected.rows.begin(), expected.rows.end()); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end()); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - // Step 2 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(secondStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = bfsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(bfs->outputVar()); - - DataSet expected; - expected.colNames = {"_vid", "edge"}; - { - Row row; - row.values = {"4", Edge("2", "4", 1, "edge1", 0, {})}; - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"5", Edge("2", "5", 1, "edge1", 0, {})}; - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"4", Edge("3", "4", 1, "edge1", 0, {})}; - expected.rows.emplace_back(std::move(row)); - } - - std::sort(expected.rows.begin(), expected.rows.end()); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end()); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(BFSShortestTest, EmptyInput) { - auto* bfs = BFSShortestPath::make(qctx_.get(), nullptr); - bfs->setInputVar("empty_get_neighbors"); - bfs->setColNames({"_vid", "edge"}); - - auto bfsExe = std::make_unique(bfs, qctx_.get()); - auto future = bfsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(bfs->outputVar()); - - DataSet expected; - expected.colNames = {"_vid", "edge"}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} -} // namespace graph -} // namespace nebula diff --git a/src/graph/executor/test/CMakeLists.txt b/src/graph/executor/test/CMakeLists.txt index 0ef0094b1cb..27f4529a2c3 100644 --- a/src/graph/executor/test/CMakeLists.txt +++ b/src/graph/executor/test/CMakeLists.txt @@ -94,15 +94,12 @@ nebula_add_test( FilterTest.cpp DedupTest.cpp LimitTest.cpp + FindPathTest.cpp SampleTest.cpp SortTest.cpp TopNTest.cpp AggregateTest.cpp JoinTest.cpp - BFSShortestTest.cpp - ConjunctPathTest.cpp - ProduceSemiShortestPathTest.cpp - ProduceAllPathsTest.cpp CartesianProductTest.cpp AssignTest.cpp ShowQueriesTest.cpp diff --git a/src/graph/executor/test/ConjunctPathTest.cpp b/src/graph/executor/test/ConjunctPathTest.cpp deleted file mode 100644 index b79a01a6c55..00000000000 --- a/src/graph/executor/test/ConjunctPathTest.cpp +++ /dev/null @@ -1,1271 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include - -#include "graph/context/QueryContext.h" -#include "graph/executor/algo/ConjunctPathExecutor.h" -#include "graph/planner/plan/Algo.h" -#include "graph/planner/plan/Logic.h" - -namespace nebula { -namespace graph { -class ConjunctPathTest : public testing::Test { - protected: - Path createPath(const std::string& src, const std::vector& steps, int type) { - Path path; - path.src = Vertex(src, {}); - for (auto& i : steps) { - path.steps.emplace_back(Step(Vertex(i, {}), type, "edge1", 0, {})); - } - return path; - } - static bool comparePath(Row& row1, Row& row2) { - // row : path| cost | - if (row1.values[1] != row2.values[1]) { - return row1.values[0] < row2.values[0]; - } - return row1[0] < row2[0]; - } - void multiplePairPathInit() { - /* - * overall path is : - * 0->1->5->7->8->9 - * 1->6->7 - * 2->6->7 - * 3->4->7 - * 7->10->11->12 - * startVids {0, 1, 2, 3} - * endVids {9, 12} - */ - qctx_->symTable()->newVariable("forwardPath1"); - qctx_->symTable()->newVariable("forwardPath2"); - qctx_->symTable()->newVariable("forwardPath3"); - qctx_->symTable()->newVariable("backwardPath1"); - qctx_->symTable()->newVariable("backwardPath2"); - qctx_->symTable()->newVariable("backwardPath3"); - qctx_->symTable()->newVariable("conditionalVar"); - { - DataSet ds; - ds.colNames = {kVid, kVid}; - std::vector dst = {"9", "12"}; - for (size_t i = 0; i < 4; i++) { - for (size_t j = 0; j < dst.size(); ++j) { - Row row; - row.values.emplace_back(folly::to(i)); - row.values.emplace_back(dst[j]); - ds.rows.emplace_back(std::move(row)); - } - } - qctx_->ectx()->setResult("conditionalVar", ResultBuilder().value(ds).build()); - } - { - DataSet ds; - auto cost = 1; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 0->1 - Row row; - Path path = createPath("0", {"1"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("1"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 1->5 - Row row; - Path path = createPath("1", {"5"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 1->6 - Row row; - Path path = createPath("1", {"6"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 2->6 - Row row; - Path path = createPath("2", {"6"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 3->4 - Row row; - Path path = createPath("3", {"4"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("4"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forwardPath1", ResultBuilder().value(ds).build()); - } - { - DataSet ds; - auto cost = 2; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 0->1->5 - Row row; - Path path = createPath("0", {"1", "5"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 0->1->6 - Row row; - Path path = createPath("0", {"1", "6"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 2->6->7 - Row row; - Path path = createPath("2", {"6", "7"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("7"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 3->4->7 - Row row; - Path path = createPath("3", {"4", "7"}, 1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("7"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 1->5->7, 1->6->7 - List paths; - for (auto i = 5; i < 7; i++) { - Path path = createPath("1", {folly::to(i), "7"}, 1); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("7"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forwardPath2", ResultBuilder().value(ds).build()); - } - { - DataSet ds; - auto cost = 3; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 0->1->5->7, 0->1->6->7 - List paths; - for (auto i = 5; i < 7; i++) { - Path path = createPath("0", {"1", folly::to(i), "7"}, 1); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("7"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 1->5->7->8, 1->6->7->8 - List paths; - for (auto i = 5; i < 7; i++) { - Path path = createPath("1", {folly::to(i), "7", "8"}, 1); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("8"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 2->6->7->8 - List paths; - Path path = createPath("2", {"6", "7", "8"}, 1); - paths.values.emplace_back(std::move(path)); - Row row; - row.values.emplace_back("8"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 3->4->7->8 - List paths; - Path path = createPath("3", {"4", "7", "8"}, 1); - paths.values.emplace_back(std::move(path)); - Row row; - row.values.emplace_back("8"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 1->5->7->10, 1->6->7->10 - List paths; - for (auto i = 5; i < 7; i++) { - Path path = createPath("1", {folly::to(i), "7", "10"}, 1); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("10"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 2->6->7->10 - List paths; - Path path = createPath("2", {"6", "7", "10"}, 1); - paths.values.emplace_back(std::move(path)); - Row row; - row.values.emplace_back("10"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 3->4->7->10 - List paths; - Path path = createPath("3", {"4", "7", "10"}, 1); - paths.values.emplace_back(std::move(path)); - Row row; - row.values.emplace_back("10"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forwardPath3", ResultBuilder().value(ds).build()); - } - // backward endVid {9, 12} - { - DataSet ds; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - Row row; - row.values = {"9", Value::kEmpty, 0, Value::kEmpty}; - ds.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"12", Value::kEmpty, 0, Value::kEmpty}; - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backwardPath1", ResultBuilder().value(ds).build()); - - DataSet ds1; - auto cost = 1; - ds1.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 9->8 - Row row; - Path path = createPath("9", {"8"}, -1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("8"); - row.values.emplace_back("9"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds1.rows.emplace_back(std::move(row)); - } - { - // 12->11 - Row row; - Path path = createPath("12", {"11"}, -1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("11"); - row.values.emplace_back("12"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backwardPath1", ResultBuilder().value(ds1).build()); - qctx_->ectx()->setResult("backwardPath2", ResultBuilder().value(ds1).build()); - } - { - DataSet ds; - auto cost = 2; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 9->8->7 - Row row; - Path path = createPath("9", {"8", "7"}, -1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("7"); - row.values.emplace_back("9"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - { - // 12->11->10 - Row row; - Path path = createPath("12", {"11", "10"}, -1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("10"); - row.values.emplace_back("12"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backwardPath2", ResultBuilder().value(ds).build()); - qctx_->ectx()->setResult("backwardPath3", ResultBuilder().value(ds).build()); - } - { - DataSet ds; - auto cost = 3; - ds.colNames = {kDst, kSrc, kCostStr, kPathStr}; - { - // 9->8->7->4, 9->8->7->5, 9->8->7->6 - for (auto i = 4; i < 7; i++) { - Row row; - Path path = createPath("9", {"8", "7", folly::to(i)}, -1); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back(folly::to(i)); - row.values.emplace_back("9"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - } - { - // 12->11->10->7 - List paths; - Path path = createPath("12", {"11", "10", "7"}, -1); - paths.values.emplace_back(std::move(path)); - Row row; - row.values.emplace_back("7"); - row.values.emplace_back("12"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backwardPath3", ResultBuilder().value(ds).build()); - } - } - void biBfsInit() { - qctx_->symTable()->newVariable("forward1"); - qctx_->symTable()->newVariable("backward1"); - qctx_->symTable()->newVariable("backward2"); - qctx_->symTable()->newVariable("backward3"); - qctx_->symTable()->newVariable("backward4"); - { - // 1->2 - // 1->3 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"1", Value::kEmpty}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forward1", ResultBuilder().value(ds1).build()); - - DataSet ds2; - ds2.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"2", Edge("1", "2", 1, "edge1", 0, {})}; - ds2.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"3", Edge("1", "3", 1, "edge1", 0, {})}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forward1", ResultBuilder().value(ds2).build()); - } - { - // 4 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"4", Value::kEmpty}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward1", ResultBuilder().value(ds1).build()); - - DataSet ds2; - ds2.colNames = {kVid, kEdgeStr}; - qctx_->ectx()->setResult("backward1", ResultBuilder().value(ds2).build()); - } - { - // 2 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"2", Value::kEmpty}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward2", ResultBuilder().value(ds1).build()); - - DataSet ds2; - ds2.colNames = {kVid, kEdgeStr}; - qctx_->ectx()->setResult("backward2", ResultBuilder().value(ds2).build()); - } - { - // 4->3 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"4", Value::kEmpty}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward3", ResultBuilder().value(ds1).build()); - - DataSet ds2; - ds2.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"3", Edge("4", "3", -1, "edge1", 0, {})}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward3", ResultBuilder().value(ds2).build()); - } - { - // 5->4 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"5", Value::kEmpty}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward4", ResultBuilder().value(ds1).build()); - - DataSet ds2; - ds2.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"4", Edge("5", "4", -1, "edge1", 0, {})}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward4", ResultBuilder().value(ds2).build()); - } - } - - void allPathInit() { - qctx_->symTable()->newVariable("all_paths_forward1"); - qctx_->symTable()->newVariable("all_paths_backward1"); - qctx_->symTable()->newVariable("all_paths_backward2"); - qctx_->symTable()->newVariable("all_paths_backward3"); - qctx_->symTable()->newVariable("all_paths_backward4"); - { - // 1->2 - // 1->3 - DataSet ds; - ds.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path = createPath("1", {"2"}, 1); - paths.values.emplace_back(std::move(path)); - row.values = {"2", std::move(paths)}; - ds.rows.emplace_back(std::move(row)); - } - { - Row row; - List paths; - Path path = createPath("1", {"3"}, 1); - paths.values.emplace_back(std::move(path)); - row.values = {"3", std::move(paths)}; - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_forward1", ResultBuilder().value(ds).build()); - } - { - // 4->7 - DataSet ds2; - ds2.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path = createPath("4", {"7"}, -1); - paths.values.emplace_back(std::move(path)); - row.values = {"7", std::move(paths)}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_backward1", ResultBuilder().value(ds2).build()); - } - { - // 2 - DataSet ds1; - ds1.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path; - path.src = Vertex("2", {}); - paths.values.emplace_back(std::move(path)); - row.values = {"2", std::move(paths)}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_backward2", ResultBuilder().value(ds1).build()); - - // 2->7 - DataSet ds2; - ds2.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path = createPath("2", {"7"}, -1); - paths.values.emplace_back(std::move(path)); - row.values = {"7", std::move(paths)}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_backward2", ResultBuilder().value(ds2).build()); - } - { - // 4->3 - DataSet ds2; - ds2.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path = createPath("4", {"3"}, -1); - paths.values.emplace_back(std::move(path)); - row.values = {"3", std::move(paths)}; - ds2.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_backward3", ResultBuilder().value(ds2).build()); - } - { - // 5->4 - DataSet ds; - ds.colNames = {kVid, kPathStr}; - { - Row row; - List paths; - Path path = createPath("5", {"4"}, -1); - paths.values.emplace_back(std::move(path)); - row.values = {"4", std::move(paths)}; - ds.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("all_paths_backward4", ResultBuilder().value(ds).build()); - } - } - - void SetUp() override { - qctx_ = std::make_unique(); - biBfsInit(); - multiplePairPathInit(); - allPathInit(); - } - - protected: - std::unique_ptr qctx_; -}; - -TEST_F(ConjunctPathTest, BiBFSNoPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kBiBFS, - 5); - conjunct->setLeftVar("forward1"); - conjunct->setRightVar("backward1"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, BiBFSOneStepPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kBiBFS, - 5); - conjunct->setLeftVar("forward1"); - conjunct->setRightVar("backward2"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, BiBFSTwoStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kBiBFS, - 5); - conjunct->setLeftVar("forward1"); - conjunct->setRightVar("backward3"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("3", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, BiBFSThreeStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kBiBFS, - 5); - conjunct->setLeftVar("forward1"); - conjunct->setRightVar("backward4"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - - { - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - { - { - // 2->4@0 - // 2->4@1 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"4", Edge("2", "4", 1, "edge1", 0, {})}; - ds1.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"4", Edge("2", "4", 1, "edge1", 1, {})}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forward1", ResultBuilder().value(ds1).build()); - } - { - // 4->6@0 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"6", Edge("4", "6", -1, "edge1", 0, {})}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward4", ResultBuilder().value(ds1).build()); - } - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - { - Row row; - Path path = createPath("1", {"2", "4", "5"}, 1); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 1, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ConjunctPathTest, BiBFSFourStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kBiBFS, - 5); - conjunct->setLeftVar("forward1"); - conjunct->setRightVar("backward4"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - - { - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - { - { - // 2->6@0 - // 2->6@1 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"6", Edge("2", "6", 1, "edge1", 0, {})}; - ds1.rows.emplace_back(std::move(row)); - } - { - Row row; - row.values = {"6", Edge("2", "6", 1, "edge1", 1, {})}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("forward1", ResultBuilder().value(ds1).build()); - } - { - // 4->6@0 - DataSet ds1; - ds1.colNames = {kVid, kEdgeStr}; - { - Row row; - row.values = {"6", Edge("4", "6", -1, "edge1", 0, {})}; - ds1.rows.emplace_back(std::move(row)); - } - qctx_->ectx()->setResult("backward4", ResultBuilder().value(ds1).build()); - } - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - { - Row row; - Path path = createPath("1", {"2", "6", "4", "5"}, 1); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 1, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ConjunctPathTest, AllPathsNoPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kAllPaths, - 5); - conjunct->setLeftVar("all_paths_forward1"); - conjunct->setRightVar("all_paths_backward1"); - conjunct->setColNames({kPathStr}); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, AllPathsOneStepPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kAllPaths, - 5); - conjunct->setLeftVar("all_paths_forward1"); - conjunct->setRightVar("all_paths_backward2"); - conjunct->setColNames({kPathStr}); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, AllPathsTwoStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kAllPaths, - 5); - conjunct->setLeftVar("all_paths_forward1"); - conjunct->setRightVar("all_paths_backward3"); - conjunct->setColNames({kPathStr}); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - Row row; - Path path = createPath("1", {"3", "4"}, 1); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, AllPathsThreeStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kAllPaths, - 5); - conjunct->setLeftVar("all_paths_forward1"); - conjunct->setRightVar("all_paths_backward4"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - - { - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - { - { - // 1->2->4@0 - // 1->2->4@1 - DataSet ds1; - ds1.colNames = {kVid, kPathStr}; - - Row row; - List paths; - { - Path path = createPath("1", {"2", "4"}, 1); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 1, {})); - paths.values.emplace_back(std::move(path)); - } - row.values = {"4", std::move(paths)}; - ds1.rows.emplace_back(std::move(row)); - qctx_->ectx()->setResult("all_paths_forward1", ResultBuilder().value(ds1).build()); - } - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - { - Row row; - Path path = createPath("1", {"2", "4", "5"}, 1); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 1, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ConjunctPathTest, AllPathsFourStepsPath) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kAllPaths, - 5); - conjunct->setLeftVar("all_paths_forward1"); - conjunct->setRightVar("all_paths_backward4"); - conjunct->setColNames({kPathStr}); - - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - - { - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - { - { - // 1->2->6@0 - // 1->2->6@1 - DataSet ds1; - ds1.colNames = {kVid, kPathStr}; - - Row row; - List paths; - { - Path path = createPath("1", {"2", "6"}, 1); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 1, {})); - paths.values.emplace_back(std::move(path)); - } - row.values = {"6", std::move(paths)}; - ds1.rows.emplace_back(std::move(row)); - qctx_->ectx()->setResult("all_paths_forward1", ResultBuilder().value(ds1).build()); - } - { - // 5->4->6@0 - DataSet ds1; - ds1.colNames = {kVid, kPathStr}; - - Row row; - List paths; - { - Path path; - path.src = Vertex("5", {}); - path.steps.emplace_back(Step(Vertex("4", {}), -1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), -1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - row.values = {"6", std::move(paths)}; - ds1.rows.emplace_back(std::move(row)); - qctx_->ectx()->setResult("all_paths_backward4", ResultBuilder().value(ds1).build()); - } - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr}; - { - Row row; - Path path = createPath("1", {"2", "6", "4", "5"}, 1); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("2", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 1, {})); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - row.values.emplace_back(std::move(path)); - expected.rows.emplace_back(std::move(row)); - } - - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ConjunctPathTest, multiplePairOneStep) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kFloyd, - 5); - conjunct->setLeftVar("forwardPath1"); - conjunct->setRightVar("backwardPath1"); - conjunct->setColNames({kPathStr, kCostStr}); - conjunct->setConditionalVar("conditionalVar"); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr, kCostStr}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, multiplePairTwoSteps) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kFloyd, - 5); - conjunct->setLeftVar("forwardPath2"); - conjunct->setRightVar("backwardPath2"); - conjunct->setColNames({kPathStr, kCostStr}); - conjunct->setConditionalVar("conditionalVar"); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr, kCostStr}; - { - // 1->5->7->8->9, 1->6->7->8->9 - for (auto i = 5; i < 7; i++) { - Row row; - Path path = createPath("1", {folly::to(i), "7", "8", "9"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(4); - expected.rows.emplace_back(std::move(row)); - } - } - { - // 2->6->7->8->9 - Row row; - Path path = createPath("2", {"6", "7", "8", "9"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(4); - expected.rows.emplace_back(std::move(row)); - } - { - // 3->4->7->8->9 - Row row; - Path path = createPath("3", {"4", "7", "8", "9"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(4); - expected.rows.emplace_back(std::move(row)); - } - std::sort(expected.rows.begin(), expected.rows.end(), comparePath); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end(), comparePath); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -TEST_F(ConjunctPathTest, multiplePairThreeSteps) { - auto* conjunct = ConjunctPath::make(qctx_.get(), - StartNode::make(qctx_.get()), - StartNode::make(qctx_.get()), - ConjunctPath::PathKind::kFloyd, - 5); - conjunct->setLeftVar("forwardPath3"); - conjunct->setRightVar("backwardPath3"); - conjunct->setColNames({kPathStr, kCostStr}); - conjunct->setConditionalVar("conditionalVar"); - auto conjunctExe = std::make_unique(conjunct, qctx_.get()); - auto future = conjunctExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(conjunct->outputVar()); - - DataSet expected; - expected.colNames = {kPathStr, kCostStr}; - { - // 1->5->7->10->11->12, 1->6->7->10->11->12 - for (auto i = 5; i < 7; i++) { - Row row; - Path path = createPath("1", {folly::to(i), "7", "10", "11", "12"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(5); - expected.rows.emplace_back(std::move(row)); - } - } - { - // 0->1->5->7->8->9, 0->1->6->7->8->9 - for (auto i = 5; i < 7; i++) { - Row row; - Path path = createPath("0", {"1", folly::to(i), "7", "8", "9"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(5); - expected.rows.emplace_back(std::move(row)); - } - } - { - // 2->6->7->10->11->12 - Row row; - Path path = createPath("2", {"6", "7", "10", "11", "12"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(5); - expected.rows.emplace_back(std::move(row)); - } - { - // 3->4->7->10->11->12 - Row row; - Path path = createPath("3", {"4", "7", "10", "11", "12"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(5); - expected.rows.emplace_back(std::move(row)); - } - { - // 0->1->5->7->10->11->12, 0->1->6->7->10->11->12 - for (auto i = 5; i < 7; i++) { - Row row; - Path path = createPath("0", {"1", folly::to(i), "7", "10", "11", "12"}, 1); - row.values.emplace_back(std::move(path)); - row.values.emplace_back(6); - expected.rows.emplace_back(std::move(row)); - } - } - std::sort(expected.rows.begin(), expected.rows.end(), comparePath); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end(), comparePath); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -} // namespace graph -} // namespace nebula diff --git a/src/graph/executor/test/FindPathTest.cpp b/src/graph/executor/test/FindPathTest.cpp new file mode 100644 index 00000000000..ec227e40f47 --- /dev/null +++ b/src/graph/executor/test/FindPathTest.cpp @@ -0,0 +1,1076 @@ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#include + +#include "graph/context/QueryContext.h" +#include "graph/executor/algo/BFSShortestPathExecutor.h" +#include "graph/executor/algo/MultiShortestPathExecutor.h" +#include "graph/executor/algo/ProduceAllPathsExecutor.h" +#include "graph/planner/plan/Algo.h" +#include "graph/planner/plan/Logic.h" + +namespace nebula { +namespace graph { +class FindPathTest : public testing::Test { + protected: + Path createPath(const std::vector& steps) { + Path path; + path.src = Vertex(steps[0], {}); + for (size_t i = 1; i < steps.size(); ++i) { + path.steps.emplace_back(Step(Vertex(steps[i], {}), EDGE_TYPE, "like", EDGE_RANK, {})); + } + return path; + } + // Topology is below + // a->b, a->c + // b->a, b->c + // c->a, c->f, c->g + // d->a, d->c, d->e + // e->b + // f->h + // g->f, g->h, g->k + // h->x, k->x + void singleSourceInit() { + // From: {a} To: {x} + { // 1 step + // From: a->b, a->c + DataSet ds; + ds.colNames = gnColNames_; + Row row; + row.values.emplace_back("a"); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : {"b", "c"}) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + single1StepFrom_ = std::move(ds); + + // To: x<-h, x<-k + DataSet ds1; + ds1.colNames = gnColNames_; + Row row1; + row1.values.emplace_back("x"); + row1.values.emplace_back(Value()); + List edges1; + for (const auto& dst : {"h", "k"}) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges1.values.emplace_back(std::move(edge)); + } + row1.values.emplace_back(edges1); + row1.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row1)); + single1StepTo_ = std::move(ds1); + } + { // 2 step + // From: b->a, b->c, c->a, c->f, c->g + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data( + {{"b", {"a", "c"}}, {"c", {"a", "f", "g"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + single2StepFrom_ = std::move(ds); + + // To : h<-f, h<-g, k<-g + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"h", {"f", "g"}}, {"k", {"g"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + single2StepTo_ = std::move(ds1); + } + } + + void mulitSourceInit() { + // From {a, d} To {x, k} + { // 1 step + // From: a->b, a->c, d->c, d->a, d->e + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data( + {{"a", {"b", "c"}}, {"d", {"a", "c", "e"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + multi1StepFrom_ = std::move(ds); + + // To: x<-h, x<-k, k<-g + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"x", {"h", "k"}}, {"k", {"g"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + multi1StepTo_ = std::move(ds1); + } + { // 2 step + // From: b->a, b->c, c->a, c->f, c->g, e->b, a->b, a->c + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data( + {{"b", {"a", "c"}}, {"c", {"a", "f", "g"}}, {"e", {"b"}}, {"a", {"b", "c"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + multi2StepFrom_ = std::move(ds); + + // To : h<-f, h<-g, k<-g, g<-c + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"h", {"f", "g"}}, {"k", {"g"}}, {"g", {"c"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + multi2StepTo_ = std::move(ds1); + } + } + + void allPathInit() { + // From {a, d} To {x, k} + { // 1 step + // From: a->b, a->c, d->c, d->a, d->e + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data( + {{"a", {"b", "c"}}, {"d", {"a", "c", "e"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + all1StepFrom_ = std::move(ds); + + // To: x<-h, x<-k, k<-g + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"x", {"h", "k"}}, {"k", {"g"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + all1StepTo_ = std::move(ds1); + } + { // 2 step + // From: b->a, b->c, c->a, c->f, c->g, e->b, a->b, a->c + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data( + {{"b", {"a", "c"}}, {"c", {"a", "f", "g"}}, {"e", {"b"}}, {"a", {"b", "c"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + all2StepFrom_ = std::move(ds); + + // To : h<-f, h<-g, k<-g, g<-c + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"h", {"f", "g"}}, {"k", {"g"}}, {"g", {"c"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + all2StepTo_ = std::move(ds1); + } + { // 3 step + // From: b->a, b->c, c->a, c->f, c->g, a->b, a->c, f->h, g->f, g->k, g->h + DataSet ds; + ds.colNames = gnColNames_; + std::unordered_map> data({{"b", {"a", "c"}}, + {"c", {"a", "f", "g"}}, + {"f", {"h"}}, + {"a", {"b", "c"}}, + {"g", {"h", "f", "k"}}}); + for (const auto& src : data) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds.rows.emplace_back(std::move(row)); + } + all3StepFrom_ = std::move(ds); + + // To : f<-c, f<-g, g<-c, c<-a, c<-b, c<-d + DataSet ds1; + ds1.colNames = gnColNames_; + std::unordered_map> data1( + {{"c", {"a", "b", "d"}}, {"f", {"c", "g"}}, {"g", {"c"}}}); + for (const auto& src : data1) { + Row row; + row.values.emplace_back(src.first); + row.values.emplace_back(Value()); + List edges; + for (const auto& dst : src.second) { + List edge; + edge.values.emplace_back(-EDGE_TYPE); + edge.values.emplace_back(dst); + edge.values.emplace_back(EDGE_RANK); + edges.values.emplace_back(std::move(edge)); + } + row.values.emplace_back(edges); + row.values.emplace_back(Value()); + ds1.rows.emplace_back(std::move(row)); + } + all3StepTo_ = std::move(ds1); + } + } + + void SetUp() override { + qctx_ = std::make_unique(); + singleSourceInit(); + mulitSourceInit(); + allPathInit(); + } + + protected: + std::unique_ptr qctx_; + const int EDGE_TYPE = 1; + const int EDGE_RANK = 0; + DataSet single1StepFrom_; + DataSet single1StepTo_; + DataSet single2StepFrom_; + DataSet single2StepTo_; + DataSet multi1StepFrom_; + DataSet multi1StepTo_; + DataSet multi2StepFrom_; + DataSet multi2StepTo_; + DataSet all1StepFrom_; + DataSet all1StepTo_; + DataSet all2StepFrom_; + DataSet all2StepTo_; + DataSet all3StepFrom_; + DataSet all3StepTo_; + const std::vector pathColNames_ = {"path"}; + const std::vector gnColNames_ = { + kVid, "_stats", "_edge:+like:_type:_dst:_rank", "_expr"}; +}; + +TEST_F(FindPathTest, singleSourceShortestPath) { + int steps = 5; + std::string leftVidVar = "leftVid"; + std::string rightVidVar = "rightVid"; + std::string fromGNInput = "fromGNInput"; + std::string toGNInput = "toGNInput"; + qctx_->symTable()->newVariable(fromGNInput); + qctx_->symTable()->newVariable(toGNInput); + { + qctx_->symTable()->newVariable(leftVidVar); + DataSet fromVid; + fromVid.colNames = {nebula::kVid}; + Row row; + row.values.emplace_back("a"); + fromVid.rows.emplace_back(std::move(row)); + ResultBuilder builder; + builder.value(std::move(fromVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(leftVidVar, builder.build()); + } + { + qctx_->symTable()->newVariable(rightVidVar); + DataSet toVid; + toVid.colNames = {nebula::kVid}; + Row row; + row.values.emplace_back("x"); + toVid.rows.emplace_back(std::move(row)); + ResultBuilder builder; + builder.value(std::move(toVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(rightVidVar, builder.build()); + } + auto fromGN = StartNode::make(qctx_.get()); + auto toGN = StartNode::make(qctx_.get()); + + auto* path = BFSShortestPath::make(qctx_.get(), fromGN, toGN, steps); + path->setLeftVar(fromGNInput); + path->setRightVar(toGNInput); + path->setLeftVidVar(leftVidVar); + path->setRightVidVar(rightVidVar); + path->setColNames(pathColNames_); + + auto pathExe = std::make_unique(path, qctx_.get()); + // Step 1 + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(single1StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(single1StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + auto resultDs = result.value().getDataSet(); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"b", "c"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"h", "k"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } + // 2 Step + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(single2StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(single2StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + std::vector> paths( + {{"a", "c", "f", "h", "x"}, {"a", "c", "g", "h", "x"}, {"a", "c", "g", "k", "x"}}); + for (const auto& p : paths) { + Row row; + row.values.emplace_back(createPath(p)); + expected.rows.emplace_back(std::move(row)); + } + auto resultDs = result.value().getDataSet(); + std::sort(expected.rows.begin(), expected.rows.end()); + std::sort(resultDs.rows.begin(), resultDs.rows.end()); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } +} + +TEST_F(FindPathTest, multiSourceShortestPath) { + int steps = 5; + std::string leftVidVar = "leftVid"; + std::string rightVidVar = "rightVid"; + std::string fromGNInput = "fromGNInput"; + std::string toGNInput = "toGNInput"; + qctx_->symTable()->newVariable(fromGNInput); + qctx_->symTable()->newVariable(toGNInput); + { + qctx_->symTable()->newVariable(leftVidVar); + DataSet fromVid; + fromVid.colNames = {nebula::kVid}; + Row row, row1; + row.values.emplace_back("a"); + row1.values.emplace_back("d"); + fromVid.rows.emplace_back(std::move(row)); + fromVid.rows.emplace_back(std::move(row1)); + ResultBuilder builder; + builder.value(std::move(fromVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(leftVidVar, builder.build()); + } + { + qctx_->symTable()->newVariable(rightVidVar); + DataSet toVid; + toVid.colNames = {nebula::kVid}; + Row row, row1; + row.values.emplace_back("x"); + row1.values.emplace_back("k"); + toVid.rows.emplace_back(std::move(row)); + toVid.rows.emplace_back(std::move(row1)); + ResultBuilder builder; + builder.value(std::move(toVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(rightVidVar, builder.build()); + } + auto fromGN = StartNode::make(qctx_.get()); + auto toGN = StartNode::make(qctx_.get()); + + auto* path = MultiShortestPath::make(qctx_.get(), fromGN, toGN, steps); + path->setLeftVar(fromGNInput); + path->setRightVar(toGNInput); + path->setLeftVidVar(leftVidVar); + path->setRightVidVar(rightVidVar); + path->setColNames(pathColNames_); + + auto pathExe = std::make_unique(path, qctx_.get()); + // Step 1 + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(multi1StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(multi1StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + auto resultDs = result.value().getDataSet(); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"b", "c", "a", "e"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"h", "k", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } + // 2 Step + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(multi2StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(multi2StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + std::vector> paths({{"a", "c", "f", "h", "x"}, + {"a", "c", "g", "h", "x"}, + {"a", "c", "g", "k", "x"}, + {"a", "c", "g", "k"}, + {"d", "c", "f", "h", "x"}, + {"d", "c", "g", "h", "x"}, + {"d", "c", "g", "k", "x"}, + {"d", "c", "g", "k"}}); + for (const auto& p : paths) { + Row row; + row.values.emplace_back(createPath(p)); + expected.rows.emplace_back(std::move(row)); + } + auto resultDs = result.value().getDataSet(); + std::sort(expected.rows.begin(), expected.rows.end()); + std::sort(resultDs.rows.begin(), resultDs.rows.end()); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"a", "b", "c", "f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"c", "f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } +} + +TEST_F(FindPathTest, allPath) { + bool noLoop = false; + int steps = 5; + std::string leftVidVar = "leftVid"; + std::string rightVidVar = "rightVid"; + std::string fromGNInput = "fromGNInput"; + std::string toGNInput = "toGNInput"; + qctx_->symTable()->newVariable(fromGNInput); + qctx_->symTable()->newVariable(toGNInput); + { + qctx_->symTable()->newVariable(leftVidVar); + DataSet fromVid; + fromVid.colNames = {nebula::kVid}; + Row row, row1; + row.values.emplace_back("a"); + row1.values.emplace_back("d"); + fromVid.rows.emplace_back(std::move(row)); + fromVid.rows.emplace_back(std::move(row1)); + ResultBuilder builder; + builder.value(std::move(fromVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(leftVidVar, builder.build()); + } + { + qctx_->symTable()->newVariable(rightVidVar); + DataSet toVid; + toVid.colNames = {nebula::kVid}; + Row row, row1; + row.values.emplace_back("x"); + row1.values.emplace_back("k"); + toVid.rows.emplace_back(std::move(row)); + toVid.rows.emplace_back(std::move(row1)); + ResultBuilder builder; + builder.value(std::move(toVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(rightVidVar, builder.build()); + } + auto fromGN = StartNode::make(qctx_.get()); + auto toGN = StartNode::make(qctx_.get()); + + auto* path = ProduceAllPaths::make(qctx_.get(), fromGN, toGN, steps, noLoop); + path->setLeftVar(fromGNInput); + path->setRightVar(toGNInput); + path->setLeftVidVar(leftVidVar); + path->setRightVidVar(rightVidVar); + path->setColNames(pathColNames_); + + auto pathExe = std::make_unique(path, qctx_.get()); + // Step 1 + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all1StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all1StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + auto resultDs = result.value().getDataSet(); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"b", "c", "a", "e"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"h", "k", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } + // 2 Step + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all2StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all2StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + std::vector> paths({{"a", "b", "c", "g", "k"}, + {"a", "c", "f", "h", "x"}, + {"a", "c", "g", "h", "x"}, + {"a", "c", "g", "k", "x"}, + {"a", "c", "g", "k"}, + {"d", "c", "f", "h", "x"}, + {"d", "a", "c", "g", "k"}, + {"d", "c", "g", "h", "x"}, + {"d", "c", "g", "k", "x"}, + {"d", "c", "g", "k"}}); + for (const auto& p : paths) { + Row row; + row.values.emplace_back(createPath(p)); + expected.rows.emplace_back(std::move(row)); + } + auto resultDs = result.value().getDataSet(); + std::sort(expected.rows.begin(), expected.rows.end()); + std::sort(resultDs.rows.begin(), resultDs.rows.end()); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"a", "b", "c", "f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"c", "f", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } + // 3 Step + { + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all3StepFrom_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + List datasets; + datasets.values.emplace_back(std::move(all3StepTo_)); + builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + std::vector> paths({{"a", "b", "a", "c", "g", "k"}, + {"a", "b", "c", "f", "h", "x"}, + {"a", "b", "c", "g", "h", "x"}, + {"a", "b", "c", "g", "k", "x"}, + {"a", "c", "g", "f", "h", "x"}, + {"d", "a", "b", "c", "g", "k"}, + {"d", "a", "c", "f", "h", "x"}, + {"d", "a", "c", "g", "h", "x"}, + {"d", "a", "c", "g", "k", "x"}, + {"d", "c", "a", "c", "g", "k"}, + {"d", "c", "g", "f", "h", "x"}, + {"d", "e", "b", "c", "g", "k"}}); + for (const auto& p : paths) { + Row row; + row.values.emplace_back(createPath(p)); + expected.rows.emplace_back(std::move(row)); + } + auto resultDs = result.value().getDataSet(); + std::sort(expected.rows.begin(), expected.rows.end()); + std::sort(resultDs.rows.begin(), resultDs.rows.end()); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + for (const auto& vid : {"a", "b", "c", "f", "g", "h", "k"}) { + Row row; + row.values.emplace_back(vid); + expectLeftVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + std::sort(resultLeftVid.rows.begin(), resultLeftVid.rows.end()); + std::sort(expectLeftVid.rows.begin(), expectLeftVid.rows.end()); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + for (const auto& vid : {"a", "b", "c", "d", "g"}) { + Row row; + row.values.emplace_back(vid); + expectRightVid.rows.emplace_back(std::move(row)); + } + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + std::sort(resultRightVid.rows.begin(), resultRightVid.rows.end()); + std::sort(expectRightVid.rows.begin(), expectRightVid.rows.end()); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + } +} + +TEST_F(FindPathTest, empthInput) { + int steps = 5; + std::string leftVidVar = "leftVid"; + std::string rightVidVar = "rightVid"; + std::string fromGNInput = "fromGNInput"; + std::string toGNInput = "toGNInput"; + qctx_->symTable()->newVariable(fromGNInput); + qctx_->symTable()->newVariable(toGNInput); + { + qctx_->symTable()->newVariable(leftVidVar); + DataSet fromVid; + fromVid.colNames = {nebula::kVid}; + ResultBuilder builder; + builder.value(std::move(fromVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(leftVidVar, builder.build()); + } + { + qctx_->symTable()->newVariable(rightVidVar); + DataSet toVid; + toVid.colNames = {nebula::kVid}; + ResultBuilder builder; + builder.value(std::move(toVid)).iter(Iterator::Kind::kSequential); + qctx_->ectx()->setResult(rightVidVar, builder.build()); + } + auto fromGN = StartNode::make(qctx_.get()); + auto toGN = StartNode::make(qctx_.get()); + + auto* path = BFSShortestPath::make(qctx_.get(), fromGN, toGN, steps); + path->setLeftVar(fromGNInput); + path->setRightVar(toGNInput); + path->setLeftVidVar(leftVidVar); + path->setRightVidVar(rightVidVar); + path->setColNames(pathColNames_); + + auto pathExe = std::make_unique(path, qctx_.get()); + { + ResultBuilder builder; + DataSet ds; + ds.colNames = gnColNames_; + builder.value(std::move(ds)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(fromGNInput, builder.build()); + } + { + ResultBuilder builder; + DataSet ds; + ds.colNames = gnColNames_; + builder.value(std::move(ds)).iter(Iterator::Kind::kGetNeighbors); + qctx_->ectx()->setResult(toGNInput, builder.build()); + } + auto future = pathExe->execute(); + auto status = std::move(future).get(); + EXPECT_TRUE(status.ok()); + auto& result = qctx_->ectx()->getResult(path->outputVar()); + + DataSet expected; + expected.colNames = pathColNames_; + auto resultDs = result.value().getDataSet(); + EXPECT_EQ(resultDs, expected); + EXPECT_EQ(result.state(), Result::State::kSuccess); + { + DataSet expectLeftVid; + expectLeftVid.colNames = {nebula::kVid}; + auto& resultVid = qctx_->ectx()->getResult(leftVidVar); + auto resultLeftVid = resultVid.value().getDataSet(); + EXPECT_EQ(resultLeftVid, expectLeftVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } + { + DataSet expectRightVid; + expectRightVid.colNames = {nebula::kVid}; + auto& resultVid = qctx_->ectx()->getResult(rightVidVar); + auto resultRightVid = resultVid.value().getDataSet(); + EXPECT_EQ(resultRightVid, expectRightVid); + EXPECT_EQ(result.state(), Result::State::kSuccess); + } +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/test/ProduceAllPathsTest.cpp b/src/graph/executor/test/ProduceAllPathsTest.cpp deleted file mode 100644 index ebce7928286..00000000000 --- a/src/graph/executor/test/ProduceAllPathsTest.cpp +++ /dev/null @@ -1,470 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include - -#include "graph/context/QueryContext.h" -#include "graph/executor/algo/ProduceAllPathsExecutor.h" -#include "graph/planner/plan/Algo.h" - -namespace nebula { -namespace graph { -class ProduceAllPathsTest : public testing::Test { - protected: - static bool compareAllPathRow(Row& lhs, Row& rhs) { - auto& lDst = lhs.values[0]; - auto& rDst = rhs.values[0]; - if (lDst != rDst) { - return lDst < rDst; - } - - auto& lEdges = lhs.values[1].getList(); - auto& rEdges = rhs.values[1].getList(); - if (lEdges != rEdges) { - return lEdges < rEdges; - } - return false; - } - - static ::testing::AssertionResult verifyAllPaths(DataSet& result, DataSet& expected) { - std::sort(expected.rows.begin(), expected.rows.end(), compareAllPathRow); - for (auto& row : expected.rows) { - auto& edges = row.values[1].mutableList().values; - std::sort(edges.begin(), edges.end()); - } - std::sort(result.rows.begin(), result.rows.end(), compareAllPathRow); - for (auto& row : result.rows) { - auto& edges = row.values[1].mutableList().values; - std::sort(edges.begin(), edges.end()); - } - EXPECT_EQ(result, expected); - return result == expected ? ::testing::AssertionSuccess() - : (::testing::AssertionFailure() << result << " vs. " << expected); - } - - void SetUp() override { - qctx_ = std::make_unique(); - /* - * 0->1->5->7; - * 1->6->7 - * 2->6->7 - * 3->4->7 - * startVids {0, 1, 2, 3} - */ - { - DataSet ds1; - ds1.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 0->1 - Row row; - row.values.emplace_back("0"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("1"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 1->5, 1->6; - Row row; - row.values.emplace_back("1"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto i = 5; i < 7; i++) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(i)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 2->6 - Row row; - row.values.emplace_back("2"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("6"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 3->4 - Row row; - row.values.emplace_back("3"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("4"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - firstStepResult_ = std::move(ds1); - - DataSet ds2; - ds2.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 1->5, 1->6; - Row row; - row.values.emplace_back("1"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto i = 5; i < 7; i++) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(i)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - { - // 4->7, 5->7, 6->7 - for (auto i = 4; i < 7; i++) { - Row row; - row.values.emplace_back(folly::to(i)); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("7"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - } - secondStepResult_ = std::move(ds2); - - DataSet ds3; - ds3.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 5->7, 6->7 - for (auto i = 5; i < 7; i++) { - Row row; - row.values.emplace_back(folly::to(i)); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("7"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds3.rows.emplace_back(std::move(row)); - } - } - thirdStepResult_ = std::move(ds3); - - { - DataSet ds; - ds.colNames = {kVid, - "_stats", - "_tag:tag1:prop1:prop2", - "_edge:+edge1:prop1:prop2:_dst:_rank", - "_expr"}; - qctx_->symTable()->newVariable("empty_get_neighbors"); - qctx_->ectx()->setResult("empty_get_neighbors", - ResultBuilder() - .value(Value(std::move(ds))) - .iter(Iterator::Kind::kGetNeighbors) - .build()); - } - } - } - - protected: - std::unique_ptr qctx_; - DataSet firstStepResult_; - DataSet secondStepResult_; - DataSet thirdStepResult_; -}; - -TEST_F(ProduceAllPathsTest, AllPath) { - qctx_->symTable()->newVariable("input"); - - auto* allPathsNode = ProduceAllPaths::make(qctx_.get(), nullptr); - allPathsNode->setInputVar("input"); - allPathsNode->setColNames({kDst, "_paths"}); - - auto allPathsExe = std::make_unique(allPathsNode, qctx_.get()); - - // Step1 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(firstStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = allPathsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(allPathsNode->outputVar()); - - DataSet expected; - expected.colNames = {kDst, "_paths"}; - { - // 0->1 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("1"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 1->5 - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 1->6 - Row row; - List paths; - - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("2", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - - row.values.emplace_back("6"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 3->4 - Row row; - Path path; - path.src = Vertex("3", {}); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("4"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - auto resultDs = result.value().getDataSet(); - EXPECT_TRUE(verifyAllPaths(resultDs, expected)); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - // Step 2 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(secondStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = allPathsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(allPathsNode->outputVar()); - - DataSet expected; - expected.colNames = {kDst, "_paths"}; - { - // 0->1->5 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 0->1->6 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - Row row; - List paths; - - // 2->6->7 - { - Path path; - path.src = Vertex("2", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - // 3->4->7 - { - Path path; - path.src = Vertex("3", {}); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - // 1->5->7 - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - // 1->6->7 - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - - row.values.emplace_back("7"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - auto resultDs = result.value().getDataSet(); - EXPECT_TRUE(verifyAllPaths(resultDs, expected)); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - // Step3 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(thirdStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = allPathsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(allPathsNode->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_paths"}; - { - // 0->1->5->7, 0->1->6->7 - List paths; - { - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("7"); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - auto resultDs = result.value().getDataSet(); - EXPECT_TRUE(verifyAllPaths(resultDs, expected)); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ProduceAllPathsTest, EmptyInput) { - auto* allPathsNode = ProduceAllPaths::make(qctx_.get(), nullptr); - allPathsNode->setInputVar("empty_get_neighbors"); - allPathsNode->setColNames({kDst, "_paths"}); - - auto allPathsExe = std::make_unique(allPathsNode, qctx_.get()); - auto future = allPathsExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(allPathsNode->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_paths"}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} -} // namespace graph -} // namespace nebula diff --git a/src/graph/executor/test/ProduceSemiShortestPathTest.cpp b/src/graph/executor/test/ProduceSemiShortestPathTest.cpp deleted file mode 100644 index 963d7d2f453..00000000000 --- a/src/graph/executor/test/ProduceSemiShortestPathTest.cpp +++ /dev/null @@ -1,509 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include - -#include "graph/context/QueryContext.h" -#include "graph/executor/algo/ProduceSemiShortestPathExecutor.h" -#include "graph/planner/plan/Algo.h" - -namespace nebula { -namespace graph { - -class ProduceSemiShortestPathTest : public testing::Test { - protected: - static bool compareShortestPath(Row& row1, Row& row2) { - // row : dst | src | cost | {paths} - if (row1.values[0] != row2.values[0]) { - return row1.values[0] < row2.values[0]; - } - if (row1.values[1] != row2.values[1]) { - return row1.values[1] < row2.values[1]; - } - if (row1.values[2] != row2.values[2]) { - return row1.values[2] < row2.values[2]; - } - auto& pathList1 = row1.values[3].getList(); - auto& pathList2 = row2.values[3].getList(); - if (pathList1.size() != pathList2.size()) { - return pathList1.size() < pathList2.size(); - } - for (size_t i = 0; i < pathList1.size(); i++) { - if (pathList1.values[i] != pathList2.values[i]) { - return pathList1.values[i] < pathList2.values[i]; - } - } - return false; - } - - void SetUp() override { - qctx_ = std::make_unique(); - /* - * 0->1->5->7; - * 1->6->7 - * 2->6->7 - * 3->4->7 - * startVids {0, 1, 2, 3} - */ - { - DataSet ds1; - ds1.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 0->1 - Row row; - row.values.emplace_back("0"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("1"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 1->5, 1->6; - Row row; - row.values.emplace_back("1"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto i = 5; i < 7; i++) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(i)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 2->6 - Row row; - row.values.emplace_back("2"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("6"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - { - // 3->4 - Row row; - row.values.emplace_back("3"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("4"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds1.rows.emplace_back(std::move(row)); - } - firstStepResult_ = std::move(ds1); - - DataSet ds2; - ds2.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 1->5, 1->6; - Row row; - row.values.emplace_back("1"); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - for (auto i = 5; i < 7; i++) { - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back(folly::to(i)); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - } - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - { - // 4->7, 5->7, 6->7 - for (auto i = 4; i < 7; i++) { - Row row; - row.values.emplace_back(folly::to(i)); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("7"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds2.rows.emplace_back(std::move(row)); - } - } - secondStepResult_ = std::move(ds2); - - DataSet ds3; - ds3.colNames = {kVid, "_stats", "_edge:+edge1:_type:_dst:_rank", "_expr"}; - { - // 5->7, 6->7 - for (auto i = 5; i < 7; i++) { - Row row; - row.values.emplace_back(folly::to(i)); - // _stats = empty - row.values.emplace_back(Value()); - // edges - List edges; - List edge; - edge.values.emplace_back(1); - edge.values.emplace_back("7"); - edge.values.emplace_back(0); - edges.values.emplace_back(std::move(edge)); - row.values.emplace_back(edges); - // _expr = empty - row.values.emplace_back(Value()); - ds3.rows.emplace_back(std::move(row)); - } - } - thirdStepResult_ = std::move(ds3); - - { - DataSet ds; - ds.colNames = {kVid, - "_stats", - "_tag:tag1:prop1:prop2", - "_edge:+edge1:prop1:prop2:_dst:_rank", - "_expr"}; - qctx_->symTable()->newVariable("empty_get_neighbors"); - qctx_->ectx()->setResult("empty_get_neighbors", - ResultBuilder() - .value(Value(std::move(ds))) - .iter(Iterator::Kind::kGetNeighbors) - .build()); - } - } - } - - protected: - std::unique_ptr qctx_; - DataSet firstStepResult_; - DataSet secondStepResult_; - DataSet thirdStepResult_; -}; - -TEST_F(ProduceSemiShortestPathTest, ShortestPath) { - qctx_->symTable()->newVariable("input"); - - auto* pssp = ProduceSemiShortestPath::make(qctx_.get(), nullptr); - pssp->setInputVar("input"); - pssp->setColNames({"_dst", "_src", "cost", "paths"}); - - auto psspExe = std::make_unique(pssp, qctx_.get()); - // Step 1 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(firstStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = psspExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(pssp->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_src", "cost", "paths"}; - auto cost = 1; - { - // 0->1 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("1"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 1->5 - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 1->6 - Row row; - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 2->6 - Row row; - Path path; - path.src = Vertex("2", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 3->4 - Row row; - Path path; - path.src = Vertex("3", {}); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("4"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - std::sort(expected.rows.begin(), expected.rows.end(), compareShortestPath); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end(), compareShortestPath); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - // Step 2 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(secondStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = psspExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(pssp->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_src", "cost", "paths"}; - auto cost = 2; - { - // 0->1->5 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("5"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 0->1->6 - Row row; - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("6"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 2->6->7 - Row row; - Path path; - path.src = Vertex("2", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("7"); - row.values.emplace_back("2"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 3->4->7 - Row row; - Path path; - path.src = Vertex("3", {}); - path.steps.emplace_back(Step(Vertex("4", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - - List paths; - paths.values.emplace_back(std::move(path)); - row.values.emplace_back("7"); - row.values.emplace_back("3"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - { - // 1->5->7, 1->6->7 - List paths; - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("1", {}); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("7"); - row.values.emplace_back("1"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - std::sort(expected.rows.begin(), expected.rows.end(), compareShortestPath); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end(), compareShortestPath); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } - - // Step3 - { - ResultBuilder builder; - List datasets; - datasets.values.emplace_back(std::move(thirdStepResult_)); - builder.value(std::move(datasets)).iter(Iterator::Kind::kGetNeighbors); - qctx_->ectx()->setResult("input", builder.build()); - - auto future = psspExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(pssp->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_src", "cost", "paths"}; - auto cost = 3; - { - // 0->1->5->7, 0->1->6->7 - List paths; - { - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("5", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - { - Path path; - path.src = Vertex("0", {}); - path.steps.emplace_back(Step(Vertex("1", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("6", {}), 1, "edge1", 0, {})); - path.steps.emplace_back(Step(Vertex("7", {}), 1, "edge1", 0, {})); - paths.values.emplace_back(std::move(path)); - } - Row row; - row.values.emplace_back("7"); - row.values.emplace_back("0"); - row.values.emplace_back(cost); - row.values.emplace_back(std::move(paths)); - expected.rows.emplace_back(std::move(row)); - } - - std::sort(expected.rows.begin(), expected.rows.end(), compareShortestPath); - auto resultDs = result.value().getDataSet(); - std::sort(resultDs.rows.begin(), resultDs.rows.end(), compareShortestPath); - EXPECT_EQ(resultDs, expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); - } -} - -TEST_F(ProduceSemiShortestPathTest, EmptyInput) { - auto* pssp = ProduceSemiShortestPath::make(qctx_.get(), nullptr); - pssp->setInputVar("empty_get_neighbors"); - pssp->setColNames({"_dst", "_src", "cost", "paths"}); - - auto psspExe = std::make_unique(pssp, qctx_.get()); - auto future = psspExe->execute(); - auto status = std::move(future).get(); - EXPECT_TRUE(status.ok()); - auto& result = qctx_->ectx()->getResult(pssp->outputVar()); - - DataSet expected; - expected.colNames = {"_dst", "_src", "cost", "paths"}; - EXPECT_EQ(result.value().getDataSet(), expected); - EXPECT_EQ(result.state(), Result::State::kSuccess); -} - -} // namespace graph -} // namespace nebula diff --git a/src/graph/planner/ngql/PathPlanner.cpp b/src/graph/planner/ngql/PathPlanner.cpp index 1d18be5b45a..9228640d768 100644 --- a/src/graph/planner/ngql/PathPlanner.cpp +++ b/src/graph/planner/ngql/PathPlanner.cpp @@ -1,7 +1,6 @@ -/* Copyright (c) 2021 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #include "graph/planner/ngql/PathPlanner.h" #include "graph/planner/plan/Algo.h" @@ -13,49 +12,63 @@ namespace nebula { namespace graph { -std::unique_ptr> PathPlanner::buildEdgeProps(bool reverse) { - auto edgeProps = std::make_unique>(); - switch (pathCtx_->over.direction) { - case storage::cpp2::EdgeDirection::IN_EDGE: { - doBuildEdgeProps(edgeProps, reverse, true); - break; - } - case storage::cpp2::EdgeDirection::OUT_EDGE: { - doBuildEdgeProps(edgeProps, reverse, false); - break; - } - case storage::cpp2::EdgeDirection::BOTH: { - doBuildEdgeProps(edgeProps, reverse, true); - doBuildEdgeProps(edgeProps, reverse, false); - break; - } + +// The plan looks like this: +// +--------+---------+ +// +-->+ PassThrough +<----+ +// | +------------------+ | +// | | +// +--------+---------+ +---------+--------+ +// | GetNeighbors | | GetNeighbors | +// +--------+---------+ +---------+--------+ +// | | +// +------------+---------------+ +// | +// +--------+---------+ +--------+--------+ +// | BFS/Multi/ALL | | LoopDepend | +// +--------+---------+ +--------+--------+ +// | | +// +--------+---------+ | +// | Loop |--------------+ +// +--------+---------+ +// | +// +--------+---------+ +// | DataCollect | +// +--------+---------+ +// | +// +--------+---------+ +// | GetPathProp | +// +--------+---------+ +// + +StatusOr PathPlanner::transform(AstContext* astCtx) { + pathCtx_ = static_cast(astCtx); + auto qctx = pathCtx_->qctx; + auto& from = pathCtx_->from; + auto& to = pathCtx_->to; + buildStart(from, pathCtx_->fromVidsVar, false); + buildStart(to, pathCtx_->toVidsVar, true); + + auto* startNode = StartNode::make(qctx); + auto* pt = PassThroughNode::make(qctx, startNode); + + PlanNode* left = getNeighbors(pt, false); + PlanNode* right = getNeighbors(pt, true); + + SubPlan subPlan; + if (!pathCtx_->isShortest || pathCtx_->noLoop) { + subPlan = allPairPlan(left, right); + } else if (from.vids.size() == 1 && to.vids.size() == 1) { + subPlan = singlePairPlan(left, right); + } else { + subPlan = multiPairPlan(left, right); } - return edgeProps; -} -void PathPlanner::doBuildEdgeProps(std::unique_ptr>& edgeProps, - bool reverse, - bool isInEdge) { - const auto& exprProps = pathCtx_->exprProps; - for (const auto& e : pathCtx_->over.edgeTypes) { - storage::cpp2::EdgeProp ep; - if (reverse == isInEdge) { - ep.type_ref() = e; - } else { - ep.type_ref() = -e; - } - const auto& found = exprProps.edgeProps().find(e); - if (found == exprProps.edgeProps().end()) { - ep.props_ref() = {kDst, kType, kRank}; - } else { - std::set props(found->second.begin(), found->second.end()); - props.emplace(kDst); - props.emplace(kType); - props.emplace(kRank); - ep.props_ref() = std::vector(props.begin(), props.end()); - } - edgeProps->emplace_back(std::move(ep)); + // get path's property + if (pathCtx_->withProp) { + subPlan.root = buildPathProp(subPlan.root); } + return subPlan; } void PathPlanner::buildStart(Starts& starts, std::string& vidsVar, bool reverse) { @@ -77,6 +90,159 @@ void PathPlanner::buildStart(Starts& starts, std::string& vidsVar, bool reverse) } } +PlanNode* PathPlanner::getNeighbors(PlanNode* dep, bool reverse) { + auto qctx = pathCtx_->qctx; + const auto& gnInputVar = reverse ? pathCtx_->toVidsVar : pathCtx_->fromVidsVar; + auto* gn = GetNeighbors::make(qctx, dep, pathCtx_->space.id); + gn->setSrc(ColumnExpression::make(qctx->objPool(), 0)); + gn->setEdgeProps(buildEdgeProps(reverse)); + gn->setInputVar(gnInputVar); + gn->setDedup(); + PlanNode* result = gn; + if (pathCtx_->filter != nullptr) { + auto* filterExpr = pathCtx_->filter->clone(); + result = Filter::make(qctx, gn, filterExpr); + } + return result; +} + +SubPlan PathPlanner::singlePairPlan(PlanNode* left, PlanNode* right) { + auto qctx = pathCtx_->qctx; + auto steps = pathCtx_->steps.steps(); + auto* path = BFSShortestPath::make(qctx, left, right, steps); + path->setLeftVidVar(pathCtx_->fromVidsVar); + path->setRightVidVar(pathCtx_->toVidsVar); + path->setColNames({kPathStr}); + + auto* loopCondition = singlePairLoopCondition(steps, path->outputVar()); + auto* loop = Loop::make(qctx, nullptr, path, loopCondition); + + auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kBFSShortest); + dc->addDep(loop); + dc->setInputVars({path->outputVar()}); + dc->setColNames(pathCtx_->colNames); + + SubPlan subPlan; + subPlan.root = dc; + subPlan.tail = loop; + return subPlan; +} + +SubPlan PathPlanner::allPairPlan(PlanNode* left, PlanNode* right) { + auto qctx = pathCtx_->qctx; + auto steps = pathCtx_->steps.steps(); + auto* path = ProduceAllPaths::make(qctx, left, right, steps, pathCtx_->noLoop); + path->setLeftVidVar(pathCtx_->fromVidsVar); + path->setRightVidVar(pathCtx_->toVidsVar); + path->setColNames({kPathStr}); + + SubPlan loopDep = loopDepPlan(); + auto* loopCondition = allPairLoopCondition(steps); + auto* loop = Loop::make(qctx, loopDep.root, path, loopCondition); + + auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kAllPaths); + dc->addDep(loop); + dc->setInputVars({path->outputVar()}); + dc->setColNames(pathCtx_->colNames); + + SubPlan subPlan; + subPlan.root = dc; + subPlan.tail = loopDep.tail; + return subPlan; +} + +SubPlan PathPlanner::multiPairPlan(PlanNode* left, PlanNode* right) { + auto qctx = pathCtx_->qctx; + auto steps = pathCtx_->steps.steps(); + auto terminationVar = qctx->vctx()->anonVarGen()->getVar(); + qctx->ectx()->setValue(terminationVar, false); + auto* path = MultiShortestPath::make(qctx, left, right, steps); + path->setLeftVidVar(pathCtx_->fromVidsVar); + path->setRightVidVar(pathCtx_->toVidsVar); + path->setTerminationVar(terminationVar); + path->setColNames({kPathStr}); + + SubPlan loopDep = loopDepPlan(); + auto* loopCondition = multiPairLoopCondition(steps, terminationVar); + auto* loop = Loop::make(qctx, loopDep.root, path, loopCondition); + + auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kAllPaths); + dc->addDep(loop); + dc->setInputVars({path->outputVar()}); + dc->setColNames(pathCtx_->colNames); + + SubPlan subPlan; + subPlan.root = dc; + subPlan.tail = loopDep.tail; + return subPlan; +} + +SubPlan PathPlanner::loopDepPlan() { + auto* qctx = pathCtx_->qctx; + auto* pool = qctx->objPool(); + SubPlan subPlan = buildRuntimeVidPlan(); + { + auto* columns = pool->add(new YieldColumns()); + auto* column = new YieldColumn(ColumnExpression::make(pool, 0), kVid); + columns->addColumn(column); + auto* project = Project::make(qctx, subPlan.root, columns); + project->setInputVar(pathCtx_->fromVidsVar); + project->setOutputVar(pathCtx_->fromVidsVar); + project->setColNames({nebula::kVid}); + subPlan.root = project; + } + subPlan.tail = subPlan.tail == nullptr ? subPlan.root : subPlan.tail; + { + auto* columns = pool->add(new YieldColumns()); + auto* column = new YieldColumn(ColumnExpression::make(pool, 0), kVid); + columns->addColumn(column); + auto* project = Project::make(qctx, subPlan.root, columns); + project->setInputVar(pathCtx_->toVidsVar); + project->setOutputVar(pathCtx_->toVidsVar); + project->setColNames({nebula::kVid}); + subPlan.root = project; + } + return subPlan; +} + +// +// The Plan looks like this: +// +--------+---------+ +// +-->+ PassThrough +<----+ +// | +------------------+ | +// +--------+---------+ +---------+------------+ +// | Project(Nodes) | |Project(RelationShips)| +// +--------+---------+ +---------+------------+ +// | | +// +--------+---------+ +---------+--------+ +// | Unwind | | Unwind | +// +--------+---------+ +---------+--------+ +// | | +// +--------+---------+ +---------+--------+ +// | GetVertices | | GetEdges | +// +--------+---------+ +---------+--------+ +// | | +// +------------+---------------+ +// | +// +--------+---------+ +// | DataCollect | +// +--------+---------+ +// +PlanNode* PathPlanner::buildPathProp(PlanNode* dep) { + auto qctx = pathCtx_->qctx; + auto* pt = PassThroughNode::make(qctx, dep); + + auto* vertexPlan = buildVertexPlan(pt, dep->outputVar()); + auto* edgePlan = buildEdgePlan(pt, dep->outputVar()); + + auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kPathProp); + dc->addDep(vertexPlan); + dc->addDep(edgePlan); + dc->setInputVars({vertexPlan->outputVar(), edgePlan->outputVar(), dep->outputVar()}); + dc->setColNames(std::move(pathCtx_->colNames)); + return dc; +} + // loopSteps{0} <= ((steps + 1) / 2) && (pathVar is Empty || size(pathVar) == // 0) Expression* PathPlanner::singlePairLoopCondition(uint32_t steps, const std::string& pathVar) { @@ -99,14 +265,14 @@ Expression* PathPlanner::allPairLoopCondition(uint32_t steps) { return ExpressionUtils::stepCondition(pool, loopSteps, ((steps + 1) / 2)); } -// loopSteps{0} <= ((steps + 1) / 2) && (size(pathVar) != 0) -Expression* PathPlanner::multiPairLoopCondition(uint32_t steps, const std::string& pathVar) { +// loopSteps{0} <= ((steps + 1) / 2) && (terminationVar) == false) +Expression* PathPlanner::multiPairLoopCondition(uint32_t steps, const std::string& terminationVar) { auto loopSteps = pathCtx_->qctx->vctx()->anonVarGen()->getVar(); pathCtx_->qctx->ectx()->setValue(loopSteps, 0); auto* pool = pathCtx_->qctx->objPool(); auto step = ExpressionUtils::stepCondition(pool, loopSteps, ((steps + 1) / 2)); - auto neZero = ExpressionUtils::neZeroCondition(pool, pathVar); - return LogicalExpression::makeAnd(pool, step, neZero); + auto terminate = ExpressionUtils::equalCondition(pool, terminationVar, false); + return LogicalExpression::makeAnd(pool, step, terminate); } SubPlan PathPlanner::buildRuntimeVidPlan() { @@ -136,252 +302,6 @@ SubPlan PathPlanner::buildRuntimeVidPlan() { return subPlan; } -PlanNode* PathPlanner::allPairStartVidDataSet(PlanNode* dep, const std::string& inputVar) { - auto* pool = pathCtx_->qctx->objPool(); - - // col 0 is vid - auto* vid = new YieldColumn(ColumnExpression::make(pool, 0), kVid); - // col 1 is list - auto* pathExpr = PathBuildExpression::make(pool); - pathExpr->add(ColumnExpression::make(pool, 0)); - - auto* exprList = ExpressionList::make(pool); - exprList->add(pathExpr); - auto* listExpr = ListExpression::make(pool, exprList); - auto* path = new YieldColumn(listExpr, kPathStr); - - auto* columns = pool->add(new YieldColumns()); - columns->addColumn(vid); - columns->addColumn(path); - - auto* project = Project::make(pathCtx_->qctx, dep, columns); - project->setInputVar(inputVar); - project->setOutputVar(inputVar); - project->setColNames({kVid, kPathStr}); - - return project; -} - -PlanNode* PathPlanner::multiPairStartVidDataSet(PlanNode* dep, const std::string& inputVar) { - auto* pool = pathCtx_->qctx->objPool(); - - // col 0 is dst - auto* dst = new YieldColumn(ColumnExpression::make(pool, 0), kDst); - // col 1 is src - auto* src = new YieldColumn(ColumnExpression::make(pool, 1), kSrc); - // col 2 is cost - auto* cost = new YieldColumn(ConstantExpression::make(pool, 0), kCostStr); - // col 3 is list - auto* pathExpr = PathBuildExpression::make(pool); - pathExpr->add(ColumnExpression::make(pool, 0)); - - auto* exprList = ExpressionList::make(pool); - exprList->add(pathExpr); - auto* listExpr = ListExpression::make(pool, exprList); - auto* path = new YieldColumn(listExpr, kPathStr); - - auto* columns = pool->add(new YieldColumns()); - columns->addColumn(dst); - columns->addColumn(src); - columns->addColumn(cost); - columns->addColumn(path); - - auto* project = Project::make(pathCtx_->qctx, dep, columns); - project->setColNames({kDst, kSrc, kCostStr, kPathStr}); - project->setInputVar(inputVar); - project->setOutputVar(inputVar); - - return project; -} - -SubPlan PathPlanner::allPairLoopDepPlan() { - SubPlan subPlan = buildRuntimeVidPlan(); - subPlan.root = allPairStartVidDataSet(subPlan.root, pathCtx_->fromVidsVar); - subPlan.tail = subPlan.tail == nullptr ? subPlan.root : subPlan.tail; - subPlan.root = allPairStartVidDataSet(subPlan.root, pathCtx_->toVidsVar); - return subPlan; -} - -SubPlan PathPlanner::multiPairLoopDepPlan() { - SubPlan subPlan = buildRuntimeVidPlan(); - subPlan.root = multiPairStartVidDataSet(subPlan.root, pathCtx_->fromVidsVar); - subPlan.tail = subPlan.tail == nullptr ? subPlan.root : subPlan.tail; - subPlan.root = multiPairStartVidDataSet(subPlan.root, pathCtx_->toVidsVar); - - /* - * Create the Cartesian product of the fromVid set and the toVid set. - * When a pair of paths (-[]->) is found, - * delete it from the DataSet of cartesianProduct->outputVar(). - * When the DataSet of cartesianProduct->outputVar() is empty - * terminate the execution early - */ - auto* cartesianProduct = CartesianProduct::make(pathCtx_->qctx, subPlan.root); - cartesianProduct->addVar(pathCtx_->fromVidsVar); - cartesianProduct->addVar(pathCtx_->toVidsVar); - subPlan.root = cartesianProduct; - return subPlan; -} - -PlanNode* PathPlanner::singlePairPath(PlanNode* dep, bool reverse) { - const auto& vidsVar = reverse ? pathCtx_->toVidsVar : pathCtx_->fromVidsVar; - auto qctx = pathCtx_->qctx; - auto* pool = qctx->objPool(); - auto* src = ColumnExpression::make(pool, 0); - - auto* gn = GetNeighbors::make(qctx, dep, pathCtx_->space.id); - gn->setSrc(src); - gn->setEdgeProps(buildEdgeProps(reverse)); - gn->setInputVar(vidsVar); - gn->setDedup(); - - PlanNode* pathDep = gn; - if (pathCtx_->filter != nullptr) { - auto* filterExpr = pathCtx_->filter->clone(); - auto* filter = Filter::make(qctx, gn, filterExpr); - pathDep = filter; - } - - auto* path = BFSShortestPath::make(qctx, pathDep); - path->setOutputVar(vidsVar); - path->setColNames({kVid, kEdgeStr}); - - // singlePair startVid dataset - const auto& starts = reverse ? pathCtx_->to : pathCtx_->from; - DataSet ds; - ds.colNames = {kVid, kEdgeStr}; - Row row; - row.values.emplace_back(starts.vids.front()); - row.values.emplace_back(Value::kEmpty); - ds.rows.emplace_back(std::move(row)); - qctx->ectx()->setResult(vidsVar, ResultBuilder().value(Value(std::move(ds))).build()); - return path; -} - -SubPlan PathPlanner::singlePairPlan(PlanNode* dep) { - auto* forwardPath = singlePairPath(dep, false); - auto* backwardPath = singlePairPath(dep, true); - auto qctx = pathCtx_->qctx; - - auto* conjunct = ConjunctPath::make( - qctx, forwardPath, backwardPath, ConjunctPath::PathKind::kBiBFS, pathCtx_->steps.steps()); - conjunct->setColNames({kPathStr}); - - auto* loopCondition = singlePairLoopCondition(pathCtx_->steps.steps(), conjunct->outputVar()); - auto* loop = Loop::make(qctx, nullptr, conjunct, loopCondition); - auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kBFSShortest); - dc->setInputVars({conjunct->outputVar()}); - dc->addDep(loop); - dc->setColNames(pathCtx_->colNames); - - SubPlan subPlan; - subPlan.root = dc; - subPlan.tail = loop; - return subPlan; -} - -PlanNode* PathPlanner::allPairPath(PlanNode* dep, bool reverse) { - const auto& vidsVar = reverse ? pathCtx_->toVidsVar : pathCtx_->fromVidsVar; - auto qctx = pathCtx_->qctx; - auto* pool = qctx->objPool(); - auto* src = ColumnExpression::make(pool, 0); - - auto* gn = GetNeighbors::make(qctx, dep, pathCtx_->space.id); - gn->setSrc(src); - gn->setEdgeProps(buildEdgeProps(reverse)); - gn->setInputVar(vidsVar); - gn->setDedup(); - - PlanNode* pathDep = gn; - if (pathCtx_->filter != nullptr) { - auto* filterExpr = pathCtx_->filter->clone(); - auto* filter = Filter::make(qctx, gn, filterExpr); - pathDep = filter; - } - - auto* path = ProduceAllPaths::make(qctx, pathDep); - path->setOutputVar(vidsVar); - path->setColNames({kVid, kPathStr}); - return path; -} - -SubPlan PathPlanner::allPairPlan(PlanNode* dep) { - auto* forwardPath = allPairPath(dep, false); - auto* backwardPath = allPairPath(dep, true); - auto qctx = pathCtx_->qctx; - - auto* conjunct = ConjunctPath::make( - qctx, forwardPath, backwardPath, ConjunctPath::PathKind::kAllPaths, pathCtx_->steps.steps()); - conjunct->setNoLoop(pathCtx_->noLoop); - conjunct->setColNames({kPathStr}); - - SubPlan loopDepPlan = allPairLoopDepPlan(); - auto* loopCondition = allPairLoopCondition(pathCtx_->steps.steps()); - auto* loop = Loop::make(qctx, loopDepPlan.root, conjunct, loopCondition); - - auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kAllPaths); - dc->addDep(loop); - dc->setInputVars({conjunct->outputVar()}); - dc->setColNames(pathCtx_->colNames); - - SubPlan subPlan; - subPlan.root = dc; - subPlan.tail = loopDepPlan.tail; - return subPlan; -} - -PlanNode* PathPlanner::multiPairPath(PlanNode* dep, bool reverse) { - const auto& vidsVar = reverse ? pathCtx_->toVidsVar : pathCtx_->fromVidsVar; - auto qctx = pathCtx_->qctx; - auto* pool = qctx->objPool(); - auto* src = ColumnExpression::make(pool, 0); - - auto* gn = GetNeighbors::make(qctx, dep, pathCtx_->space.id); - gn->setSrc(src); - gn->setEdgeProps(buildEdgeProps(reverse)); - gn->setInputVar(vidsVar); - gn->setDedup(); - - PlanNode* pathDep = gn; - if (pathCtx_->filter != nullptr) { - auto* filterExpr = pathCtx_->filter->clone(); - auto* filter = Filter::make(qctx, gn, filterExpr); - pathDep = filter; - } - - auto* path = ProduceSemiShortestPath::make(qctx, pathDep); - path->setOutputVar(vidsVar); - path->setColNames({kDst, kSrc, kCostStr, kPathStr}); - - return path; -} - -SubPlan PathPlanner::multiPairPlan(PlanNode* dep) { - auto* forwardPath = multiPairPath(dep, false); - auto* backwardPath = multiPairPath(dep, true); - auto qctx = pathCtx_->qctx; - - auto* conjunct = ConjunctPath::make( - qctx, forwardPath, backwardPath, ConjunctPath::PathKind::kFloyd, pathCtx_->steps.steps()); - conjunct->setColNames({kPathStr, kCostStr}); - - SubPlan loopDepPlan = multiPairLoopDepPlan(); - // loopDepPlan.root is cartesianProduct - const auto& endConditionVar = loopDepPlan.root->outputVar(); - conjunct->setConditionalVar(endConditionVar); - auto* loopCondition = multiPairLoopCondition(pathCtx_->steps.steps(), endConditionVar); - auto* loop = Loop::make(qctx, loopDepPlan.root, conjunct, loopCondition); - - auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kMultiplePairShortest); - dc->addDep(loop); - dc->setInputVars({conjunct->outputVar()}); - dc->setColNames(pathCtx_->colNames); - - SubPlan subPlan; - subPlan.root = dc; - subPlan.tail = loopDepPlan.tail; - return subPlan; -} - PlanNode* PathPlanner::buildVertexPlan(PlanNode* dep, const std::string& input) { auto qctx = pathCtx_->qctx; auto* pool = qctx->objPool(); @@ -469,73 +389,49 @@ PlanNode* PathPlanner::buildEdgePlan(PlanNode* dep, const std::string& input) { return getEdge; } -// -// The Plan looks like this: -// +--------+---------+ -// +-->+ PassThrough +<----+ -// | +------------------+ | -// +--------+---------+ +---------+------------+ -// | Project(Nodes) | |Project(RelationShips)| -// +--------+---------+ +---------+------------+ -// | | -// +--------+---------+ +---------+--------+ -// | Unwind | | Unwind | -// +--------+---------+ +---------+--------+ -// | | -// +--------+---------+ +---------+--------+ -// | GetVertices | | GetEdges | -// +--------+---------+ +---------+--------+ -// | | -// +------------+---------------+ -// | -// +--------+---------+ -// | DataCollect | -// +--------+---------+ -// -PlanNode* PathPlanner::buildPathProp(PlanNode* dep) { - auto qctx = pathCtx_->qctx; - auto* pt = PassThroughNode::make(qctx, dep); - - auto* vertexPlan = buildVertexPlan(pt, dep->outputVar()); - auto* edgePlan = buildEdgePlan(pt, dep->outputVar()); - - auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kPathProp); - dc->addDep(vertexPlan); - dc->addDep(edgePlan); - dc->setInputVars({vertexPlan->outputVar(), edgePlan->outputVar(), dep->outputVar()}); - dc->setColNames(std::move(pathCtx_->colNames)); - return dc; -} - -StatusOr PathPlanner::transform(AstContext* astCtx) { - pathCtx_ = static_cast(astCtx); - auto qctx = pathCtx_->qctx; - auto& from = pathCtx_->from; - auto& to = pathCtx_->to; - buildStart(from, pathCtx_->fromVidsVar, false); - buildStart(to, pathCtx_->toVidsVar, true); - - auto* startNode = StartNode::make(qctx); - auto* pt = PassThroughNode::make(qctx, startNode); - - SubPlan subPlan; - do { - if (!pathCtx_->isShortest || pathCtx_->noLoop) { - subPlan = allPairPlan(pt); +std::unique_ptr> PathPlanner::buildEdgeProps(bool reverse) { + auto edgeProps = std::make_unique>(); + switch (pathCtx_->over.direction) { + case storage::cpp2::EdgeDirection::IN_EDGE: { + doBuildEdgeProps(edgeProps, reverse, true); break; } - if (from.vids.size() == 1 && to.vids.size() == 1) { - subPlan = singlePairPlan(pt); + case storage::cpp2::EdgeDirection::OUT_EDGE: { + doBuildEdgeProps(edgeProps, reverse, false); + break; + } + case storage::cpp2::EdgeDirection::BOTH: { + doBuildEdgeProps(edgeProps, reverse, true); + doBuildEdgeProps(edgeProps, reverse, false); break; } - subPlan = multiPairPlan(pt); - } while (0); - // get path's property - if (pathCtx_->withProp) { - subPlan.root = buildPathProp(subPlan.root); } + return edgeProps; +} - return subPlan; +void PathPlanner::doBuildEdgeProps(std::unique_ptr>& edgeProps, + bool reverse, + bool isInEdge) { + const auto& exprProps = pathCtx_->exprProps; + for (const auto& e : pathCtx_->over.edgeTypes) { + storage::cpp2::EdgeProp ep; + if (reverse == isInEdge) { + ep.type_ref() = e; + } else { + ep.type_ref() = -e; + } + const auto& found = exprProps.edgeProps().find(e); + if (found == exprProps.edgeProps().end()) { + ep.props_ref() = {kDst, kType, kRank}; + } else { + std::set props(found->second.begin(), found->second.end()); + props.emplace(kDst); + props.emplace(kType); + props.emplace(kRank); + ep.props_ref() = std::vector(props.begin(), props.end()); + } + edgeProps->emplace_back(std::move(ep)); + } } } // namespace graph diff --git a/src/graph/planner/ngql/PathPlanner.h b/src/graph/planner/ngql/PathPlanner.h index e0c6912119d..63d1d974c1f 100644 --- a/src/graph/planner/ngql/PathPlanner.h +++ b/src/graph/planner/ngql/PathPlanner.h @@ -1,7 +1,6 @@ -/* Copyright (c) 2021 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ +// Copyright (c) 2022 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. #ifndef NGQL_PLANNERS_PATHPLANNER_H #define NGQL_PLANNERS_PATHPLANNER_H @@ -28,17 +27,15 @@ class PathPlanner final : public Planner { StatusOr transform(AstContext* astCtx) override; private: - SubPlan singlePairPlan(PlanNode* dep); + SubPlan loopDepPlan(); - SubPlan multiPairPlan(PlanNode* dep); + PlanNode* getNeighbors(PlanNode* dep, bool reverse); - SubPlan allPairPlan(PlanNode* dep); + SubPlan singlePairPlan(PlanNode* left, PlanNode* right); - PlanNode* singlePairPath(PlanNode* dep, bool reverse); + SubPlan multiPairPlan(PlanNode* left, PlanNode* right); - PlanNode* multiPairPath(PlanNode* dep, bool reverse); - - PlanNode* allPairPath(PlanNode* dep, bool reverse); + SubPlan allPairPlan(PlanNode* left, PlanNode* right); PlanNode* buildPathProp(PlanNode* dep); @@ -73,24 +70,6 @@ class PathPlanner final : public Planner { */ SubPlan buildRuntimeVidPlan(); - /* - * When the number of steps is odd - * For example: A->B start: A, end: B - * we start to expand from the starting point and the ending point at the same - * time expand from start is pathA : A->B expand from to is pathB : B->A When - * conjunct paths we should determine whether the end B and end point of pathA - * are equal first then determine whether the end point of pathB and the end - * point of pathA are equal so we should build path(B) and path(A) - */ - PlanNode* allPairStartVidDataSet(PlanNode* dep, const std::string& input); - - // refer to allPairStartVidDataSet - PlanNode* multiPairStartVidDataSet(PlanNode* dep, const std::string& input); - - SubPlan multiPairLoopDepPlan(); - - SubPlan allPairLoopDepPlan(); - private: PathPlanner() = default; diff --git a/src/graph/planner/plan/Algo.cpp b/src/graph/planner/plan/Algo.cpp index 502326f5065..86a782a1350 100644 --- a/src/graph/planner/plan/Algo.cpp +++ b/src/graph/planner/plan/Algo.cpp @@ -9,34 +9,28 @@ namespace nebula { namespace graph { -std::unique_ptr ConjunctPath::explain() const { +std::unique_ptr BFSShortestPath::explain() const { auto desc = BinaryInputNode::explain(); - switch (pathKind_) { - case PathKind::kBiBFS: { - addDescription("kind", "BFS", desc.get()); - break; - } - case PathKind::kBiDijkstra: { - addDescription("kind", "Dijkstra", desc.get()); - break; - } - case PathKind::kFloyd: { - addDescription("kind", "Floyd", desc.get()); - break; - } - case PathKind::kAllPaths: { - addDescription("kind", "AllPath", desc.get()); - break; - } - } - addDescription("conditionalVar", util::toJson(conditionalVar_), desc.get()); - addDescription("noloop", util::toJson(noLoop_), desc.get()); + addDescription("LeftNextVidVar", util::toJson(leftVidVar_), desc.get()); + addDescription("RightNextVidVar", util::toJson(rightVidVar_), desc.get()); + addDescription("steps", util::toJson(steps_), desc.get()); + return desc; +} + +std::unique_ptr MultiShortestPath::explain() const { + auto desc = BinaryInputNode::explain(); + addDescription("LeftNextVidVar", util::toJson(leftVidVar_), desc.get()); + addDescription("RightNextVidVar", util::toJson(rightVidVar_), desc.get()); + addDescription("steps", util::toJson(steps_), desc.get()); return desc; } std::unique_ptr ProduceAllPaths::explain() const { - auto desc = SingleDependencyNode::explain(); + auto desc = BinaryInputNode::explain(); + addDescription("LeftNextVidVar", util::toJson(leftVidVar_), desc.get()); + addDescription("RightNextVidVar", util::toJson(rightVidVar_), desc.get()); addDescription("noloop ", util::toJson(noLoop_), desc.get()); + addDescription("steps", util::toJson(steps_), desc.get()); return desc; } diff --git a/src/graph/planner/plan/Algo.h b/src/graph/planner/plan/Algo.h index 17e2887354e..242195f3889 100644 --- a/src/graph/planner/plan/Algo.h +++ b/src/graph/planner/plan/Algo.h @@ -11,101 +11,138 @@ namespace nebula { namespace graph { -class ProduceSemiShortestPath : public SingleInputNode { +class MultiShortestPath : public BinaryInputNode { public: - static ProduceSemiShortestPath* make(QueryContext* qctx, PlanNode* input) { - return qctx->objPool()->add(new ProduceSemiShortestPath(qctx, input)); + static MultiShortestPath* make(QueryContext* qctx, + PlanNode* left, + PlanNode* right, + size_t steps) { + return qctx->objPool()->add(new MultiShortestPath(qctx, left, right, steps)); } - private: - ProduceSemiShortestPath(QueryContext* qctx, PlanNode* input) - : SingleInputNode(qctx, Kind::kProduceSemiShortestPath, input) {} -}; + size_t steps() const { + return steps_; + } -class BFSShortestPath : public SingleInputNode { - public: - static BFSShortestPath* make(QueryContext* qctx, PlanNode* input) { - return qctx->objPool()->add(new BFSShortestPath(qctx, input)); + std::string leftVidVar() const { + return leftVidVar_; } - private: - BFSShortestPath(QueryContext* qctx, PlanNode* input) - : SingleInputNode(qctx, Kind::kBFSShortest, input) {} -}; + std::string rightVidVar() const { + return rightVidVar_; + } -class ConjunctPath : public BinaryInputNode { - public: - enum class PathKind : uint8_t { - kBiBFS, - kBiDijkstra, - kFloyd, - kAllPaths, - }; + std::string terminationVar() const { + return terminationVar_; + } - static ConjunctPath* make( - QueryContext* qctx, PlanNode* left, PlanNode* right, PathKind pathKind, size_t steps) { - return qctx->objPool()->add(new ConjunctPath(qctx, left, right, pathKind, steps)); + void setLeftVidVar(const std::string& var) { + leftVidVar_ = var; } - PathKind pathKind() const { - return pathKind_; + void setRightVidVar(const std::string& var) { + rightVidVar_ = var; + } + + void setTerminationVar(const std::string& var) { + terminationVar_ = var; + } + + std::unique_ptr explain() const override; + + private: + MultiShortestPath(QueryContext* qctx, PlanNode* left, PlanNode* right, size_t steps) + : BinaryInputNode(qctx, Kind::kMultiShortestPath, left, right), steps_(steps) {} + + private: + size_t steps_{0}; + std::string leftVidVar_; + std::string rightVidVar_; + std::string terminationVar_; +}; + +class BFSShortestPath : public BinaryInputNode { + public: + static BFSShortestPath* make(QueryContext* qctx, PlanNode* left, PlanNode* right, size_t steps) { + return qctx->objPool()->add(new BFSShortestPath(qctx, left, right, steps)); } size_t steps() const { return steps_; } - void setConditionalVar(std::string varName) { - conditionalVar_ = std::move(varName); + std::string leftVidVar() const { + return leftVidVar_; } - std::string conditionalVar() const { - return conditionalVar_; + std::string rightVidVar() const { + return rightVidVar_; } - bool noLoop() const { - return noLoop_; + void setLeftVidVar(const std::string& var) { + leftVidVar_ = var; } - void setNoLoop(bool noLoop) { - noLoop_ = noLoop; + void setRightVidVar(const std::string& var) { + rightVidVar_ = var; } + std::unique_ptr explain() const override; private: - ConjunctPath(QueryContext* qctx, PlanNode* left, PlanNode* right, PathKind pathKind, size_t steps) - : BinaryInputNode(qctx, Kind::kConjunctPath, left, right) { - pathKind_ = pathKind; - steps_ = steps; - } + BFSShortestPath(QueryContext* qctx, PlanNode* left, PlanNode* right, size_t steps) + : BinaryInputNode(qctx, Kind::kBFSShortest, left, right), steps_(steps) {} - PathKind pathKind_; + private: + std::string leftVidVar_; + std::string rightVidVar_; size_t steps_{0}; - std::string conditionalVar_; - bool noLoop_; }; -class ProduceAllPaths final : public SingleInputNode { +class ProduceAllPaths final : public BinaryInputNode { public: - static ProduceAllPaths* make(QueryContext* qctx, PlanNode* input) { - return qctx->objPool()->add(new ProduceAllPaths(qctx, input)); + static ProduceAllPaths* make( + QueryContext* qctx, PlanNode* left, PlanNode* right, size_t steps, bool noLoop) { + return qctx->objPool()->add(new ProduceAllPaths(qctx, left, right, steps, noLoop)); + } + + size_t steps() const { + return steps_; } bool noLoop() const { return noLoop_; } - void setNoLoop(bool noLoop) { - noLoop_ = noLoop; + std::string leftVidVar() const { + return leftVidVar_; + } + + std::string rightVidVar() const { + return rightVidVar_; + } + + void setLeftVidVar(const std::string& var) { + leftVidVar_ = var; + } + + void setRightVidVar(const std::string& var) { + rightVidVar_ = var; } + std::unique_ptr explain() const override; private: - ProduceAllPaths(QueryContext* qctx, PlanNode* input) - : SingleInputNode(qctx, Kind::kProduceAllPaths, input) {} + ProduceAllPaths(QueryContext* qctx, PlanNode* left, PlanNode* right, size_t steps, bool noLoop) + : BinaryInputNode(qctx, Kind::kProduceAllPaths, left, right), + steps_(steps), + noLoop_(noLoop) {} private: + size_t steps_{0}; bool noLoop_{false}; + std::string leftVidVar_; + std::string rightVidVar_; }; class CartesianProduct final : public SingleDependencyNode { diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 8ceade20b92..f39847295d1 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -231,10 +231,8 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "GetConfig"; case Kind::kBFSShortest: return "BFSShortest"; - case Kind::kProduceSemiShortestPath: - return "ProduceSemiShortestPath"; - case Kind::kConjunctPath: - return "ConjunctPath"; + case Kind::kMultiShortestPath: + return "MultiShortestPath"; case Kind::kProduceAllPaths: return "ProduceAllPaths"; case Kind::kCartesianProduct: diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index 9c1e49b7cd9..d182d6fc9fe 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -57,8 +57,7 @@ class PlanNode { kDedup, kAssign, kBFSShortest, - kProduceSemiShortestPath, - kConjunctPath, + kMultiShortestPath, kProduceAllPaths, kCartesianProduct, kSubgraph, diff --git a/src/graph/service/GraphFlags.cpp b/src/graph/service/GraphFlags.cpp index 6532a480700..e4a3a71ea36 100644 --- a/src/graph/service/GraphFlags.cpp +++ b/src/graph/service/GraphFlags.cpp @@ -18,6 +18,7 @@ DEFINE_int32(num_netio_threads, "The number of networking threads, 0 for number of physical CPU cores"); DEFINE_int32(num_accept_threads, 1, "Number of threads to accept incoming connections"); DEFINE_int32(num_worker_threads, 0, "Number of threads to execute user queries"); +DEFINE_int32(num_operator_threads, 5, "Number of threads to execute a single operator"); DEFINE_bool(reuse_port, true, "Whether to turn on the SO_REUSEPORT option"); DEFINE_int32(listen_backlog, 1024, "Backlog of the listen socket"); DEFINE_string(listen_netdev, "any", "The network device to listen on"); diff --git a/src/graph/service/GraphFlags.h b/src/graph/service/GraphFlags.h index 340a3f7e009..cddd8c02e83 100644 --- a/src/graph/service/GraphFlags.h +++ b/src/graph/service/GraphFlags.h @@ -15,6 +15,7 @@ DECLARE_int32(session_reclaim_interval_secs); DECLARE_int32(num_netio_threads); DECLARE_int32(num_accept_threads); DECLARE_int32(num_worker_threads); +DECLARE_int32(num_operator_threads); DECLARE_bool(reuse_port); DECLARE_int32(listen_backlog); DECLARE_string(listen_netdev); diff --git a/src/graph/validator/test/FindPathValidatorTest.cpp b/src/graph/validator/test/FindPathValidatorTest.cpp index 03ba19fc4d5..d803c4eb8b8 100644 --- a/src/graph/validator/test/FindPathValidatorTest.cpp +++ b/src/graph/validator/test/FindPathValidatorTest.cpp @@ -61,8 +61,6 @@ TEST_F(FindPathValidatorTest, SinglePairPath) { PK::kDataCollect, PK::kLoop, PK::kStart, - PK::kConjunctPath, - PK::kBFSShortest, PK::kBFSShortest, PK::kGetNeighbors, PK::kGetNeighbors, @@ -78,8 +76,6 @@ TEST_F(FindPathValidatorTest, SinglePairPath) { PK::kDataCollect, PK::kLoop, PK::kStart, - PK::kConjunctPath, - PK::kBFSShortest, PK::kBFSShortest, PK::kGetNeighbors, PK::kGetNeighbors, @@ -97,11 +93,8 @@ TEST_F(FindPathValidatorTest, MultiPairPath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -118,11 +111,8 @@ TEST_F(FindPathValidatorTest, MultiPairPath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -141,13 +131,11 @@ TEST_F(FindPathValidatorTest, ALLPath) { PK::kDataCollect, PK::kLoop, PK::kProject, - PK::kConjunctPath, - PK::kProject, PK::kProduceAllPaths, - PK::kProduceAllPaths, - PK::kStart, + PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, + PK::kStart, PK::kPassThrough, PK::kStart, }; @@ -160,13 +148,11 @@ TEST_F(FindPathValidatorTest, ALLPath) { PK::kDataCollect, PK::kLoop, PK::kProject, - PK::kConjunctPath, - PK::kProject, - PK::kProduceAllPaths, PK::kProduceAllPaths, - PK::kStart, + PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, + PK::kStart, PK::kPassThrough, PK::kStart, }; @@ -183,11 +169,8 @@ TEST_F(FindPathValidatorTest, RunTimePath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -211,17 +194,15 @@ TEST_F(FindPathValidatorTest, RunTimePath) { PK::kDataCollect, PK::kLoop, PK::kProject, - PK::kConjunctPath, - PK::kProject, - PK::kProduceAllPaths, PK::kProduceAllPaths, - PK::kDedup, + PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, - PK::kProject, - PK::kPassThrough, PK::kDedup, + PK::kPassThrough, + PK::kProject, PK::kStart, + PK::kDedup, PK::kProject, PK::kProject, PK::kGetNeighbors, @@ -237,11 +218,8 @@ TEST_F(FindPathValidatorTest, RunTimePath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -263,11 +241,8 @@ TEST_F(FindPathValidatorTest, RunTimePath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -289,11 +264,8 @@ TEST_F(FindPathValidatorTest, RunTimePath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -319,11 +291,8 @@ TEST_F(FindPathValidatorTest, RunTimePath) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, @@ -346,17 +315,15 @@ TEST_F(FindPathValidatorTest, RunTimePath) { PK::kDataCollect, PK::kLoop, PK::kProject, - PK::kConjunctPath, - PK::kProject, - PK::kProduceAllPaths, PK::kProduceAllPaths, - PK::kDedup, + PK::kProject, PK::kGetNeighbors, PK::kGetNeighbors, - PK::kProject, - PK::kPassThrough, PK::kDedup, + PK::kPassThrough, + PK::kProject, PK::kStart, + PK::kDedup, PK::kProject, PK::kProject, PK::kStart, @@ -374,13 +341,11 @@ TEST_F(FindPathValidatorTest, PathWithFilter) { PK::kDataCollect, PK::kLoop, PK::kProject, - PK::kConjunctPath, - PK::kProject, - PK::kProduceAllPaths, PK::kProduceAllPaths, - PK::kStart, + PK::kProject, PK::kFilter, PK::kFilter, + PK::kStart, PK::kGetNeighbors, PK::kGetNeighbors, PK::kPassThrough, @@ -396,8 +361,6 @@ TEST_F(FindPathValidatorTest, PathWithFilter) { PK::kDataCollect, PK::kLoop, PK::kStart, - PK::kConjunctPath, - PK::kBFSShortest, PK::kBFSShortest, PK::kFilter, PK::kFilter, @@ -415,11 +378,8 @@ TEST_F(FindPathValidatorTest, PathWithFilter) { std::vector expected = { PK::kDataCollect, PK::kLoop, - PK::kCartesianProduct, - PK::kConjunctPath, PK::kProject, - PK::kProduceSemiShortestPath, - PK::kProduceSemiShortestPath, + PK::kMultiShortestPath, PK::kProject, PK::kFilter, PK::kFilter, diff --git a/tests/tck/features/path/ShortestPath.IntVid.feature b/tests/tck/features/path/ShortestPath.IntVid.feature index fbce67790c1..9db24ca1368 100644 --- a/tests/tck/features/path/ShortestPath.IntVid.feature +++ b/tests/tck/features/path/ShortestPath.IntVid.feature @@ -446,6 +446,39 @@ Feature: Integer Vid Shortest Path | <("Tony Parker")<-[:teammate]-("Manu Ginobili")> | | <("Tony Parker")-[:like]->("Manu Ginobili")> | | <("Tony Parker")-[:teammate]->("Manu Ginobili")> | + When executing query: + """ + FIND SHORTEST PATH FROM hash("Joel Embiid") TO hash("Giannis Antetokounmpo") OVER * BIDIRECT UPTO 18 STEPS YIELD path as p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Bulls")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + When executing query: + """ + FIND SHORTEST PATH FROM hash("Tim Duncan"), hash("Joel Embiid") TO hash("Giannis Antetokounmpo"), hash("Yao Ming") OVER * BIDIRECT UPTO 18 STEPS YIELD path as p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Tim Duncan")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:like@0 {}]->("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:like@0 {}]->("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Magic")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Magic")<-[:serve@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Raptors")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Bulls")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | Scenario: Integer Vid Shortest Path With PROP When executing query: diff --git a/tests/tck/features/path/ShortestPath.feature b/tests/tck/features/path/ShortestPath.feature index c4af624f489..3c950029381 100644 --- a/tests/tck/features/path/ShortestPath.feature +++ b/tests/tck/features/path/ShortestPath.feature @@ -446,6 +446,39 @@ Feature: Shortest Path | <("Tony Parker")<-[:teammate]-("Manu Ginobili")> | | <("Tony Parker")-[:like]->("Manu Ginobili")> | | <("Tony Parker")-[:teammate]->("Manu Ginobili")> | + When executing query: + """ + FIND SHORTEST PATH FROM "Joel Embiid" TO "Giannis Antetokounmpo" OVER * BIDIRECT UPTO 18 STEPS YIELD path as p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Bulls")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + When executing query: + """ + FIND SHORTEST PATH FROM "Tim Duncan", "Joel Embiid" TO "Giannis Antetokounmpo", "Yao Ming" OVER * BIDIRECT UPTO 18 STEPS YIELD path as p + """ + Then the result should be, in any order, with relax comparison: + | p | + | <("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Tim Duncan")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:like@0 {}]->("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:like@0 {}]->("Tim Duncan")<-[:like@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Magic")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Magic")<-[:serve@0 {}]-("Shaquille O'Neal")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Raptors")<-[:serve@0 {}]-("Tracy McGrady")<-[:like@0 {}]-("Yao Ming")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Bulls")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Jonathon Simmons")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Marco Belinelli")-[:serve@1 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | + | <("Joel Embiid")-[:serve@0 {}]->("76ers")<-[:serve@0 {}]-("Tiago Splitter")-[:serve@0 {}]->("Spurs")<-[:serve@0 {}]-("Paul Gasol")-[:serve@0 {}]->("Bucks")<-[:serve@0 {}]-("Giannis Antetokounmpo")> | Scenario: Shortest Path With PROP When executing query: