diff --git a/src/common/datatypes/Edge.cpp b/src/common/datatypes/Edge.cpp index ea11b5ec240..fad349cef7b 100644 --- a/src/common/datatypes/Edge.cpp +++ b/src/common/datatypes/Edge.cpp @@ -131,6 +131,16 @@ void Edge::clear() { props.clear(); } +bool Edge::keyEqual(const Edge& rhs) const { + if (type != rhs.type && type != -rhs.type) { + return false; + } + if (type == rhs.type) { + return src == rhs.src && dst == rhs.dst && ranking == rhs.ranking; + } + return src == rhs.dst && dst == rhs.src && ranking == rhs.ranking; +} + } // namespace nebula namespace std { diff --git a/src/common/datatypes/Edge.h b/src/common/datatypes/Edge.h index 65bd439855d..5fe10af251b 100644 --- a/src/common/datatypes/Edge.h +++ b/src/common/datatypes/Edge.h @@ -68,6 +68,8 @@ struct Edge { bool contains(const Value& key) const; const Value& value(const std::string& key) const; + + bool keyEqual(const Edge& rhs) const; }; inline std::ostream& operator<<(std::ostream& os, const Edge& v) { return os << v.toString(); } diff --git a/src/common/expression/PathBuildExpression.cpp b/src/common/expression/PathBuildExpression.cpp index fba2c5791e3..03a274b83b8 100644 --- a/src/common/expression/PathBuildExpression.cpp +++ b/src/common/expression/PathBuildExpression.cpp @@ -26,46 +26,60 @@ const Value& PathBuildExpression::eval(ExpressionContext& ctx) { for (size_t i = 1; i < items_.size(); ++i) { auto& value = items_[i]->eval(ctx); - if (value.isEdge()) { + if (!buildPath(value, path)) { + return Value::kNullBadData; + } + } + + result_ = path; + return result_; +} + +bool PathBuildExpression::buildPath(const Value& value, Path& path) const { + switch (value.type()) { + case Value::Type::EDGE: { + Step step; if (!path.steps.empty()) { - const auto& lastStep = path.steps.back(); - const auto& edge = value.getEdge(); - if (lastStep.dst.vid != edge.src) { - return Value::kNullBadData; - } + getEdge(value, path.steps.back().dst.vid, step); + } else { + getEdge(value, path.src.vid, step); } - Step step; - getEdge(value, step); path.steps.emplace_back(std::move(step)); - } else if (value.isVertex()) { + break; + } + case Value::Type::VERTEX: { if (path.steps.empty()) { - return Value::kNullBadData; + if (path.src.vid == value.getVertex().vid) { + return true; + } + return false; } auto& lastStep = path.steps.back(); - const auto& vert = value.getVertex(); - if (lastStep.dst.vid != vert.vid) { - return Value::kNullBadData; - } getVertex(value, lastStep.dst); - } else if (value.isPath()) { + break; + } + case Value::Type::PATH: { const auto& p = value.getPath(); if (!path.steps.empty()) { auto& lastStep = path.steps.back(); - if (lastStep.dst.vid != p.src.vid) { - return Value::kNullBadData; - } lastStep.dst = p.src; } path.steps.insert(path.steps.end(), p.steps.begin(), p.steps.end()); - } else { - if ((i & 1) == 1 || path.steps.empty() || !getVertex(value, path.steps.back().dst)) { - return Value::kNullBadData; + break; + } + case Value::Type::LIST: { + for (const auto& val : value.getList().values) { + if (!buildPath(val, path)) { + return false; + } } + break; + } + default: { + return false; } } - - result_ = path; - return result_; + return true; } bool PathBuildExpression::getVertex(const Value& value, Vertex& vertex) const { @@ -84,18 +98,26 @@ bool PathBuildExpression::getVertex(const Value& value, Vertex& vertex) const { return false; } -bool PathBuildExpression::getEdge(const Value& value, Step& step) const { - if (value.isEdge()) { - const auto& edge = value.getEdge(); +bool PathBuildExpression::getEdge(const Value& value, const Value& lastStepVid, Step& step) const { + if (!value.isEdge()) { + return false; + } + + const auto& edge = value.getEdge(); + if (lastStepVid == edge.src) { step.type = edge.type; step.name = edge.name; step.ranking = edge.ranking; step.props = edge.props; step.dst.vid = edge.dst; - return true; + } else { + step.type = -edge.type; + step.name = edge.name; + step.ranking = edge.ranking; + step.props = edge.props; + step.dst.vid = edge.src; } - - return false; + return true; } bool PathBuildExpression::operator==(const Expression& rhs) const { diff --git a/src/common/expression/PathBuildExpression.h b/src/common/expression/PathBuildExpression.h index f1f03e62726..aaea497db9d 100644 --- a/src/common/expression/PathBuildExpression.h +++ b/src/common/expression/PathBuildExpression.h @@ -56,7 +56,9 @@ class PathBuildExpression final : public Expression { bool getVertex(const Value& value, Vertex& vertex) const; - bool getEdge(const Value& value, Step& step) const; + bool getEdge(const Value& value, const Value& lastStepVid, Step& step) const; + + bool buildPath(const Value& value, Path& path) const; private: std::vector items_; diff --git a/src/common/expression/test/ExpressionContextMock.cpp b/src/common/expression/test/ExpressionContextMock.cpp index b0318536710..9a54ce323d1 100644 --- a/src/common/expression/test/ExpressionContextMock.cpp +++ b/src/common/expression/test/ExpressionContextMock.cpp @@ -36,9 +36,9 @@ std::unordered_map ExpressionContextMock::vals_ = { {"srcProperty", Value(13)}, {"dstProperty", Value(3)}, - {"path_src", Value("1")}, + {"path_src", Value(Vertex("1", {}))}, {"path_edge1", Value(Edge("1", "2", 1, "edge", 0, {}))}, - {"path_v1", Value("2")}, + {"path_v1", Value(Vertex("2", {}))}, {"path_edge2", Value(Edge("2", "3", 1, "edge", 0, {}))}, {"path_v2", Value(Vertex("3", {}))}, {"path_edge3", Value(Edge("3", "4", 1, "edge", 0, {}))}, diff --git a/src/common/function/FunctionManager.cpp b/src/common/function/FunctionManager.cpp index 2a38b6736f3..097c3eba278 100644 --- a/src/common/function/FunctionManager.cpp +++ b/src/common/function/FunctionManager.cpp @@ -413,6 +413,7 @@ std::unordered_map> FunctionManager::typ Value::Type::FLOAT}, Value::Type::LIST), }}, + {"is_edge", {TypeSignature({Value::Type::EDGE}, Value::Type::BOOL)}}, }; // static @@ -1945,6 +1946,41 @@ FunctionManager::FunctionManager() { } }; } + { + auto &attr = functions_["none_direct_dst"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { + switch (args[0].get().type()) { + case Value::Type::NULLVALUE: { + return Value::kNullValue; + } + case Value::Type::EDGE: { + const auto &edge = args[0].get().getEdge(); + return edge.dst; + } + case Value::Type::VERTEX: { + const auto &v = args[0].get().getVertex(); + return v.vid; + } + case Value::Type::LIST: { + const auto &listVal = args[0].get().getList(); + auto &lastVal = listVal.values.back(); + if (lastVal.isEdge()) { + return lastVal.getEdge().dst; + } else if (lastVal.isVertex()) { + return lastVal.getVertex().vid; + } else { + return Value::kNullBadType; + } + } + default: { + return Value::kNullBadType; + } + } + }; + } { auto &attr = functions_["rank"]; attr.minArity_ = 1; @@ -2636,6 +2672,13 @@ FunctionManager::FunctionManager() { return List(vals); }; } + { + auto &attr = functions_["is_edge"]; + attr.minArity_ = 1; + attr.maxArity_ = 1; + attr.isPure_ = true; + attr.body_ = [](const auto &args) -> Value { return args[0].get().isEdge(); }; + } } // NOLINT // static diff --git a/src/graph/context/Iterator.cpp b/src/graph/context/Iterator.cpp index fad0b67d64c..e14a4e6ede4 100644 --- a/src/graph/context/Iterator.cpp +++ b/src/graph/context/Iterator.cpp @@ -324,26 +324,54 @@ const Value& GetNeighborsIter::getTagProp(const std::string& tag, const std::str return Value::kNullValue; } - auto& tagPropIndices = currentDs_->tagPropsMap; - auto index = tagPropIndices.find(tag); - if (index == tagPropIndices.end()) { - return Value::kEmpty; - } - auto propIndex = index->second.propIndices.find(prop); - if (propIndex == index->second.propIndices.end()) { - return Value::kEmpty; - } - auto colId = index->second.colIdx; + size_t colId = 0; + size_t propId = 0; auto& row = *currentRow_; - DCHECK_GT(row.size(), colId); - if (row[colId].empty()) { + if (tag == "*") { + for (auto& index : currentDs_->tagPropsMap) { + auto propIndex = index.second.propIndices.find(prop); + if (propIndex != index.second.propIndices.end()) { + colId = index.second.colIdx; + propId = propIndex->second; + DCHECK_GT(row.size(), colId); + if (row[colId].empty()) { + continue; + } + if (!row[colId].isList()) { + return Value::kNullBadType; + } + auto& list = row[colId].getList(); + auto& val = list.values[propId]; + if (val.empty()) { + continue; + } else { + return val; + } + } + } return Value::kEmpty; + } else { + auto& tagPropIndices = currentDs_->tagPropsMap; + auto index = tagPropIndices.find(tag); + if (index == tagPropIndices.end()) { + return Value::kEmpty; + } + auto propIndex = index->second.propIndices.find(prop); + if (propIndex == index->second.propIndices.end()) { + return Value::kEmpty; + } + colId = index->second.colIdx; + propId = propIndex->second; + DCHECK_GT(row.size(), colId); + if (row[colId].empty()) { + return Value::kEmpty; + } + if (!row[colId].isList()) { + return Value::kNullBadType; + } + auto& list = row[colId].getList(); + return list.values[propId]; } - if (!row[colId].isList()) { - return Value::kNullBadType; - } - auto& list = row[colId].getList(); - return list.values[propIndex->second]; } const Value& GetNeighborsIter::getEdgeProp(const std::string& edge, const std::string& prop) const { @@ -688,20 +716,37 @@ const Value& PropIter::getProp(const std::string& name, const std::string& prop) return Value::kNullValue; } auto& propsMap = dsIndex_.propsMap; - auto index = propsMap.find(name); - if (index == propsMap.end()) { - return Value::kEmpty; - } - - auto propIndex = index->second.find(prop); - if (propIndex == index->second.end()) { - VLOG(1) << "No prop found : " << prop; + size_t colId = 0; + auto& row = *iter_; + if (name == "*") { + for (auto& index : propsMap) { + auto propIndex = index.second.find(prop); + if (propIndex == index.second.end()) { + continue; + } + colId = propIndex->second; + DCHECK_GT(row.size(), colId); + auto& val = row[colId]; + if (val.empty()) { + continue; + } else { + return val; + } + } return Value::kNullValue; + } else { + auto index = propsMap.find(name); + if (index == propsMap.end()) { + return Value::kEmpty; + } + auto propIndex = index->second.find(prop); + if (propIndex == index->second.end()) { + return Value::kNullValue; + } + colId = propIndex->second; + DCHECK_GT(row.size(), colId); + return row[colId]; } - auto colId = propIndex->second; - auto& row = *iter_; - DCHECK_GT(row.size(), colId); - return row[colId]; } Value PropIter::getVertex(const std::string& name) const { diff --git a/src/graph/context/Iterator.h b/src/graph/context/Iterator.h index ff0217d8596..562117e99b6 100644 --- a/src/graph/context/Iterator.h +++ b/src/graph/context/Iterator.h @@ -436,6 +436,8 @@ class SequentialIter : public Iterator { // Notice: We only use this interface when return results to client. friend class DataCollectExecutor; + friend class AppendVerticesExecutor; + friend class TraverseExecutor; Row&& moveRow() { return std::move(*iter_); } void doReset(size_t pos) override; diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index a1649d87ff6..261f9128113 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -34,6 +34,8 @@ nebula_add_library( query/InnerJoinExecutor.cpp query/IndexScanExecutor.cpp query/AssignExecutor.cpp + query/TraverseExecutor.cpp + query/AppendVerticesExecutor.cpp algo/ConjunctPathExecutor.cpp algo/BFSShortestPathExecutor.cpp algo/ProduceSemiShortestPathExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index db8178cea46..93115ba4fc8 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -65,6 +65,7 @@ #include "graph/executor/mutate/InsertExecutor.h" #include "graph/executor/mutate/UpdateExecutor.h" #include "graph/executor/query/AggregateExecutor.h" +#include "graph/executor/query/AppendVerticesExecutor.h" #include "graph/executor/query/AssignExecutor.h" #include "graph/executor/query/DataCollectExecutor.h" #include "graph/executor/query/DedupExecutor.h" @@ -82,6 +83,7 @@ #include "graph/executor/query/SampleExecutor.h" #include "graph/executor/query/SortExecutor.h" #include "graph/executor/query/TopNExecutor.h" +#include "graph/executor/query/TraverseExecutor.h" #include "graph/executor/query/UnionAllVersionVarExecutor.h" #include "graph/executor/query/UnionExecutor.h" #include "graph/executor/query/UnwindExecutor.h" @@ -507,6 +509,12 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kKillQuery: { return pool->add(new KillQueryExecutor(node, qctx)); } + case PlanNode::Kind::kTraverse: { + return pool->add(new TraverseExecutor(node, qctx)); + } + case PlanNode::Kind::kAppendVertices: { + return pool->add(new AppendVerticesExecutor(node, qctx)); + } case PlanNode::Kind::kUnknown: { LOG(FATAL) << "Unknown plan node kind " << static_cast(node->kind()); break; @@ -583,7 +591,7 @@ void Executor::drop() { // Make sure drop happened-after count decrement CHECK_EQ(inputVar->userCount.load(std::memory_order_acquire), 0); ectx_->dropResult(inputVar->name); - VLOG(1) << "Drop variable " << node()->outputVar(); + VLOG(1) << node()->kind() << " Drop variable " << inputVar->name; } } } diff --git a/src/graph/executor/query/AppendVerticesExecutor.cpp b/src/graph/executor/query/AppendVerticesExecutor.cpp new file mode 100644 index 00000000000..94f67efc581 --- /dev/null +++ b/src/graph/executor/query/AppendVerticesExecutor.cpp @@ -0,0 +1,105 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/executor/query/AppendVerticesExecutor.h" + +using nebula::storage::GraphStorageClient; +using nebula::storage::StorageRpcResponse; +using nebula::storage::cpp2::GetPropResponse; + +namespace nebula { +namespace graph { +folly::Future AppendVerticesExecutor::execute() { return appendVertices(); } + +DataSet AppendVerticesExecutor::buildRequestDataSet(const AppendVertices *av) { + if (av == nullptr) { + return nebula::DataSet({kVid}); + } + auto valueIter = ectx_->getResult(av->inputVar()).iter(); + return buildRequestDataSetByVidType(valueIter.get(), av->src(), av->dedup()); +} + +folly::Future AppendVerticesExecutor::appendVertices() { + SCOPED_TIMER(&execTime_); + + auto *av = asNode(node()); + GraphStorageClient *storageClient = qctx()->getStorageClient(); + + DataSet vertices = buildRequestDataSet(av); + if (vertices.rows.empty()) { + return finish(ResultBuilder().value(Value(DataSet(av->colNames()))).build()); + } + + GraphStorageClient::CommonRequestParam param(av->space(), + qctx()->rctx()->session()->id(), + qctx()->plan()->id(), + qctx()->plan()->isProfileEnabled()); + time::Duration getPropsTime; + return DCHECK_NOTNULL(storageClient) + ->getProps(param, + std::move(vertices), + av->props(), + nullptr, + av->exprs(), + av->dedup(), + av->orderBy(), + av->limit(), + av->filter()) + .via(runner()) + .ensure([this, getPropsTime]() { + SCOPED_TIMER(&execTime_); + otherStats_.emplace("total_rpc", folly::sformat("{}(us)", getPropsTime.elapsedInUSec())); + }) + .thenValue([this](StorageRpcResponse &&rpcResp) { + SCOPED_TIMER(&execTime_); + addStats(rpcResp, otherStats_); + return handleResp(std::move(rpcResp)); + }); +} + +Status AppendVerticesExecutor::handleResp( + storage::StorageRpcResponse &&rpcResp) { + auto result = handleCompleteness(rpcResp, FLAGS_accept_partial_success); + NG_RETURN_IF_ERROR(result); + auto state = std::move(result).value(); + std::unordered_map map; + auto *av = asNode(node()); + auto *vFilter = av->vFilter(); + QueryExpressionContext ctx(qctx()->ectx()); + for (auto &resp : rpcResp.responses()) { + if (resp.props_ref().has_value()) { + auto iter = PropIter(std::make_shared(std::move(*resp.props_ref()))); + for (; iter.valid(); iter.next()) { + if (vFilter != nullptr) { + auto &vFilterVal = vFilter->eval(ctx(&iter)); + if (!vFilterVal.isBool() || !vFilterVal.getBool()) { + continue; + } + } + map.emplace(iter.getColumn(kVid), iter.getVertex()); + } + } + } + + auto iter = qctx()->ectx()->getResult(av->inputVar()).iter(); + auto *src = av->src(); + + DataSet ds; + ds.colNames = av->colNames(); + ds.rows.reserve(iter->size()); + for (; iter->valid(); iter->next()) { + auto dstFound = map.find(src->eval(ctx(iter.get()))); + auto row = static_cast(iter.get())->moveRow(); + if (dstFound == map.end()) { + continue; + } + row.values.emplace_back(dstFound->second); + ds.rows.emplace_back(std::move(row)); + } + return finish(ResultBuilder().value(Value(std::move(ds))).state(state).build()); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/query/AppendVerticesExecutor.h b/src/graph/executor/query/AppendVerticesExecutor.h new file mode 100644 index 00000000000..109b7a08fc2 --- /dev/null +++ b/src/graph/executor/query/AppendVerticesExecutor.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef GRAPH_EXECUTOR_QUERY_APPENDVERTICESEXECUTOR_H_ +#define GRAPH_EXECUTOR_QUERY_APPENDVERTICESEXECUTOR_H_ + +#include "graph/executor/query/GetPropExecutor.h" +#include "graph/planner/plan/Query.h" + +namespace nebula { +namespace graph { + +class AppendVerticesExecutor final : public GetPropExecutor { + public: + AppendVerticesExecutor(const PlanNode *node, QueryContext *qctx) + : GetPropExecutor("AppendVerticesExecutor", node, qctx) {} + + folly::Future execute() override; + + private: + DataSet buildRequestDataSet(const AppendVertices *gv); + + folly::Future appendVertices(); + + Status handleResp(storage::StorageRpcResponse &&rpcResp); +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_QUERY_GETVERTICESEXECUTOR_H_ diff --git a/src/graph/executor/query/TraverseExecutor.cpp b/src/graph/executor/query/TraverseExecutor.cpp new file mode 100644 index 00000000000..d2e23e2d458 --- /dev/null +++ b/src/graph/executor/query/TraverseExecutor.cpp @@ -0,0 +1,351 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#include "graph/executor/query/TraverseExecutor.h" + +#include + +#include "clients/storage/GraphStorageClient.h" +#include "common/datatypes/List.h" +#include "common/datatypes/Vertex.h" +#include "common/time/ScopedTimer.h" +#include "graph/context/QueryContext.h" +#include "graph/service/GraphFlags.h" +#include "graph/util/SchemaUtil.h" + +using nebula::storage::GraphStorageClient; +using nebula::storage::StorageRpcResponse; +using nebula::storage::cpp2::GetNeighborsResponse; + +namespace nebula { +namespace graph { + +folly::Future TraverseExecutor::execute() { + range_ = traverse_->stepRange(); + auto status = buildRequestDataSet(); + if (!status.ok()) { + return error(std::move(status)); + } + return traverse(); +} + +Status TraverseExecutor::close() { + // clear the members + reqDs_.rows.clear(); + return Executor::close(); +} + +Status TraverseExecutor::buildRequestDataSet() { + SCOPED_TIMER(&execTime_); + auto inputVar = traverse_->inputVar(); + auto& inputResult = ectx_->getResult(inputVar); + auto inputIter = inputResult.iter(); + auto iter = static_cast(inputIter.get()); + + reqDs_.colNames = {kVid}; + reqDs_.rows.reserve(iter->size()); + + std::unordered_set uniqueSet; + uniqueSet.reserve(iter->size()); + std::unordered_map prev; + const auto& spaceInfo = qctx()->rctx()->session()->space(); + const auto& vidType = *(spaceInfo.spaceDesc.vid_type_ref()); + auto* src = traverse_->src(); + QueryExpressionContext ctx(ectx_); + + for (; iter->valid(); iter->next()) { + auto vid = src->eval(ctx(iter)); + if (!SchemaUtil::isValidVid(vid, vidType)) { + LOG(WARNING) << "Mismatched vid type: " << vid.type() + << ", space vid type: " << SchemaUtil::typeToString(vidType); + continue; + } + buildPath(prev, vid, iter->moveRow()); + if (!uniqueSet.emplace(vid).second) { + continue; + } + reqDs_.emplace_back(Row({std::move(vid)})); + } + paths_.emplace_back(std::move(prev)); + return Status::OK(); +} + +folly::Future TraverseExecutor::traverse() { + if (reqDs_.rows.empty()) { + VLOG(1) << "Empty input."; + DataSet emptyResult; + return finish(ResultBuilder().value(Value(std::move(emptyResult))).build()); + } + getNeighbors(); + return promise_.getFuture(); +} + +void TraverseExecutor::getNeighbors() { + currentStep_++; + time::Duration getNbrTime; + GraphStorageClient* storageClient = qctx_->getStorageClient(); + bool finalStep = isFinalStep(); + GraphStorageClient::CommonRequestParam param(traverse_->space(), + qctx()->rctx()->session()->id(), + qctx()->plan()->id(), + qctx()->plan()->isProfileEnabled()); + storageClient + ->getNeighbors(param, + reqDs_.colNames, + std::move(reqDs_.rows), + traverse_->edgeTypes(), + traverse_->edgeDirection(), + finalStep ? traverse_->statProps() : nullptr, + traverse_->vertexProps(), + traverse_->edgeProps(), + finalStep ? traverse_->exprs() : nullptr, + finalStep ? traverse_->dedup() : false, + finalStep ? traverse_->random() : false, + finalStep ? traverse_->orderBy() : std::vector(), + finalStep ? traverse_->limit() : -1, + finalStep ? traverse_->filter() : nullptr) + .via(runner()) + .thenValue([this, getNbrTime](StorageRpcResponse&& resp) mutable { + SCOPED_TIMER(&execTime_); + addStats(resp, getNbrTime.elapsedInUSec()); + handleResponse(resp); + }); +} + +void TraverseExecutor::addStats(RpcResponse& resp, int64_t getNbrTimeInUSec) { + auto& hostLatency = resp.hostLatency(); + std::stringstream ss; + ss << "{\n"; + for (size_t i = 0; i < hostLatency.size(); ++i) { + size_t size = 0u; + auto& result = resp.responses()[i]; + if (result.vertices_ref().has_value()) { + size = (*result.vertices_ref()).size(); + } + auto& info = hostLatency[i]; + ss << "{" << folly::sformat("{} exec/total/vertices: ", std::get<0>(info).toString()) + << folly::sformat("{}(us)/{}(us)/{},", std::get<1>(info), std::get<2>(info), size) << "\n" + << folly::sformat("total_rpc_time: {}(us)", getNbrTimeInUSec) << "\n"; + auto detail = getStorageDetail(result.result.latency_detail_us_ref()); + if (!detail.empty()) { + ss << folly::sformat("storage_detail: {}", detail); + } + ss << "\n}"; + } + ss << "\n}"; + otherStats_.emplace(folly::sformat("step {}", currentStep_), ss.str()); +} + +void TraverseExecutor::handleResponse(RpcResponse& resps) { + SCOPED_TIMER(&execTime_); + auto result = handleCompleteness(resps, FLAGS_accept_partial_success); + if (!result.ok()) { + promise_.setValue(std::move(result).status()); + } + + auto& responses = resps.responses(); + List list; + for (auto& resp : responses) { + auto dataset = resp.get_vertices(); + if (dataset == nullptr) { + LOG(INFO) << "Empty dataset in response"; + continue; + } + list.values.emplace_back(std::move(*dataset)); + } + auto listVal = std::make_shared(std::move(list)); + auto iter = std::make_unique(listVal); + + auto status = buildInterimPath(iter.get()); + if (!status.ok()) { + promise_.setValue(status); + return; + } + if (!isFinalStep()) { + if (reqDs_.rows.empty()) { + if (range_ != nullptr) { + promise_.setValue(buildResult()); + } else { + promise_.setValue(Status::OK()); + } + } else { + getNeighbors(); + } + } else { + promise_.setValue(buildResult()); + } +} + +Status TraverseExecutor::buildInterimPath(GetNeighborsIter* iter) { + const auto& spaceInfo = qctx()->rctx()->session()->space(); + DataSet reqDs; + reqDs.colNames = reqDs_.colNames; + size_t count = 0; + + const std::unordered_map& prev = paths_.back(); + if (currentStep_ == 1 && zeroStep()) { + paths_.emplace_back(); + NG_RETURN_IF_ERROR(handleZeroStep(prev, iter->getVertices(), paths_.back(), count)); + // If 0..0 case, release memory and return immediately. + if (range_ != nullptr && range_->max() == 0) { + releasePrevPaths(count); + return Status::OK(); + } + } + paths_.emplace_back(); + std::unordered_map& current = paths_.back(); + + auto* vFilter = traverse_->vFilter(); + auto* eFilter = traverse_->eFilter(); + QueryExpressionContext ctx(ectx_); + std::unordered_set uniqueDst; + + for (; iter->valid(); iter->next()) { + auto& dst = iter->getEdgeProp("*", kDst); + if (!SchemaUtil::isValidVid(dst, *(spaceInfo.spaceDesc.vid_type_ref()))) { + continue; + } + if (vFilter != nullptr && currentStep_ == 1) { + auto& vFilterVal = vFilter->eval(ctx(iter)); + if (!vFilterVal.isBool() || !vFilterVal.getBool()) { + continue; + } + } + if (eFilter != nullptr) { + auto& eFilterVal = eFilter->eval(ctx(iter)); + if (!eFilterVal.isBool() || !eFilterVal.getBool()) { + continue; + } + } + auto srcV = iter->getVertex(); + auto e = iter->getEdge(); + // Join on dst = src + auto pathToSrcFound = prev.find(srcV.getVertex().vid); + if (pathToSrcFound == prev.end()) { + return Status::Error("Can't find prev paths."); + } + const auto& paths = pathToSrcFound->second; + for (auto& prevPath : paths) { + if (hasSameEdge(prevPath, e.getEdge())) { + continue; + } + if (uniqueDst.emplace(dst).second) { + reqDs.rows.emplace_back(Row({std::move(dst)})); + } + auto path = prevPath; + if (currentStep_ == 1) { + path.values.emplace_back(srcV); + List neighbors; + neighbors.values.emplace_back(e); + path.values.emplace_back(std::move(neighbors)); + buildPath(current, dst, std::move(path)); + ++count; + } else { + auto& eList = path.values.back().mutableList().values; + eList.emplace_back(srcV); + eList.emplace_back(e); + buildPath(current, dst, std::move(path)); + ++count; + } + } // `prevPath' + } // `iter' + + releasePrevPaths(count); + reqDs_ = std::move(reqDs); + return Status::OK(); +} + +void TraverseExecutor::buildPath(std::unordered_map>& currentPaths, + const Value& dst, + Row&& path) { + auto pathToDstFound = currentPaths.find(dst); + if (pathToDstFound == currentPaths.end()) { + Paths interimPaths; + interimPaths.emplace_back(std::move(path)); + currentPaths.emplace(dst, std::move(interimPaths)); + } else { + auto& interimPaths = pathToDstFound->second; + interimPaths.emplace_back(std::move(path)); + } +} + +Status TraverseExecutor::buildResult() { + // This means we are reaching a dead end, return empty. + if (range_ != nullptr && currentStep_ < range_->min()) { + return finish(ResultBuilder().value(Value(DataSet())).build()); + } + + DataSet result; + result.colNames = traverse_->colNames(); + result.rows.reserve(cnt_); + for (auto& currentStepPaths : paths_) { + for (auto& paths : currentStepPaths) { + std::move(paths.second.begin(), paths.second.end(), std::back_inserter(result.rows)); + } + } + + return finish(ResultBuilder().value(Value(std::move(result))).build()); +} + +bool TraverseExecutor::hasSameEdge(const Row& prevPath, const Edge& currentEdge) { + for (const auto& v : prevPath.values) { + if (v.isList()) { + for (const auto& e : v.getList().values) { + if (e.isEdge() && e.getEdge().keyEqual(currentEdge)) { + return true; + } + } + } + } + return false; +} + +void TraverseExecutor::releasePrevPaths(size_t cnt) { + if (range_ != nullptr) { + if (currentStep_ == range_->min() && paths_.size() > 1) { + auto rangeEnd = paths_.begin(); + std::advance(rangeEnd, paths_.size() - 1); + paths_.erase(paths_.begin(), rangeEnd); + } else if (range_->min() == 0 && currentStep_ == 1 && paths_.size() > 1) { + paths_.pop_front(); + } + + if (currentStep_ >= range_->min()) { + cnt_ += cnt; + } + } else { + paths_.pop_front(); + cnt_ = cnt; + } +} + +Status TraverseExecutor::handleZeroStep(const std::unordered_map& prev, + List&& vertices, + std::unordered_map& zeroSteps, + size_t& count) { + std::unordered_set uniqueSrc; + for (auto& srcV : vertices.values) { + auto src = srcV.getVertex().vid; + if (!uniqueSrc.emplace(src).second) { + continue; + } + auto pathToSrcFound = prev.find(src); + if (pathToSrcFound == prev.end()) { + return Status::Error("Can't find prev paths."); + } + const auto& paths = pathToSrcFound->second; + for (auto path : paths) { + path.values.emplace_back(srcV); + List neighbors; + neighbors.values.emplace_back(srcV); + path.values.emplace_back(std::move(neighbors)); + buildPath(zeroSteps, src, std::move(path)); + ++count; + } + } + return Status::OK(); +} +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/query/TraverseExecutor.h b/src/graph/executor/query/TraverseExecutor.h new file mode 100644 index 00000000000..4f2802cb68d --- /dev/null +++ b/src/graph/executor/query/TraverseExecutor.h @@ -0,0 +1,85 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ + +#ifndef EXECUTOR_QUERY_TRAVERSEEXECUTOR_H_ +#define EXECUTOR_QUERY_TRAVERSEEXECUTOR_H_ + +#include + +#include "clients/storage/GraphStorageClient.h" +#include "common/base/StatusOr.h" +#include "common/datatypes/Value.h" +#include "common/datatypes/Vertex.h" +#include "graph/executor/StorageAccessExecutor.h" +#include "graph/planner/plan/Query.h" +#include "interface/gen-cpp2/storage_types.h" + +namespace nebula { +namespace graph { + +using RpcResponse = storage::StorageRpcResponse; + +class TraverseExecutor final : public StorageAccessExecutor { + public: + TraverseExecutor(const PlanNode* node, QueryContext* qctx) + : StorageAccessExecutor("Traverse", node, qctx) { + traverse_ = asNode(node); + } + + folly::Future execute() override; + + Status close() override; + + private: + using Dst = Value; + using Paths = std::vector; + Status buildRequestDataSet(); + + folly::Future traverse(); + + void addStats(RpcResponse& resps, int64_t getNbrTimeInUSec); + + void getNeighbors(); + + void handleResponse(RpcResponse& resps); + + Status buildInterimPath(GetNeighborsIter* iter); + + Status buildResult(); + + bool isFinalStep() const { + return (range_ == nullptr && currentStep_ == 1) || + (range_ != nullptr && (currentStep_ == range_->max() || range_->max() == 0)); + } + + bool zeroStep() const { return range_ != nullptr && range_->min() == 0; } + + bool hasSameEdge(const Row& prevPath, const Edge& currentEdge); + + void releasePrevPaths(size_t cnt); + + void buildPath(std::unordered_map>& currentPaths, + const Value& dst, + Row&& path); + + Status handleZeroStep(const std::unordered_map& prev, + List&& vertices, + std::unordered_map& zeroSteps, + size_t& count); + + private: + DataSet reqDs_; + const Traverse* traverse_{nullptr}; + folly::Promise promise_; + MatchStepRange* range_{nullptr}; + size_t currentStep_{0}; + std::list> paths_; + size_t cnt_{0}; +}; + +} // namespace graph +} // namespace nebula + +#endif // EXECUTOR_QUERY_TRAVERSEEXECUTOR_H_ diff --git a/src/graph/planner/CMakeLists.txt b/src/graph/planner/CMakeLists.txt index b79f97edefd..f5e0eab5d70 100644 --- a/src/graph/planner/CMakeLists.txt +++ b/src/graph/planner/CMakeLists.txt @@ -25,7 +25,6 @@ nebula_add_library( match/StartVidFinder.cpp match/PropIndexSeek.cpp match/VertexIdSeek.cpp - match/Expand.cpp match/LabelIndexSeek.cpp plan/PlanNode.cpp plan/ExecutionPlan.cpp diff --git a/src/graph/planner/match/Expand.cpp b/src/graph/planner/match/Expand.cpp deleted file mode 100644 index 6a546c8f145..00000000000 --- a/src/graph/planner/match/Expand.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#include "graph/planner/match/Expand.h" - -#include "graph/planner/match/MatchSolver.h" -#include "graph/planner/match/SegmentsConnector.h" -#include "graph/planner/plan/Logic.h" -#include "graph/planner/plan/Query.h" -#include "graph/util/AnonColGenerator.h" -#include "graph/util/ExpressionUtils.h" -#include "graph/visitor/RewriteVisitor.h" - -using nebula::storage::cpp2::EdgeProp; -using nebula::storage::cpp2::VertexProp; -using PNKind = nebula::graph::PlanNode::Kind; - -namespace nebula { -namespace graph { - -static std::unique_ptr> genVertexProps() { - return std::make_unique>(); -} - -std::unique_ptr> Expand::genEdgeProps(const EdgeInfo& edge) { - auto edgeProps = std::make_unique>(); - for (auto edgeType : edge.edgeTypes) { - auto edgeSchema = matchCtx_->qctx->schemaMng()->getEdgeSchema(matchCtx_->space.id, edgeType); - - switch (edge.direction) { - case Direction::OUT_EDGE: { - if (reversely_) { - edgeType = -edgeType; - } - break; - } - case Direction::IN_EDGE: { - if (!reversely_) { - edgeType = -edgeType; - } - break; - } - case Direction::BOTH: { - EdgeProp edgeProp; - edgeProp.set_type(-edgeType); - std::vector props{kSrc, kType, kRank, kDst}; - for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { - props.emplace_back(edgeSchema->getFieldName(i)); - } - edgeProp.set_props(std::move(props)); - edgeProps->emplace_back(std::move(edgeProp)); - break; - } - } - EdgeProp edgeProp; - edgeProp.set_type(edgeType); - std::vector props{kSrc, kType, kRank, kDst}; - for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { - props.emplace_back(edgeSchema->getFieldName(i)); - } - edgeProp.set_props(std::move(props)); - edgeProps->emplace_back(std::move(edgeProp)); - } - return edgeProps; -} - -static Expression* mergePathColumnsExpr(ObjectPool* pool, - const std::string& lcol, - const std::string& rcol) { - auto expr = PathBuildExpression::make(pool); - expr->add(InputPropertyExpression::make(pool, lcol)); - expr->add(InputPropertyExpression::make(pool, rcol)); - return expr; -} - -static Expression* buildPathExpr(ObjectPool* pool) { - auto expr = PathBuildExpression::make(pool); - expr->add(VertexExpression::make(pool)); - expr->add(EdgeExpression::make(pool)); - return expr; -} - -Status Expand::doExpand(const NodeInfo& node, const EdgeInfo& edge, SubPlan* plan) { - NG_RETURN_IF_ERROR(expandSteps(node, edge, plan)); - NG_RETURN_IF_ERROR(filterDatasetByPathLength(edge, plan->root, plan)); - return Status::OK(); -} - -// Build subplan: Project->Dedup->GetNeighbors->[Filter]->Project2-> -// DataJoin->Project3->[Filter]->Passthrough->Loop->UnionAllVer -Status Expand::expandSteps(const NodeInfo& node, const EdgeInfo& edge, SubPlan* plan) { - SubPlan subplan; - int64_t startIndex = 0; - auto minHop = edge.range ? edge.range->min() : 1; - auto maxHop = edge.range ? edge.range->max() : 1; - - // Build first step - // In the case of 0 step, src node is the dst node, return the vertex directly - if (minHop == 0) { - subplan = *plan; - startIndex = 0; - // Get vertex - NG_RETURN_IF_ERROR(MatchSolver::appendFetchVertexPlan( - node.filter, matchCtx_->space, matchCtx_->qctx, &initialExpr_, inputVar_, subplan)); - } else { // Case 1 to n steps - startIndex = 1; - // Expand first step from src - NG_RETURN_IF_ERROR(expandStep(edge, dependency_, inputVar_, node.filter, &subplan)); - } - // No need to further expand if maxHop is the start Index - if (maxHop == startIndex) { - plan->root = subplan.root; - return Status::OK(); - } - // Result of first step expansion - PlanNode* firstStep = subplan.root; - - // Build Start node from first step - SubPlan loopBodyPlan; - PlanNode* startNode = StartNode::make(matchCtx_->qctx); - startNode->setOutputVar(firstStep->outputVar()); - startNode->setColNames(firstStep->colNames()); - loopBodyPlan.tail = startNode; - loopBodyPlan.root = startNode; - - // Construct loop body - NG_RETURN_IF_ERROR(expandStep(edge, - startNode, // dep - startNode->outputVar(), // inputVar - nullptr, - &loopBodyPlan)); - - NG_RETURN_IF_ERROR(collectData(startNode, // left join node - loopBodyPlan.root, // right join node - &firstStep, // passThrough - &subplan)); - // Union node - auto body = subplan.root; - - // Loop condition - auto condition = buildExpandCondition(body->outputVar(), startIndex, maxHop); - - // Create loop - auto* loop = Loop::make(matchCtx_->qctx, firstStep, body, condition); - - // Unionize the results of each expansion which are stored in the firstStep - // node - auto uResNode = UnionAllVersionVar::make(matchCtx_->qctx, loop); - uResNode->setInputVar(firstStep->outputVar()); - uResNode->setColNames({kPathStr}); - - subplan.root = uResNode; - plan->root = subplan.root; - return Status::OK(); -} - -// Build subplan: Project->Dedup->GetNeighbors->[Filter]->Project -Status Expand::expandStep(const EdgeInfo& edge, - PlanNode* dep, - const std::string& inputVar, - const Expression* nodeFilter, - SubPlan* plan) { - auto qctx = matchCtx_->qctx; - auto* pool = qctx->objPool(); - // Extract dst vid from input project node which output dataset format is: - // [v1,e1,...,vn,en] - SubPlan curr; - curr.root = dep; - MatchSolver::extractAndDedupVidColumn(qctx, &initialExpr_, dep, inputVar, curr); - // [GetNeighbors] - auto gn = GetNeighbors::make(qctx, curr.root, matchCtx_->space.id); - auto srcExpr = InputPropertyExpression::make(pool, kVid); - gn->setSrc(srcExpr); - gn->setVertexProps(genVertexProps()); - gn->setEdgeProps(genEdgeProps(edge)); - gn->setEdgeDirection(edge.direction); - - PlanNode* root = gn; - if (nodeFilter != nullptr) { - auto* newFilter = MatchSolver::rewriteLabel2Vertex(qctx, nodeFilter); - auto filterNode = Filter::make(matchCtx_->qctx, root, newFilter); - filterNode->setColNames(root->colNames()); - root = filterNode; - } - - if (edge.filter != nullptr) { - auto* newFilter = MatchSolver::rewriteLabel2Edge(qctx, edge.filter); - auto filterNode = Filter::make(qctx, root, newFilter); - filterNode->setColNames(root->colNames()); - root = filterNode; - } - - auto listColumns = saveObject(new YieldColumns); - listColumns->addColumn(new YieldColumn(buildPathExpr(pool), kPathStr)); - // [Project] - root = Project::make(qctx, root, listColumns); - root->setColNames({kPathStr}); - - plan->root = root; - plan->tail = curr.tail; - return Status::OK(); -} - -// Build subplan: DataJoin->Project->Filter -Status Expand::collectData(const PlanNode* joinLeft, - const PlanNode* joinRight, - PlanNode** passThrough, - SubPlan* plan) { - auto qctx = matchCtx_->qctx; - // [dataJoin] read start node (joinLeft) - auto join = SegmentsConnector::innerJoinSegments(qctx, joinLeft, joinRight); - auto lpath = folly::stringPrintf("%s_%d", kPathStr, 0); - auto rpath = folly::stringPrintf("%s_%d", kPathStr, 1); - join->setColNames({lpath, rpath}); - plan->tail = join; - - auto columns = saveObject(new YieldColumns); - auto listExpr = mergePathColumnsExpr(qctx->objPool(), lpath, rpath); - columns->addColumn(new YieldColumn(listExpr)); - // [Project] - auto project = Project::make(qctx, join, columns); - project->setColNames({kPathStr}); - // [Filter] - auto filter = MatchSolver::filtPathHasSameEdge(project, kPathStr, qctx); - // Update start node - filter->setOutputVar((*passThrough)->outputVar()); - plan->root = filter; - return Status::OK(); -} - -Status Expand::filterDatasetByPathLength(const EdgeInfo& edge, PlanNode* input, SubPlan* plan) { - auto qctx = matchCtx_->qctx; - auto* pool = qctx->objPool(); - - // Filter rows whose edges number less than min hop - auto args = ArgumentList::make(pool); - // Expr: length(relationships(p)) >= minHop - auto pathExpr = InputPropertyExpression::make(pool, kPathStr); - args->addArgument(pathExpr); - auto edgeExpr = FunctionCallExpression::make(pool, "length", args); - auto minHop = edge.range == nullptr ? 1 : edge.range->min(); - auto minHopExpr = ConstantExpression::make(pool, minHop); - auto expr = RelationalExpression::makeGE(pool, edgeExpr, minHopExpr); - - auto filter = Filter::make(qctx, input, expr); - filter->setColNames(input->colNames()); - plan->root = filter; - return Status::OK(); -} - -// loopSteps{startIndex} <= maxHop && ($lastStepResult == empty || -// size($lastStepResult) != 0) -Expression* Expand::buildExpandCondition(const std::string& lastStepResult, - int64_t startIndex, - int64_t maxHop) const { - VLOG(1) << "match expand maxHop: " << maxHop; - auto pool = matchCtx_->qctx->objPool(); - auto loopSteps = matchCtx_->qctx->vctx()->anonVarGen()->getVar(); - matchCtx_->qctx->ectx()->setValue(loopSteps, startIndex); - // ++loopSteps{startIndex} << maxHop - auto stepCondition = ExpressionUtils::stepCondition(pool, loopSteps, maxHop); - // lastStepResult == empty || size(lastStepReult) != 0 - auto* eqEmpty = RelationalExpression::makeEQ(pool, - VariableExpression::make(pool, lastStepResult), - ConstantExpression::make(pool, Value())); - auto neZero = ExpressionUtils::neZeroCondition(pool, lastStepResult); - auto* existValCondition = LogicalExpression::makeOr(pool, eqEmpty, neZero); - return LogicalExpression::makeAnd(pool, stepCondition, existValCondition); -} - -} // namespace graph -} // namespace nebula diff --git a/src/graph/planner/match/Expand.h b/src/graph/planner/match/Expand.h deleted file mode 100644 index 9292173b5d0..00000000000 --- a/src/graph/planner/match/Expand.h +++ /dev/null @@ -1,77 +0,0 @@ -/* Copyright (c) 2020 vesoft inc. All rights reserved. - * - * This source code is licensed under Apache 2.0 License. - */ - -#ifndef GRAPH_PLANNER_MATCH_EXPAND_H_ -#define GRAPH_PLANNER_MATCH_EXPAND_H_ - -#include "common/base/Base.h" -#include "graph/context/ast/CypherAstContext.h" -#include "graph/planner/Planner.h" -#include "graph/planner/plan/PlanNode.h" -#include "graph/util/ExpressionUtils.h" - -namespace nebula { -namespace graph { -/* - * The Expand was designed to handle the pattern expanding. - */ -class Expand final { - public: - Expand(MatchClauseContext* matchCtx, Expression* initialExpr) - : matchCtx_(matchCtx), initialExpr_(initialExpr) {} - - Expand* reversely() { - reversely_ = true; - return this; - } - - Expand* depends(PlanNode* dep) { - dependency_ = dep; - return this; - } - - Expand* inputVar(const std::string& inputVar) { - inputVar_ = inputVar; - return this; - } - - Status doExpand(const NodeInfo& node, const EdgeInfo& edge, SubPlan* plan); - - private: - Status expandSteps(const NodeInfo& node, const EdgeInfo& edge, SubPlan* plan); - - Status expandStep(const EdgeInfo& edge, - PlanNode* dep, - const std::string& inputVar, - const Expression* nodeFilter, - SubPlan* plan); - - Status collectData(const PlanNode* joinLeft, - const PlanNode* joinRight, - PlanNode** passThrough, - SubPlan* plan); - - Status filterDatasetByPathLength(const EdgeInfo& edge, PlanNode* input, SubPlan* plan); - - Expression* buildExpandCondition(const std::string& lastStepResult, - int64_t startIndex, - int64_t maxHop) const; - - template - T* saveObject(T* obj) const { - return matchCtx_->qctx->objPool()->add(obj); - } - - std::unique_ptr> genEdgeProps(const EdgeInfo& edge); - - MatchClauseContext* matchCtx_; - Expression* initialExpr_{nullptr}; - bool reversely_{false}; - PlanNode* dependency_{nullptr}; - std::string inputVar_; -}; -} // namespace graph -} // namespace nebula -#endif // GRAPH_PLANNER_MATCH_EXPAND_H_ diff --git a/src/graph/planner/match/LabelIndexSeek.cpp b/src/graph/planner/match/LabelIndexSeek.cpp index 07187484509..ae9e6473be6 100644 --- a/src/graph/planner/match/LabelIndexSeek.cpp +++ b/src/graph/planner/match/LabelIndexSeek.cpp @@ -170,21 +170,29 @@ StatusOr LabelIndexSeek::transformEdge(EdgeContext* edgeCtx) { auto* pool = qctx->objPool(); if (edgeCtx->scanInfo.direction == MatchEdge::Direction::BOTH) { - // merge the src,dst to one column - auto* yieldColumns = pool->makeAndAdd(); - auto* exprList = ExpressionList::make(pool); - exprList->add(ColumnExpression::make(pool, 0)); // src - exprList->add(ColumnExpression::make(pool, 1)); // dst - yieldColumns->addColumn(new YieldColumn(ListExpression::make(pool, exprList))); - auto* project = Project::make(qctx, scan, yieldColumns); - project->setColNames({kVid}); - - auto* unwindExpr = ColumnExpression::make(pool, 0); - auto* unwind = Unwind::make(matchClauseCtx->qctx, project, unwindExpr, kVid); - unwind->setColNames({"vidList", kVid}); - plan.root = unwind; + PlanNode* left = nullptr; + { + auto* yieldColumns = pool->makeAndAdd(); + yieldColumns->addColumn(new YieldColumn(InputPropertyExpression::make(pool, kSrc))); + left = Project::make(qctx, scan, yieldColumns); + left->setColNames({kVid}); + } + PlanNode* right = nullptr; + { + auto* yieldColumns = pool->makeAndAdd(); + yieldColumns->addColumn(new YieldColumn(InputPropertyExpression::make(pool, kDst))); + right = Project::make(qctx, scan, yieldColumns); + right->setColNames({kVid}); + } + + plan.root = Union::make(qctx, left, right); + plan.root->setColNames({kVid}); } + auto* dedup = Dedup::make(qctx, plan.root); + dedup->setColNames(plan.root->colNames()); + plan.root = dedup; + // initialize start expression in project node edgeCtx->initialExpr = VariablePropertyExpression::make(pool, "", kVid); return plan; diff --git a/src/graph/planner/match/MatchClausePlanner.cpp b/src/graph/planner/match/MatchClausePlanner.cpp index 4a191ab466f..7ff42c9914b 100644 --- a/src/graph/planner/match/MatchClausePlanner.cpp +++ b/src/graph/planner/match/MatchClausePlanner.cpp @@ -6,19 +6,99 @@ #include "graph/planner/match/MatchClausePlanner.h" #include "graph/context/ast/CypherAstContext.h" -#include "graph/planner/match/Expand.h" #include "graph/planner/match/MatchSolver.h" #include "graph/planner/match/SegmentsConnector.h" #include "graph/planner/match/StartVidFinder.h" #include "graph/planner/match/WhereClausePlanner.h" #include "graph/planner/plan/Query.h" #include "graph/util/ExpressionUtils.h" +#include "graph/util/SchemaUtil.h" #include "graph/visitor/RewriteVisitor.h" using JoinStrategyPos = nebula::graph::InnerJoinStrategy::JoinPos; namespace nebula { namespace graph { +static std::vector genTraverseColNames(const std::vector& inputCols, + const NodeInfo& node, + const EdgeInfo& edge) { + auto cols = inputCols; + cols.emplace_back(node.alias); + cols.emplace_back(edge.alias); + return cols; +} + +static std::vector genAppendVColNames(const std::vector& inputCols, + const NodeInfo& node) { + auto cols = inputCols; + cols.emplace_back(node.alias); + return cols; +} + +static Expression* genNextTraverseStart(ObjectPool* pool, const EdgeInfo& edge) { + auto args = ArgumentList::make(pool); + args->addArgument(InputPropertyExpression::make(pool, edge.alias)); + return FunctionCallExpression::make(pool, "none_direct_dst", args); +} + +static Expression* genVertexFilter(const NodeInfo& node) { return node.filter; } + +static Expression* genEdgeFilter(const EdgeInfo& edge) { return edge.filter; } + +static std::unique_ptr> genVertexProps(const NodeInfo& node, + QueryContext* qctx, + GraphSpaceID spaceId) { + // TODO + UNUSED(node); + UNUSED(qctx); + UNUSED(spaceId); + return std::make_unique>(); +} + +static std::unique_ptr> genEdgeProps(const EdgeInfo& edge, + bool reversely, + QueryContext* qctx, + GraphSpaceID spaceId) { + auto edgeProps = std::make_unique>(); + for (auto edgeType : edge.edgeTypes) { + auto edgeSchema = qctx->schemaMng()->getEdgeSchema(spaceId, edgeType); + + switch (edge.direction) { + case Direction::OUT_EDGE: { + if (reversely) { + edgeType = -edgeType; + } + break; + } + case Direction::IN_EDGE: { + if (!reversely) { + edgeType = -edgeType; + } + break; + } + case Direction::BOTH: { + EdgeProp edgeProp; + edgeProp.set_type(-edgeType); + std::vector props{kSrc, kType, kRank, kDst}; + for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { + props.emplace_back(edgeSchema->getFieldName(i)); + } + edgeProp.set_props(std::move(props)); + edgeProps->emplace_back(std::move(edgeProp)); + break; + } + } + EdgeProp edgeProp; + edgeProp.set_type(edgeType); + std::vector props{kSrc, kType, kRank, kDst}; + for (std::size_t i = 0; i < edgeSchema->getNumFields(); ++i) { + props.emplace_back(edgeSchema->getFieldName(i)); + } + edgeProp.set_props(std::move(props)); + edgeProps->emplace_back(std::move(edgeProp)); + } + return edgeProps; +} StatusOr MatchClausePlanner::transform(CypherClauseContextBase* clauseCtx) { if (clauseCtx->kind != CypherClauseKind::kMatch) { @@ -35,7 +115,7 @@ StatusOr MatchClausePlanner::transform(CypherClauseContextBase* clauseC NG_RETURN_IF_ERROR(findStarts(matchClauseCtx, startFromEdge, startIndex, matchClausePlan)); NG_RETURN_IF_ERROR( expand(nodeInfos, edgeInfos, matchClauseCtx, startFromEdge, startIndex, matchClausePlan)); - NG_RETURN_IF_ERROR(projectColumnsBySymbols(matchClauseCtx, startIndex, matchClausePlan)); + NG_RETURN_IF_ERROR(projectColumnsBySymbols(matchClauseCtx, matchClausePlan)); NG_RETURN_IF_ERROR(appendFilterPlan(matchClauseCtx, matchClausePlan)); return matchClausePlan; } @@ -130,14 +210,9 @@ Status MatchClausePlanner::expandFromNode(const std::vector& nodeInfos // Pattern: ()-[]-...-(start)-...-[]-() NG_RETURN_IF_ERROR( rightExpandFromNode(nodeInfos, edgeInfos, matchClauseCtx, startIndex, subplan)); - auto left = subplan.root; - NG_RETURN_IF_ERROR( - leftExpandFromNode(nodeInfos, edgeInfos, matchClauseCtx, startIndex, var, subplan)); + NG_RETURN_IF_ERROR(leftExpandFromNode( + nodeInfos, edgeInfos, matchClauseCtx, startIndex, subplan.root->outputVar(), subplan)); - // Connect the left expand and right expand part. - auto right = subplan.root; - subplan.root = SegmentsConnector::innerJoinSegments( - matchClauseCtx->qctx, left, right, JoinStrategyPos::kStart, JoinStrategyPos::kStart); return Status::OK(); } @@ -147,48 +222,47 @@ Status MatchClausePlanner::leftExpandFromNode(const std::vector& nodeI size_t startIndex, std::string inputVar, SubPlan& subplan) { - std::vector joinColNames = { - folly::stringPrintf("%s_%lu", kPathStr, nodeInfos.size())}; + Expression* nextTraverseStart = nullptr; + auto qctx = matchClauseCtx->qctx; + if (startIndex == nodeInfos.size() - 1) { + nextTraverseStart = initialExpr_; + } else { + auto* pool = qctx->objPool(); + auto args = ArgumentList::make(pool); + args->addArgument(InputPropertyExpression::make(pool, nodeInfos[startIndex].alias)); + nextTraverseStart = FunctionCallExpression::make(pool, "id", args); + } + auto spaceId = matchClauseCtx->space.id; + bool reversely = true; for (size_t i = startIndex; i > 0; --i) { - auto left = subplan.root; - auto status = - std::make_unique(matchClauseCtx, i == startIndex ? initialExpr_->clone() : nullptr) - ->depends(subplan.root) - ->inputVar(inputVar) - ->reversely() - ->doExpand(nodeInfos[i], edgeInfos[i - 1], &subplan); - if (!status.ok()) { - return status; - } - if (i < startIndex) { - auto right = subplan.root; - VLOG(1) << "left: " << folly::join(",", left->colNames()) - << " right: " << folly::join(",", right->colNames()); - subplan.root = SegmentsConnector::innerJoinSegments(matchClauseCtx->qctx, left, right); - joinColNames.emplace_back(folly::stringPrintf("%s_%lu", kPathStr, nodeInfos.size() + i)); - subplan.root->setColNames(joinColNames); - } - inputVar = subplan.root->outputVar(); + auto& node = nodeInfos[i]; + auto& edge = edgeInfos[i - 1]; + auto traverse = Traverse::make(qctx, subplan.root, spaceId); + traverse->setSrc(nextTraverseStart); + traverse->setVertexProps(genVertexProps(node, qctx, spaceId)); + traverse->setEdgeProps(genEdgeProps(edge, reversely, qctx, spaceId)); + traverse->setVertexFilter(genVertexFilter(node)); + traverse->setEdgeFilter(genEdgeFilter(edge)); + traverse->setEdgeDirection(edge.direction); + traverse->setColNames(genTraverseColNames(subplan.root->colNames(), node, edge)); + traverse->setStepRange(edge.range); + traverse->setDedup(); + subplan.root = traverse; + nextTraverseStart = genNextTraverseStart(qctx->objPool(), edge); + inputVar = traverse->outputVar(); } VLOG(1) << subplan; - auto left = subplan.root; - auto* initialExprCopy = initialExpr_->clone(); - NG_RETURN_IF_ERROR( - MatchSolver::appendFetchVertexPlan(nodeInfos.front().filter, - matchClauseCtx->space, - matchClauseCtx->qctx, - edgeInfos.empty() ? &initialExprCopy : nullptr, - subplan)); - if (!edgeInfos.empty()) { - auto right = subplan.root; - VLOG(1) << "left: " << folly::join(",", left->colNames()) - << " right: " << folly::join(",", right->colNames()); - subplan.root = SegmentsConnector::innerJoinSegments(matchClauseCtx->qctx, left, right); - joinColNames.emplace_back( - folly::stringPrintf("%s_%lu", kPathStr, nodeInfos.size() + startIndex)); - subplan.root->setColNames(joinColNames); - } + auto& node = nodeInfos.front(); + auto appendV = AppendVertices::make(qctx, subplan.root, spaceId); + auto vertexProps = SchemaUtil::getAllVertexProp(qctx, spaceId, true); + NG_RETURN_IF_ERROR(vertexProps); + appendV->setVertexProps(std::move(vertexProps).value()); + appendV->setSrc(nextTraverseStart); + appendV->setVertexFilter(genVertexFilter(node)); + appendV->setColNames(genAppendVColNames(subplan.root->colNames(), node)); + appendV->setDedup(); + subplan.root = appendV; VLOG(1) << subplan; return Status::OK(); @@ -199,44 +273,40 @@ Status MatchClausePlanner::rightExpandFromNode(const std::vector& node MatchClauseContext* matchClauseCtx, size_t startIndex, SubPlan& subplan) { - std::vector joinColNames = {folly::stringPrintf("%s_%lu", kPathStr, startIndex)}; + auto inputVar = subplan.root->outputVar(); + auto qctx = matchClauseCtx->qctx; + auto spaceId = matchClauseCtx->space.id; + Expression* nextTraverseStart = initialExpr_; + bool reversely = false; for (size_t i = startIndex; i < edgeInfos.size(); ++i) { - auto left = subplan.root; - auto status = - std::make_unique(matchClauseCtx, i == startIndex ? initialExpr_->clone() : nullptr) - ->depends(subplan.root) - ->inputVar(subplan.root->outputVar()) - ->doExpand(nodeInfos[i], edgeInfos[i], &subplan); - if (!status.ok()) { - return status; - } - if (i > startIndex) { - auto right = subplan.root; - VLOG(1) << "left: " << folly::join(",", left->colNames()) - << " right: " << folly::join(",", right->colNames()); - subplan.root = SegmentsConnector::innerJoinSegments(matchClauseCtx->qctx, left, right); - joinColNames.emplace_back(folly::stringPrintf("%s_%lu", kPathStr, i)); - subplan.root->setColNames(joinColNames); - } + auto& node = nodeInfos[i]; + auto& edge = edgeInfos[i]; + auto traverse = Traverse::make(qctx, subplan.root, spaceId); + traverse->setSrc(nextTraverseStart); + traverse->setVertexProps(genVertexProps(node, qctx, spaceId)); + traverse->setEdgeProps(genEdgeProps(edge, reversely, qctx, spaceId)); + traverse->setVertexFilter(genVertexFilter(node)); + traverse->setEdgeFilter(genEdgeFilter(edge)); + traverse->setEdgeDirection(edge.direction); + traverse->setColNames(genTraverseColNames(subplan.root->colNames(), node, edge)); + traverse->setStepRange(edge.range); + traverse->setDedup(); + subplan.root = traverse; + nextTraverseStart = genNextTraverseStart(qctx->objPool(), edge); + inputVar = traverse->outputVar(); } VLOG(1) << subplan; - auto left = subplan.root; - auto* initialExprCopy = initialExpr_->clone(); - NG_RETURN_IF_ERROR( - MatchSolver::appendFetchVertexPlan(nodeInfos.back().filter, - matchClauseCtx->space, - matchClauseCtx->qctx, - edgeInfos.empty() ? &initialExprCopy : nullptr, - subplan)); - if (!edgeInfos.empty()) { - auto right = subplan.root; - VLOG(1) << "left: " << folly::join(",", left->colNames()) - << " right: " << folly::join(",", right->colNames()); - subplan.root = SegmentsConnector::innerJoinSegments(matchClauseCtx->qctx, left, right); - joinColNames.emplace_back(folly::stringPrintf("%s_%lu", kPathStr, edgeInfos.size())); - subplan.root->setColNames(joinColNames); - } + auto& node = nodeInfos.back(); + auto appendV = AppendVertices::make(qctx, subplan.root, spaceId); + auto vertexProps = SchemaUtil::getAllVertexProp(qctx, spaceId, true); + NG_RETURN_IF_ERROR(vertexProps); + appendV->setVertexProps(std::move(vertexProps).value()); + appendV->setSrc(nextTraverseStart); + appendV->setVertexFilter(genVertexFilter(node)); + appendV->setColNames(genAppendVColNames(subplan.root->colNames(), node)); + appendV->setDedup(); + subplan.root = appendV; VLOG(1) << subplan; return Status::OK(); @@ -251,146 +321,80 @@ Status MatchClausePlanner::expandFromEdge(const std::vector& nodeInfos } Status MatchClausePlanner::projectColumnsBySymbols(MatchClauseContext* matchClauseCtx, - size_t startIndex, SubPlan& plan) { auto qctx = matchClauseCtx->qctx; auto& nodeInfos = matchClauseCtx->nodeInfos; auto& edgeInfos = matchClauseCtx->edgeInfos; - auto input = plan.root; - const auto& inColNames = input->colNames(); auto columns = qctx->objPool()->add(new YieldColumns); std::vector colNames; - auto addNode = [&, this](size_t i) { - auto& nodeInfo = nodeInfos[i]; + auto addNode = [this, columns, &colNames, matchClauseCtx](auto& nodeInfo) { if (!nodeInfo.alias.empty() && !nodeInfo.anonymous) { - if (i >= startIndex) { - columns->addColumn( - buildVertexColumn(matchClauseCtx, inColNames[i - startIndex], nodeInfo.alias)); - } else if (startIndex == (nodeInfos.size() - 1)) { - columns->addColumn( - buildVertexColumn(matchClauseCtx, inColNames[startIndex - i], nodeInfo.alias)); - } else { - columns->addColumn( - buildVertexColumn(matchClauseCtx, inColNames[nodeInfos.size() - i], nodeInfo.alias)); - } + columns->addColumn(buildVertexColumn(matchClauseCtx, nodeInfo.alias)); colNames.emplace_back(nodeInfo.alias); } }; - for (size_t i = 0; i < edgeInfos.size(); i++) { - VLOG(1) << "colSize: " << inColNames.size() << "i: " << i << " nodesize: " << nodeInfos.size() - << " start: " << startIndex; - addNode(i); - auto& edgeInfo = edgeInfos[i]; + auto addEdge = [this, columns, &colNames, matchClauseCtx](auto& edgeInfo) { if (!edgeInfo.alias.empty() && !edgeInfo.anonymous) { - if (i >= startIndex) { - columns->addColumn(buildEdgeColumn(matchClauseCtx, inColNames[i - startIndex], edgeInfo)); - } else if (startIndex == (nodeInfos.size() - 1)) { - columns->addColumn( - buildEdgeColumn(matchClauseCtx, inColNames[edgeInfos.size() - 1 - i], edgeInfo)); - } else { - columns->addColumn( - buildEdgeColumn(matchClauseCtx, inColNames[edgeInfos.size() - i], edgeInfo)); - } + columns->addColumn(buildEdgeColumn(matchClauseCtx, edgeInfo)); colNames.emplace_back(edgeInfo.alias); } + }; + + for (size_t i = 0; i < edgeInfos.size(); i++) { + addNode(nodeInfos[i]); + addEdge(edgeInfos[i]); } // last vertex DCHECK(!nodeInfos.empty()); - addNode(nodeInfos.size() - 1); + addNode(nodeInfos.back()); const auto& aliases = matchClauseCtx->aliasesGenerated; auto iter = std::find_if(aliases.begin(), aliases.end(), [](const auto& alias) { return alias.second == AliasType::kPath; }); - std::string alias = iter != aliases.end() ? iter->first : qctx->vctx()->anonColGen()->getCol(); - columns->addColumn( - buildPathColumn(matchClauseCtx, alias, startIndex, inColNames, nodeInfos.size())); - colNames.emplace_back(alias); + if (iter != aliases.end()) { + auto& alias = iter->first; + columns->addColumn(buildPathColumn(matchClauseCtx, alias)); + colNames.emplace_back(alias); + } - auto project = Project::make(qctx, input, columns); + auto project = Project::make(qctx, plan.root, columns); project->setColNames(std::move(colNames)); - plan.root = MatchSolver::filtPathHasSameEdge(project, alias, qctx); + plan.root = project; VLOG(1) << plan; return Status::OK(); } YieldColumn* MatchClausePlanner::buildVertexColumn(MatchClauseContext* matchClauseCtx, - const std::string& colName, const std::string& alias) const { - auto* pool = matchClauseCtx->qctx->objPool(); - auto colExpr = InputPropertyExpression::make(pool, colName); - // startNode(path) => head node of path - auto args = ArgumentList::make(pool); - args->addArgument(colExpr); - auto firstVertexExpr = FunctionCallExpression::make(pool, "startNode", args); - return new YieldColumn(firstVertexExpr, alias); + return new YieldColumn(InputPropertyExpression::make(matchClauseCtx->qctx->objPool(), alias), + alias); } YieldColumn* MatchClausePlanner::buildEdgeColumn(MatchClauseContext* matchClauseCtx, - const std::string& colName, EdgeInfo& edge) const { auto* pool = matchClauseCtx->qctx->objPool(); - auto colExpr = InputPropertyExpression::make(pool, colName); - // relationships(p) - auto args = ArgumentList::make(pool); - args->addArgument(colExpr); - auto relExpr = FunctionCallExpression::make(pool, "relationships", args); Expression* expr = nullptr; - if (edge.range != nullptr) { - expr = relExpr; + if (edge.range == nullptr) { + expr = SubscriptExpression::make( + pool, InputPropertyExpression::make(pool, edge.alias), ConstantExpression::make(pool, 0)); } else { - // Get first edge in path list [e1, e2, ...] - auto idxExpr = ConstantExpression::make(pool, 0); - auto subExpr = SubscriptExpression::make(pool, relExpr, idxExpr); - expr = subExpr; + auto* args = ArgumentList::make(pool); + args->addArgument(VariableExpression::make(pool, "e")); + auto* filter = FunctionCallExpression::make(pool, "is_edge", args); + expr = ListComprehensionExpression::make( + pool, "e", InputPropertyExpression::make(pool, edge.alias), filter); } return new YieldColumn(expr, edge.alias); } YieldColumn* MatchClausePlanner::buildPathColumn(MatchClauseContext* matchClauseCtx, - const std::string& alias, - size_t startIndex, - const std::vector colNames, - size_t nodeInfoSize) const { - auto colSize = colNames.size(); - DCHECK((nodeInfoSize == colSize) || (nodeInfoSize + 1 == colSize)); - size_t bound = 0; - if (colSize > nodeInfoSize) { - bound = colSize - startIndex - 1; - } else if (startIndex == (nodeInfoSize - 1)) { - bound = 0; - } else { - bound = colSize - startIndex; - } - auto* pool = matchClauseCtx->qctx->objPool(); - auto rightExpandPath = PathBuildExpression::make(pool); - for (size_t i = 0; i < bound; ++i) { - rightExpandPath->add(InputPropertyExpression::make(pool, colNames[i])); - } - - auto leftExpandPath = PathBuildExpression::make(pool); - for (size_t i = bound; i < colNames.size(); ++i) { - leftExpandPath->add(InputPropertyExpression::make(pool, colNames[i])); - } - - auto finalPath = PathBuildExpression::make(pool); - if (leftExpandPath->size() != 0) { - auto args = ArgumentList::make(pool); - args->addArgument(leftExpandPath); - auto reversePath = FunctionCallExpression::make(pool, "reversePath", args); - if (rightExpandPath->size() == 0) { - return new YieldColumn(reversePath, alias); - } - finalPath->add(reversePath); - } - if (rightExpandPath->size() != 0) { - finalPath->add(rightExpandPath); - } - return new YieldColumn(finalPath, alias); + const std::string& alias) const { + return new YieldColumn(matchClauseCtx->pathBuild, alias); } Status MatchClausePlanner::appendFilterPlan(MatchClauseContext* matchClauseCtx, SubPlan& subplan) { diff --git a/src/graph/planner/match/MatchClausePlanner.h b/src/graph/planner/match/MatchClausePlanner.h index 151e774a27a..19b7c860701 100644 --- a/src/graph/planner/match/MatchClausePlanner.h +++ b/src/graph/planner/match/MatchClausePlanner.h @@ -59,23 +59,14 @@ class MatchClausePlanner final : public CypherClausePlanner { size_t startIndex, SubPlan& subplan); - Status projectColumnsBySymbols(MatchClauseContext* matchClauseCtx, - size_t startIndex, - SubPlan& plan); + Status projectColumnsBySymbols(MatchClauseContext* matchClauseCtx, SubPlan& plan); YieldColumn* buildVertexColumn(MatchClauseContext* matchClauseCtx, - const std::string& colName, const std::string& alias) const; - YieldColumn* buildEdgeColumn(MatchClauseContext* matchClauseCtx, - const std::string& colName, - EdgeInfo& edge) const; + YieldColumn* buildEdgeColumn(MatchClauseContext* matchClauseCtx, EdgeInfo& edge) const; - YieldColumn* buildPathColumn(MatchClauseContext* matchClauseCtx, - const std::string& alias, - size_t startIndex, - const std::vector colNames, - size_t nodeInfoSize) const; + YieldColumn* buildPathColumn(MatchClauseContext* matchClauseCtx, const std::string& alias) const; Status appendFilterPlan(MatchClauseContext* matchClauseCtx, SubPlan& subplan); diff --git a/src/graph/planner/match/MatchSolver.cpp b/src/graph/planner/match/MatchSolver.cpp index b66ec18312e..92e9832199d 100644 --- a/src/graph/planner/match/MatchSolver.cpp +++ b/src/graph/planner/match/MatchSolver.cpp @@ -276,7 +276,7 @@ Status MatchSolver::appendFetchVertexPlan(const Expression* nodeFilter, extractAndDedupVidColumn(qctx, initialExpr, plan.root, inputVar, plan); auto srcExpr = InputPropertyExpression::make(pool, kVid); // [Get vertices] - auto props = SchemaUtil::getAllVertexProp(qctx, space, true); + auto props = SchemaUtil::getAllVertexProp(qctx, space.id, true); NG_RETURN_IF_ERROR(props); auto gv = GetVertices::make(qctx, plan.root, space.id, srcExpr, std::move(props).value(), {}); diff --git a/src/graph/planner/match/PropIndexSeek.cpp b/src/graph/planner/match/PropIndexSeek.cpp index e1c2d47b785..257f24f7e46 100644 --- a/src/graph/planner/match/PropIndexSeek.cpp +++ b/src/graph/planner/match/PropIndexSeek.cpp @@ -89,21 +89,29 @@ StatusOr PropIndexSeek::transformEdge(EdgeContext* edgeCtx) { auto* pool = qctx->objPool(); if (edgeCtx->scanInfo.direction == MatchEdge::Direction::BOTH) { - // merge the src,dst to one column - auto* yieldColumns = pool->makeAndAdd(); - auto* exprList = ExpressionList::make(pool); - exprList->add(ColumnExpression::make(pool, 0)); // src - exprList->add(ColumnExpression::make(pool, 1)); // dst - yieldColumns->addColumn(new YieldColumn(ListExpression::make(pool, exprList))); - auto* project = Project::make(qctx, scan, yieldColumns); - project->setColNames({kVid}); - - auto* unwindExpr = ColumnExpression::make(pool, 0); - auto* unwind = Unwind::make(qctx, project, unwindExpr, kVid); - unwind->setColNames({"vidList", kVid}); - plan.root = unwind; + PlanNode* left = nullptr; + { + auto* yieldColumns = pool->makeAndAdd(); + yieldColumns->addColumn(new YieldColumn(InputPropertyExpression::make(pool, kSrc))); + left = Project::make(qctx, scan, yieldColumns); + left->setColNames({kVid}); + } + PlanNode* right = nullptr; + { + auto* yieldColumns = pool->makeAndAdd(); + yieldColumns->addColumn(new YieldColumn(InputPropertyExpression::make(pool, kDst))); + right = Project::make(qctx, scan, yieldColumns); + right->setColNames({kVid}); + } + + plan.root = Union::make(qctx, left, right); + plan.root->setColNames({kVid}); } + auto* dedup = Dedup::make(qctx, plan.root); + dedup->setColNames(plan.root->colNames()); + plan.root = dedup; + // initialize start expression in project edge edgeCtx->initialExpr = VariablePropertyExpression::make(pool, "", kVid); return plan; diff --git a/src/graph/planner/match/VertexIdSeek.cpp b/src/graph/planner/match/VertexIdSeek.cpp index 1b27b877319..9d0df2a085d 100644 --- a/src/graph/planner/match/VertexIdSeek.cpp +++ b/src/graph/planner/match/VertexIdSeek.cpp @@ -7,6 +7,7 @@ #include "graph/planner/match/MatchSolver.h" #include "graph/planner/plan/Logic.h" +#include "graph/planner/plan/Query.h" #include "graph/util/ExpressionUtils.h" #include "graph/util/SchemaUtil.h" #include "graph/visitor/VidExtractVisitor.h" @@ -53,34 +54,16 @@ bool VertexIdSeek::matchNode(NodeContext *nodeCtx) { return false; } -std::pair VertexIdSeek::listToAnnoVarVid(QueryContext *qctx, - const List &list) { +std::string VertexIdSeek::listToAnnoVarVid(QueryContext *qctx, const List &list) { auto input = qctx->vctx()->anonVarGen()->getVar(); DataSet vids({kVid}); - QueryExpressionContext dummy; for (auto &v : list.values) { vids.emplace_back(Row({std::move(v)})); } qctx->ectx()->setResult(input, ResultBuilder().value(Value(std::move(vids))).build()); - auto *pool = qctx->objPool(); - auto *src = VariablePropertyExpression::make(pool, input, kVid); - return std::pair(input, src); -} - -std::pair VertexIdSeek::constToAnnoVarVid(QueryContext *qctx, - const Value &v) { - auto input = qctx->vctx()->anonVarGen()->getVar(); - DataSet vids({kVid}); - QueryExpressionContext dummy; - vids.emplace_back(Row({v})); - - qctx->ectx()->setResult(input, ResultBuilder().value(Value(std::move(vids))).build()); - - auto *pool = qctx->objPool(); - auto *src = VariablePropertyExpression::make(pool, input, kVid); - return std::pair(input, src); + return input; } StatusOr VertexIdSeek::transformNode(NodeContext *nodeCtx) { @@ -88,16 +71,19 @@ StatusOr VertexIdSeek::transformNode(NodeContext *nodeCtx) { auto *matchClauseCtx = nodeCtx->matchClauseCtx; auto *qctx = matchClauseCtx->qctx; - QueryExpressionContext dummy; - std::pair vidsResult = listToAnnoVarVid(qctx, nodeCtx->ids); + std::string inputVar = listToAnnoVarVid(qctx, nodeCtx->ids); auto *passThrough = PassThroughNode::make(qctx, nullptr); - passThrough->setOutputVar(vidsResult.first); + passThrough->setOutputVar(inputVar); passThrough->setColNames({kVid}); - plan.root = passThrough; + + auto *dedup = Dedup::make(qctx, passThrough); + dedup->setColNames({kVid}); + + plan.root = dedup; plan.tail = passThrough; - nodeCtx->initialExpr = vidsResult.second; + nodeCtx->initialExpr = InputPropertyExpression::make(qctx->objPool(), kVid); return plan; } diff --git a/src/graph/planner/match/VertexIdSeek.h b/src/graph/planner/match/VertexIdSeek.h index 891b3b2b8c6..2fd58a172aa 100644 --- a/src/graph/planner/match/VertexIdSeek.h +++ b/src/graph/planner/match/VertexIdSeek.h @@ -31,9 +31,7 @@ class VertexIdSeek final : public StartVidFinder { StatusOr transformEdge(EdgeContext* edgeCtx) override; - std::pair listToAnnoVarVid(QueryContext* qctx, const List& list); - - std::pair constToAnnoVarVid(QueryContext* qctx, const Value& v); + std::string listToAnnoVarVid(QueryContext* qctx, const List& list); private: VertexIdSeek() = default; diff --git a/src/graph/planner/ngql/PathPlanner.cpp b/src/graph/planner/ngql/PathPlanner.cpp index 00b683627e6..f6f7843788c 100644 --- a/src/graph/planner/ngql/PathPlanner.cpp +++ b/src/graph/planner/ngql/PathPlanner.cpp @@ -409,7 +409,7 @@ PlanNode* PathPlanner::buildVertexPlan(PlanNode* dep, const std::string& input) idArgs->addArgument(ColumnExpression::make(pool, 1)); auto* src = FunctionCallExpression::make(pool, "id", idArgs); // get all vertexprop - auto vertexProp = SchemaUtil::getAllVertexProp(qctx, pathCtx_->space, true); + auto vertexProp = SchemaUtil::getAllVertexProp(qctx, pathCtx_->space.id, true); auto* getVertices = GetVertices::make( qctx, unwind, pathCtx_->space.id, src, std::move(vertexProp).value(), {}, true); diff --git a/src/graph/planner/ngql/SubgraphPlanner.cpp b/src/graph/planner/ngql/SubgraphPlanner.cpp index 339724b15bd..ce7a583701c 100644 --- a/src/graph/planner/ngql/SubgraphPlanner.cpp +++ b/src/graph/planner/ngql/SubgraphPlanner.cpp @@ -55,7 +55,7 @@ StatusOr SubgraphPlanner::nSteps(SubPlan& startVidPlan, const std::stri auto* startNode = StartNode::make(qctx); bool getVertexProp = subgraphCtx_->withProp && subgraphCtx_->getVertexProp; - auto vertexProps = SchemaUtil::getAllVertexProp(qctx, space, getVertexProp); + auto vertexProps = SchemaUtil::getAllVertexProp(qctx, space.id, getVertexProp); NG_RETURN_IF_ERROR(vertexProps); auto edgeProps = buildEdgeProps(); NG_RETURN_IF_ERROR(edgeProps); @@ -93,7 +93,7 @@ StatusOr SubgraphPlanner::zeroStep(SubPlan& startVidPlan, const std::st const auto& space = subgraphCtx_->space; auto* pool = qctx->objPool(); // get all vertexProp - auto vertexProp = SchemaUtil::getAllVertexProp(qctx, space, subgraphCtx_->withProp); + auto vertexProp = SchemaUtil::getAllVertexProp(qctx, space.id, subgraphCtx_->withProp); NG_RETURN_IF_ERROR(vertexProp); auto* getVertex = GetVertices::make(qctx, startVidPlan.root, diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index 6b0cfcfc8ad..587041ee401 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -276,7 +276,6 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "Download"; case Kind::kIngest: return "Ingest"; - // no default so the compiler will warning when lack case Kind::kShowSessions: return "ShowSessions"; case Kind::kUpdateSession: @@ -285,6 +284,10 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "ShowQueries"; case Kind::kKillQuery: return "KillQuery"; + case Kind::kTraverse: + return "Traverse"; + case Kind::kAppendVertices: + return "AppendVertices"; // no default so the compiler will warning when lack } LOG(FATAL) << "Impossible kind plan node " << static_cast(kind); diff --git a/src/graph/planner/plan/PlanNode.h b/src/graph/planner/plan/PlanNode.h index d81bf638242..83ce1c9e1be 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -27,6 +27,9 @@ class PlanNode { kGetNeighbors, kGetVertices, kGetEdges, + kTraverse, + kAppendVertices, + // ------------------ // TODO(yee): refactor in logical plan kIndexScan, diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index 07954678eee..988f8b33245 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -601,5 +601,52 @@ void UnionAllVersionVar::cloneMembers(const UnionAllVersionVar& f) { SingleInputNode::cloneMembers(f); } +Traverse* Traverse::clone() const { + auto newGN = Traverse::make(qctx_, nullptr, space_); + newGN->cloneMembers(*this); + return newGN; +} + +void Traverse::cloneMembers(const Traverse& g) { + GetNeighbors::cloneMembers(g); + + setStepRange(g.range_); + setVertexFilter(g.vFilter_->clone()); + setEdgeFilter(g.eFilter_->clone()); +} + +std::unique_ptr Traverse::explain() const { + auto desc = GetNeighbors::explain(); + if (range_ != nullptr) { + addDescription("steps", range_->toString(), desc.get()); + } + if (vFilter_ != nullptr) { + addDescription("vertex filter", vFilter_->toString(), desc.get()); + } + if (eFilter_ != nullptr) { + addDescription("edge filter", eFilter_->toString(), desc.get()); + } + return desc; +} + +AppendVertices* AppendVertices::clone() const { + auto newAV = AppendVertices::make(qctx_, nullptr, space_); + newAV->cloneMembers(*this); + return newAV; +} + +void AppendVertices::cloneMembers(const AppendVertices& a) { + GetVertices::cloneMembers(a); + + setVertexFilter(a.vFilter_->clone()); +} + +std::unique_ptr AppendVertices::explain() const { + auto desc = GetVertices::explain(); + if (vFilter_ != nullptr) { + addDescription("vertex filter", vFilter_->toString(), desc.get()); + } + return desc; +} } // namespace graph } // namespace nebula diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index b62622bb0e4..13629bf2ef5 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -120,10 +120,10 @@ using Direction = nebula::storage::cpp2::EdgeDirection; /** * Get neighbors' property */ -class GetNeighbors final : public Explore { +class GetNeighbors : public Explore { public: static GetNeighbors* make(QueryContext* qctx, PlanNode* input, GraphSpaceID space) { - return qctx->objPool()->add(new GetNeighbors(qctx, input, space)); + return qctx->objPool()->add(new GetNeighbors(qctx, Kind::kGetNeighbors, input, space)); } static GetNeighbors* make(QueryContext* qctx, @@ -198,15 +198,15 @@ class GetNeighbors final : public Explore { PlanNode* clone() const override; std::unique_ptr explain() const override; - private: - GetNeighbors(QueryContext* qctx, PlanNode* input, GraphSpaceID space) - : Explore(qctx, Kind::kGetNeighbors, input, space) { + protected: + GetNeighbors(QueryContext* qctx, Kind kind, PlanNode* input, GraphSpaceID space) + : Explore(qctx, kind, input, space) { setLimit(-1); } - private: void cloneMembers(const GetNeighbors&); + private: Expression* src_{nullptr}; std::vector edgeTypes_; storage::cpp2::EdgeDirection edgeDirection_{Direction::OUT_EDGE}; @@ -220,7 +220,7 @@ class GetNeighbors final : public Explore { /** * Get property with given vertex keys. */ -class GetVertices final : public Explore { +class GetVertices : public Explore { public: static GetVertices* make(QueryContext* qctx, PlanNode* input, @@ -233,6 +233,7 @@ class GetVertices final : public Explore { int64_t limit = std::numeric_limits::max(), Expression* filter = nullptr) { return qctx->objPool()->add(new GetVertices(qctx, + Kind::kGetVertices, input, space, src, @@ -259,8 +260,9 @@ class GetVertices final : public Explore { PlanNode* clone() const override; std::unique_ptr explain() const override; - private: + protected: GetVertices(QueryContext* qctx, + Kind kind, PlanNode* input, GraphSpaceID space, Expression* src, @@ -270,7 +272,7 @@ class GetVertices final : public Explore { std::vector orderBy, int64_t limit, Expression* filter) - : Explore(qctx, Kind::kGetVertices, input, space, dedup, limit, filter, std::move(orderBy)), + : Explore(qctx, kind, input, space, dedup, limit, filter, std::move(orderBy)), src_(src), props_(std::move(props)), exprs_(std::move(exprs)) {} @@ -1146,6 +1148,110 @@ class UnionAllVersionVar final : public SingleInputNode { void cloneMembers(const UnionAllVersionVar&); }; +class Traverse final : public GetNeighbors { + public: + using VertexProps = std::unique_ptr>; + using EdgeProps = std::unique_ptr>; + using StatProps = std::unique_ptr>; + using Exprs = std::unique_ptr>; + + static Traverse* make(QueryContext* qctx, PlanNode* input, GraphSpaceID space) { + return qctx->objPool()->add(new Traverse(qctx, input, space)); + } + + static Traverse* make(QueryContext* qctx, + PlanNode* input, + GraphSpaceID space, + Expression* src, + std::vector edgeTypes, + storage::cpp2::EdgeDirection edgeDirection, + VertexProps&& vertexProps, + EdgeProps&& edgeProps, + StatProps&& statProps, + Exprs&& exprs, + bool dedup = false, + bool random = false, + std::vector orderBy = {}, + int64_t limit = -1, + Expression* filter = nullptr) { + auto traverse = make(qctx, input, space); + traverse->setSrc(src); + traverse->setEdgeTypes(std::move(edgeTypes)); + traverse->setEdgeDirection(edgeDirection); + traverse->setVertexProps(std::move(vertexProps)); + traverse->setEdgeProps(std::move(edgeProps)); + traverse->setExprs(std::move(exprs)); + traverse->setStatProps(std::move(statProps)); + traverse->setRandom(random); + traverse->setDedup(dedup); + traverse->setOrderBy(std::move(orderBy)); + traverse->setLimit(limit); + traverse->setFilter(std::move(filter)); + return traverse; + } + + std::unique_ptr explain() const override; + + Traverse* clone() const override; + + MatchStepRange* stepRange() const { return range_; } + + Expression* vFilter() const { return vFilter_; } + + Expression* eFilter() const { return eFilter_; } + + void setStepRange(MatchStepRange* range) { range_ = range; } + + void setVertexFilter(Expression* vFilter) { vFilter_ = vFilter; } + + void setEdgeFilter(Expression* eFilter) { eFilter_ = eFilter; } + + private: + Traverse(QueryContext* qctx, PlanNode* input, GraphSpaceID space) + : GetNeighbors(qctx, Kind::kTraverse, input, space) { + setLimit(-1); + } + + private: + void cloneMembers(const Traverse& g); + + MatchStepRange* range_{nullptr}; + Expression* vFilter_{nullptr}; + Expression* eFilter_{nullptr}; +}; + +class AppendVertices final : public GetVertices { + public: + static AppendVertices* make(QueryContext* qctx, PlanNode* input, GraphSpaceID space) { + return qctx->objPool()->add(new AppendVertices(qctx, input, space)); + } + + std::unique_ptr explain() const override; + + AppendVertices* clone() const override; + + Expression* vFilter() const { return vFilter_; } + + void setVertexFilter(Expression* vFilter) { vFilter_ = vFilter; } + + private: + AppendVertices(QueryContext* qctx, PlanNode* input, GraphSpaceID space) + : GetVertices(qctx, + Kind::kAppendVertices, + input, + space, + nullptr, + nullptr, + nullptr, + false, + {}, + 0, + nullptr) {} + + void cloneMembers(const AppendVertices& a); + + Expression* vFilter_; +}; } // namespace graph } // namespace nebula #endif // GRAPH_PLANNER_PLAN_QUERY_H_ diff --git a/src/graph/util/SchemaUtil.cpp b/src/graph/util/SchemaUtil.cpp index a19e381e4ea..8dbf5eb593b 100644 --- a/src/graph/util/SchemaUtil.cpp +++ b/src/graph/util/SchemaUtil.cpp @@ -322,9 +322,9 @@ bool SchemaUtil::isValidVid(const Value &value) { } StatusOr>> SchemaUtil::getAllVertexProp( - QueryContext *qctx, const SpaceInfo &space, bool withProp) { + QueryContext *qctx, GraphSpaceID spaceId, bool withProp) { // Get all tags in the space - const auto allTagsResult = qctx->schemaMng()->getAllLatestVerTagSchema(space.id); + const auto allTagsResult = qctx->schemaMng()->getAllLatestVerTagSchema(spaceId); NG_RETURN_IF_ERROR(allTagsResult); // allTags: std::unordered_map> diff --git a/src/graph/util/SchemaUtil.h b/src/graph/util/SchemaUtil.h index 3e6f6f13af4..b32e461d130 100644 --- a/src/graph/util/SchemaUtil.h +++ b/src/graph/util/SchemaUtil.h @@ -61,7 +61,7 @@ class SchemaUtil final { // Fetch all tags in the space and retrieve props from tags // only take _tag when withProp is false static StatusOr>> getAllVertexProp(QueryContext* qctx, - const SpaceInfo& space, + GraphSpaceID spaceId, bool withProp); // retrieve prop from specific edgetypes diff --git a/src/graph/validator/MatchValidator.cpp b/src/graph/validator/MatchValidator.cpp index 8e36383c309..c2c93d8d024 100644 --- a/src/graph/validator/MatchValidator.cpp +++ b/src/graph/validator/MatchValidator.cpp @@ -138,10 +138,10 @@ Status MatchValidator::buildPathExpr(const MatchPath *path, auto *pool = qctx_->objPool(); auto pathBuild = PathBuildExpression::make(pool); for (size_t i = 0; i < edgeInfos.size(); ++i) { - pathBuild->add(VariablePropertyExpression::make(pool, "", nodeInfos[i].alias)); - pathBuild->add(VariablePropertyExpression::make(pool, "", edgeInfos[i].alias)); + pathBuild->add(InputPropertyExpression::make(pool, nodeInfos[i].alias)); + pathBuild->add(InputPropertyExpression::make(pool, edgeInfos[i].alias)); } - pathBuild->add(VariablePropertyExpression::make(pool, "", nodeInfos.back().alias)); + pathBuild->add(InputPropertyExpression::make(pool, nodeInfos.back().alias)); matchClauseCtx.pathBuild = std::move(pathBuild); return Status::OK(); } @@ -182,13 +182,13 @@ Status MatchValidator::buildNodeInfo(const MatchPath *path, } Expression *filter = nullptr; if (props != nullptr) { - auto result = makeSubFilter(alias, props); + auto result = makeNodeSubFilter(props, "*"); NG_RETURN_IF_ERROR(result); filter = result.value(); } else if (node->labels() != nullptr && !node->labels()->labels().empty()) { const auto &labels = node->labels()->labels(); for (const auto &label : labels) { - auto result = makeSubFilter(alias, label->props(), *label->label()); + auto result = makeNodeSubFilter(label->props(), *label->label()); NG_RETURN_IF_ERROR(result); filter = andConnect(pool, filter, result.value()); } @@ -250,7 +250,7 @@ Status MatchValidator::buildEdgeInfo(const MatchPath *path, } Expression *filter = nullptr; if (props != nullptr) { - auto result = makeSubFilter(alias, props); + auto result = makeEdgeSubFilter(props); NG_RETURN_IF_ERROR(result); filter = result.value(); } @@ -424,14 +424,9 @@ Status MatchValidator::validateStepRange(const MatchStepRange *range) const { return Status::SemanticError( "Max hop must be greater equal than min hop: %ld vs. %ld", max, min); } - if (max == std::numeric_limits::max()) { + if (max == std::numeric_limits::max()) { return Status::SemanticError("Cannot set maximum hop for variable length relationships"); } - if (min < 0) { - return Status::SemanticError( - "Cannot set negative steps minumum hop for variable length " - "relationships"); - } return Status::OK(); } @@ -526,16 +521,40 @@ Status MatchValidator::validateUnwind(const UnwindClause *unwindClause, return Status::OK(); } -StatusOr MatchValidator::makeSubFilter(const std::string &alias, - const MapExpression *map, - const std::string &label) const { +StatusOr MatchValidator::makeEdgeSubFilter(const MapExpression *map) const { + auto *pool = qctx_->objPool(); + DCHECK(map != nullptr); + auto &items = map->items(); + DCHECK(!items.empty()); + + if (!ExpressionUtils::isEvaluableExpr(items[0].second)) { + return Status::SemanticError("Props must be constant: `%s'", + items[0].second->toString().c_str()); + } + Expression *root = RelationalExpression::makeEQ( + pool, EdgePropertyExpression::make(pool, "*", items[0].first), items[0].second->clone()); + for (auto i = 1u; i < items.size(); i++) { + if (!ExpressionUtils::isEvaluableExpr(items[i].second)) { + return Status::SemanticError("Props must be constant: `%s'", + items[i].second->toString().c_str()); + } + auto *left = root; + auto *right = RelationalExpression::makeEQ( + pool, EdgePropertyExpression::make(pool, "*", items[i].first), items[i].second->clone()); + root = LogicalExpression::makeAnd(pool, left, right); + } + return root; +} + +StatusOr MatchValidator::makeNodeSubFilter(const MapExpression *map, + const std::string &label) const { auto *pool = qctx_->objPool(); // Node has tag without property if (!label.empty() && map == nullptr) { auto *left = ConstantExpression::make(pool, label); auto *args = ArgumentList::make(pool); - args->addArgument(LabelExpression::make(pool, alias)); + args->addArgument(VertexExpression::make(pool)); auto *right = FunctionCallExpression::make(pool, "tags", args); Expression *root = RelationalExpression::makeIn(pool, left, right); @@ -546,28 +565,20 @@ StatusOr MatchValidator::makeSubFilter(const std::string &alias, auto &items = map->items(); DCHECK(!items.empty()); - // TODO(dutor) Check if evaluable and evaluate - if (items[0].second->kind() != Expression::Kind::kConstant) { + if (!ExpressionUtils::isEvaluableExpr(items[0].second)) { return Status::SemanticError("Props must be constant: `%s'", items[0].second->toString().c_str()); } Expression *root = RelationalExpression::makeEQ( - pool, - LabelAttributeExpression::make( - pool, LabelExpression::make(pool, alias), ConstantExpression::make(pool, items[0].first)), - items[0].second->clone()); + pool, TagPropertyExpression::make(pool, label, items[0].first), items[0].second->clone()); for (auto i = 1u; i < items.size(); i++) { - if (items[i].second->kind() != Expression::Kind::kConstant) { + if (!ExpressionUtils::isEvaluableExpr(items[i].second)) { return Status::SemanticError("Props must be constant: `%s'", items[i].second->toString().c_str()); } auto *left = root; auto *right = RelationalExpression::makeEQ( - pool, - LabelAttributeExpression::make(pool, - LabelExpression::make(pool, alias), - ConstantExpression::make(pool, items[i].first)), - items[i].second->clone()); + pool, TagPropertyExpression::make(pool, label, items[i].first), items[i].second->clone()); root = LogicalExpression::makeAnd(pool, left, right); } return root; diff --git a/src/graph/validator/MatchValidator.h b/src/graph/validator/MatchValidator.h index 10e69a1450d..14259adc8d5 100644 --- a/src/graph/validator/MatchValidator.h +++ b/src/graph/validator/MatchValidator.h @@ -59,10 +59,6 @@ class MatchValidator final : public Validator { Status includeExisting(const CypherClauseContextBase *cypherClauseCtx, YieldColumns *columns) const; - StatusOr makeSubFilter(const std::string &alias, - const MapExpression *map, - const std::string &label = "") const; - static Expression *andConnect(ObjectPool *pool, Expression *left, Expression *right); template @@ -93,6 +89,11 @@ class MatchValidator final : public Validator { Status buildOutputs(const YieldColumns *yields); + StatusOr makeEdgeSubFilter(const MapExpression *map) const; + + StatusOr makeNodeSubFilter(const MapExpression *map, + const std::string &label) const; + private: std::unique_ptr matchCtx_; }; diff --git a/src/graph/validator/test/MatchValidatorTest.cpp b/src/graph/validator/test/MatchValidatorTest.cpp index 4e234d7fd9e..481b09fc7fc 100644 --- a/src/graph/validator/test/MatchValidatorTest.cpp +++ b/src/graph/validator/test/MatchValidatorTest.cpp @@ -16,14 +16,8 @@ TEST_F(MatchValidatorTest, SeekByTagIndex) { { std::string query = "MATCH (v:person) RETURN id(v) AS id;"; std::vector expected = {PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - // TODO this tag filter could remove in this case - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -32,14 +26,8 @@ TEST_F(MatchValidatorTest, SeekByTagIndex) { { std::string query = "MATCH (v:book) RETURN id(v) AS id;"; std::vector expected = {PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - // TODO this tag filter could remove in this case - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -48,20 +36,9 @@ TEST_F(MatchValidatorTest, SeekByTagIndex) { { std::string query = "MATCH (p:person)-[:like]->(b:book) RETURN b.name AS book;"; std::vector expected = {PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -78,18 +55,10 @@ TEST_F(MatchValidatorTest, SeekByEdgeIndex) { { std::string query = "MATCH (v1)-[:like]->(v2) RETURN id(v1), id(v2);"; std::vector expected = {PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -108,13 +77,8 @@ TEST_F(MatchValidatorTest, groupby) { "avg(distinct n.age) AS age," "labels(n) AS lb;"; std::vector expected = {PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -131,13 +95,8 @@ TEST_F(MatchValidatorTest, groupby) { "labels(n) AS lb;"; std::vector expected = {PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -157,13 +116,8 @@ TEST_F(MatchValidatorTest, groupby) { PlanNode::Kind::kSort, PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -185,13 +139,8 @@ TEST_F(MatchValidatorTest, groupby) { PlanNode::Kind::kSort, PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -207,19 +156,9 @@ TEST_F(MatchValidatorTest, groupby) { "avg(distinct n.age) AS age," "labels(m) AS lb;"; std::vector expected = {PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -236,19 +175,9 @@ TEST_F(MatchValidatorTest, groupby) { "labels(m) AS lb;"; std::vector expected = {PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -266,19 +195,9 @@ TEST_F(MatchValidatorTest, groupby) { "labels(m) AS lb "; std::vector expected = {PlanNode::Kind::kAggregate, PlanNode::Kind::kFilter, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -299,19 +218,9 @@ TEST_F(MatchValidatorTest, groupby) { PlanNode::Kind::kLimit, PlanNode::Kind::kAggregate, PlanNode::Kind::kFilter, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -331,19 +240,9 @@ TEST_F(MatchValidatorTest, groupby) { PlanNode::Kind::kDedup, PlanNode::Kind::kAggregate, PlanNode::Kind::kFilter, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); @@ -360,14 +259,16 @@ TEST_F(MatchValidatorTest, groupby) { "avg(distinct n.age)+1 AS age," "labels(m) AS lb " "SKIP 10 LIMIT 20;"; - std::vector expected = { - PlanNode::Kind::kDataCollect, PlanNode::Kind::kLimit, PlanNode::Kind::kProject, - PlanNode::Kind::kAggregate, PlanNode::Kind::kFilter, PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, PlanNode::Kind::kInnerJoin, PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, PlanNode::Kind::kDedup, PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, PlanNode::Kind::kProject, PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, PlanNode::Kind::kDedup, PlanNode::Kind::kProject, - PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; + std::vector expected = {PlanNode::Kind::kDataCollect, + PlanNode::Kind::kLimit, + PlanNode::Kind::kProject, + PlanNode::Kind::kAggregate, + PlanNode::Kind::kFilter, + PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, + PlanNode::Kind::kIndexScan, + PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); } { @@ -383,15 +284,18 @@ TEST_F(MatchValidatorTest, groupby) { "labels(m) AS lb " "ORDER BY id " "SKIP 10 LIMIT 20;"; - std::vector expected = { - PlanNode::Kind::kDataCollect, PlanNode::Kind::kLimit, PlanNode::Kind::kSort, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, PlanNode::Kind::kFilter, PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, PlanNode::Kind::kProject, PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, PlanNode::Kind::kFilter, PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kIndexScan, - PlanNode::Kind::kStart}; + std::vector expected = {PlanNode::Kind::kDataCollect, + PlanNode::Kind::kLimit, + PlanNode::Kind::kSort, + PlanNode::Kind::kDedup, + PlanNode::Kind::kProject, + PlanNode::Kind::kAggregate, + PlanNode::Kind::kFilter, + PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, + PlanNode::Kind::kIndexScan, + PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); } { @@ -407,14 +311,17 @@ TEST_F(MatchValidatorTest, groupby) { "labels(m) AS lb " "ORDER BY id " "SKIP 10 LIMIT 20;"; - std::vector expected = { - PlanNode::Kind::kDataCollect, PlanNode::Kind::kLimit, PlanNode::Kind::kSort, - PlanNode::Kind::kDedup, PlanNode::Kind::kAggregate, PlanNode::Kind::kFilter, - PlanNode::Kind::kFilter, PlanNode::Kind::kProject, PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, PlanNode::Kind::kGetVertices, PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, PlanNode::Kind::kFilter, PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, PlanNode::Kind::kGetNeighbors, PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; + std::vector expected = {PlanNode::Kind::kDataCollect, + PlanNode::Kind::kLimit, + PlanNode::Kind::kSort, + PlanNode::Kind::kDedup, + PlanNode::Kind::kAggregate, + PlanNode::Kind::kFilter, + PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, + PlanNode::Kind::kIndexScan, + PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); } { @@ -430,17 +337,19 @@ TEST_F(MatchValidatorTest, groupby) { "labels(m) AS lb " "ORDER BY id " "SKIP 10 LIMIT 20;"; - std::vector expected = { - PlanNode::Kind::kDataCollect, PlanNode::Kind::kLimit, PlanNode::Kind::kSort, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, PlanNode::Kind::kFilter, PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, PlanNode::Kind::kProject, PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kFilter, PlanNode::Kind::kProject, PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, PlanNode::Kind::kFilter, PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, PlanNode::Kind::kIndexScan, - PlanNode::Kind::kStart}; + std::vector expected = {PlanNode::Kind::kDataCollect, + PlanNode::Kind::kLimit, + PlanNode::Kind::kSort, + PlanNode::Kind::kDedup, + PlanNode::Kind::kProject, + PlanNode::Kind::kAggregate, + PlanNode::Kind::kFilter, + PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, + PlanNode::Kind::kTraverse, + PlanNode::Kind::kIndexScan, + PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); } } @@ -454,19 +363,9 @@ TEST_F(MatchValidatorTest, with) { std::vector expected = {PlanNode::Kind::kProject, PlanNode::Kind::kProject, PlanNode::Kind::kAggregate, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kInnerJoin, - PlanNode::Kind::kProject, - PlanNode::Kind::kGetVertices, - PlanNode::Kind::kDedup, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kProject, - PlanNode::Kind::kFilter, - PlanNode::Kind::kGetNeighbors, - PlanNode::Kind::kDedup, PlanNode::Kind::kProject, + PlanNode::Kind::kAppendVertices, + PlanNode::Kind::kTraverse, PlanNode::Kind::kIndexScan, PlanNode::Kind::kStart}; EXPECT_TRUE(checkResult(query, expected)); diff --git a/src/graph/validator/test/QueryValidatorTest.cpp b/src/graph/validator/test/QueryValidatorTest.cpp index f50c22c27c6..eaa3c449a1c 100644 --- a/src/graph/validator/test/QueryValidatorTest.cpp +++ b/src/graph/validator/test/QueryValidatorTest.cpp @@ -1177,19 +1177,9 @@ TEST_F(QueryValidatorTest, TestMatch) { "RETURN type(r) AS Type, v2.name AS Name"; std::vector expected = { PK::kProject, - PK::kFilter, - PK::kProject, - PK::kInnerJoin, - PK::kProject, - PK::kGetVertices, - PK::kDedup, - PK::kProject, - PK::kFilter, - PK::kProject, - PK::kFilter, - PK::kGetNeighbors, - PK::kDedup, PK::kProject, + PK::kAppendVertices, + PK::kTraverse, PK::kIndexScan, PK::kStart, }; @@ -1200,11 +1190,15 @@ TEST_F(QueryValidatorTest, TestMatch) { "MATCH (:person{name:'Dwyane Wade'}) -[:like]-> () -[:like]-> (v3) " "RETURN DISTINCT v3.name AS Name"; std::vector expected = { - PK::kDataCollect, PK::kDedup, PK::kProject, PK::kFilter, PK::kProject, - PK::kInnerJoin, PK::kProject, PK::kGetVertices, PK::kDedup, PK::kProject, - PK::kInnerJoin, PK::kFilter, PK::kProject, PK::kGetNeighbors, PK::kDedup, - PK::kProject, PK::kFilter, PK::kProject, PK::kFilter, PK::kGetNeighbors, - PK::kDedup, PK::kProject, PK::kIndexScan, PK::kStart, + PK::kDataCollect, + PK::kDedup, + PK::kProject, + PK::kProject, + PK::kAppendVertices, + PK::kTraverse, + PK::kTraverse, + PK::kIndexScan, + PK::kStart, }; EXPECT_TRUE(checkResult(query, expected)); } @@ -1216,18 +1210,10 @@ TEST_F(QueryValidatorTest, TestMatch) { std::vector expected = { PK::kProject, PK::kFilter, - PK::kFilter, PK::kProject, - PK::kInnerJoin, - PK::kProject, - PK::kGetVertices, + PK::kAppendVertices, + PK::kTraverse, PK::kDedup, - PK::kProject, - PK::kFilter, - PK::kProject, - PK::kGetNeighbors, - PK::kDedup, - PK::kProject, PK::kPassThrough, PK::kStart, }; @@ -1238,53 +1224,25 @@ TEST_F(QueryValidatorTest, TestMatch) { "MATCH (v1)-[e:serve*2..3{start_year: 2000}]-(v2) " "WHERE id(v1) == \"LeBron James\"" "RETURN v1, v2"; - std::vector expected = {PK::kProject, - PK::kFilter, - PK::kFilter, - PK::kProject, - PK::kInnerJoin, - PK::kProject, - PK::kGetVertices, - PK::kDedup, - PK::kProject, - PK::kFilter, - PK::kUnionAllVersionVar, - PK::kLoop, - PK::kProject, - PK::kFilter, - PK::kFilter, - PK::kProject, - PK::kGetNeighbors, - PK::kInnerJoin, - PK::kDedup, - PK::kProject, - PK::kProject, - PK::kFilter, - PK::kPassThrough, - PK::kGetNeighbors, - PK::kStart, - PK::kDedup, - PK::kProject, - PK::kStart}; - EXPECT_TRUE(checkResult(query, expected)); - } - { - std::string query = "MATCH p = (n)-[]-(m:person{name:\"LeBron James\"}) RETURN p"; std::vector expected = { PK::kProject, PK::kFilter, PK::kProject, - PK::kInnerJoin, - PK::kProject, - PK::kGetVertices, + PK::kAppendVertices, + PK::kTraverse, PK::kDedup, + PK::kPassThrough, + PK::kStart, + }; + EXPECT_TRUE(checkResult(query, expected)); + } + { + std::string query = "MATCH p = (n)-[]-(m:person{name:\"LeBron James\"}) RETURN p"; + std::vector expected = { PK::kProject, - PK::kFilter, - PK::kProject, - PK::kFilter, - PK::kGetNeighbors, - PK::kDedup, PK::kProject, + PK::kAppendVertices, + PK::kTraverse, PK::kIndexScan, PK::kStart, }; diff --git a/src/parser/MatchSentence.cpp b/src/parser/MatchSentence.cpp index a0fe1de9a35..a6f06fe3c93 100644 --- a/src/parser/MatchSentence.cpp +++ b/src/parser/MatchSentence.cpp @@ -7,6 +7,10 @@ namespace nebula { +std::string MatchStepRange::toString() const { + return folly::stringPrintf("%lu..%lu", min(), max()); +} + std::string MatchClause::toString() const { std::string buf; buf.reserve(256); @@ -107,7 +111,7 @@ std::string MatchEdge::toString() const { buf += "*"; if (range_->min() == range_->max()) { buf += folly::to(range_->min()); - } else if (range_->max() == std::numeric_limits::max()) { + } else if (range_->max() == std::numeric_limits::max()) { if (range_->min() != 1) { buf += folly::to(range_->min()); buf += ".."; diff --git a/src/parser/MatchSentence.h b/src/parser/MatchSentence.h index 923dda6f31d..4390650b164 100644 --- a/src/parser/MatchSentence.h +++ b/src/parser/MatchSentence.h @@ -28,7 +28,7 @@ class MatchEdgeTypeList final { class MatchStepRange final { public: - explicit MatchStepRange(int64_t min, int64_t max = std::numeric_limits::max()) { + explicit MatchStepRange(size_t min = 0, size_t max = std::numeric_limits::max()) { min_ = min; max_ = max; } @@ -37,9 +37,11 @@ class MatchStepRange final { auto max() const { return max_; } + std::string toString() const; + private: - int64_t min_{1}; - int64_t max_{1}; + size_t min_{1}; + size_t max_{1}; }; class MatchEdgeProp final { diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 94d3f20c15d..9322db81431 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -1687,16 +1687,36 @@ match_step_range $$ = new MatchStepRange(1); } | STAR legal_integer { - $$ = new MatchStepRange($2, $2); + if ($2 < 0) { + throw nebula::GraphParser::syntax_error(@2, "Expected an unsigned integer."); + } + auto step = static_cast($2); + $$ = new MatchStepRange(step, step); } | STAR DOT_DOT legal_integer { - $$ = new MatchStepRange(1, $3); + if ($3 < 0) { + throw nebula::GraphParser::syntax_error(@3, "Expected an unsigned integer."); + } + auto step = static_cast($3); + $$ = new MatchStepRange(1, step); } | STAR legal_integer DOT_DOT { - $$ = new MatchStepRange($2); + if ($2 < 0) { + throw nebula::GraphParser::syntax_error(@2, "Expected an unsigned integer."); + } + auto step = static_cast($2); + $$ = new MatchStepRange(step); } | STAR legal_integer DOT_DOT legal_integer { - $$ = new MatchStepRange($2, $4); + if ($2 < 0) { + throw nebula::GraphParser::syntax_error(@2, "Expected an unsigned integer."); + } + auto min = static_cast($2); + if ($4 < 0) { + throw nebula::GraphParser::syntax_error(@4, "Expected an unsigned integer."); + } + auto max = static_cast($4); + $$ = new MatchStepRange(min, max); } ; diff --git a/tests/tck/features/expression/RelationalExpr.feature b/tests/tck/features/expression/RelationalExpr.feature index 5bae6fe14c8..f1c0f64e33a 100644 --- a/tests/tck/features/expression/RelationalExpr.feature +++ b/tests/tck/features/expression/RelationalExpr.feature @@ -227,12 +227,9 @@ Feature: RelationalExpression | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 10 | Project | 13 | | - | 13 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 13 | | - | 15 | GetVertices | 11 | | - | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE"}}} | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | Project | 8 | | + | 8 | Filter | 2 | | + | 2 | AppendVertices | 6 | | + | 6 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE"}}} | + | 0 | Start | | | diff --git a/tests/tck/features/expression/UnaryExpr.feature b/tests/tck/features/expression/UnaryExpr.feature index b117cfca3fb..8ef2ad970bf 100644 --- a/tests/tck/features/expression/UnaryExpr.feature +++ b/tests/tck/features/expression/UnaryExpr.feature @@ -95,12 +95,9 @@ Feature: UnaryExpression | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 10 | Project | 12 | | - | 12 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 14 | | - | 14 | GetVertices | 11 | | - | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE"}}} | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | Project | 8 | | + | 8 | Filter | 2 | | + | 2 | AppendVertices | 6 | | + | 6 | IndexScan | 0 | | + | 0 | Start | | | diff --git a/tests/tck/features/match/With.feature b/tests/tck/features/match/With.feature index f7a013e6af3..0cccf1132a3 100644 --- a/tests/tck/features/match/With.feature +++ b/tests/tck/features/match/With.feature @@ -126,18 +126,15 @@ Feature: With clause | ("Carmelo Anthony" :player{age: 34, name: "Carmelo Anthony"}) | 34 | | ("LeBron James" :player{age: 34, name: "LeBron James"}) | 34 | And the execution plan should be: - | id | name | dependencies | operator info | - | 13 | Project | 12 | | - | 12 | Filter | 17 | {"isStable": "true"} | - | 17 | TopN | 9 | | - | 9 | Project | 8 | | - | 8 | Filter | 7 | {"isStable": "false"} | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 16 | {"isStable": "false"} | - | 16 | GetVertices | 1 | | - | 1 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 8 | Project | 7 | | + | 7 | Filter | 11 | | + | 11 | TopN | 4 | | + | 4 | Project | 3 | | + | 3 | Project | 2 | | + | 2 | AppendVertices | 1 | | + | 1 | IndexScan | 0 | | + | 0 | Start | | | When executing query: """ MATCH (v:player)-[:like]->(v2) diff --git a/tests/tck/features/optimizer/CollapseProjectRule.feature b/tests/tck/features/optimizer/CollapseProjectRule.feature index ded6117e92b..22712c9cb02 100644 --- a/tests/tck/features/optimizer/CollapseProjectRule.feature +++ b/tests/tck/features/optimizer/CollapseProjectRule.feature @@ -28,15 +28,12 @@ Feature: Collapse Project Rule | 4 | | 3 | And the execution plan should be: - | id | name | dependencies | operator info | - | 16 | Project | 14 | | - | 14 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 18 | | - | 18 | GetVertices | 12 | | - | 12 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 12 | Project | 10 | | + | 10 | Filter | 2 | | + | 2 | AppendVertices | 7 | | + | 7 | IndexScan | 0 | | + | 0 | Start | | | When profiling query: """ LOOKUP ON player diff --git a/tests/tck/features/optimizer/CombineFilterRule.feature b/tests/tck/features/optimizer/CombineFilterRule.feature index 1a8f7b807b4..08392f5c30f 100644 --- a/tests/tck/features/optimizer/CombineFilterRule.feature +++ b/tests/tck/features/optimizer/CombineFilterRule.feature @@ -19,16 +19,10 @@ Feature: combine filters | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | | ("Jason Kidd" :player{age: 45, name: "Jason Kidd"}) | ("Steve Nash" :player{age: 45, name: "Steve Nash"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 16 | Project | 18 | | - | 18 | Filter | 13 | {"condition": "(($v.age>40) AND ($n.age>42) AND !(hasSameEdgeInPath($-.__COL_0)))"} | - | 13 | Project | 12 | | - | 12 | InnerJoin | 11 | | - | 11 | Project | 20 | | - | 20 | GetVertices | 7 | | - | 7 | Filter | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 22 | | - | 22 | GetNeighbors | 17 | | - | 17 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 10 | Project | 9 | | + | 9 | Filter | 3 | | + | 3 | AppendVertices | 2 | | + | 2 | Traverse | 7 | | + | 7 | IndexScan | 0 | | + | 0 | Start | | | diff --git a/tests/tck/features/optimizer/IndexScanRule.feature b/tests/tck/features/optimizer/IndexScanRule.feature index d44c5701d7e..8f8e5be6d84 100644 --- a/tests/tck/features/optimizer/IndexScanRule.feature +++ b/tests/tck/features/optimizer/IndexScanRule.feature @@ -20,15 +20,12 @@ Feature: Match index selection | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | | ("Vince Carter" :player{age: 42, name: "Vince Carter"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 10 | Project | 13 | | - | 13 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 15 | | - | 15 | GetVertices | 11 | | - | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE","column":"name","beginValue":"\"Tim Duncan\"","endValue":"\"Yao Ming\"","includeBegin":"false","includeEnd":"true"}}} | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | Project | 8 | | + | 8 | Filter | 2 | | + | 2 | AppendVertices | 6 | | + | 6 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE","column":"name","beginValue":"\"Tim Duncan\"","endValue":"\"Yao Ming\"","includeBegin":"false","includeEnd":"true"}}} | + | 0 | Start | | | When profiling query: """ MATCH (v:player) @@ -62,15 +59,12 @@ Feature: Match index selection | ("JaVale McGee" :player{age: 31, name: "JaVale McGee"}) | | ("Dwight Howard" :player{age: 33, name: "Dwight Howard"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 10 | Project | 13 | | - | 13 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 15 | | - | 15 | GetVertices | 11 | | - | 11 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE","column":"age","beginValue":"30","endValue":"40","includeBegin":"false","includeEnd":"true"}}} | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | Project | 8 | | + | 8 | Filter | 2 | | + | 2 | AppendVertices | 6 | | + | 6 | IndexScan | 0 | {"indexCtx": {"columnHints":{"scanType":"RANGE","column":"age","beginValue":"30","endValue":"40","includeBegin":"false","includeEnd":"true"}}} | + | 0 | Start | | | Scenario: or filter embeding When profiling query: @@ -97,15 +91,11 @@ Feature: Match index selection | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 10 | Project | 13 | | - | 13 | Filter | 7 | {"condition":"!(hasSameEdgeInPath($-.__COL_0))"} | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 15 | | - | 15 | GetVertices | 11 | | - | 11 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 6 | Project | 2 | | + | 2 | AppendVertices | 5 | | + | 5 | IndexScan | 0 | | + | 0 | Start | | | Scenario: degenerate to full tag scan When profiling query: @@ -123,19 +113,13 @@ Feature: Match index selection | ("Yao Ming" :player{age: 38, name: "Yao Ming"}) | ("Shaquile O'Neal" :player{age: 47, name: "Shaquile O'Neal"}) | | ("Aron Baynes" :player{age: 32, name: "Aron Baynes"}) | ("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"}) | And the execution plan should be: - | id | name | dependencies | operator info | - | 16 | Project | 19 | | - | 19 | Filter | 13 | { "condition": "((($v.name<=\"Aron Baynes\") OR ($n.age>45)) AND !(hasSameEdgeInPath($-.__COL_0)))"} | - | 13 | Project | 12 | | - | 12 | InnerJoin | 11 | | - | 11 | Project | 21 | | - | 21 | GetVertices | 7 | | - | 7 | Filter | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 23 | | - | 23 | GetNeighbors | 17 | | - | 17 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | Project | 8 | | + | 8 | Filter | 3 | | + | 3 | AppendVertices | 2 | | + | 2 | Traverse | 1 | | + | 1 | IndexScan | 0 | | + | 0 | Start | | | # This is actually the optimization for another optRule, # but it is necessary to ensure that the current optimization does not destroy this scenario # and it can be considered in the subsequent refactoring @@ -154,16 +138,11 @@ Feature: Match index selection | count | | 81 | And the execution plan should be: - | id | name | dependencies | operator info | - | 16 | Aggregate | 18 | | - | 18 | Filter | 13 | | - | 13 | Project | 12 | | - | 12 | InnerJoin | 11 | | - | 11 | Project | 20 | | - | 20 | GetVertices | 7 | | - | 7 | Filter | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 22 | | - | 22 | GetNeighbors | 17 | | - | 17 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 6 | Aggregate | 8 | | + | 8 | Project | 7 | | + | 7 | Filter | 3 | | + | 3 | AppendVertices | 2 | | + | 2 | Traverse | 1 | | + | 1 | IndexScan | 0 | | + | 0 | Start | | | diff --git a/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature b/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature index 9a2d37097f0..7eec224d430 100644 --- a/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature +++ b/tests/tck/features/optimizer/MergeGetNbrsDedupProjectRule.feature @@ -1,6 +1,8 @@ # Copyright (c) 2021 vesoft inc. All rights reserved. # # This source code is licensed under Apache 2.0 License. +# This optimization rule is not neccessary now +@skip Feature: merge get neighbors, dedup and project rule Background: diff --git a/tests/tck/features/optimizer/MergeGetVerticesDedupProjectRule.feature b/tests/tck/features/optimizer/MergeGetVerticesDedupProjectRule.feature index a514e9009ea..02beb81b619 100644 --- a/tests/tck/features/optimizer/MergeGetVerticesDedupProjectRule.feature +++ b/tests/tck/features/optimizer/MergeGetVerticesDedupProjectRule.feature @@ -1,6 +1,8 @@ # Copyright (c) 2021 vesoft inc. All rights reserved. # # This source code is licensed under Apache 2.0 License. +# This optimization rule is not neccesarry now. +@skip Feature: merge get vertices, dedup and project rule Background: diff --git a/tests/tck/features/optimizer/PushLimitDownProjectRule.feature b/tests/tck/features/optimizer/PushLimitDownProjectRule.feature index b0bc1bf4f23..4ab978c904d 100644 --- a/tests/tck/features/optimizer/PushLimitDownProjectRule.feature +++ b/tests/tck/features/optimizer/PushLimitDownProjectRule.feature @@ -22,18 +22,13 @@ Feature: Push Limit down project rule | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Manu Ginobili" :player{age: 41, name: "Manu Ginobili"})> | | <("Tim Duncan" :bachelor{name: "Tim Duncan", speciality: "psychology"} :player{age: 42, name: "Tim Duncan"})-[:like@0 {likeness: 95}]->("Tony Parker" :player{age: 36, name: "Tony Parker"})> | And the execution plan should be: - | id | name | dependencies | operator info | - | 18 | DataCollect | 26 | | - | 26 | Project | 25 | | - | 25 | Limit | 20 | | - | 20 | Filter | 13 | | - | 13 | Project | 12 | | - | 12 | InnerJoin | 11 | | - | 11 | Project | 22 | | - | 22 | GetVertices | 7 | | - | 7 | Filter | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 24 | | - | 24 | GetNeighbors | 1 | | - | 1 | PassThrough | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 9 | DataCollect | 19 | | + | 19 | Project | 16 | | + | 16 | Limit | 11 | | + | 11 | Filter | 4 | | + | 4 | AppendVertices | 3 | | + | 3 | Traverse | 2 | | + | 2 | Dedup | 1 | | + | 1 | PassThrough | 0 | | + | 0 | Start | | | diff --git a/tests/tck/features/optimizer/RemoveUselessProjectRule.feature b/tests/tck/features/optimizer/RemoveUselessProjectRule.feature index b25f2e831c7..894c472ac73 100644 --- a/tests/tck/features/optimizer/RemoveUselessProjectRule.feature +++ b/tests/tck/features/optimizer/RemoveUselessProjectRule.feature @@ -49,17 +49,14 @@ Feature: Remove Useless Project Rule | 47 | 1 | | 48 | 1 | And the execution plan should be: - | id | name | dependencies | operator info | - | 12 | DataCollect | 11 | | - | 11 | Sort | 14 | | - | 14 | Aggregate | 8 | | - | 8 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 16 | | - | 16 | GetVertices | 13 | | - | 13 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 7 | DataCollect | 6 | | + | 6 | Sort | 8 | | + | 8 | Aggregate | 3 | | + | 3 | Project | 2 | | + | 2 | AppendVertices | 1 | | + | 1 | IndexScan | 0 | | + | 0 | Start | | | When profiling query: """ MATCH p = (n:player{name:"Tony Parker"}) @@ -69,11 +66,8 @@ Feature: Remove Useless Project Rule | n | p | | ("Tony Parker" :player{age: 36, name: "Tony Parker"}) | <("Tony Parker" :player{age: 36, name: "Tony Parker"})> | And the execution plan should be: - | id | name | dependencies | operator info | - | 11 | Filter | 7 | | - | 7 | Project | 6 | | - | 6 | Project | 5 | | - | 5 | Filter | 13 | | - | 13 | GetVertices | 10 | | - | 10 | IndexScan | 0 | | - | 0 | Start | | | + | id | name | dependencies | operator info | + | 6 | Project | 2 | | + | 2 | AppendVertices | 5 | | + | 5 | IndexScan | 0 | | + | 0 | Start | | |