From be325e51dd106e732e57dfb67ca8f490b9a71a67 Mon Sep 17 00:00:00 2001 From: Yee <2520865+yixinglu@users.noreply.github.com> Date: Mon, 6 Feb 2023 14:01:08 +0800 Subject: [PATCH] Improve the traverse executor performance (#5308) --- src/clients/storage/StorageClientBase.h | 5 + src/common/base/Base.h | 3 + src/common/datatypes/List.h | 4 + src/common/datatypes/Value.cpp | 39 +++++ src/common/datatypes/Value.h | 10 ++ src/common/datatypes/Vertex.h | 1 + src/graph/context/CMakeLists.txt | 1 + .../iterator/GetNbrsRespDataSetIter.cpp | 154 ++++++++++++++++++ .../context/iterator/GetNbrsRespDataSetIter.h | 58 +++++++ src/graph/executor/query/TraverseExecutor.cpp | 129 ++++++++++----- src/graph/executor/query/TraverseExecutor.h | 56 ++----- .../PushFilterDownTraverseRule.feature | 12 +- 12 files changed, 379 insertions(+), 93 deletions(-) create mode 100644 src/graph/context/iterator/GetNbrsRespDataSetIter.cpp create mode 100644 src/graph/context/iterator/GetNbrsRespDataSetIter.h diff --git a/src/clients/storage/StorageClientBase.h b/src/clients/storage/StorageClientBase.h index 1b2799c943f..b120dec009e 100644 --- a/src/clients/storage/StorageClientBase.h +++ b/src/clients/storage/StorageClientBase.h @@ -97,6 +97,11 @@ class StorageRpcResponse final { return responses_; } + // Not thread-safe. + const std::vector& responses() const { + return responses_; + } + // Not thread-safe. const std::vector>& hostLatency() const { return hostLatency_; diff --git a/src/common/base/Base.h b/src/common/base/Base.h index 1e76a93dc14..35a72dc8d77 100644 --- a/src/common/base/Base.h +++ b/src/common/base/Base.h @@ -133,6 +133,9 @@ constexpr char kSrc[] = "_src"; constexpr char kType[] = "_type"; constexpr char kRank[] = "_rank"; constexpr char kDst[] = "_dst"; +constexpr char kEdgePrefix[] = "_edge"; +constexpr char kStatsPrefix[] = "_stats"; +constexpr char kExprPrefix[] = "_expr"; // Useful type traits diff --git a/src/common/datatypes/List.h b/src/common/datatypes/List.h index d2509d9b687..a2694c52ca8 100644 --- a/src/common/datatypes/List.h +++ b/src/common/datatypes/List.h @@ -80,6 +80,10 @@ struct List { return values[i]; } + Value& operator[](size_t i) { + return values[i]; + } + bool contains(const Value& value) const { return std::find(values.begin(), values.end(), value) != values.end(); } diff --git a/src/common/datatypes/Value.cpp b/src/common/datatypes/Value.cpp index 12d2b143ada..79b4a0a48ef 100644 --- a/src/common/datatypes/Value.cpp +++ b/src/common/datatypes/Value.cpp @@ -3000,4 +3000,43 @@ Value operator^(const Value& lhs, const Value& rhs) { } } } + +std::size_t VertexHash::operator()(const Value& v) const { + switch (v.type()) { + case Value::Type::VERTEX: { + auto& vid = v.getVertex().vid; + if (vid.type() == Value::Type::STRING) { + return std::hash()(vid.getStr()); + } else { + return vid.getInt(); + } + } + case Value::Type::STRING: { + return std::hash()(v.getStr()); + } + case Value::Type::INT: { + return v.getInt(); + } + default: { + return v.hash(); + } + } +} + +bool VertexEqual::operator()(const Value& lhs, const Value& rhs) const { + if (lhs.type() == rhs.type()) { + if (lhs.isVertex()) { + return lhs.getVertex().vid == rhs.getVertex().vid; + } + return lhs == rhs; + } + if (lhs.type() == Value::Type::VERTEX) { + return lhs.getVertex().vid == rhs; + } + if (rhs.type() == Value::Type::VERTEX) { + return lhs == rhs.getVertex().vid; + } + return lhs == rhs; +} + } // namespace nebula diff --git a/src/common/datatypes/Value.h b/src/common/datatypes/Value.h index 6d693533995..595ef6b58a8 100644 --- a/src/common/datatypes/Value.h +++ b/src/common/datatypes/Value.h @@ -550,6 +550,16 @@ inline uint64_t operator&(const Value::Type& lhs, const uint64_t rhs) { return static_cast(lhs) & rhs; } +struct VertexHash { + std::size_t operator()(const Value& v) const; +}; + +struct VertexEqual { + bool operator()(const Value& lhs, const Value& rhs) const; +}; + +using VidHashSet = std::unordered_set; + } // namespace nebula namespace std { diff --git a/src/common/datatypes/Vertex.h b/src/common/datatypes/Vertex.h index 539d256f28a..03561b3cd1d 100644 --- a/src/common/datatypes/Vertex.h +++ b/src/common/datatypes/Vertex.h @@ -24,6 +24,7 @@ struct Tag { Tag(const Tag& tag) : name(tag.name), props(tag.props) {} Tag(std::string tagName, std::unordered_map tagProps) : name(std::move(tagName)), props(std::move(tagProps)) {} + explicit Tag(const std::string& tagName) : name(tagName), props() {} void clear() { name.clear(); diff --git a/src/graph/context/CMakeLists.txt b/src/graph/context/CMakeLists.txt index 0916bb754ba..69636e4f2c0 100644 --- a/src/graph/context/CMakeLists.txt +++ b/src/graph/context/CMakeLists.txt @@ -10,6 +10,7 @@ nebula_add_library( Iterator.cpp Result.cpp Symbols.cpp + iterator/GetNbrsRespDataSetIter.cpp ) diff --git a/src/graph/context/iterator/GetNbrsRespDataSetIter.cpp b/src/graph/context/iterator/GetNbrsRespDataSetIter.cpp new file mode 100644 index 00000000000..369f4e5acb9 --- /dev/null +++ b/src/graph/context/iterator/GetNbrsRespDataSetIter.cpp @@ -0,0 +1,154 @@ +/* Copyright (c) 2023 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/context/iterator/GetNbrsRespDataSetIter.h" + +#include "common/base/Base.h" +#include "common/datatypes/Edge.h" +#include "common/datatypes/Vertex.h" + +namespace nebula { +namespace graph { + +bool isDataSetInvalid(const DataSet* dataset) { + const auto& colNames = dataset->colNames; + return colNames.size() < 3 || // the dataset has vid/_tag/_edge 3 columns at least + colNames[0] != nebula::kVid || // the first column should be Vid + colNames[1].find(kStatsPrefix) != 0 || // the second column could not be _stats column now + colNames.back().find(kExprPrefix) != 0; // the last column could not be _expr column now +} + +GetNbrsRespDataSetIter::GetNbrsRespDataSetIter(const DataSet* dataset) + : dataset_(DCHECK_NOTNULL(dataset)), curRowIdx_(0) { + DCHECK(!isDataSetInvalid(dataset)); + for (size_t i = 0, e = dataset->colNames.size(); i < e; ++i) { + buildPropIndex(dataset->colNames[i], i); + } +} + +void GetNbrsRespDataSetIter::buildPropIndex(const std::string& colName, size_t colIdx) { + std::vector pieces; + folly::split(":", colName, pieces); + if (pieces.size() < 2) { + // Skip the column which is not tag or edge + return; + } + + PropIndex propIdx; + propIdx.colIdx = colIdx; + + propIdx.propIdxMap.reserve(pieces.size() - 2); + // if size == 2, it is the tag defined without props. + for (size_t i = 2; i < pieces.size(); ++i) { + const auto& name = pieces[i]; + size_t idx = i - 2; + if (name == kType) { + propIdx.edgeTypeIdx = idx; + } else if (name == kRank) { + propIdx.edgeRankIdx = idx; + } else if (name == kDst) { + propIdx.edgeDstIdx = idx; + } else if (name == kSrc) { + // Always skip the src of edge since it is returned by the first column + } else if (name == kTag) { + // Skip the _tag prop of vertex since it is not used to create vertex + } else { + propIdx.propIdxMap.emplace(name, idx); + } + } + + folly::StringPiece prefix(pieces[0]), name(pieces[1]); + DCHECK(!name.empty()) << "The name of tag/edge is empty"; + if (prefix.startsWith(kEdgePrefix)) { + DCHECK(name.startsWith("-") || name.startsWith("+")) << "the edge name has to start with '-/+'"; + edgePropsMap_.emplace(name, std::move(propIdx)); + } else if (prefix.startsWith(kTag)) { + tagPropsMap_.emplace(name, std::move(propIdx)); + } +} + +Value GetNbrsRespDataSetIter::getVertex() const { + // Always check the valid() before getVertex + DCHECK(valid()); + const Row& curRow = dataset_->rows[curRowIdx_]; + Vertex vertex; + vertex.vid = curRow[0]; + vertex.tags.reserve(tagPropsMap_.size()); + for (const auto& [tagName, propIdx] : tagPropsMap_) { + DCHECK_LT(propIdx.colIdx, curRow.size()); + const Value& propColumn = curRow[propIdx.colIdx]; + if (propColumn.isList()) { + const List& propList = propColumn.getList(); + + Tag tag(tagName); + tag.props.reserve(propIdx.propIdxMap.size()); + for (const auto& [propName, pIdx] : propIdx.propIdxMap) { + DCHECK_LT(pIdx, propList.size()); + tag.props.emplace(propName, propList[pIdx]); + } + + vertex.tags.emplace_back(std::move(tag)); + } + } + return vertex; +} + +Value GetNbrsRespDataSetIter::createEdgeByPropList(const PropIndex& propIdx, + const Value& edgeVal, + const Value& src, + const std::string& edgeName) const { + if (!edgeVal.isList() || edgeVal.getList().empty()) { + return Value::kEmpty; + } + const List& propList = edgeVal.getList(); + DCHECK_LT(propIdx.edgeDstIdx, propList.size()); + DCHECK_LT(propIdx.edgeTypeIdx, propList.size()); + DCHECK_LT(propIdx.edgeRankIdx, propList.size()); + + Edge edge; + edge.name = edgeName; + edge.src = src; + edge.dst = propList[propIdx.edgeDstIdx]; + const Value& typeVal = propList[propIdx.edgeTypeIdx]; + edge.type = typeVal.isInt() ? typeVal.getInt() : 0; + const Value& rankVal = propList[propIdx.edgeRankIdx]; + edge.ranking = rankVal.isInt() ? rankVal.getInt() : 0; + + edge.props.reserve(propIdx.propIdxMap.size()); + for (const auto& [propName, pIdx] : propIdx.propIdxMap) { + DCHECK_LT(pIdx, propList.size()); + edge.props.emplace(propName, propList[pIdx]); + } + + return edge; +} + +std::vector GetNbrsRespDataSetIter::getAdjEdges(VidHashSet* dstSet) const { + DCHECK(valid()); + + std::vector adjEdges; + const Row& curRow = dataset_->rows[curRowIdx_]; + for (const auto& [edgeName, propIdx] : edgePropsMap_) { + DCHECK_LT(propIdx.colIdx, curRow.size()); + const Value& edgeColumn = curRow[propIdx.colIdx]; + if (edgeColumn.isList()) { + for (const Value& edgeVal : edgeColumn.getList().values) { + // skip the edge direction symbol: `-/+` + auto name = edgeName.substr(1); + Value edge = createEdgeByPropList(propIdx, edgeVal, curRow[0], name); + if (!edge.empty()) { + if (dstSet) { + dstSet->emplace(edge.getEdge().dst); + } + adjEdges.emplace_back(std::move(edge)); + } + } + } + } + return adjEdges; +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/context/iterator/GetNbrsRespDataSetIter.h b/src/graph/context/iterator/GetNbrsRespDataSetIter.h new file mode 100644 index 00000000000..9100463967c --- /dev/null +++ b/src/graph/context/iterator/GetNbrsRespDataSetIter.h @@ -0,0 +1,58 @@ +/* Copyright (c) 2023 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_CONTEXT_ITERATOR_GETNBRSRESPDATASETITER_H_ +#define GRAPH_CONTEXT_ITERATOR_GETNBRSRESPDATASETITER_H_ + +#include "common/datatypes/DataSet.h" +#include "common/datatypes/Value.h" + +namespace nebula { +namespace graph { + +class GetNbrsRespDataSetIter final { + public: + explicit GetNbrsRespDataSetIter(const DataSet* dataset); + + bool valid() const { + return curRowIdx_ < dataset_->rowSize(); + } + // Next row in dataset + void next() { + curRowIdx_++; + } + + Value getVertex() const; + std::vector getAdjEdges(VidHashSet* dstSet) const; + + private: + struct PropIndex { + size_t colIdx; + size_t edgeTypeIdx; + size_t edgeRankIdx; + size_t edgeDstIdx; + std::unordered_map propIdxMap; + }; + + void buildPropIndex(const std::string& colName, size_t colIdx); + Value createEdgeByPropList(const PropIndex& propIdx, + const Value& edgeVal, + const Value& src, + const std::string& edgeName) const; + + // my fields + const DataSet* dataset_; + size_t curRowIdx_; + + // _tag:t1:p1:p2 -> {t1 : [column_idx, [p1, p2], {p1 : 0, p2 : 1}]} + std::unordered_map tagPropsMap_; + // _edge:e1:p1:p2 -> {e1 : [column_idx, [p1, p2], {p1 : 0, p2 : 1}]} + std::unordered_map edgePropsMap_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_CONTEXT_ITERATOR_GETNBRSRESPDATASETITER_H_ diff --git a/src/graph/executor/query/TraverseExecutor.cpp b/src/graph/executor/query/TraverseExecutor.cpp index e1c28b8c13a..b0663edd734 100644 --- a/src/graph/executor/query/TraverseExecutor.cpp +++ b/src/graph/executor/query/TraverseExecutor.cpp @@ -6,6 +6,7 @@ #include "clients/storage/StorageClient.h" #include "common/memory/MemoryTracker.h" +#include "graph/context/iterator/GetNbrsRespDataSetIter.h" #include "graph/service/GraphFlags.h" #include "graph/util/SchemaUtil.h" #include "graph/util/Utils.h" @@ -19,17 +20,19 @@ namespace graph { folly::Future TraverseExecutor::execute() { range_ = traverse_->stepRange(); - auto status = buildRequestVids(); - if (!status.ok()) { - return error(std::move(status)); - } + NG_RETURN_IF_ERROR(buildRequestVids()); if (vids_.empty()) { DataSet emptyDs; return finish(ResultBuilder().value(Value(std::move(emptyDs))).build()); } return getNeighbors().ensure([this]() { // fill some profile time stats - otherStats_.emplace("expandTime", folly::sformat("{}(us)", expandTime_)); + if (expandTime_) { + otherStats_.emplace("expandTime", folly::sformat("{}(us)", expandTime_)); + } + if (expandOneStepTime_) { + otherStats_.emplace("expandOneStepTime", folly::sformat("{}(us)", expandOneStepTime_)); + } }); } @@ -45,21 +48,16 @@ Status TraverseExecutor::buildRequestVids() { bool mv = movable(traverse_->inputVars().front()); if (traverse_->trackPrevPath()) { - std::unordered_set uniqueVid; - uniqueVid.reserve(iterSize); for (; iter->valid(); iter->next()) { const auto& vid = src->eval(ctx(iter)); auto prevPath = mv ? iter->moveRow() : *iter->row(); auto vidIter = dst2PathsMap_.find(vid); if (vidIter == dst2PathsMap_.end()) { - std::vector tmp({std::move(prevPath)}); - dst2PathsMap_.emplace(vid, std::move(tmp)); + dst2PathsMap_.emplace(vid, std::vector{std::move(prevPath)}); } else { vidIter->second.emplace_back(std::move(prevPath)); } - if (uniqueVid.emplace(vid).second) { - vids_.emplace_back(vid); - } + vids_.emplace(vid); } } else { const auto& spaceInfo = qctx()->rctx()->session()->space(); @@ -67,11 +65,11 @@ Status TraverseExecutor::buildRequestVids() { auto vidType = SchemaUtil::propTypeToValueType(metaVidType.get_type()); for (; iter->valid(); iter->next()) { const auto& vid = src->eval(ctx(iter)); - if (vid.type() != vidType) { - LOG(ERROR) << "Mismatched vid type: " << vid.type() << ", space vid type: " << vidType; - continue; + DCHECK_EQ(vid.type(), vidType) + << "Mismatched vid type: " << vid.type() << ", space vid type: " << vidType; + if (vid.type() == vidType) { + vids_.emplace(vid); } - vids_.emplace_back(vid); } } return Status::OK(); @@ -86,10 +84,12 @@ folly::Future TraverseExecutor::getNeighbors() { qctx()->rctx()->session()->id(), qctx()->plan()->id(), qctx()->plan()->isProfileEnabled()); + std::vector vids(vids_.size()); + std::move(vids_.begin(), vids_.end(), vids.begin()); return storageClient ->getNeighbors(param, {nebula::kVid}, - std::move(vids_), + std::move(vids), traverse_->edgeTypes(), traverse_->edgeDirection(), finalStep ? traverse_->statProps() : nullptr, @@ -146,35 +146,79 @@ void TraverseExecutor::addStats(RpcResponse& resp, int64_t getNbrTimeInUSec) { otherStats_.emplace(folly::sformat("step[{}]", currentStep_), folly::toPrettyJson(stepObj)); } -folly::Future TraverseExecutor::handleResponse(RpcResponse&& resps) { - NG_RETURN_IF_ERROR(handleCompleteness(resps, FLAGS_accept_partial_success)); +size_t TraverseExecutor::numRowsOfRpcResp(const RpcResponse& resps) const { + size_t numRows = 0; + for (const auto& resp : resps.responses()) { + auto dataset = resp.get_vertices(); + if (dataset) { + numRows += dataset->rowSize(); + } + } + return numRows; +} - List list; - for (auto& resp : resps.responses()) { +void TraverseExecutor::expandOneStep(const RpcResponse& resps) { + SCOPED_TIMER(&expandOneStepTime_); + initVertices_.reserve(numRowsOfRpcResp(resps)); + + for (const auto& resp : resps.responses()) { auto dataset = resp.get_vertices(); if (dataset) { - list.values.emplace_back(std::move(*dataset)); + for (GetNbrsRespDataSetIter iter(dataset); iter.valid(); iter.next()) { + Value v = iter.getVertex(); + initVertices_.emplace_back(v); + VidHashSet dstSet; + auto adjEdges = iter.getAdjEdges(&dstSet); + for (const Value& dst : dstSet) { + if (adjList_.find(dst) == adjList_.end()) { + vids_.emplace(dst); + } + } + DCHECK(adjList_.find(v) == adjList_.end()) + << "The adjacency list should not contain the source vertex"; + adjList_.emplace(v, std::move(adjEdges)); + } } } - auto listVal = std::make_shared(std::move(list)); - auto iter = std::make_unique(listVal); - if (currentStep_ == 1) { - initVertices_.reserve(iter->numRows()); - auto vertices = iter->getVertices(); - // match (v)-[e:Rel]-(v1:Label1)-[e1*2]->() where id(v0) in [6, 23] return v1 - // save the vertex that meets the filter conditions as the starting vertex of the current - // traverse - for (auto& vertex : vertices.values) { - if (vertex.isVertex()) { - initVertices_.emplace_back(vertex); + + if (range_.min() == 0) { + result_.rows = buildZeroStepPath(); + } +} + +folly::Future TraverseExecutor::handleResponse(RpcResponse&& resps) { + NG_RETURN_IF_ERROR(handleCompleteness(resps, FLAGS_accept_partial_success)); + + if (currentStep_ == 1 && !traverse_->eFilter() && !traverse_->vFilter()) { + expandOneStep(resps); + } else { + List list; + for (auto& resp : resps.responses()) { + auto dataset = resp.get_vertices(); + if (dataset) { + list.values.emplace_back(std::move(*dataset)); } } - if (range_.min() == 0) { - result_.rows = buildZeroStepPath(); + auto listVal = std::make_shared(std::move(list)); + auto iter = std::make_unique(listVal); + if (currentStep_ == 1) { + initVertices_.reserve(iter->numRows()); + auto vertices = iter->getVertices(); + // match (v)-[e:Rel]-(v1:Label1)-[e1*2]->() where id(v0) in [6, 23] return v1 + // save the vertex that meets the filter conditions as the starting vertex of the current + // traverse + for (auto& vertex : vertices.values) { + if (vertex.isVertex()) { + initVertices_.emplace_back(vertex); + } + } + if (range_.min() == 0) { + result_.rows = buildZeroStepPath(); + } } - } - expand(iter.get()); + expand(iter.get()); + } if (!isFinalStep() && !vids_.empty()) { return getNeighbors(); @@ -191,9 +235,12 @@ void TraverseExecutor::expand(GetNeighborsIter* iter) { auto* eFilter = traverse_->eFilter(); QueryExpressionContext ctx(ectx_); - std::unordered_set uniqueVids; Value curVertex; std::vector adjEdges; + auto sz = iter->size(); + adjEdges.reserve(sz); + vids_.reserve(vids_.size() + sz); + adjList_.reserve(adjList_.size() + iter->numRows() + 1u); for (; iter->valid(); iter->next()) { if (vFilter != nullptr && currentStep_ == 1) { const auto& vFilterVal = vFilter->eval(ctx(iter)); @@ -212,8 +259,8 @@ void TraverseExecutor::expand(GetNeighborsIter* iter) { continue; } const auto& dst = edge.getEdge().dst; - if (adjList_.find(dst) == adjList_.end() && uniqueVids.emplace(dst).second) { - vids_.emplace_back(dst); + if (adjList_.find(dst) == adjList_.end()) { + vids_.emplace(dst); } const auto& vertex = iter->getVertex(); curVertex = curVertex.empty() ? vertex : curVertex; @@ -333,7 +380,7 @@ folly::Future TraverseExecutor::buildPathMultiJobs(size_t minStep, size_ return runMultiJobs(std::move(scatter), std::move(gather), iter.get()); } -// build path based on BFS through adjancency list +// build path based on BFS through adjacency list std::vector TraverseExecutor::buildPath(const Value& initVertex, size_t minStep, size_t maxStep) { diff --git a/src/graph/executor/query/TraverseExecutor.h b/src/graph/executor/query/TraverseExecutor.h index c54f9877528..569652a0d59 100644 --- a/src/graph/executor/query/TraverseExecutor.h +++ b/src/graph/executor/query/TraverseExecutor.h @@ -51,6 +51,9 @@ class TraverseExecutor final : public StorageAccessExecutor { folly::Future execute() override; + template + using VertexMap = std::unordered_map, VertexHash, VertexEqual>; + private: Status buildRequestVids(); @@ -58,8 +61,10 @@ class TraverseExecutor final : public StorageAccessExecutor { folly::Future getNeighbors(); - void expand(GetNeighborsIter* iter); + size_t numRowsOfRpcResp(const RpcResponse& resps) const; + void expand(GetNeighborsIter* iter); + void expandOneStep(const RpcResponse& resps); folly::Future handleResponse(RpcResponse&& resps); folly::Future buildResult(); @@ -80,62 +85,21 @@ class TraverseExecutor final : public StorageAccessExecutor { Expression* selectFilter(); - struct VertexHash { - std::size_t operator()(const Value& v) const { - switch (v.type()) { - case Value::Type::VERTEX: { - auto& vid = v.getVertex().vid; - if (vid.type() == Value::Type::STRING) { - return std::hash()(vid.getStr()); - } else { - return vid.getInt(); - } - } - case Value::Type::STRING: { - return std::hash()(v.getStr()); - } - case Value::Type::INT: { - return v.getInt(); - } - default: { - return v.hash(); - } - } - } - }; - - struct VertexEqual { - bool operator()(const Value& lhs, const Value& rhs) const { - if (lhs.type() == rhs.type()) { - if (lhs.isVertex()) { - return lhs.getVertex().vid == rhs.getVertex().vid; - } - return lhs == rhs; - } - if (lhs.type() == Value::Type::VERTEX) { - return lhs.getVertex().vid == rhs; - } - if (rhs.type() == Value::Type::VERTEX) { - return lhs == rhs.getVertex().vid; - } - return lhs == rhs; - } - }; - private: ObjectPool objPool_; - std::vector vids_; + VidHashSet vids_; std::vector initVertices_; DataSet result_; // Key : vertex Value : adjacent edges - std::unordered_map, VertexHash, VertexEqual> adjList_; - std::unordered_map, VertexHash, VertexEqual> dst2PathsMap_; + VertexMap adjList_; + VertexMap dst2PathsMap_; const Traverse* traverse_{nullptr}; MatchStepRange range_; size_t currentStep_{0}; size_t expandTime_{0u}; + size_t expandOneStepTime_{0u}; }; } // namespace graph diff --git a/tests/tck/features/optimizer/PushFilterDownTraverseRule.feature b/tests/tck/features/optimizer/PushFilterDownTraverseRule.feature index f31f5117dcc..ce9de5da759 100644 --- a/tests/tck/features/optimizer/PushFilterDownTraverseRule.feature +++ b/tests/tck/features/optimizer/PushFilterDownTraverseRule.feature @@ -207,15 +207,15 @@ Feature: Push Filter down Traverse rule """ MATCH (v:player)-[e:like]->(v2) WHERE v.player.age != 35 and (e.likeness + 100) != 199 and none_direct_dst(e) in ["Tony Parker", "Tim Duncan", "Yao Ming"] - RETURN e.likeness, v2.player.age as age - ORDER BY age + RETURN e.likeness AS likeness, v2.player.age as age + ORDER BY age, likeness DESC LIMIT 3 """ Then the result should be, in any order: - | e.likeness | age | - | 80 | 36 | - | 75 | 36 | - | 50 | 36 | + | likeness | age | + | 95 | 36 | + | 80 | 36 | + | 75 | 36 | And the execution plan should be: | id | name | dependencies | operator info | | 11 | TopN | 10 | |