diff --git a/src/context/Result.cpp b/src/context/Result.cpp
index 702a2c9128981bc92de1e9fe15cfc52db892dbf7..0be5c03e6920a8bed32929c6c7d339e7a2fe71d8 100644
--- a/src/context/Result.cpp
+++ b/src/context/Result.cpp
@@ -10,7 +10,8 @@ namespace nebula {
namespace graph {
const Result& Result::EmptyResult() {
- static Result kEmptyResult = ResultBuilder().iter(Iterator::Kind::kDefault).finish();
+ static Result kEmptyResult =
+ ResultBuilder().value(Value()).iter(Iterator::Kind::kDefault).finish();
return kEmptyResult;
}
diff --git a/src/executor/CMakeLists.txt b/src/executor/CMakeLists.txt
index c4ec39f0699ad3ce122a5267e269ec304047b6c2..06133dba7189303ae33f254247b20a36c7f2c91c 100644
--- a/src/executor/CMakeLists.txt
+++ b/src/executor/CMakeLists.txt
@@ -28,6 +28,8 @@ nebula_add_library(
query/DataCollectExecutor.cpp
query/DataJoinExecutor.cpp
query/IndexScanExecutor.cpp
+ algo/ConjunctPathExecutor.cpp
+ algo/BFSShortestPathExecutor.cpp
admin/SwitchSpaceExecutor.cpp
admin/CreateUserExecutor.cpp
admin/DropUserExecutor.cpp
diff --git a/src/executor/Executor.cpp b/src/executor/Executor.cpp
index 0c8882c43033b6f89112e79fa2538fe6d5718fcd..4e3fe652e18a5530182a990c2fe1ea1f1e2b7f12 100644
--- a/src/executor/Executor.cpp
+++ b/src/executor/Executor.cpp
@@ -34,6 +34,8 @@
#include "executor/admin/SubmitJobExecutor.h"
#include "executor/admin/SwitchSpaceExecutor.h"
#include "executor/admin/UpdateUserExecutor.h"
+#include "executor/algo/BFSShortestPathExecutor.h"
+#include "executor/algo/ConjunctPathExecutor.h"
#include "executor/logic/LoopExecutor.h"
#include "executor/logic/PassThroughExecutor.h"
#include "executor/logic/SelectExecutor.h"
@@ -365,6 +367,12 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) {
case PlanNode::Kind::kShowCollation: {
return pool->add(new ShowCollationExecutor(node, qctx));
}
+ case PlanNode::Kind::kBFSShortest: {
+ return pool->add(new BFSShortestPathExecutor(node, qctx));
+ }
+ case PlanNode::Kind::kConjunctPath: {
+ return pool->add(new ConjunctPathExecutor(node, qctx));
+ }
case PlanNode::Kind::kUnknown: {
break;
}
diff --git a/src/executor/algo/BFSShortestPathExecutor.cpp b/src/executor/algo/BFSShortestPathExecutor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..45975574dcd61bd23be02205517d88482032e243
--- /dev/null
+++ b/src/executor/algo/BFSShortestPathExecutor.cpp
@@ -0,0 +1,53 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "executor/algo/BFSShortestPathExecutor.h"
+
+#include "planner/Algo.h"
+
+namespace nebula {
+namespace graph {
+folly::Future<Status> BFSShortestPathExecutor::execute() {
+ SCOPED_TIMER(&execTime_);
+ auto* bfs = asNode<BFSShortestPath>(node());
+ auto iter = ectx_->getResult(bfs->inputVar()).iter();
+ VLOG(1) << "current: " << node()->outputVar();
+ VLOG(1) << "input: " << bfs->inputVar();
+ DCHECK(!!iter);
+
+ DataSet ds;
+ ds.colNames = node()->colNames();
+ std::multimap<Value, Value> 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))).finish());
+}
+} // namespace graph
+} // namespace nebula
diff --git a/src/executor/algo/BFSShortestPathExecutor.h b/src/executor/algo/BFSShortestPathExecutor.h
new file mode 100644
index 0000000000000000000000000000000000000000..cd06777dccd1ef8643d8b3271ea14c4db5a754d7
--- /dev/null
+++ b/src/executor/algo/BFSShortestPathExecutor.h
@@ -0,0 +1,26 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef EXECUTOR_QUERY_BFSSHORTESTPATHEXECUTOR_H_
+#define EXECUTOR_QUERY_BFSSHORTESTPATHEXECUTOR_H_
+
+#include "executor/Executor.h"
+
+namespace nebula {
+namespace graph {
+class BFSShortestPathExecutor final : public Executor {
+public:
+ BFSShortestPathExecutor(const PlanNode* node, QueryContext* qctx)
+ : Executor("BFSShortestPath", node, qctx) {}
+
+ folly::Future<Status> execute() override;
+
+private:
+ std::unordered_set<Value> visited_;
+};
+} // namespace graph
+} // namespace nebula
+#endif
diff --git a/src/executor/algo/ConjunctPathExecutor.cpp b/src/executor/algo/ConjunctPathExecutor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..534acb3fee16f6480faf6c7d0e36bd96c7b5956d
--- /dev/null
+++ b/src/executor/algo/ConjunctPathExecutor.cpp
@@ -0,0 +1,235 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "executor/algo/ConjunctPathExecutor.h"
+
+#include "planner/Algo.h"
+
+namespace nebula {
+namespace graph {
+folly::Future<Status> ConjunctPathExecutor::execute() {
+ SCOPED_TIMER(&execTime_);
+ auto* conjunct = asNode<ConjunctPath>(node());
+ switch (conjunct->pathKind()) {
+ case ConjunctPath::PathKind::kBiBFS:
+ return bfsShortestPath();
+ default:
+ LOG(FATAL) << "Not implement.";
+ }
+}
+
+folly::Future<Status> ConjunctPathExecutor::bfsShortestPath() {
+ auto* conjunct = asNode<ConjunctPath>(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);
+
+ 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("edge");
+ 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))).finish());
+ }
+ }
+
+ 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))).finish());
+}
+
+std::vector<Row> ConjunctPathExecutor::findBfsShortestPath(
+ Iterator* iter,
+ bool isLatest,
+ std::multimap<Value, const Edge*>& table) {
+ std::unordered_set<Value> meets;
+ for (; iter->valid(); iter->next()) {
+ auto& dst = iter->getColumn(kVid);
+ if (isLatest) {
+ auto& edge = iter->getColumn("edge");
+ 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<Row> 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::multimap<Value, Path> ConjunctPathExecutor::buildBfsInterimPath(
+ std::unordered_set<Value>& meets,
+ std::vector<std::multimap<Value, const Edge*>>& hists) {
+ std::multimap<Value, Path> results;
+ for (auto& v : meets) {
+ VLOG(1) << "Meet at: " << v;
+ Path start;
+ start.src = Vertex(v.getStr(), {});
+ 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<Path> interimPaths = {std::move(start)};
+ for (auto hist = hists.rbegin(); hist < hists.rend(); ++hist) {
+ std::vector<Path> 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<Status> ConjunctPathExecutor::conjunctPath() {
+ auto* conjunct = asNode<ConjunctPath>(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);
+
+ DataSet ds;
+ ds.colNames = conjunct->colNames();
+
+ std::multimap<Value, const Path*> table;
+ for (; lIter->valid(); lIter->next()) {
+ auto& dst = lIter->getColumn(kVid);
+ auto& path = lIter->getColumn("path");
+ if (path.isPath() && !path.getPath().steps.empty()) {
+ VLOG(1) << "Forward dst: " << dst;
+ table.emplace(dst, &path.getPath());
+ }
+ }
+
+ if (rHist.size() >= 2) {
+ auto previous = rHist[rHist.size() - 2].iter();
+ if (findPath(previous.get(), table, ds)) {
+ VLOG(1) << "Meet odd length path.";
+ return finish(ResultBuilder().value(Value(std::move(ds))).finish());
+ }
+ }
+
+ auto latest = rHist.back().iter();
+ findPath(latest.get(), table, ds);
+ return finish(ResultBuilder().value(Value(std::move(ds))).finish());
+}
+
+bool ConjunctPathExecutor::findPath(Iterator* iter,
+ std::multimap<Value, const Path*>& table,
+ DataSet& ds) {
+ bool found = false;
+ for (; iter->valid(); iter->next()) {
+ auto& dst = iter->getColumn(kVid);
+ VLOG(1) << "Backward dst: " << dst;
+ auto& path = iter->getColumn("path");
+ if (path.isPath()) {
+ auto paths = table.equal_range(dst);
+ if (paths.first != paths.second) {
+ for (auto i = paths.first; i != paths.second; ++i) {
+ Row row;
+ auto forward = *i->second;
+ 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));
+ VLOG(1) << "Found path: " << forward;
+ row.values.emplace_back(std::move(forward));
+ ds.rows.emplace_back(std::move(row));
+ }
+ found = true;
+ }
+ }
+ }
+ return found;
+}
+} // namespace graph
+} // namespace nebula
diff --git a/src/executor/algo/ConjunctPathExecutor.h b/src/executor/algo/ConjunctPathExecutor.h
new file mode 100644
index 0000000000000000000000000000000000000000..0244a752535f42474e6a8aff92b518c2f9a2b5cf
--- /dev/null
+++ b/src/executor/algo/ConjunctPathExecutor.h
@@ -0,0 +1,41 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef EXECUTOR_QUERY_CONJUNCTPATHEXECUTOR_H_
+#define EXECUTOR_QUERY_CONJUNCTPATHEXECUTOR_H_
+
+#include "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<Status> execute() override;
+
+private:
+ folly::Future<Status> bfsShortestPath();
+
+ std::vector<Row> findBfsShortestPath(Iterator* iter,
+ bool isLatest,
+ std::multimap<Value, const Edge*>& table);
+
+ std::multimap<Value, Path> buildBfsInterimPath(
+ std::unordered_set<Value>& meets,
+ std::vector<std::multimap<Value, const Edge*>>& hist);
+
+ folly::Future<Status> conjunctPath();
+
+ bool findPath(Iterator* iter, std::multimap<Value, const Path*>& table, DataSet& ds);
+
+ std::vector<std::multimap<Value, const Edge*>> forward_;
+ std::vector<std::multimap<Value, const Edge*>> backward_;
+};
+} // namespace graph
+} // namespace nebula
+#endif // EXECUTOR_QUERY_CONJUNCTPATHEXECUTOR_H_
diff --git a/src/executor/logic/LoopExecutor.cpp b/src/executor/logic/LoopExecutor.cpp
index ee29f64bc653934ff46637adb0513079262ec21d..29327cf7224df317192fdecb44a39885e6e4b092 100644
--- a/src/executor/logic/LoopExecutor.cpp
+++ b/src/executor/logic/LoopExecutor.cpp
@@ -28,8 +28,9 @@ folly::Future<Status> LoopExecutor::execute() {
auto *loopNode = asNode<Loop>(node());
Expression *expr = loopNode->condition();
QueryExpressionContext ctx(ectx_);
+
auto value = expr->eval(ctx);
- VLOG(1) << "Loop condition: " << value;
+ VLOG(1) << "Loop condition: " << expr->toString() << " val: " << value;
DCHECK(value.isBool());
return finish(ResultBuilder().value(std::move(value)).iter(Iterator::Kind::kDefault).finish());
}
diff --git a/src/executor/query/DataCollectExecutor.cpp b/src/executor/query/DataCollectExecutor.cpp
index 3f9a92e79c99a9956d46e482aca4606bea364b67..adeb88a54801407314762eea748161b76846e77c 100644
--- a/src/executor/query/DataCollectExecutor.cpp
+++ b/src/executor/query/DataCollectExecutor.cpp
@@ -37,6 +37,10 @@ folly::Future<Status> DataCollectExecutor::doCollect() {
NG_RETURN_IF_ERROR(collectMToN(vars, dc->mToN(), dc->distinct()));
break;
}
+ case DataCollect::CollectKind::kBFSShortest: {
+ NG_RETURN_IF_ERROR(collectBFSShortest(vars));
+ break;
+ }
default:
LOG(FATAL) << "Unknown data collect type: " << static_cast<int64_t>(dc->collectKind());
}
@@ -83,7 +87,9 @@ Status DataCollectExecutor::collectSubgraph(const std::vector<std::string>& vars
}
ds.rows.emplace_back(Row({std::move(vertices), std::move(edges)}));
} else {
- return Status::Error("Iterator should be kind of GetNeighborIter.");
+ std::stringstream msg;
+ msg << "Iterator should be kind of GetNeighborIter, but was: " << iter->kind();
+ return Status::Error(msg.str());
}
}
}
@@ -134,7 +140,9 @@ Status DataCollectExecutor::collectMToN(const std::vector<std::string>& vars,
ds.rows.emplace_back(seqIter->moveRow());
}
} else {
- return Status::Error("Iterator should be kind of SequentialIter.");
+ std::stringstream msg;
+ msg << "Iterator should be kind of SequentialIter, but was: " << iter->kind();
+ return Status::Error(msg.str());
}
itersHolder.emplace_back(std::move(iter));
}
@@ -142,5 +150,10 @@ Status DataCollectExecutor::collectMToN(const std::vector<std::string>& vars,
result_.setDataSet(std::move(ds));
return Status::OK();
}
+
+Status DataCollectExecutor::collectBFSShortest(const std::vector<std::string>& vars) {
+ // Will rewrite this method once we implement returning the props for the path.
+ return rowBasedMove(vars);
+}
} // namespace graph
} // namespace nebula
diff --git a/src/executor/query/DataCollectExecutor.h b/src/executor/query/DataCollectExecutor.h
index be48dc90350dd0febd25306704e60f5bfbfed649..b376354ddb43c7bc3a550151c7d6c3ba31ab296f 100644
--- a/src/executor/query/DataCollectExecutor.h
+++ b/src/executor/query/DataCollectExecutor.h
@@ -27,6 +27,8 @@ private:
Status collectMToN(const std::vector<std::string>& vars, StepClause::MToN* mToN, bool distinct);
+ Status collectBFSShortest(const std::vector<std::string>& vars);
+
std::vector<std::string> colNames_;
Value result_;
};
diff --git a/src/executor/test/BFSShortestTest.cpp b/src/executor/test/BFSShortestTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..af7f26be83bbf10e1a4083d43cf8dcdc5c0ae7d6
--- /dev/null
+++ b/src/executor/test/BFSShortestTest.cpp
@@ -0,0 +1,218 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include <gtest/gtest.h>
+
+#include "context/QueryContext.h"
+#include "planner/Algo.h"
+#include "executor/algo/BFSShortestPathExecutor.h"
+
+namespace nebula {
+namespace graph {
+class BFSShortestTest : public testing::Test {
+protected:
+ void SetUp() override {
+ qctx_ = std::make_unique<QueryContext>();
+ {
+ 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<std::string>(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<std::string>(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_->ectx()->setResult("empty_get_neighbors",
+ ResultBuilder()
+ .value(Value(std::move(ds)))
+ .iter(Iterator::Kind::kGetNeighbors)
+ .finish());
+ }
+ }
+
+protected:
+ std::unique_ptr<QueryContext> qctx_;
+ DataSet firstStepResult_;
+ DataSet secondStepResult_;
+};
+
+TEST_F(BFSShortestTest, BFSShortest) {
+ auto* bfs = BFSShortestPath::make(qctx_.get(), nullptr);
+ bfs->setInputVar("input");
+ bfs->setColNames({"_vid", "edge"});
+
+ auto bfsExe = std::make_unique<BFSShortestPathExecutor>(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.finish());
+
+ 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.finish());
+
+ 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<BFSShortestPathExecutor>(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/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt
index b777afa533f3342f16db38576bad3c6b3c44541d..75788f00f54e1bb28e3e30d92f8e6936b15e1379 100644
--- a/src/executor/test/CMakeLists.txt
+++ b/src/executor/test/CMakeLists.txt
@@ -48,7 +48,6 @@ SET(EXEC_QUERY_TEST_OBJS
SET(EXEC_QUERY_TEST_LIBS
${THRIFT_LIBRARIES}
gtest
- gtest_main
wangle
proxygenhttpserver
proxygenlib
@@ -58,6 +57,7 @@ nebula_add_test(
NAME
executor_test
SOURCES
+ TestMain.cpp
LogicExecutorsTest.cpp
ProjectTest.cpp
GetNeighborsTest.cpp
@@ -70,6 +70,8 @@ nebula_add_test(
TopNTest.cpp
AggregateTest.cpp
DataJoinTest.cpp
+ BFSShortestTest.cpp
+ ConjunctPathTest.cpp
OBJECTS
${EXEC_QUERY_TEST_OBJS}
LIBRARIES
diff --git a/src/executor/test/ConjunctPathTest.cpp b/src/executor/test/ConjunctPathTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5c469fe7d1c26e28825d6e4b1c241fac9d1cd923
--- /dev/null
+++ b/src/executor/test/ConjunctPathTest.cpp
@@ -0,0 +1,361 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include <gtest/gtest.h>
+
+#include "context/QueryContext.h"
+#include "planner/Algo.h"
+#include "executor/algo/ConjunctPathExecutor.h"
+
+namespace nebula {
+namespace graph {
+class ConjunctPathTest : public testing::Test {
+protected:
+ void SetUp() override {
+ qctx_ = std::make_unique<QueryContext>();
+ {
+ // 1->2
+ // 1->3
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ Row row;
+ row.values = {"1", Value::kEmpty};
+ ds1.rows.emplace_back(std::move(row));
+ }
+ qctx_->ectx()->setResult("forward1", ResultBuilder().value(ds1).finish());
+
+ DataSet ds2;
+ ds2.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ {
+ // 4
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ Row row;
+ row.values = {"4", Value::kEmpty};
+ ds1.rows.emplace_back(std::move(row));
+ }
+ qctx_->ectx()->setResult("backward1", ResultBuilder().value(ds1).finish());
+
+ DataSet ds2;
+ ds2.colNames = {kVid, "edge"};
+ qctx_->ectx()->setResult("backward1", ResultBuilder().value(ds2).finish());
+ }
+ {
+ // 2
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ Row row;
+ row.values = {"2", Value::kEmpty};
+ ds1.rows.emplace_back(std::move(row));
+ }
+ qctx_->ectx()->setResult("backward2", ResultBuilder().value(ds1).finish());
+
+ DataSet ds2;
+ ds2.colNames = {kVid, "edge"};
+ qctx_->ectx()->setResult("backward2", ResultBuilder().value(ds2).finish());
+ }
+ {
+ // 4->3
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ Row row;
+ row.values = {"4", Value::kEmpty};
+ ds1.rows.emplace_back(std::move(row));
+ }
+ qctx_->ectx()->setResult("backward3", ResultBuilder().value(ds1).finish());
+
+ DataSet ds2;
+ ds2.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ {
+ // 5->4
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ Row row;
+ row.values = {"5", Value::kEmpty};
+ ds1.rows.emplace_back(std::move(row));
+ }
+ qctx_->ectx()->setResult("backward4", ResultBuilder().value(ds1).finish());
+
+ DataSet ds2;
+ ds2.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ }
+
+protected:
+ std::unique_ptr<QueryContext> qctx_;
+};
+
+TEST_F(ConjunctPathTest, BiBFSNoPath) {
+ auto* conjunct =
+ ConjunctPath::make(qctx_.get(), nullptr, nullptr, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar("forward1");
+ conjunct->setRightVar("backward1");
+ conjunct->setColNames({"_path"});
+
+ auto conjunctExe = std::make_unique<ConjunctPathExecutor>(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 = {"_path"};
+ EXPECT_EQ(result.value().getDataSet(), expected);
+ EXPECT_EQ(result.state(), Result::State::kSuccess);
+}
+
+TEST_F(ConjunctPathTest, BiBFSOneStepPath) {
+ auto* conjunct =
+ ConjunctPath::make(qctx_.get(), nullptr, nullptr, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar("forward1");
+ conjunct->setRightVar("backward2");
+ conjunct->setColNames({"_path"});
+
+ auto conjunctExe = std::make_unique<ConjunctPathExecutor>(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 = {"_path"};
+ 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(), nullptr, nullptr, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar("forward1");
+ conjunct->setRightVar("backward3");
+ conjunct->setColNames({"_path"});
+
+ auto conjunctExe = std::make_unique<ConjunctPathExecutor>(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 = {"_path"};
+ 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(), nullptr, nullptr, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar("forward1");
+ conjunct->setRightVar("backward4");
+ conjunct->setColNames({"_path"});
+
+ auto conjunctExe = std::make_unique<ConjunctPathExecutor>(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 = {"_path"};
+ EXPECT_EQ(result.value().getDataSet(), expected);
+ EXPECT_EQ(result.state(), Result::State::kSuccess);
+ }
+
+ {
+ {
+ // 2->4@0
+ // 2->4@1
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ {
+ // 4->6@0
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ 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 = {"_path"};
+ {
+ 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", 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));
+ }
+ {
+ 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(), nullptr, nullptr, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar("forward1");
+ conjunct->setRightVar("backward4");
+ conjunct->setColNames({"_path"});
+
+ auto conjunctExe = std::make_unique<ConjunctPathExecutor>(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 = {"_path"};
+ EXPECT_EQ(result.value().getDataSet(), expected);
+ EXPECT_EQ(result.state(), Result::State::kSuccess);
+ }
+
+ {
+ {
+ // 2->6@0
+ // 2->6@1
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ {
+ // 4->6@0
+ DataSet ds1;
+ ds1.colNames = {kVid, "edge"};
+ {
+ 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).finish());
+ }
+ 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 = {"_path"};
+ {
+ 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", 0, {}));
+ 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));
+ }
+ {
+ 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);
+ }
+}
+} // namespace graph
+} // namespace nebula
diff --git a/src/executor/test/TestMain.cpp b/src/executor/test/TestMain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..23b013e43d493379a9dcc9534543368a020feeeb
--- /dev/null
+++ b/src/executor/test/TestMain.cpp
@@ -0,0 +1,18 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include <gtest/gtest.h>
+
+#include "common/base/Base.h"
+
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ folly::init(&argc, &argv, true);
+ google::SetStderrLogging(google::INFO);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/src/optimizer/OptGroup.cpp b/src/optimizer/OptGroup.cpp
index 4a055ee031225cd1742faa0b678a7a98996b8753..cb97fd1f9aded0cae1aaefadaeccebd8a34253b8 100644
--- a/src/optimizer/OptGroup.cpp
+++ b/src/optimizer/OptGroup.cpp
@@ -169,8 +169,8 @@ const PlanNode *OptGroupExpr::getPlan() const {
case 2: {
DCHECK_EQ(dependencies_.size(), 2U);
auto bNode = static_cast<BiInputNode *>(node_);
- bNode->setLeft(dependencies_[0]->getPlan());
- bNode->setRight(dependencies_[1]->getPlan());
+ bNode->setLeftDep(dependencies_[0]->getPlan());
+ bNode->setRightDep(dependencies_[1]->getPlan());
break;
}
}
diff --git a/src/planner/Algo.cpp b/src/planner/Algo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..14453bdf49f5b790e92d21821932da0657abecf2
--- /dev/null
+++ b/src/planner/Algo.cpp
@@ -0,0 +1,13 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "planner/Algo.h"
+
+namespace nebula {
+namespace graph {
+
+} // namnspace graph
+} // namespace nebula
diff --git a/src/planner/Algo.h b/src/planner/Algo.h
new file mode 100644
index 0000000000000000000000000000000000000000..b33018717299b81a06d6924a06efd51e4cd3f557
--- /dev/null
+++ b/src/planner/Algo.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#ifndef PLANNER_ALGO_H_
+#define PLANNER_ALGO_H_
+
+#include "common/base/Base.h"
+#include "context/QueryContext.h"
+#include "planner/PlanNode.h"
+
+namespace nebula {
+namespace graph {
+class ProduceSemiShortestPath : public PlanNode {
+};
+
+class BFSShortestPath : public SingleInputNode {
+public:
+ static BFSShortestPath* make(QueryContext* qctx, PlanNode* input) {
+ return qctx->objPool()->add(new BFSShortestPath(qctx->genId(), input));
+ }
+
+private:
+ BFSShortestPath(int64_t id, PlanNode* input)
+ : SingleInputNode(id, Kind::kBFSShortest, input) {}
+};
+
+class ConjunctPath : public BiInputNode {
+public:
+ enum class PathKind : uint8_t {
+ kBiBFS,
+ kBiDijkstra,
+ kFloyd,
+ kAllPath,
+ };
+
+ static ConjunctPath* make(QueryContext* qctx,
+ PlanNode* left,
+ PlanNode* right,
+ PathKind pathKind) {
+ return qctx->objPool()->add(new ConjunctPath(qctx->genId(), left, right, pathKind));
+ }
+
+ PathKind pathKind() const {
+ return pathKind_;
+ }
+
+private:
+ ConjunctPath(int64_t id, PlanNode* left, PlanNode* right, PathKind pathKind)
+ : BiInputNode(id, Kind::kConjunctPath, left, right) {
+ pathKind_ = pathKind;
+ }
+
+ PathKind pathKind_;
+};
+
+} // namespace graph
+} // namespace nebula
+#endif // PLANNER_ALGO_H_
diff --git a/src/planner/ExecutionPlan.cpp b/src/planner/ExecutionPlan.cpp
index 3730bd97d45ef4a258692cebd4bbc15984bf4563..7229f5d4a03adae48035b27d74801e6d72719a20 100644
--- a/src/planner/ExecutionPlan.cpp
+++ b/src/planner/ExecutionPlan.cpp
@@ -36,7 +36,8 @@ static size_t makePlanNodeDesc(const PlanNode* node, cpp2::PlanDescription* plan
}
case PlanNode::Kind::kUnion:
case PlanNode::Kind::kIntersect:
- case PlanNode::Kind::kMinus: {
+ case PlanNode::Kind::kMinus:
+ case PlanNode::Kind::kConjunctPath: {
auto bNode = static_cast<const BiInputNode*>(node);
makePlanNodeDesc(bNode->left(), planDesc);
makePlanNodeDesc(bNode->right(), planDesc);
diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp
index 312634c95ded4b527e82ddfe179ca005485dfcec..913ff4a24237313c647dcbdfbe26c056da33ba35 100644
--- a/src/planner/PlanNode.cpp
+++ b/src/planner/PlanNode.cpp
@@ -176,6 +176,10 @@ const char* PlanNode::toString(PlanNode::Kind kind) {
return "SetConfig";
case Kind::kGetConfig:
return "GetConfig";
+ case Kind::kBFSShortest:
+ return "BFSShortest";
+ case Kind::kConjunctPath:
+ return "ConjunctPath";
// no default so the compiler will warning when lack
}
LOG(FATAL) << "Impossible kind plan node " << static_cast<int>(kind);
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index 321d7cc6f11f92587ad69a6c012fb320c56bd92b..2d31f595df6d298760a0bb7e739aab2a60f71879 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -104,6 +104,8 @@ public:
kShowConfigs,
kSetConfig,
kGetConfig,
+ kBFSShortest,
+ kConjunctPath,
};
PlanNode(int64_t id, Kind kind);
@@ -237,11 +239,11 @@ protected:
class BiInputNode : public PlanNode {
public:
- void setLeft(const PlanNode* left) {
+ void setLeftDep(const PlanNode* left) {
setDep(0, left);
}
- void setRight(const PlanNode* right) {
+ void setRightDep(const PlanNode* right) {
setDep(1, right);
}
@@ -274,12 +276,21 @@ public:
protected:
BiInputNode(int64_t id, Kind kind, PlanNode* left, PlanNode* right)
: PlanNode(id, kind) {
- DCHECK(left != nullptr);
- DCHECK(right != nullptr);
- dependencies_.emplace_back(left);
- dependencies_.emplace_back(right);
- inputVars_.emplace_back(left->outputVar());
- inputVars_.emplace_back(right->outputVar());
+ if (left != nullptr) {
+ dependencies_.emplace_back(left);
+ inputVars_.emplace_back(left->outputVar());
+ } else {
+ dependencies_.emplace_back();
+ inputVars_.emplace_back();
+ }
+
+ if (right != nullptr) {
+ dependencies_.emplace_back(right);
+ inputVars_.emplace_back(right->outputVar());
+ } else {
+ dependencies_.emplace_back();
+ inputVars_.emplace_back();
+ }
}
};
diff --git a/src/planner/Query.cpp b/src/planner/Query.cpp
index 7de517ebabe3930c23b2b5d6ec93977f021caf9e..f854840b5ac5c21e3fbc197c9c7a56a7e3a46106 100644
--- a/src/planner/Query.cpp
+++ b/src/planner/Query.cpp
@@ -128,15 +128,19 @@ std::unique_ptr<cpp2::PlanNodeDescription> DataCollect::explain() const {
addDescription("inputVars", folly::toJson(util::toJson(inputVars_)), desc.get());
switch (collectKind_) {
case CollectKind::kSubgraph: {
- addDescription("kind", "subgraph", desc.get());
+ addDescription("kind", "SUBGRAPH", desc.get());
break;
}
case CollectKind::kRowBasedMove: {
- addDescription("kind", "row", desc.get());
+ addDescription("kind", "ROW", desc.get());
break;
}
case CollectKind::kMToN: {
- addDescription("kind", "m to n", desc.get());
+ addDescription("kind", "M TO N", desc.get());
+ break;
+ }
+ case CollectKind::kBFSShortest: {
+ addDescription("kind", "BFS SHORTEST", desc.get());
break;
}
}
diff --git a/src/planner/Query.h b/src/planner/Query.h
index 5cf4072cd263eee7fee8787571ac45d7916dd1ca..3885c48e769854b35618153cd6fc2e32d10bf1a9 100644
--- a/src/planner/Query.h
+++ b/src/planner/Query.h
@@ -838,6 +838,7 @@ public:
kSubgraph,
kRowBasedMove,
kMToN,
+ kBFSShortest,
};
static DataCollect* make(QueryContext* qctx,
@@ -946,15 +947,6 @@ private:
std::vector<Expression*> hashKeys_;
std::vector<Expression*> probeKeys_;
};
-
-class ProduceSemiShortestPath : public PlanNode {
-public:
-};
-
-class ConjunctPath : public PlanNode {
-public:
-};
-
} // namespace graph
} // namespace nebula
#endif // PLANNER_QUERY_H_
diff --git a/src/validator/FindPathValidator.cpp b/src/validator/FindPathValidator.cpp
index dd41123b9d93ace77a21f4316004e4adbeeae617..1d5d4fd8540b4474ca5c9f2937943f401ef4bfe7 100644
--- a/src/validator/FindPathValidator.cpp
+++ b/src/validator/FindPathValidator.cpp
@@ -5,6 +5,9 @@
*/
#include "validator/FindPathValidator.h"
+
+#include "common/expression/VariableExpression.h"
+#include "planner/Algo.h"
#include "planner/Logic.h"
namespace nebula {
@@ -22,10 +25,122 @@ Status FindPathValidator::validateImpl() {
Status FindPathValidator::toPlan() {
// TODO: Implement the path plan.
- auto* passThrough = PassThroughNode::make(qctx_, nullptr);
- tail_ = passThrough;
- root_ = tail_;
+ if (from_.vids.size() == 1 && to_.vids.size() == 1) {
+ return singlePairPlan();
+ } else {
+ auto* passThrough = PassThroughNode::make(qctx_, nullptr);
+ tail_ = passThrough;
+ root_ = tail_;
+ }
+ return Status::OK();
+}
+
+Status FindPathValidator::singlePairPlan() {
+ auto* bodyStart = StartNode::make(qctx_);
+ auto* passThrough = PassThroughNode::make(qctx_, bodyStart);
+
+ auto* forward = bfs(passThrough, from_, false);
+ VLOG(1) << "forward: " << forward->outputVar();
+
+ auto* backward = bfs(passThrough, to_, true);
+ VLOG(1) << "backward: " << backward->outputVar();
+
+ auto* conjunct =
+ ConjunctPath::make(qctx_, forward, backward, ConjunctPath::PathKind::kBiBFS);
+ conjunct->setLeftVar(forward->outputVar());
+ conjunct->setRightVar(backward->outputVar());
+ conjunct->setColNames({"_path"});
+
+ auto* loop = Loop::make(
+ qctx_, nullptr, conjunct, buildBfsLoopCondition(steps_.steps, conjunct->outputVar()));
+
+ auto* dataCollect = DataCollect::make(
+ qctx_, loop, DataCollect::CollectKind::kBFSShortest, {conjunct->outputVar()});
+ dataCollect->setColNames({"_path"});
+
+ root_ = dataCollect;
+ tail_ = loop;
return Status::OK();
}
+
+void FindPathValidator::buildStart(const Starts& starts,
+ std::string& startVidsVar,
+ PlanNode* dedupStartVid,
+ Expression* src) {
+ if (!starts.vids.empty() && starts.srcRef == nullptr) {
+ buildConstantInput(starts, startVidsVar, src);
+ } else {
+ dedupStartVid = buildRuntimeInput();
+ startVidsVar = dedupStartVid->outputVar();
+ }
+}
+
+PlanNode* FindPathValidator::bfs(PlanNode* dep, const Starts& starts, bool reverse) {
+ std::string startVidsVar;
+ Expression* vids = nullptr;
+ buildConstantInput(starts, startVidsVar, vids);
+
+ DCHECK(!!vids);
+ auto* gn = GetNeighbors::make(qctx_, dep, space_.id);
+ gn->setSrc(vids);
+ gn->setEdgeProps(buildEdgeKey(reverse));
+ gn->setInputVar(startVidsVar);
+
+ auto* bfs = BFSShortestPath::make(qctx_, gn);
+ bfs->setInputVar(gn->outputVar());
+ bfs->setColNames({"_vid", "edge"});
+ bfs->setOutputVar(startVidsVar);
+
+ DataSet ds;
+ ds.colNames = {"_vid", "edge"};
+ 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(startVidsVar, ResultBuilder().value(Value(std::move(ds))).finish());
+
+ return bfs;
+}
+
+Expression* FindPathValidator::buildBfsLoopCondition(uint32_t steps, const std::string& pathVar) {
+ // ++loopSteps{0} <= (steps/2+steps%2) && size(pathVar) == 0
+ auto loopSteps = vctx_->anonVarGen()->getVar();
+ qctx_->ectx()->setValue(loopSteps, 0);
+
+ auto* nSteps = new RelationalExpression(
+ Expression::Kind::kRelLE,
+ new UnaryExpression(
+ Expression::Kind::kUnaryIncr,
+ new VersionedVariableExpression(new std::string(loopSteps), new ConstantExpression(0))),
+ new ConstantExpression(static_cast<int32_t>(steps / 2 + steps % 2)));
+
+ auto* args = new ArgumentList();
+ args->addArgument(std::make_unique<VariableExpression>(new std::string(pathVar)));
+ auto* pathEmpty =
+ new RelationalExpression(Expression::Kind::kRelEQ,
+ new FunctionCallExpression(new std::string("size"), args),
+ new ConstantExpression(0));
+ auto* notFoundPath = new LogicalExpression(
+ Expression::Kind::kLogicalOr,
+ new RelationalExpression(Expression::Kind::kRelEQ,
+ new VariableExpression(new std::string(pathVar)),
+ new ConstantExpression(Value())),
+ pathEmpty);
+ return qctx_->objPool()->add(
+ new LogicalExpression(Expression::Kind::kLogicalAnd, nSteps, notFoundPath));
+}
+
+GetNeighbors::EdgeProps FindPathValidator::buildEdgeKey(bool reverse) {
+ auto edgeProps = std::make_unique<std::vector<storage::cpp2::EdgeProp>>(
+ over_.edgeTypes.size());
+ std::transform(over_.edgeTypes.begin(), over_.edgeTypes.end(), edgeProps->begin(),
+ [reverse](auto& type) {
+ storage::cpp2::EdgeProp ep;
+ ep.type = reverse ? -type : type;
+ ep.props = {kDst, kType, kRank};
+ return ep;
+ });
+ return edgeProps;
+}
} // namespace graph
} // namespace nebula
diff --git a/src/validator/FindPathValidator.h b/src/validator/FindPathValidator.h
index e9769284a78f37c74429e6fb36e3bb2b8b5bc59e..77f06975f7be933b857d97c38e0065a1ba431f2b 100644
--- a/src/validator/FindPathValidator.h
+++ b/src/validator/FindPathValidator.h
@@ -13,7 +13,6 @@
namespace nebula {
namespace graph {
-
class FindPathValidator final : public TraversalValidator {
public:
FindPathValidator(Sentence* sentence, QueryContext* context)
@@ -24,9 +23,21 @@ private:
Status toPlan() override;
+ Status singlePairPlan();
+
+ void buildStart(const Starts& starts,
+ std::string& startVidsVar,
+ PlanNode* dedupStartVid,
+ Expression* src);
+
+ PlanNode* bfs(PlanNode* dep, const Starts& starts, bool reverse);
+
+ Expression* buildBfsLoopCondition(uint32_t steps, const std::string& pathVar);
+
+ GetNeighbors::EdgeProps buildEdgeKey(bool reverse);
+
private:
bool isShortest_{false};
- Starts from_;
Starts to_;
Over over_;
Steps steps_;
diff --git a/src/validator/GetSubgraphValidator.cpp b/src/validator/GetSubgraphValidator.cpp
index 6c2c4103008992a69b0b615e44d53da7c42ec75b..501730177d868b3604cd9e442e9b6836b4e0678b 100644
--- a/src/validator/GetSubgraphValidator.cpp
+++ b/src/validator/GetSubgraphValidator.cpp
@@ -217,7 +217,7 @@ Status GetSubgraphValidator::toPlan() {
std::string startVidsVar;
SingleInputNode* collectRunTimeStartVids = nullptr;
if (!from_.vids.empty() && from_.srcRef == nullptr) {
- startVidsVar = buildConstantInput();
+ buildConstantInput(from_, startVidsVar, src_);
} else {
PlanNode* dedupStartVid = buildRuntimeInput();
startVidsVar = dedupStartVid->outputVar();
diff --git a/src/validator/GoValidator.cpp b/src/validator/GoValidator.cpp
index 035c0c1b5124c77914ec2ee34f0d52d4ff656ceb..2311f0b2afbeda656bf323936d9d5a9648679af6 100644
--- a/src/validator/GoValidator.cpp
+++ b/src/validator/GoValidator.cpp
@@ -222,7 +222,7 @@ Status GoValidator::buildNStepsPlan() {
std::string startVidsVar;
PlanNode* dedupStartVid = nullptr;
if (!from_.vids.empty() && from_.srcRef == nullptr) {
- startVidsVar = buildConstantInput();
+ buildConstantInput(from_, startVidsVar, src_);
} else {
dedupStartVid = buildRuntimeInput();
startVidsVar = dedupStartVid->outputVar();
@@ -277,7 +277,7 @@ Status GoValidator::buildMToNPlan() {
std::string startVidsVar;
PlanNode* dedupStartVid = nullptr;
if (!from_.vids.empty() && from_.srcRef == nullptr) {
- startVidsVar = buildConstantInput();
+ buildConstantInput(from_, startVidsVar, src_);
} else {
dedupStartVid = buildRuntimeInput();
startVidsVar = dedupStartVid->outputVar();
@@ -590,7 +590,7 @@ Status GoValidator::buildOneStepPlan() {
std::string startVidsVar;
PlanNode* dedupStartVid = nullptr;
if (!from_.vids.empty() && from_.srcRef == nullptr) {
- startVidsVar = buildConstantInput();
+ buildConstantInput(from_, startVidsVar, src_);
} else {
dedupStartVid = buildRuntimeInput();
startVidsVar = dedupStartVid->outputVar();
diff --git a/src/validator/TraversalValidator.cpp b/src/validator/TraversalValidator.cpp
index 83d5a106cf38a77d146b3184ddf7080159d0f26c..ffaeca6d2292182be8226be37fa9e1b904090403 100644
--- a/src/validator/TraversalValidator.cpp
+++ b/src/validator/TraversalValidator.cpp
@@ -167,22 +167,21 @@ PlanNode* TraversalValidator::projectDstVidsFromGN(PlanNode* gn, const std::stri
return dedupDstVids;
}
-std::string TraversalValidator::buildConstantInput() {
- auto input = vctx_->anonVarGen()->getVar();
+void TraversalValidator::buildConstantInput(const Starts& starts,
+ std::string& startVidsVar,
+ Expression*& vids) {
+ startVidsVar = vctx_->anonVarGen()->getVar();
DataSet ds;
ds.colNames.emplace_back(kVid);
- for (auto& vid : from_.vids) {
+ for (auto& vid : starts.vids) {
Row row;
row.values.emplace_back(vid);
ds.rows.emplace_back(std::move(row));
}
- qctx_->ectx()->setResult(input, ResultBuilder().value(Value(std::move(ds))).finish());
+ qctx_->ectx()->setResult(startVidsVar, ResultBuilder().value(Value(std::move(ds))).finish());
- auto* vids = new VariablePropertyExpression(new std::string(input),
- new std::string(kVid));
+ vids = new VariablePropertyExpression(new std::string(startVidsVar), new std::string(kVid));
qctx_->objPool()->add(vids);
- src_ = vids;
- return input;
}
PlanNode* TraversalValidator::buildRuntimeInput() {
diff --git a/src/validator/TraversalValidator.h b/src/validator/TraversalValidator.h
index a1ada764ca89ecd1f2016a3d2ee31200b01f7851..28c1592b819d15633b9e91531dc9798721efd1e3 100644
--- a/src/validator/TraversalValidator.h
+++ b/src/validator/TraversalValidator.h
@@ -56,7 +56,7 @@ protected:
PlanNode* projectDstVidsFromGN(PlanNode* gn, const std::string& outputVar);
- std::string buildConstantInput();
+ void buildConstantInput(const Starts& starts, std::string& startVidsVar, Expression*& vids);
PlanNode* buildRuntimeInput();
diff --git a/tests/common/nebula_test_suite.py b/tests/common/nebula_test_suite.py
index 47149c1e21bb6c43990b4cc7d6a3070e6c5eb008..c05460523c2d25681376ab2af8c1e4362b5266d2 100644
--- a/tests/common/nebula_test_suite.py
+++ b/tests/common/nebula_test_suite.py
@@ -486,40 +486,44 @@ class NebulaTestSuite(object):
assert empty, msg
@classmethod
- def check_path_result(self, rows, expect):
+ def check_path_result_without_prop(self, rows, expect):
msg = 'len(rows)[%d] != len(expect)[%d]' % (len(rows), len(expect))
assert len(rows) == len(expect), msg
for exp in expect:
- path = ttypes.Path()
- path.entry_list = []
- for ecol, j in zip(exp, range(len(exp))):
- if j % 2 == 0 or j == len(exp):
- pathEntry = ttypes.PathEntry()
- vertex = ttypes.Vertex()
- vertex.id = ecol
- pathEntry.set_vertex(vertex)
- path.entry_list.append(pathEntry)
+ path = CommonTtypes.Path()
+ path.steps = []
+ for col, j in zip(exp, range(len(exp))):
+ if j == 0:
+ src = CommonTtypes.Vertex()
+ src.vid = col
+ src.tags = []
+ path.src = src
else:
- assert len(ecol) == 2, \
- "invalid values size in expect result"
- pathEntry = ttypes.PathEntry()
- edge = ttypes.Edge()
- edge.type = ecol[0]
- edge.ranking = ecol[1]
- pathEntry.set_edge(edge)
- path.entry_list.append(pathEntry)
+ assert len(col) == 3, \
+ "{} invalid values size in expect result".format(exp.__repr__())
+ step = CommonTtypes.Step()
+ step.name = col[0]
+ step.ranking = col[1]
+ step.type = 1
+ dst = CommonTtypes.Vertex()
+ dst.vid = col[2]
+ dst.tags = []
+ step.dst = dst
+ step.props = {}
+ path.steps.append(step)
find = False
for row in rows:
assert len(row.values) == 1, \
"invalid values size in rows: {}".format(row)
- assert row.values[0].getType()() == ttypes.Value.PATH, \
+ assert row.values[0].getType() == CommonTtypes.Value.PVAL, \
"invalid column path type: {}".format(row.values[0].getType()())
- if row.values[0].get_path() == path:
+ if row.values[0].get_pVal() == path:
find = True
break
- msg = self.check_format_str.format(row.values[0].get_path(), path)
+ msg = self.check_format_str.format(row.values[0].get_pVal(), path)
assert find, msg
rows.remove(row)
+ assert len(rows) == 0
@classmethod
def check_error_msg(self, resp, expect):
diff --git a/tests/query/v1/test_find_path.py b/tests/query/v1/test_find_path.py
new file mode 100644
index 0000000000000000000000000000000000000000..737e242e7457c634db5f7c99bf6a96fa1a424817
--- /dev/null
+++ b/tests/query/v1/test_find_path.py
@@ -0,0 +1,78 @@
+# --coding:utf-8--
+#
+# Copyright (c) 2020 vesoft inc. All rights reserved.
+#
+# This source code is licensed under Apache 2.0 License,
+# attached with Common Clause Condition 1.0, found in the LICENSES directory.
+
+from tests.common.nebula_test_suite import NebulaTestSuite
+
+class TestFindPath(NebulaTestSuite):
+ @classmethod
+ def prepare(self):
+ self.use_nba()
+ self.load_vertex_edge()
+
+ def cleanup():
+ pass
+
+ def test_single_pair_constant_input(self):
+ stmt = 'FIND SHORTEST PATH FROM "Tim Duncan" TO "Tony Parker" OVER like'
+ resp = self.execute_query(stmt)
+ self.check_resp_succeeded(resp)
+ expected_data = {
+ "column_names" : ["_path"],
+ "rows" : [
+ [b"Tim Duncan", (b"like", 0, b"Tony Parker")]
+ ]
+ }
+ self.check_column_names(resp, expected_data["column_names"])
+ self.check_path_result_without_prop(resp.data.rows, expected_data["rows"])
+
+ stmt = 'FIND SHORTEST PATH FROM "Tim Duncan" TO "LaMarcus Aldridge" OVER like'
+ resp = self.execute_query(stmt)
+ self.check_resp_succeeded(resp)
+ expected_data = {
+ "column_names" : ["_path"],
+ "rows" : [
+ [b"Tim Duncan", (b"like", 0, b"Tony Parker"), (b"like", 0, b"LaMarcus Aldridge")]
+ ]
+ }
+ self.check_column_names(resp, expected_data["column_names"])
+ self.check_path_result_without_prop(resp.data.rows, expected_data["rows"])
+
+ stmt = 'FIND SHORTEST PATH FROM "Tiago Splitter" TO "LaMarcus Aldridge" OVER like'
+ resp = self.execute_query(stmt)
+ self.check_resp_succeeded(resp)
+ expected_data = {
+ "column_names" : ["_path"],
+ "rows" : [
+ [b"Tiago Splitter", (b"like", 0, b"Tim Duncan"), (b"like", 0, b"Tony Parker"), (b"like", 0, b"LaMarcus Aldridge")]
+ ]
+ }
+ self.check_column_names(resp, expected_data["column_names"])
+ self.check_path_result_without_prop(resp.data.rows, expected_data["rows"])
+
+ stmt = 'FIND SHORTEST PATH FROM "Tiago Splitter" TO "LaMarcus Aldridge" OVER like, teammate'
+ resp = self.execute_query(stmt)
+ self.check_resp_succeeded(resp)
+ expected_data = {
+ "column_names" : ["_path"],
+ "rows" : [
+ [b"Tiago Splitter", (b"like", 0, b"Tim Duncan"), (b"teammate", 0, b"LaMarcus Aldridge")]
+ ]
+ }
+ self.check_column_names(resp, expected_data["column_names"])
+ self.check_path_result_without_prop(resp.data.rows, expected_data["rows"])
+
+ stmt = 'FIND SHORTEST PATH FROM "Tiago Splitter" TO "LaMarcus Aldridge" OVER *'
+ resp = self.execute_query(stmt)
+ self.check_resp_succeeded(resp)
+ expected_data = {
+ "column_names" : ["_path"],
+ "rows" : [
+ [b"Tiago Splitter", (b"like", 0, b"Tim Duncan"), (b"teammate", 0, b"LaMarcus Aldridge")]
+ ]
+ }
+ self.check_column_names(resp, expected_data["column_names"])
+ self.check_path_result_without_prop(resp.data.rows, expected_data["rows"])