diff --git a/src/console/CmdProcessor.cpp b/src/console/CmdProcessor.cpp index e099c75fef27dfb7ab9c03c16a671b0ea06e0f82..2db6a251e5458ce132cd868843d02962c2480813 100644 --- a/src/console/CmdProcessor.cpp +++ b/src/console/CmdProcessor.cpp @@ -176,6 +176,46 @@ void CmdProcessor::calColumnWidths( } break; } + case cpp2::ColumnValue::Type::path: { + auto pathValue = col.get_path(); + auto entryList = pathValue.get_entry_list(); + decltype(entryList.size()) entryIdx = 0; + formats.resize(entryList.size(), ""); + widths.resize(entryList.size(), 0); + for (auto &entry : entryList) { + if (entry.getType() == cpp2::PathEntry::vertex) { + auto v = entry.get_vertex(); + auto id = v.get_id(); + size_t idLen = folly::stringPrintf("%ld", id).size(); + if (widths[entryIdx] < idLen) { + widths[entryIdx] = idLen; + genFmt = true; + } + if (genFmt) { + formats[entryIdx] = + folly::stringPrintf(" %%%ldld", idLen); + } + } + if (entry.getType() == cpp2::PathEntry::edge) { + auto e = entry.get_edge(); + auto type = e.get_type(); + auto ranking = e.get_ranking(); + size_t typeLen = folly::stringPrintf("%s", type.c_str()).size(); + size_t rankingLen = folly::stringPrintf("%ld", ranking).size(); + size_t len = typeLen + rankingLen + 4; + if (widths[entryIdx] < len) { + widths[entryIdx] = len; + genFmt = true; + } + if (genFmt) { + formats[entryIdx] = + folly::stringPrintf(" <%%%lds,%%%ldld>", typeLen, rankingLen); + } + } + ++entryIdx; + } + break; + } } ++idx; } @@ -317,6 +357,22 @@ void CmdProcessor::printData(const cpp2::ExecutionResponse& resp, dt.get_microsec()); break; } + case cpp2::ColumnValue::Type::path: { + auto pathValue = col.get_path(); + auto entryList = pathValue.get_entry_list(); + cIdx = 0; + for (auto &entry : entryList) { + if (entry.getType() == cpp2::PathEntry::vertex) { + PRINT_FIELD_VALUE(entry.get_vertex().get_id()); + } + if (entry.getType() == cpp2::PathEntry::edge) { + auto e = entry.get_edge(); + PRINT_FIELD_VALUE(e.get_type().c_str(), e.get_ranking()); + } + ++cIdx; + } + break; + } } ++cIdx; } diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt index 621ab31b2dc4f2c3186aefee5aa4a08949d749c8..62b853379089ffadda26767e51ba419e66484cad 100644 --- a/src/daemons/CMakeLists.txt +++ b/src/daemons/CMakeLists.txt @@ -29,6 +29,7 @@ nebula_add_executable( $<TARGET_OBJECTS:time_obj> $<TARGET_OBJECTS:fs_obj> $<TARGET_OBJECTS:base_obj> + $<TARGET_OBJECTS:concurrent_obj> LIBRARIES proxygenhttpserver proxygenlib diff --git a/src/executor/CMakeLists.txt b/src/executor/CMakeLists.txt index 16676818977371ae44c7691a72a98757df667337..f8e7ffffd8f17b019608b2b998a52d3343c47983 100644 --- a/src/executor/CMakeLists.txt +++ b/src/executor/CMakeLists.txt @@ -48,6 +48,7 @@ nebula_add_library( FindExecutor.cpp MatchExecutor.cpp DeleteVertexExecutor.cpp + FindPathExecutor.cpp ) nebula_add_library( diff --git a/src/executor/Executor.cpp b/src/executor/Executor.cpp index 5f82aeaa181e83618ee7661015c658fa73ebeb56..abd261c99be3e3c66bdf049bacf9c20d51e75a72 100644 --- a/src/executor/Executor.cpp +++ b/src/executor/Executor.cpp @@ -45,6 +45,7 @@ #include "graph/DeleteVertexExecutor.h" #include "graph/UpdateVertexExecutor.h" #include "graph/UpdateEdgeExecutor.h" +#include "graph/FindPathExecutor.h" namespace nebula { namespace graph { @@ -155,6 +156,9 @@ std::unique_ptr<Executor> Executor::makeExecutor(Sentence *sentence) { case Sentence::Kind::kUpdateEdge: executor = std::make_unique<UpdateEdgeExecutor>(sentence, ectx()); break; + case Sentence::Kind::kFindPath: + executor = std::make_unique<FindPathExecutor>(sentence, ectx()); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/executor/FindPathExecutor.cpp b/src/executor/FindPathExecutor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..49cd9bc78e3bd9a8f72359d00aba95efa0bd771b --- /dev/null +++ b/src/executor/FindPathExecutor.cpp @@ -0,0 +1,709 @@ +/* Copyright (c) 2019 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 "base/Base.h" +#include "FindPathExecutor.h" + +namespace nebula { +namespace graph { + +FindPathExecutor::FindPathExecutor(Sentence *sentence, ExecutionContext *exct) + : TraverseExecutor(exct) { + sentence_ = static_cast<FindPathSentence*>(sentence); +} + +Status FindPathExecutor::prepare() { + Status status; + expCtx_ = std::make_unique<ExpressionContext>(); + do { + if (sentence_->from() != nullptr) { + status = sentence_->from()->prepare(from_); + if (!status.ok()) { + break; + } + } + if (sentence_->to() != nullptr) { + status = sentence_->to()->prepare(to_); + if (!status.ok()) { + break; + } + } + if (sentence_->over() != nullptr) { + status = sentence_->over()->prepare(over_); + if (!status.ok()) { + break; + } + } + if (sentence_->step() != nullptr) { + status = sentence_->step()->prepare(step_); + if (!status.ok()) { + break; + } + } + if (sentence_->where() != nullptr) { + status = sentence_->where()->prepare(where_); + if (!status.ok()) { + break; + } + } + shortest_ = sentence_->isShortest(); + } while (false); + return status; +} + +Status FindPathExecutor::beforeExecute() { + Status status; + do { + status = checkIfGraphSpaceChosen(); + if (!status.ok()) { + break;; + } + spaceId_ = ectx()->rctx()->session()->space(); + + status = prepareOver(); + if (!status.ok()) { + break; + } + + status = setupVids(); + if (!status.ok()) { + break; + } + } while (false); + return status; +} + +Status FindPathExecutor::prepareOverAll() { + auto edgeAllStatus = ectx()->schemaManager()->getAllEdge(spaceId_); + + if (!edgeAllStatus.ok()) { + return edgeAllStatus.status(); + } + + auto allEdge = edgeAllStatus.value(); + for (auto &e : allEdge) { + auto edgeStatus = ectx()->schemaManager()->toEdgeType(spaceId_, e); + if (!edgeStatus.ok()) { + return edgeStatus.status(); + } + + auto v = edgeStatus.value(); + over_.edgeTypes_.emplace_back(v); + over_.oppositeTypes_.emplace_back(-v); + + if (!expCtx_->addEdge(e, v)) { + return Status::Error(folly::sformat("edge alias({}) was dup", e)); + } + edgeTypeNameMap_.emplace(v, e); + } + + return Status::OK(); +} + +Status FindPathExecutor::prepareOver() { + Status status = Status::OK(); + + for (auto e : over_.edges_) { + if (e->isOverAll()) { + expCtx_->setOverAllEdge(); + return prepareOverAll(); + } + + auto edgeStatus = ectx()->schemaManager()->toEdgeType(spaceId_, *e->edge()); + if (!edgeStatus.ok()) { + return edgeStatus.status(); + } + + auto v = edgeStatus.value(); + if (e->isReversely()) { + over_.edgeTypes_.emplace_back(-v); + over_.oppositeTypes_.emplace_back(v); + } else { + over_.edgeTypes_.emplace_back(v); + over_.oppositeTypes_.emplace_back(-v); + } + + if (e->alias() != nullptr) { + if (!expCtx_->addEdge(*e->alias(), v)) { + return Status::Error(folly::sformat("edge alias({}) was dup", *e->alias())); + } + } else { + if (!expCtx_->addEdge(*e->edge(), v)) { + return Status::Error(folly::sformat("edge alias({}) was dup", *e->edge())); + } + } + + edgeTypeNameMap_.emplace(v, *e->edge()); + } + + return status; +} + +void FindPathExecutor::execute() { + DCHECK(onError_); + DCHECK(onFinish_); + + auto status = beforeExecute(); + if (!status.ok()) { + onError_(std::move(status)); + return; + } + + steps_ = step_.steps_ / 2 + step_.steps_ % 2; + fromVids_ = from_.vids_; + toVids_ = to_.vids_; + visitedFrom_.insert(fromVids_.begin(), fromVids_.end()); + visitedTo_.insert(toVids_.begin(), toVids_.end()); + targetNotFound_.insert(toVids_.begin(), toVids_.end()); + for (auto &v : fromVids_) { + Path path; + pathFrom_.emplace(v, std::move(path)); + } + for (auto &v : toVids_) { + Path path; + pathTo_.emplace(v, std::move(path)); + } + + getNeighborsAndFindPath(); +} + +void FindPathExecutor::getNeighborsAndFindPath() { + fPro_ = std::make_unique<folly::Promise<folly::Unit>>(); + tPro_ = std::make_unique<folly::Promise<folly::Unit>>(); + std::vector<folly::Future<folly::Unit>> futures; + futures.emplace_back(fPro_->getFuture()); + futures.emplace_back(tPro_->getFuture()); + + auto props = getStepOutProps(false); + if (!props.ok()) { + onError_(std::move(props).status()); + return; + } + getFromFrontiers(std::move(props).value()); + + props = getStepOutProps(true); + if (!props.ok()) { + onError_(std::move(props).status()); + return; + } + getToFrontiers(std::move(props).value()); + + auto *runner = ectx()->rctx()->runner(); + auto cb = [this] (auto &&result) { + UNUSED(result); + if (!fStatus_.ok() || !tStatus_.ok()) { + std::string msg = fStatus_.toString() + " " + tStatus_.toString(); + onError_(Status::Error(std::move(msg))); + return; + } + + findPath(); + }; + auto error = [this] (auto &&e) { + LOG(ERROR) << "Exception caught: " << e.what(); + onError_(Status::Error("Internal error.")); + }; + folly::collectAll(futures).via(runner).thenValue(cb).thenError(error); +} + +void FindPathExecutor::findPath() { + VLOG(2) << "Find Path."; + visitedFrom_.clear(); + std::multimap<VertexID, Path> pathF; + for (auto &frontier : fromFrontiers_.second) { + // Notice: we treat edges with different ranking + // between two vertices as different path + for (auto &neighbor : frontier.second) { + auto dstId = std::get<0>(neighbor); + VLOG(2) << "src vertex:" << frontier.first; + VLOG(2) << "dst vertex:" << dstId; + // if frontiers of F are neighbors of visitedByT, + // we found an odd path + if (visitedTo_.count(dstId) == 1) { + meetOddPath(frontier.first, dstId, neighbor); + } + + // update the path to frontiers + updatePath(frontier.first, pathFrom_, neighbor, pathF, VisitedBy::FROM); + visitedFrom_.emplace(dstId); + } // for `neighbor' + } // for `frontier' + pathFrom_ = std::move(pathF); + fromVids_.clear(); + fromVids_.reserve(visitedFrom_.size()); + std::copy(visitedFrom_.begin(), + visitedFrom_.end(), std::back_inserter(fromVids_)); + + visitedTo_.clear(); + std::multimap<VertexID, Path> pathT; + for (auto &frontier : toFrontiers_.second) { + for (auto &neighbor : frontier.second) { + auto dstId = std::get<0>(neighbor); + // update the path to frontiers + updatePath(frontier.first, pathTo_, neighbor, pathT, VisitedBy::TO); + visitedTo_.emplace(dstId); + } // for `neighbor' + } // for `frontier' + pathTo_ = std::move(pathT); + toVids_.clear(); + toVids_.reserve(visitedTo_.size()); + std::copy(visitedTo_.begin(), + visitedTo_.end(), std::back_inserter(toVids_)); + + std::sort(fromVids_.begin(), fromVids_.end()); + std::sort(toVids_.begin(), toVids_.end()); + std::set<VertexID> intersect; + std::set_intersection(fromVids_.begin(), fromVids_.end(), + toVids_.begin(), toVids_.end(), + std::inserter(intersect, intersect.end())); + // if frontiersF meets frontiersT, we found an even path + if (!intersect.empty()) { + if (shortest_ && targetNotFound_.empty()) { + onFinish_(); + return; + } + for (auto intersectId : intersect) { + meetEvenPath(intersectId); + } // `intersectId' + } + + if (isFinalStep() || + (shortest_ && targetNotFound_.empty())) { + onFinish_(); + return; + } else { + LOG(INFO) << "Current step:" << currentStep_; + ++currentStep_; + } + getNeighborsAndFindPath(); +} + +inline void FindPathExecutor::meetOddPath(VertexID src, VertexID dst, Neighbor &neighbor) { + VLOG(2) << "Meet Odd Path."; + auto rangeF = pathFrom_.equal_range(src); + for (auto i = rangeF.first; i != rangeF.second; ++i) { + auto rangeT = pathTo_.equal_range(dst); + for (auto j = rangeT.first; j != rangeT.second; ++j) { + if (j->second.size() + i->second.size() > step_.steps_) { + continue; + } + // Build path: + // i->second + (src,type,ranking) + (dst, -type, ranking) + j->second + Path path = i->second; + auto s0 = std::make_unique<StepOut>(neighbor); + std::get<0>(*s0) = src; + path.emplace_back(s0.get()); + stepOutHolder_.emplace(std::move(s0)); + VLOG(2) << "PathF: " << buildPathString(path); + + auto s1 = std::make_unique<StepOut>(neighbor); + std::get<1>(*s1) = - std::get<1>(neighbor); + path.emplace_back(s1.get()); + stepOutHolder_.emplace(std::move(s1)); + path.insert(path.end(), j->second.begin(), j->second.end()); + VLOG(2) << "PathT: " << buildPathString(j->second); + + auto target = std::get<0>(*(path.back())); + if (shortest_) { + targetNotFound_.erase(target); + if (finalPath_.count(target) > 0) { + // already found a shorter path + continue; + } else { + VLOG(2) << "Found path: " << buildPathString(path); + finalPath_.emplace(target, std::move(path)); + } + } else { + VLOG(2) << "Found path: " << buildPathString(path); + finalPath_.emplace(target, std::move(path)); + } + } // for `j' + } // for `i' +} + +inline void FindPathExecutor::meetEvenPath(VertexID intersectId) { + VLOG(2) << "Meet Even Path."; + auto rangeF = pathFrom_.equal_range(intersectId); + auto rangeT = pathTo_.equal_range(intersectId); + for (auto i = rangeF.first; i != rangeF.second; ++i) { + for (auto j = rangeT.first; j != rangeT.second; ++j) { + if (j->second.size() + i->second.size() > step_.steps_) { + continue; + } + // Build path: + // i->second + (src,type,ranking) + j->second + Path path = i->second; + VLOG(2) << "PathF: " << buildPathString(path); + if (j->second.size() > 0) { + StepOut *s = j->second.front(); + auto s0 = std::make_unique<StepOut>(*s); + std::get<0>(*s0) = intersectId; + std::get<1>(*s0) = - std::get<1>(*s0); + path.emplace_back(s0.get()); + stepOutHolder_.emplace(std::move(s0)); + VLOG(2) << "Joiner: " << buildPathString(path); + } else if (i->second.size() > 0) { + StepOut *s = i->second.back(); + auto s0 = std::make_unique<StepOut>(*s); + std::get<0>(*s0) = intersectId; + path.emplace_back(s0.get()); + stepOutHolder_.emplace(std::move(s0)); + VLOG(2) << "Joiner: " << buildPathString(path); + } + VLOG(2) << "PathT: " << buildPathString(j->second); + path.insert(path.end(), j->second.begin(), j->second.end()); + auto target = std::get<0>(*(path.back())); + if (shortest_) { + if (finalPath_.count(target) > 0) { + // already found a shorter path + continue; + } else { + targetNotFound_.erase(target); + VLOG(2) << "Found path: " << buildPathString(path); + finalPath_.emplace(target, std::move(path)); + } + } else { + VLOG(2) << "Found path: " << buildPathString(path); + finalPath_.emplace(target, std::move(path)); + } + } + } +} + +inline void FindPathExecutor::updatePath( + VertexID &src, + std::multimap<VertexID, Path> &pathToSrc, + Neighbor &neighbor, + std::multimap<VertexID, Path> &pathToNeighbor, + VisitedBy visitedBy) { + VLOG(2) << "Update Path."; + auto range = pathToSrc.equal_range(src); + for (auto i = range.first; i != range.second; ++i) { + // Build path: + // i->second + (src,type,ranking) + Path path = i->second; + VLOG(2) << "Interim path before :" << buildPathString(path); + VLOG(2) << "Interim path length before:" << path.size(); + auto s = std::make_unique<StepOut>(neighbor); + std::get<0>(*s) = src; + if (visitedBy == VisitedBy::FROM) { + path.emplace_back(s.get()); + } else { + path.emplace(path.begin(), s.get()); + } + VLOG(2) << "Neighbor: " << std::get<0>(neighbor); + VLOG(2) << "Interim path:" << buildPathString(path); + VLOG(2) << "Interim path length:" << path.size(); + stepOutHolder_.emplace(std::move(s)); + pathToNeighbor.emplace(std::get<0>(neighbor), std::move(path)); + } // for `i' +} + +Status FindPathExecutor::setupVids() { + Status status = Status::OK(); + do { + if (sentence_->from()->isRef()) { + status = setupVidsFromRef(from_); + if (!status.ok()) { + break; + } + } + + if (sentence_->to()->isRef()) { + status = setupVidsFromRef(to_); + if (!status.ok()) { + break; + } + } + } while (false); + + return status; +} + +Status FindPathExecutor::setupVidsFromRef(Clause::Vertices &vertices) { + const InterimResult *inputs; + if (vertices.varname_ == nullptr) { + inputs = inputs_.get(); + if (inputs == nullptr) { + return Status::OK(); + } + } else { + inputs = ectx()->variableHolder()->get(*(vertices.varname_)); + if (inputs == nullptr) { + return Status::Error("Variable `%s' not defined", vertices.varname_->c_str()); + } + } + + auto result = inputs->getDistinctVIDs(*(vertices.colname_)); + if (!result.ok()) { + return std::move(result).status(); + } + vertices.vids_ = std::move(result).value(); + return Status::OK(); +} + +void FindPathExecutor::getFromFrontiers( + std::vector<storage::cpp2::PropDef> props) { + auto future = ectx()->getStorageClient()->getNeighbors(spaceId_, + std::move(fromVids_), + over_.edgeTypes_, + "", + std::move(props)); + auto *runner = ectx()->rctx()->runner(); + auto cb = [this] (auto &&result) { + Frontiers frontiers; + auto completeness = result.completeness(); + if (completeness == 0) { + fStatus_ = Status::Error("Get neighbors failed."); + fPro_->setValue(); + return; + } else if (completeness != 100) { + LOG(INFO) << "Get neighbors partially failed: " << completeness << "%"; + for (auto &error : result.failedParts()) { + LOG(ERROR) << "part: " << error.first + << "error code: " << static_cast<int>(error.second); + } + } + auto status = doFilter(std::move(result), where_.filter_, true, frontiers); + if (!status.ok()) { + fStatus_ = std::move(status); + fPro_->setValue(); + return; + } + fromFrontiers_ = std::make_pair(VisitedBy::FROM, std::move(frontiers)); + fPro_->setValue(); + }; + auto error = [this] (auto &&e) { + LOG(ERROR) << "Exception caught: " << e.what(); + fStatus_ = Status::Error("Get neighbors failed."); + }; + std::move(future).via(runner, folly::Executor::HI_PRI).thenValue(cb).thenError(error); +} + +void FindPathExecutor::getToFrontiers( + std::vector<storage::cpp2::PropDef> props) { + auto future = ectx()->getStorageClient()->getNeighbors(spaceId_, + std::move(toVids_), + over_.oppositeTypes_, + "", + std::move(props)); + auto *runner = ectx()->rctx()->runner(); + auto cb = [this] (auto &&result) { + Frontiers frontiers; + auto completeness = result.completeness(); + if (completeness == 0) { + tStatus_ = Status::Error("Get neighbors failed."); + tPro_->setValue(); + return; + } else if (completeness != 100) { + LOG(INFO) << "Get neighbors partially failed: " << completeness << "%"; + for (auto &error : result.failedParts()) { + LOG(ERROR) << "part: " << error.first + << "error code: " << static_cast<int>(error.second); + } + } + auto status = doFilter(std::move(result), where_.filter_, false, frontiers); + if (!status.ok()) { + tStatus_ = std::move(status); + tPro_->setValue(); + return; + } + toFrontiers_ = std::make_pair(VisitedBy::TO, std::move(frontiers)); + tPro_->setValue(); + }; + auto error = [this] (auto &&e) { + LOG(ERROR) << "Exception caught: " << e.what(); + tStatus_ = Status::Error("Get neighbors failed."); + }; + std::move(future).via(runner, folly::Executor::HI_PRI).thenValue(cb).thenError(error); +} + +Status FindPathExecutor::doFilter( + storage::StorageRpcResponse<storage::cpp2::QueryResponse> &&result, + Expression *filter, + bool isOutBound, + Frontiers &frontiers) { + UNUSED(filter); + UNUSED(isOutBound); + + auto &resps = result.responses(); + for (auto &resp : resps) { + if (resp.get_vertices() == nullptr) { + continue; + } + + std::unordered_map<EdgeType, std::shared_ptr<ResultSchemaProvider>> edgeSchema; + auto *eschema = resp.get_edge_schema(); + if (eschema != nullptr) { + std::transform(eschema->cbegin(), eschema->cend(), + std::inserter(edgeSchema, edgeSchema.begin()), [](auto &schema) { + return std::make_pair( + schema.first, + std::make_shared<ResultSchemaProvider>(schema.second)); + }); + } + + if (edgeSchema.empty()) { + continue; + } + + for (auto &vdata : resp.vertices) { + DCHECK(vdata.__isset.edge_data); + for (auto &edata : vdata.edge_data) { + auto edgeType = edata.type; + auto it = edgeSchema.find(edgeType); + DCHECK(it != edgeSchema.end()); + RowSetReader rsReader(it->second, edata.data); + auto iter = rsReader.begin(); + Neighbors neighbors; + while (iter) { + std::vector<VariantType> temps; + for (auto &prop : kReserveProps_) { + auto res = RowReader::getPropByName(&*iter, prop); + if (ok(res)) { + temps.emplace_back(std::move(value(res))); + } else { + return Status::Error("get edge prop failed %s", prop.c_str()); + } + } + Neighbor neighbor( + boost::get<int64_t>(temps[0]), + boost::get<int64_t>(temps[1]), + boost::get<int64_t>(temps[2])); + neighbors.emplace_back(std::move(neighbor)); + ++iter; + } // while `iter' + auto frontier = std::make_pair(vdata.get_vertex_id(), std::move(neighbors)); + frontiers.emplace_back(std::move(frontier)); + } // `edata' + } // for `vdata' + } // for `resp' + return Status::OK(); +} + +StatusOr<std::vector<storage::cpp2::PropDef>> +FindPathExecutor::getStepOutProps(bool reversely) { + auto *edges = &over_.edgeTypes_; + if (reversely) { + edges = &over_.oppositeTypes_; + } + std::vector<storage::cpp2::PropDef> props; + for (auto &e : *edges) { + for (auto &prop : kReserveProps_) { + storage::cpp2::PropDef pd; + pd.owner = storage::cpp2::PropOwner::EDGE; + pd.name = prop; + pd.id.set_edge_type(e); + props.emplace_back(std::move(pd)); + } + } + + return props; +} + +std::string FindPathExecutor::buildPathString(const Path &path) { + std::string pathStr; + auto iter = path.begin(); + for (; iter != path.end(); ++iter) { + auto *step = *iter; + auto id = std::get<0>(*step); + auto type = std::get<1>(*step); + auto ranking = std::get<2>(*step); + if (type < 0) { + pathStr += folly::to<std::string>(id); + ++iter; + break; + } + + pathStr += folly::stringPrintf("%ld<%d,%ld>", id, type, ranking); + } + + for (; iter != path.end(); ++iter) { + auto *step = *iter; + auto id = std::get<0>(*step); + auto type = std::get<1>(*step); + auto ranking = std::get<2>(*step); + + pathStr += folly::stringPrintf("<%d,%ld>%ld", -type, ranking, id); + } + + return pathStr; +} + +cpp2::RowValue FindPathExecutor::buildPathRow(const Path &path) { + cpp2::RowValue rowValue; + std::vector<cpp2::ColumnValue> row; + cpp2::Path pathValue; + auto entryList = pathValue.get_entry_list(); + auto iter = path.begin(); + for (; iter != path.end(); ++iter) { + auto *step = *iter; + auto id = std::get<0>(*step); + auto type = std::get<1>(*step); + auto ranking = std::get<2>(*step); + if (type < 0) { + entryList.emplace_back(); + cpp2::Vertex vertex; + vertex.set_id(id); + entryList.back().set_vertex(std::move(vertex)); + ++iter; + break; + } + entryList.emplace_back(); + cpp2::Vertex vertex; + vertex.set_id(id); + entryList.back().set_vertex(std::move(vertex)); + + entryList.emplace_back(); + cpp2::Edge edge; + auto typeName = edgeTypeNameMap_.find(type); + DCHECK(typeName != edgeTypeNameMap_.end()) << type; + edge.set_type(typeName->second); + edge.set_ranking(ranking); + entryList.back().set_edge(std::move(edge)); + } + + for (; iter != path.end(); ++iter) { + auto *step = *iter; + auto id = std::get<0>(*step); + auto type = std::get<1>(*step); + auto ranking = std::get<2>(*step); + + entryList.emplace_back(); + cpp2::Edge edge; + auto typeName = edgeTypeNameMap_.find(-type); + DCHECK(typeName != edgeTypeNameMap_.end()) << type; + edge.set_type(typeName->second); + edge.set_ranking(ranking); + entryList.back().set_edge(std::move(edge)); + + entryList.emplace_back(); + cpp2::Vertex vertex; + vertex.set_id(id); + entryList.back().set_vertex(std::move(vertex)); + } + + row.emplace_back(); + pathValue.set_entry_list(std::move(entryList)); + row.back().set_path(std::move(pathValue)); + rowValue.set_columns(std::move(row)); + return rowValue; +} + +void FindPathExecutor::setupResponse(cpp2::ExecutionResponse &resp) { + std::vector<cpp2::RowValue> rows; + for (auto &path : finalPath_) { + auto row = buildPathRow(path.second); + rows.emplace_back(std::move(row)); + VLOG(1) << "Path: " << buildPathString(path.second); + } + + std::vector<std::string> colNames = {"_path_"}; + resp.set_column_names(std::move(colNames)); + resp.set_rows(std::move(rows)); +} +} // namespace graph +} // namespace nebula diff --git a/src/executor/FindPathExecutor.h b/src/executor/FindPathExecutor.h new file mode 100644 index 0000000000000000000000000000000000000000..87aa250ec3a85ced1e5bfbcd894176dfa0929e1c --- /dev/null +++ b/src/executor/FindPathExecutor.h @@ -0,0 +1,145 @@ +/* Copyright (c) 2019 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 GRAPH_FINDPATHEXECUTOR_H_ +#define GRAPH_FINDPATHEXECUTOR_H_ + +#include "base/Base.h" +#include "graph/TraverseExecutor.h" +#include "storage/client/StorageClient.h" +#include "common/concurrent/Barrier.h" + +namespace nebula { +namespace graph { + +using SchemaProps = std::unordered_map<std::string, std::vector<std::string>>; +const std::vector<std::string> kReserveProps_ = {"_dst", "_type", "_rank"}; +using Neighbor = std::tuple<VertexID, EdgeType, EdgeRanking>; /* dst, type, rank*/ +using Neighbors = std::vector<Neighbor>; +using Frontiers = + std::vector< + std::pair< + VertexID, /* start */ + Neighbors /* frontiers of vertex*/ + > + >; + +using StepOut = std::tuple<VertexID, EdgeType, EdgeRanking>; /* src, type, rank*/ +using Path = std::list<StepOut*>; +enum class VisitedBy : char { + FROM, + TO, +}; + +class FindPathExecutor final : public TraverseExecutor { +public: + FindPathExecutor(Sentence *sentence, ExecutionContext *ectx); + + const char* name() const override { + return "FindPathExecutor"; + } + + Status MUST_USE_RESULT prepare() override; + + void execute() override; + + void feedResult(std::unique_ptr<InterimResult> result) override { + inputs_ = std::move(result); + } + + void setupResponse(cpp2::ExecutionResponse &resp) override; + + static std::string buildPathString(const Path &path); + + cpp2::RowValue buildPathRow(const Path &path); + +private: + // Do some prepare work that can not do in prepare() + Status beforeExecute(); + + Status prepareOver(); + + Status prepareOverAll(); + + void getNeighborsAndFindPath(); + + bool isFinalStep() { + return currentStep_ == steps_; + } + + void getFromFrontiers(std::vector<storage::cpp2::PropDef> props); + + void getToFrontiers(std::vector<storage::cpp2::PropDef> props); + + void findPath(); + + inline void meetOddPath(VertexID src, VertexID dst, Neighbor &neighbor); + + inline void meetEvenPath(VertexID intersectId); + + inline void updatePath( + VertexID &src, + std::multimap<VertexID, Path> &pathToSrc, + Neighbor &neighbor, + std::multimap<VertexID, Path> &pathToNeighbor, + VisitedBy visitedBy); + + Status setupVids(); + + Status setupVidsFromRef(Clause::Vertices &vertices); + + Status doFilter( + storage::StorageRpcResponse<storage::cpp2::QueryResponse> &&result, + Expression *filter, + bool isOutBound, + Frontiers &frontiers); + + StatusOr<std::vector<storage::cpp2::PropDef>> getStepOutProps(bool reversely); + + StatusOr<std::vector<storage::cpp2::PropDef>> getDstProps(); + +private: + FindPathSentence *sentence_{nullptr}; + std::unique_ptr<ExpressionContext> expCtx_; + GraphSpaceID spaceId_{INT_MIN}; + Clause::Vertices from_; + Clause::Vertices to_; + Clause::Over over_; + Clause::Step step_; + Clause::Where where_; + bool shortest_{false}; + std::unique_ptr<InterimResult> inputs_; + using SchemaPropIndex = std::unordered_map<std::pair<std::string, std::string>, int64_t>; + SchemaPropIndex srcTagProps_; + SchemaPropIndex dstTagProps_; + std::unordered_map<EdgeType, std::string> edgeTypeNameMap_; + std::unique_ptr<folly::Promise<folly::Unit>> fPro_; + std::unique_ptr<folly::Promise<folly::Unit>> tPro_; + Status fStatus_; + Status tStatus_; + std::unordered_set<VertexID> targetNotFound_; + using StepOutHolder = std::unordered_set<std::unique_ptr<StepOut>>; + StepOutHolder stepOutHolder_; + // next step starting vertices + std::unordered_set<VertexID> visitedFrom_; + std::unordered_set<VertexID> visitedTo_; + // next step starting vertices + std::vector<VertexID> fromVids_; + std::vector<VertexID> toVids_; + // frontiers of vertices + std::pair<VisitedBy, Frontiers> fromFrontiers_; + std::pair<VisitedBy, Frontiers> toFrontiers_; + // interim path + std::multimap<VertexID, Path> pathFrom_; + std::multimap<VertexID, Path> pathTo_; + // final path(shortest or all) + std::multimap<VertexID, Path> finalPath_; + uint64_t currentStep_{1}; + uint64_t steps_{0}; +}; +} // namespace graph +} // namespace nebula +#endif diff --git a/src/executor/PipeExecutor.cpp b/src/executor/PipeExecutor.cpp index 5bb66904acd190e2d9baefc52bd1556314d4d892..d47c7f079995f8d529e6c7323b9b53e14df97ea4 100644 --- a/src/executor/PipeExecutor.cpp +++ b/src/executor/PipeExecutor.cpp @@ -104,6 +104,10 @@ Status PipeExecutor::syntaxPreCheck() { return Status::SyntaxError("Set op not support input."); } + if (sentence_->left()->kind() == Sentence::Kind::kFindPath) { + return Status::SyntaxError("Can not reference the result of FindPath."); + } + return Status::OK(); } diff --git a/src/executor/TraverseExecutor.cpp b/src/executor/TraverseExecutor.cpp index 51a00d10d54098d1690611b273a61fda5573d6b0..e3f8211b6c3fd91cb40762ee2311b020fc89b461 100644 --- a/src/executor/TraverseExecutor.cpp +++ b/src/executor/TraverseExecutor.cpp @@ -17,6 +17,7 @@ #include "graph/SetExecutor.h" #include "graph/FindExecutor.h" #include "graph/MatchExecutor.h" +#include "graph/FindPathExecutor.h" namespace nebula { namespace graph { @@ -56,6 +57,9 @@ TraverseExecutor::makeTraverseExecutor(Sentence *sentence, ExecutionContext *ect case Sentence::Kind::kFind: executor = std::make_unique<FindExecutor>(sentence, ectx); break; + case Sentence::Kind::kFindPath: + executor = std::make_unique<FindPathExecutor>(sentence, ectx); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt index dd02a13a6f885d8f8b436c895a7903d5e54ba804..b0feeb6a0268ae91808cc699f15368770aca268c 100644 --- a/src/executor/test/CMakeLists.txt +++ b/src/executor/test/CMakeLists.txt @@ -234,3 +234,19 @@ nebula_add_test( gtest ) +nebula_add_test( + NAME + find_path_test + SOURCES + FindPathTest.cpp + OBJECTS + $<TARGET_OBJECTS:graph_test_common_obj> + $<TARGET_OBJECTS:client_cpp_obj> + $<TARGET_OBJECTS:adHocSchema_obj> + ${GRAPH_TEST_LIBS} + LIBRARIES + ${THRIFT_LIBRARIES} + ${ROCKSDB_LIBRARIES} + wangle + gtest +) diff --git a/src/executor/test/FindPathTest.cpp b/src/executor/test/FindPathTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6bc6d6f281499fd6549f6b2b4078c879423281d9 --- /dev/null +++ b/src/executor/test/FindPathTest.cpp @@ -0,0 +1,441 @@ +/* Copyright (c) 2019 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 "base/Base.h" +#include "graph/test/TestEnv.h" +#include "graph/test/TestBase.h" +#include "graph/test/TraverseTestBase.h" +#include "meta/test/TestUtils.h" + +namespace nebula { +namespace graph { + +class FindPathTest : public TraverseTestBase { +protected: + void SetUp() override { + TraverseTestBase::SetUp(); + } + + void TearDown() override { + TraverseTestBase::TearDown(); + } +}; + +TEST_F(FindPathTest, singleEdgeShortest) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tim.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like UPTO 5 STEPS"; + auto &tiago = players_["Tiago Splitter"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624" + "<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + // we only find the shortest path to the dest, + // so -8160811731890648949 to -1782445125509592239 is not in result + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld,%ld TO %ld,%ld,%ld OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tiago = players_["Tiago Splitter"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tiago.vid(), + tony.vid(), manu.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "GO FROM %ld OVER like yield like._src AS src, like._dst AS dst | " + "FIND SHORTEST PATH FROM $-.src TO $-.dst OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto query = folly::stringPrintf(fmt, tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "$var = GO FROM %ld OVER like yield like._src AS src, like._dst AS dst;" + "FIND SHORTEST PATH FROM $var.src TO $var.dst OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto query = folly::stringPrintf(fmt, tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "$var = GO FROM %ld OVER like yield like._src AS src;" + "GO FROM %ld OVER like yield like._src AS src, like._dst AS dst | " + "FIND SHORTEST PATH FROM $var.src TO $-.dst OVER like UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tim.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } +} + +TEST_F(FindPathTest, singleEdgeAll) { + /* + * TODO: There might exist loops when find all path, + * we should provide users with an option on whether or not a loop is required. + */ + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<like,0>-7579316172763586624" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER like UPTO 3 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), manu.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<like,0>3394245602834314645", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<like,0>3394245602834314645" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tim.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld OVER like UPTO 3 STEPS"; + auto &tiago = players_["Tiago Splitter"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624" + "<like,0>-1782445125509592239" + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } +} + +TEST_F(FindPathTest, multiEdgesShortest) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER like,serve UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld,%ld " + "OVER like,serve UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, + tim.vid(), + tony.vid(), manu.vid(), al.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239", + "5662213458193308137<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 5 STEPS"; + auto &tiago = players_["Tiago Splitter"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624" + "<like,0>-1782445125509592239", + "-8160811731890648949<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld OVER * UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER * UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld,%ld,%ld " + "OVER * UPTO 5 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto &manu = players_["Manu Ginobili"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, + tim.vid(), + tony.vid(), manu.vid(), al.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>3394245602834314645", + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239", + "5662213458193308137<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND SHORTEST PATH FROM %ld TO %ld,%ld OVER * UPTO 5 STEPS"; + auto &tiago = players_["Tiago Splitter"]; + auto &al = players_["LaMarcus Aldridge"]; + auto query = folly::stringPrintf(fmt, tiago.vid(), al.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "-8160811731890648949<like,0>5662213458193308137<like,0>-7579316172763586624" + "<like,0>-1782445125509592239", + "-8160811731890648949<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } +} + +TEST_F(FindPathTest, multiEdgesAll) { + /* + * TODO: There might exist loops when find all path, + * we should provide users with an option on whether or not a loop is required. + */ + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER like,serve UPTO 3 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<serve,0>7193291116733635180", + "5662213458193308137<like,0>3394245602834314645<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto *fmt = "FIND ALL PATH FROM %ld TO %ld,%ld OVER * UPTO 3 STEPS"; + auto &tim = players_["Tim Duncan"]; + auto &tony = players_["Tony Parker"]; + auto query = folly::stringPrintf(fmt, tim.vid(), tony.vid(), teams_["Spurs"].vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code) << *(resp.get_error_msg()); + std::vector<std::string> expected = { + "5662213458193308137<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<like,0>-7579316172763586624", + "5662213458193308137<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<serve,0>7193291116733635180", + "5662213458193308137<like,0>3394245602834314645<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>-1782445125509592239" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>3394245602834314645" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>-7579316172763586624<like,0>5662213458193308137" + "<serve,0>7193291116733635180", + "5662213458193308137<like,0>3394245602834314645<like,0>5662213458193308137" + "<serve,0>7193291116733635180", + }; + ASSERT_TRUE(verifyPath(resp, expected)); + } +} +} // namespace graph +} // namespace nebula diff --git a/src/executor/test/TestBase.h b/src/executor/test/TestBase.h index 379e31a46dc363f2c3e08bafa74ab0126515a7f2..171a0efd91175b51d816aca492c4602a53f9987d 100644 --- a/src/executor/test/TestBase.h +++ b/src/executor/test/TestBase.h @@ -214,7 +214,7 @@ protected: std::sort(rows.begin(), rows.end()); std::sort(expected.begin(), expected.end()); } - for (auto i = 0u; i < rows.size(); i++) { + for (decltype(rows.size()) i = 0; i < rows.size(); ++i) { if (rows[i] != expected[i]) { return TestError() << rows[i] << " vs. " << expected[i]; } diff --git a/src/executor/test/TraverseTestBase.h b/src/executor/test/TraverseTestBase.h index 021dcae532a7ceb15e7be9925d2a8e7f3866d882..f1d2cb80285e179d5410875fdc0500fd1fc744e5 100644 --- a/src/executor/test/TraverseTestBase.h +++ b/src/executor/test/TraverseTestBase.h @@ -44,6 +44,59 @@ protected: client_.reset(); } + AssertionResult verifyPath(const cpp2::ExecutionResponse &resp, + std::vector<std::string> &expected) { + if (resp.get_error_code() != cpp2::ErrorCode::SUCCEEDED) { + auto *errmsg = resp.get_error_msg(); + return TestError() << "Query failed with `" + << static_cast<int32_t>(resp.get_error_code()) + << (errmsg == nullptr ? "'" : "': " + *errmsg); + } + + if (resp.get_rows() == nullptr && expected.empty()) { + return TestOK(); + } + + auto rows = buildPathString(*resp.get_rows()); + + if (expected.size() != rows.size()) { + return TestError() << "Rows' count not match: " + << rows.size() << " vs. " << expected.size(); + } + + std::sort(rows.begin(), rows.end()); + std::sort(expected.begin(), expected.end()); + + for (decltype(rows.size()) i = 0; i < rows.size(); ++i) { + if (rows[i] != expected[i]) { + return TestError() << rows[i] << " vs. " << expected[i]; + } + } + return TestOK(); + } + + static std::vector<std::string> buildPathString(std::vector<cpp2::RowValue> rows) { + std::vector<std::string> paths; + for (auto &row : rows) { + auto &pathValue = row.get_columns().back().get_path(); + auto &cols = pathValue.get_entry_list(); + std::string pathStr; + auto iter = cols.begin(); + while (iter < (cols.end() - 1)) { + pathStr += folly::stringPrintf("%ld<%s,%ld>", + iter->get_vertex().get_id(), + (iter + 1)->get_edge().get_type().c_str(), + (iter + 1)->get_edge().get_ranking()); + iter = iter + 2; + } + pathStr += folly::to<std::string>(iter->get_vertex().get_id()); + paths.emplace_back(std::move(pathStr)); + } + + return paths; + } + + static AssertionResult prepareSchema(); static AssertionResult prepareData(); diff --git a/src/parser/Clauses.cpp b/src/parser/Clauses.cpp index 278cc44b27f337dec63dab2e95cfb97c93c12733..bcc8de2a539feaf5d7006e1bac9dcc2c84863b62 100644 --- a/src/parser/Clauses.cpp +++ b/src/parser/Clauses.cpp @@ -48,6 +48,51 @@ std::string VertexIDList::toString() const { return buf; } +Status VerticesClause::prepare(Clause::Vertices &vertices) const { + Status status; + if (isRef()) { + if (ref_->isInputExpression()) { + auto *iexpr = static_cast<InputPropertyExpression*>(ref_.get()); + vertices.colname_ = iexpr->prop(); + } else if (ref_->isVariableExpression()) { + auto *vexpr = static_cast<VariablePropertyExpression*>(ref_.get()); + vertices.varname_ = vexpr->alias(); + vertices.colname_ = vexpr->prop(); + } else { + // should never come to here. + // only support input and variable yet. + status = Status::Error("Unknown kind of expression."); + } + } else { + auto uniqID = std::make_unique<std::unordered_set<VertexID>>(); + auto vidList = vidList_->vidList(); + vertices.vids_.reserve(vidList.size()); + for (auto *expr : vidList) { + status = expr->prepare(); + if (!status.ok()) { + break; + } + auto value = expr->eval(); + if (!value.ok()) { + status = value.status(); + break; + } + auto v = value.value(); + if (!Expression::isInt(v)) { + status = Status::Error("Vertex ID should be of type integer"); + break; + } + + auto valInt = Expression::asInt(v); + auto result = uniqID->emplace(valInt); + if (result.second) { + vertices.vids_.emplace_back(valInt); + } + } + } + return status; +} + std::string FromClause::toString() const { std::string buf; buf.reserve(256); @@ -60,6 +105,22 @@ std::string FromClause::toString() const { return buf; } +std::string ToClause::toString() const { + std::string buf; + buf.reserve(256); + buf += "TO "; + if (isRef()) { + buf += ref_->toString(); + } else { + buf += vidList_->toString(); + } + return buf; +} + +Status OverClause::prepare(Over &over) const { + over.edges_ = edges(); + return Status::OK(); +} std::string OverEdge::toString() const { std::string buf; @@ -95,6 +156,11 @@ std::string OverClause::toString() const { return buf; } +Status WhereClause::prepare(Where &where) const { + where.filter_ = filter_.get(); + return Status::OK(); +} + std::string WhereClause::toString() const { std::string buf; buf.reserve(256); @@ -131,5 +197,4 @@ std::string YieldClause::toString() const { buf += yieldColumns_->toString(); return buf; } - } // namespace nebula diff --git a/src/parser/Clauses.h b/src/parser/Clauses.h index 1f8f8b6de258d665a1484f3b6e5a90eb86710d7d..96b9e53141e10e026b8b53d490798e70ec3d795e 100644 --- a/src/parser/Clauses.h +++ b/src/parser/Clauses.h @@ -11,11 +11,60 @@ namespace nebula { -class StepClause final { +class OverEdge; +class Clause { +public: + struct Vertices { + std::string *colname_{nullptr}; + std::string *varname_{nullptr}; + std::vector<VertexID> vids_; + }; + + struct Over { + std::vector<OverEdge*> edges_{nullptr}; + std::vector<EdgeType> edgeTypes_; + std::vector<EdgeType> oppositeTypes_; + }; + + struct Step { + uint32_t steps_{0}; + bool upto_{false}; + }; + + struct Where { + Expression *filter_{nullptr}; + }; + +protected: + enum Kind : uint8_t { + kUnknown = 0, + + kStepClause, + kOverClause, + kFromClause, + kToClause, + kWhereClause, + kYieldClause, + + kMax, + }; + +protected: + Kind kind_{kUnknown}; +}; + +class StepClause final : public Clause { public: explicit StepClause(uint64_t steps = 1, bool isUpto = false) { steps_ = steps; isUpto_ = isUpto; + kind_ = Kind::kStepClause; + } + + Status prepare(Step &step) const { + step.steps_ = steps_; + step.upto_ = isUpto_; + return Status::OK(); } uint32_t steps() const { @@ -51,7 +100,6 @@ private: }; - class VertexIDList final { public: void add(Expression *expr) { @@ -74,13 +122,13 @@ private: }; -class FromClause final { +class VerticesClause : public Clause { public: - explicit FromClause(VertexIDList *vidList) { + explicit VerticesClause(VertexIDList *vidList) { vidList_.reset(vidList); } - explicit FromClause(Expression *ref) { + explicit VerticesClause(Expression *ref) { ref_.reset(ref); } @@ -96,13 +144,39 @@ public: return ref_.get(); } - std::string toString() const; + Status prepare(Vertices &vertices) const; -private: +protected: std::unique_ptr<VertexIDList> vidList_; std::unique_ptr<Expression> ref_; }; +class FromClause final : public VerticesClause { +public: + explicit FromClause(VertexIDList *vidList) : VerticesClause(vidList) { + kind_ = kFromClause; + } + + explicit FromClause(Expression *ref) : VerticesClause(ref) { + kind_ = kFromClause; + } + + std::string toString() const; +}; + +class ToClause final : public VerticesClause { +public: + explicit ToClause(VertexIDList *vidList) : VerticesClause(vidList) { + kind_ = kToClause; + } + + explicit ToClause(Expression *ref) : VerticesClause(ref) { + kind_ = kToClause; + } + + std::string toString() const; +}; + class OverEdge final { public: explicit OverEdge(std::string *edge, std::string *alias = nullptr, bool isReversely = false) { @@ -146,19 +220,24 @@ private: std::vector<std::unique_ptr<OverEdge>> edges_; }; -class OverClause final { +class OverClause final : public Clause { public: - explicit OverClause(OverEdges *edges) { overEdges_.reset(edges); } + explicit OverClause(OverEdges *edges) { + kind_ = kOverClause; + overEdges_.reset(edges); + } std::vector<OverEdge *> edges() const { return overEdges_->edges(); } + Status prepare(Over &over) const; + std::string toString() const; private: std::unique_ptr<OverEdges> overEdges_; }; -class WhereClause final { +class WhereClause final : public Clause { public: explicit WhereClause(Expression *filter) { filter_.reset(filter); @@ -168,6 +247,8 @@ public: return filter_.get(); } + Status prepare(Where &where) const; + std::string toString() const; private: @@ -239,7 +320,5 @@ private: std::unique_ptr<YieldColumns> yieldColumns_; bool distinct_; }; - -} // namespace nebula - +} // namespace nebula #endif // PARSER_CLAUSES_H_ diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h index a69eecc9917e9783e3230bd48f072c63240c5bb0..9a6bd30cf0b8938f89d5e53cb4147e02fa24aae8 100644 --- a/src/parser/Sentence.h +++ b/src/parser/Sentence.h @@ -59,6 +59,7 @@ public: kFetchVertices, kFetchEdges, kBalance, + kFindPath, }; Kind kind() const { diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp index ab2e388d21058294ace0200a28aacdf6e57e1bfd..60be945dee4c293d1d74d3bf5ce437d1adcc9ade 100644 --- a/src/parser/TraverseSentences.cpp +++ b/src/parser/TraverseSentences.cpp @@ -261,4 +261,37 @@ std::string FetchEdgesSentence::toString() const { } return buf; } + +std::string FindPathSentence::toString() const { + std::string buf; + buf.reserve(256); + buf += "FIND "; + if (isShortest_) { + buf += "SHORTEST PATH "; + } else { + buf += "ALL PATH "; + } + + if (from_ != nullptr) { + buf += from_->toString(); + buf += " "; + } + if (to_ != nullptr) { + buf += to_->toString(); + buf += " "; + } + if (over_ != nullptr) { + buf += over_->toString(); + buf += " "; + } + if (step_ != nullptr) { + buf += step_->toString(); + buf += " "; + } + if (where_ != nullptr) { + buf += where_->toString(); + buf += " "; + } + return buf; +} } // namespace nebula diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h index abf242d92828d0628eeb32113f50a2904c3c5caa..e00684da3d3b94980a70c8d321e92ee3f7d1f433 100644 --- a/src/parser/TraverseSentences.h +++ b/src/parser/TraverseSentences.h @@ -483,5 +483,67 @@ private: std::unique_ptr<EdgeKeyRef> keyRef_; std::unique_ptr<YieldClause> yieldClause_; }; + +class FindPathSentence final : public Sentence { +public: + explicit FindPathSentence(bool isShortest) { + kind_ = Kind::kFindPath; + isShortest_ = isShortest; + } + + void setFrom(FromClause *clause) { + from_.reset(clause); + } + + void setTo(ToClause *clause) { + to_.reset(clause); + } + + void setOver(OverClause *clause) { + over_.reset(clause); + } + + void setStep(StepClause *clause) { + step_.reset(clause); + } + + void setWhere(WhereClause *clause) { + where_.reset(clause); + } + + FromClause* from() const { + return from_.get(); + } + + ToClause* to() const { + return to_.get(); + } + + OverClause* over() const { + return over_.get(); + } + + StepClause* step() const { + return step_.get(); + } + + WhereClause* where() const { + return where_.get(); + } + + bool isShortest() const { + return isShortest_; + } + + std::string toString() const override; + +private: + bool isShortest_; + std::unique_ptr<FromClause> from_; + std::unique_ptr<ToClause> to_; + std::unique_ptr<OverClause> over_; + std::unique_ptr<StepClause> step_; + std::unique_ptr<WhereClause> where_; +}; } // namespace nebula #endif // PARSER_TRAVERSESENTENCES_H_ diff --git a/src/parser/parser.yy b/src/parser/parser.yy index ddd79b974f7a6a72217d2c0ef376bea0bd87d27b..e73838e01656acdb6e32d2c1fad5ad4ccd1e7032 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -42,7 +42,9 @@ class GraphScanner; nebula::ColumnNameList *colsnamelist; nebula::ColumnType type; nebula::StepClause *step_clause; + nebula::StepClause *find_path_upto_clause; nebula::FromClause *from_clause; + nebula::ToClause *to_clause; nebula::VertexIDList *vid_list; nebula::OverEdge *over_edge; nebula::OverEdges *over_edges; @@ -108,6 +110,7 @@ class GraphScanner; %token KW_FETCH KW_PROP KW_UPDATE KW_UPSERT KW_WHEN %token KW_DISTINCT KW_ALL KW_OF %token KW_BALANCE KW_LEADER KW_DATA +%token KW_SHORTEST KW_PATH /* symbols */ %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA @@ -175,6 +178,8 @@ class GraphScanner; %type <edge_key> edge_key %type <edge_keys> edge_keys %type <edge_key_ref> edge_key_ref +%type <to_clause> to_clause +%type <find_path_upto_clause> find_path_upto_clause %type <intval> port unary_integer rank @@ -187,7 +192,7 @@ class GraphScanner; %type <role_type_clause> role_type_clause %type <acl_item_clause> acl_item_clause -%type <sentence> go_sentence match_sentence use_sentence find_sentence +%type <sentence> go_sentence match_sentence use_sentence find_sentence find_path_sentence %type <sentence> order_by_sentence %type <sentence> fetch_vertices_sentence fetch_edges_sentence %type <sentence> create_tag_sentence create_edge_sentence @@ -716,9 +721,9 @@ edge_key : vid R_ARROW vid AT rank { $$ = new EdgeKey($1, $3, $5); } - | vid R_ARROW vid { + | vid R_ARROW vid { $$ = new EdgeKey($1, $3, 0); - } + } ; edge_keys @@ -767,6 +772,43 @@ fetch_sentence | fetch_edges_sentence { $$ = $1; } ; +find_path_sentence + : KW_FIND KW_ALL KW_PATH from_clause to_clause over_clause find_path_upto_clause + /* where_clause */ { + auto *s = new FindPathSentence(false); + s->setFrom($4); + s->setTo($5); + s->setOver($6); + s->setStep($7); + /* s->setWhere($8); */ + $$ = s; + } + | KW_FIND KW_SHORTEST KW_PATH from_clause to_clause over_clause find_path_upto_clause + /* where_clause */ { + auto *s = new FindPathSentence(true); + s->setFrom($4); + s->setTo($5); + s->setOver($6); + s->setStep($7); + /* s->setWhere($8); */ + $$ = s; + } + ; + +find_path_upto_clause + : %empty { $$ = new StepClause(5, true); } + | KW_UPTO INTEGER KW_STEPS { $$ = new StepClause($2, true); } + ; + +to_clause + : KW_TO vid_list { + $$ = new ToClause($2); + } + | KW_TO vid_ref_expression { + $$ = new ToClause($2); + } + ; + use_sentence : KW_USE name_label { $$ = new UseSentence($2); } ; @@ -976,13 +1018,14 @@ drop_edge_sentence ; traverse_sentence - : go_sentence { $$ = $1; } + : L_PAREN piped_sentence R_PAREN { $$ = $2; } + | L_PAREN set_sentence R_PAREN { $$ = $2; } + | go_sentence { $$ = $1; } | match_sentence { $$ = $1; } | find_sentence { $$ = $1; } | order_by_sentence { $$ = $1; } | fetch_sentence { $$ = $1; } - | L_PAREN piped_sentence R_PAREN { $$ = $2; } - | L_PAREN set_sentence R_PAREN { $$ = $2; } + | find_path_sentence { $$ = $1; } ; set_sentence diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index b6755139462e3b88d578c8323306e12c88dc5987..4c5165882586534e7d343060f8436c322a0e85b5 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -122,6 +122,8 @@ LEADER ([Ll][Ee][Aa][Dd][Ee][Rr]) UUID ([Uu][Uu][Ii][Dd]) OF ([Oo][Ff]) DATA ([Dd][Aa][Tt][Aa]) +SHORTEST ([Ss][Hh][Oo][Rr][Tt][Ee][Ss][Tt]) +PATH ([Pp][Aa][Tt][Hh]) LABEL ([a-zA-Z][_a-zA-Z0-9]*) DEC ([0-9]) @@ -233,6 +235,8 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) {LEADER} { return TokenType::KW_LEADER; } {UUID} { return TokenType::KW_UUID; } {DATA} { return TokenType::KW_DATA; } +{SHORTEST} { return TokenType::KW_SHORTEST; } +{PATH} { return TokenType::KW_PATH; } "." { return TokenType::DOT; } "," { return TokenType::COMMA; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index eff8d674045c7140fabb1cb019bec450f626c511..13b689021df25a6991a9460c37461d1972b9fa6a 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -1368,4 +1368,12 @@ TEST(Parser, CrashByFuzzer) { } } +TEST(Parser, FindPath) { + { + GQLParser parser; + std::string query = "FIND SHORTEST PATH FROM 1 TO 2 OVER like"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } +} } // namespace nebula