diff --git a/src/clients/storage/StorageClient.cpp b/src/clients/storage/StorageClient.cpp index c92f0951af6..faee39c02a2 100644 --- a/src/clients/storage/StorageClient.cpp +++ b/src/clients/storage/StorageClient.cpp @@ -9,6 +9,7 @@ using nebula::cpp2::PropertyType; using nebula::storage::cpp2::ExecResponse; +using nebula::storage::cpp2::GetDstBySrcResponse; using nebula::storage::cpp2::GetNeighborsResponse; using nebula::storage::cpp2::GetPropResponse; @@ -107,6 +108,39 @@ StorageRpcRespFuture StorageClient::getNeighbors( }); } +StorageRpcRespFuture StorageClient::getDstBySrc( + const CommonRequestParam& param, const List& vertices, const std::vector& edgeTypes) { + auto cbStatus = getIdFromValue(param.space); + if (!cbStatus.ok()) { + return folly::makeFuture>( + std::runtime_error(cbStatus.status().toString())); + } + + auto status = clusterIdsToHosts(param.space, vertices.values, std::move(cbStatus).value()); + if (!status.ok()) { + return folly::makeFuture>( + std::runtime_error(status.status().toString())); + } + + auto& clusters = status.value(); + auto common = param.toReqCommon(); + std::unordered_map requests; + for (auto& c : clusters) { + auto& host = c.first; + auto& req = requests[host]; + req.space_id_ref() = param.space; + req.parts_ref() = std::move(c.second); + req.edge_types_ref() = edgeTypes; + req.common_ref() = common; + } + + return collectResponse(param.evb, + std::move(requests), + [](ThriftClientType* client, const cpp2::GetDstBySrcRequest& r) { + return client->future_getDstBySrc(r); + }); +} + StorageRpcRespFuture StorageClient::addVertices( const CommonRequestParam& param, std::vector vertices, diff --git a/src/clients/storage/StorageClient.h b/src/clients/storage/StorageClient.h index 00eaf240967..3b7f73edeca 100644 --- a/src/clients/storage/StorageClient.h +++ b/src/clients/storage/StorageClient.h @@ -80,6 +80,11 @@ class StorageClient int64_t limit = std::numeric_limits::max(), const Expression* filter = nullptr); + StorageRpcRespFuture getDstBySrc( + const CommonRequestParam& param, + const List& vertices, + const std::vector& edgeTypes); + StorageRpcRespFuture getProps( const CommonRequestParam& param, const DataSet& input, diff --git a/src/graph/context/ast/QueryAstContext.h b/src/graph/context/ast/QueryAstContext.h index 8fc3c2d1015..dc918076a95 100644 --- a/src/graph/context/ast/QueryAstContext.h +++ b/src/graph/context/ast/QueryAstContext.h @@ -93,6 +93,9 @@ struct GoContext final : AstContext { bool joinInput{false}; // true when $$.tag.prop exist bool joinDst{false}; + // Optimize for some simple go sentence which only need dst id. + // eg. GO 1 TO N STEPS FROM "A" OVER like YIELD DISTINCT like._dst + bool isSimple{false}; ExpressionProps exprProps; diff --git a/src/graph/executor/CMakeLists.txt b/src/graph/executor/CMakeLists.txt index 963c7c29769..d33f3b6afa1 100644 --- a/src/graph/executor/CMakeLists.txt +++ b/src/graph/executor/CMakeLists.txt @@ -40,6 +40,7 @@ nebula_add_library( query/TraverseExecutor.cpp query/AppendVerticesExecutor.cpp query/RollUpApplyExecutor.cpp + query/GetDstBySrcExecutor.cpp algo/BFSShortestPathExecutor.cpp algo/MultiShortestPathExecutor.cpp algo/ProduceAllPathsExecutor.cpp diff --git a/src/graph/executor/Executor.cpp b/src/graph/executor/Executor.cpp index 70290e4b5b4..7bff5ecc3c9 100644 --- a/src/graph/executor/Executor.cpp +++ b/src/graph/executor/Executor.cpp @@ -70,6 +70,7 @@ #include "graph/executor/query/DataCollectExecutor.h" #include "graph/executor/query/DedupExecutor.h" #include "graph/executor/query/FilterExecutor.h" +#include "graph/executor/query/GetDstBySrcExecutor.h" #include "graph/executor/query/GetEdgesExecutor.h" #include "graph/executor/query/GetNeighborsExecutor.h" #include "graph/executor/query/GetVerticesExecutor.h" @@ -550,6 +551,9 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kShortestPath: { return pool->makeAndAdd(node, qctx); } + case PlanNode::Kind::kGetDstBySrc: { + return pool->makeAndAdd(node, qctx); + } case PlanNode::Kind::kUnknown: { LOG(FATAL) << "Unknown plan node kind " << static_cast(node->kind()); break; diff --git a/src/graph/executor/StorageAccessExecutor.cpp b/src/graph/executor/StorageAccessExecutor.cpp index 86b100a6076..62aa7c53554 100644 --- a/src/graph/executor/StorageAccessExecutor.cpp +++ b/src/graph/executor/StorageAccessExecutor.cpp @@ -76,6 +76,42 @@ StatusOr buildRequestDataSet(const SpaceInfo &space, return vertices; } +template +StatusOr buildRequestList(const SpaceInfo &space, + QueryExpressionContext &exprCtx, + Iterator *iter, + Expression *expr, + bool dedup) { + DCHECK(iter && expr) << "iter=" << iter << ", expr=" << expr; + nebula::List vertices; + auto s = iter->size(); + vertices.reserve(s); + + std::unordered_set uniqueSet; + uniqueSet.reserve(s); + + const auto &vidType = *(space.spaceDesc.vid_type_ref()); + + for (; iter->valid(); iter->next()) { + auto vid = expr->eval(exprCtx(iter)); + if (vid.empty()) { + continue; + } + if (!SchemaUtil::isValidVid(vid, vidType)) { + std::stringstream ss; + ss << "`" << vid.toString() << "', the srcs should be type of " + << apache::thrift::util::enumNameSafe(vidType.get_type()) << ", but was`" << vid.type() + << "'"; + return Status::Error(ss.str()); + } + if (dedup && !uniqueSet.emplace(Vid::value(vid)).second) { + continue; + } + vertices.emplace_back(std::move(vid)); + } + return vertices; +} + } // namespace internal bool StorageAccessExecutor::isIntVidType(const SpaceInfo &space) const { @@ -95,6 +131,18 @@ StatusOr StorageAccessExecutor::buildRequestDataSetByVidType(Iterator * return internal::buildRequestDataSet(space, exprCtx, iter, expr, dedup, isCypher); } +StatusOr StorageAccessExecutor::buildRequestListByVidType(Iterator *iter, + Expression *expr, + bool dedup) { + const auto &space = qctx()->rctx()->session()->space(); + QueryExpressionContext exprCtx(qctx()->ectx()); + + if (isIntVidType(space)) { + return internal::buildRequestList(space, exprCtx, iter, expr, dedup); + } + return internal::buildRequestList(space, exprCtx, iter, expr, dedup); +} + std::string StorageAccessExecutor::getStorageDetail( optional_field_ref &> ref) const { if (ref.has_value()) { diff --git a/src/graph/executor/StorageAccessExecutor.h b/src/graph/executor/StorageAccessExecutor.h index 0e18c9333bb..457729b7dd2 100644 --- a/src/graph/executor/StorageAccessExecutor.h +++ b/src/graph/executor/StorageAccessExecutor.h @@ -162,6 +162,8 @@ class StorageAccessExecutor : public Executor { Expression *expr, bool dedup, bool isCypher = false); + + StatusOr buildRequestListByVidType(Iterator *iter, Expression *expr, bool dedup); }; } // namespace graph diff --git a/src/graph/executor/query/GetDstBySrcExecutor.cpp b/src/graph/executor/query/GetDstBySrcExecutor.cpp new file mode 100644 index 00000000000..3f0025b3e58 --- /dev/null +++ b/src/graph/executor/query/GetDstBySrcExecutor.cpp @@ -0,0 +1,92 @@ +// Copyright (c) 2020 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#include "graph/executor/query/GetDstBySrcExecutor.h" + +#include "graph/service/GraphFlags.h" + +using nebula::storage::StorageClient; +using nebula::storage::StorageRpcResponse; +using nebula::storage::cpp2::GetDstBySrcResponse; + +namespace nebula { +namespace graph { + +StatusOr GetDstBySrcExecutor::buildRequestList() { + SCOPED_TIMER(&execTime_); + auto inputVar = gd_->inputVar(); + auto iter = ectx_->getResult(inputVar).iter(); + return buildRequestListByVidType(iter.get(), gd_->src(), gd_->dedup()); +} + +folly::Future GetDstBySrcExecutor::execute() { + auto res = buildRequestList(); + NG_RETURN_IF_ERROR(res); + auto reqList = std::move(res).value(); + if (reqList.empty()) { + DataSet emptyResult; + return finish(ResultBuilder() + .value(Value(std::move(emptyResult))) + .iter(Iterator::Kind::kSequential) + .build()); + } + + time::Duration getDstTime; + StorageClient* storageClient = qctx_->getStorageClient(); + QueryExpressionContext qec(qctx()->ectx()); + StorageClient::CommonRequestParam param(gd_->space(), + qctx()->rctx()->session()->id(), + qctx()->plan()->id(), + qctx()->plan()->isProfileEnabled()); + return storageClient->getDstBySrc(param, std::move(reqList), gd_->edgeTypes()) + .via(runner()) + .ensure([this, getDstTime]() { + SCOPED_TIMER(&execTime_); + otherStats_.emplace("total_rpc_time", folly::sformat("{}(us)", getDstTime.elapsedInUSec())); + }) + .thenValue([this](StorageRpcResponse&& resp) { + SCOPED_TIMER(&execTime_); + auto& hostLatency = resp.hostLatency(); + for (size_t i = 0; i < hostLatency.size(); ++i) { + size_t size = 0u; + auto& result = resp.responses()[i]; + if (result.dsts_ref().has_value()) { + size = (*result.dsts_ref()).size(); + } + auto& info = hostLatency[i]; + otherStats_.emplace( + folly::sformat("{} exec/total/vertices", std::get<0>(info).toString()), + folly::sformat("{}(us)/{}(us)/{},", std::get<1>(info), std::get<2>(info), size)); + auto detail = getStorageDetail(result.result.latency_detail_us_ref()); + if (!detail.empty()) { + otherStats_.emplace("storage_detail", detail); + } + } + return handleResponse(resp, this->gd_->colNames()); + }); +} + +Status GetDstBySrcExecutor::handleResponse(RpcResponse& resps, + const std::vector& colNames) { + auto result = handleCompleteness(resps, FLAGS_accept_partial_success); + NG_RETURN_IF_ERROR(result); + ResultBuilder builder; + builder.state(result.value()); + + auto& responses = resps.responses(); + DataSet ds; + for (auto& resp : responses) { + auto* dataset = resp.get_dsts(); + if (dataset == nullptr) { + continue; + } + dataset->colNames = colNames; + ds.append(std::move(*dataset)); + } + builder.value(Value(std::move(ds))).iter(Iterator::Kind::kSequential); + return finish(builder.build()); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/executor/query/GetDstBySrcExecutor.h b/src/graph/executor/query/GetDstBySrcExecutor.h new file mode 100644 index 00000000000..bf296895a6d --- /dev/null +++ b/src/graph/executor/query/GetDstBySrcExecutor.h @@ -0,0 +1,36 @@ +// Copyright (c) 2020 vesoft inc. All rights reserved. +// +// This source code is licensed under Apache 2.0 License. + +#ifndef GRAPH_EXECUTOR_QUERY_GETDSTBYSRCEXECUTOR_H_ +#define GRAPH_EXECUTOR_QUERY_GETDSTBYSRCEXECUTOR_H_ + +#include "graph/executor/StorageAccessExecutor.h" +#include "graph/planner/plan/Query.h" + +// get the dst id of the src id +namespace nebula { +namespace graph { +class GetDstBySrcExecutor final : public StorageAccessExecutor { + public: + GetDstBySrcExecutor(const PlanNode* node, QueryContext* qctx) + : StorageAccessExecutor("GetDstBySrcExecutor", node, qctx) { + gd_ = asNode(node); + } + + folly::Future execute() override; + + StatusOr buildRequestList(); + + private: + using RpcResponse = storage::StorageRpcResponse; + Status handleResponse(RpcResponse& resps, const std::vector& colNames); + + private: + const GetDstBySrc* gd_; +}; + +} // namespace graph +} // namespace nebula + +#endif // GRAPH_EXECUTOR_QUERY_GETDSTBYSRCEXECUTOR_H_ diff --git a/src/graph/executor/test/StorageServerStub.cpp b/src/graph/executor/test/StorageServerStub.cpp index 0ad57cb12af..b86ff02c1a3 100644 --- a/src/graph/executor/test/StorageServerStub.cpp +++ b/src/graph/executor/test/StorageServerStub.cpp @@ -36,6 +36,11 @@ folly::Future GraphStorageLocalServer::future_getNei LOCAL_RETURN_FUTURE(threadManager_, cpp2::GetNeighborsResponse, future_getNeighbors); } +folly::Future GraphStorageLocalServer::future_getDstBySrc( + const cpp2::GetDstBySrcRequest& request) { + LOCAL_RETURN_FUTURE(threadManager_, cpp2::GetDstBySrcResponse, future_getDstBySrc); +} + folly::Future GraphStorageLocalServer::future_addVertices( const cpp2::AddVerticesRequest& request) { LOCAL_RETURN_FUTURE(threadManager_, cpp2::ExecResponse, future_addVertices); diff --git a/src/graph/planner/ngql/GoPlanner.cpp b/src/graph/planner/ngql/GoPlanner.cpp index afc95df18a7..325b056eb41 100644 --- a/src/graph/planner/ngql/GoPlanner.cpp +++ b/src/graph/planner/ngql/GoPlanner.cpp @@ -309,14 +309,25 @@ PlanNode* GoPlanner::buildLastStepJoinPlan(PlanNode* gn, PlanNode* join) { PlanNode* GoPlanner::lastStep(PlanNode* dep, PlanNode* join) { auto qctx = goCtx_->qctx; - auto* gn = GetNeighbors::make(qctx, dep, goCtx_->space.id); - gn->setSrc(goCtx_->from.src); - gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); - gn->setEdgeProps(buildEdgeProps(false)); - gn->setInputVar(goCtx_->vidsVar); + PlanNode* scan = nullptr; + if (goCtx_->isSimple) { + auto* gd = GetDstBySrc::make(qctx, dep, goCtx_->space.id); + gd->setSrc(goCtx_->from.src); + gd->setEdgeTypes(buildEdgeTypes()); + gd->setInputVar(goCtx_->vidsVar); + gd->setColNames({kDst}); + scan = gd; + } else { + auto* gn = GetNeighbors::make(qctx, dep, goCtx_->space.id); + gn->setSrc(goCtx_->from.src); + gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); + gn->setEdgeProps(buildEdgeProps(false)); + gn->setInputVar(goCtx_->vidsVar); + scan = gn; + } const auto& steps = goCtx_->steps; - auto* sampleLimit = buildSampleLimit(gn, steps.isMToN() ? steps.nSteps() : steps.steps()); + auto* sampleLimit = buildSampleLimit(scan, steps.isMToN() ? steps.nSteps() : steps.steps()); auto* root = buildLastStepJoinPlan(sampleLimit, join); @@ -324,7 +335,9 @@ PlanNode* GoPlanner::lastStep(PlanNode* dep, PlanNode* join) { root = Filter::make(qctx, root, goCtx_->filter); } - root = Project::make(qctx, root, goCtx_->yieldExpr); + if (!goCtx_->isSimple) { + root = Project::make(qctx, root, goCtx_->yieldExpr); + } root->setColNames(std::move(goCtx_->colNames)); if (goCtx_->distinct) { @@ -384,24 +397,38 @@ Expression* GoPlanner::stepSampleLimit() { SubPlan GoPlanner::oneStepPlan(SubPlan& startVidPlan) { auto qctx = goCtx_->qctx; + auto isSimple = goCtx_->isSimple; + + PlanNode* scan = nullptr; + if (isSimple) { + auto* gd = GetDstBySrc::make(qctx, startVidPlan.root, goCtx_->space.id); + gd->setSrc(goCtx_->from.src); + gd->setEdgeTypes(buildEdgeTypes()); + gd->setInputVar(goCtx_->vidsVar); + gd->setColNames({kDst}); + scan = gd; + } else { + auto* gn = GetNeighbors::make(qctx, startVidPlan.root, goCtx_->space.id); + gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); + gn->setEdgeProps(buildEdgeProps(false)); + gn->setSrc(goCtx_->from.src); + gn->setInputVar(goCtx_->vidsVar); + scan = gn; + } - auto* gn = GetNeighbors::make(qctx, startVidPlan.root, goCtx_->space.id); - gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); - gn->setEdgeProps(buildEdgeProps(false)); - gn->setSrc(goCtx_->from.src); - gn->setInputVar(goCtx_->vidsVar); - - auto* sampleLimit = buildSampleLimit(gn, 1 /* one step */); + auto* sampleLimit = buildSampleLimit(scan, 1 /* one step */); SubPlan subPlan; - subPlan.tail = startVidPlan.tail != nullptr ? startVidPlan.tail : gn; + subPlan.tail = startVidPlan.tail != nullptr ? startVidPlan.tail : scan; subPlan.root = buildOneStepJoinPlan(sampleLimit); if (goCtx_->filter != nullptr) { subPlan.root = Filter::make(qctx, subPlan.root, goCtx_->filter); } - subPlan.root = Project::make(qctx, subPlan.root, goCtx_->yieldExpr); + if (!isSimple) { + subPlan.root = Project::make(qctx, subPlan.root, goCtx_->yieldExpr); + } subPlan.root->setColNames(std::move(goCtx_->colNames)); if (goCtx_->distinct) { subPlan.root = Dedup::make(qctx, subPlan.root); @@ -412,17 +439,36 @@ SubPlan GoPlanner::oneStepPlan(SubPlan& startVidPlan) { SubPlan GoPlanner::nStepsPlan(SubPlan& startVidPlan) { auto qctx = goCtx_->qctx; + auto isSimple = goCtx_->isSimple; loopStepVar_ = qctx->vctx()->anonVarGen()->getVar(); auto* start = StartNode::make(qctx); - auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); - gn->setSrc(goCtx_->from.src); - gn->setEdgeProps(buildEdgeProps(true)); - gn->setInputVar(goCtx_->vidsVar); - - auto* sampleLimit = buildSampleLimit(gn); - - auto* getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); + PlanNode* scan = nullptr; + if (isSimple) { + auto* gd = GetDstBySrc::make(qctx, start, goCtx_->space.id); + gd->setSrc(goCtx_->from.src); + gd->setEdgeTypes(buildEdgeTypes()); + gd->setInputVar(goCtx_->vidsVar); + gd->setColNames({kDst}); + scan = gd; + } else { + auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); + gn->setSrc(goCtx_->from.src); + gn->setEdgeProps(buildEdgeProps(true)); + gn->setInputVar(goCtx_->vidsVar); + scan = gn; + } + auto* sampleLimit = buildSampleLimit(scan); + + PlanNode* getDst = nullptr; + if (isSimple) { + auto* dedup = Dedup::make(qctx, sampleLimit); + dedup->setOutputVar(goCtx_->vidsVar); + dedup->setColNames(goCtx_->colNames); + getDst = dedup; + } else { + getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); + } PlanNode* loopBody = getDst; PlanNode* loopDep = nullptr; @@ -448,19 +494,41 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { auto qctx = goCtx_->qctx; auto joinInput = goCtx_->joinInput; auto joinDst = goCtx_->joinDst; + auto isSimple = goCtx_->isSimple; loopStepVar_ = qctx->vctx()->anonVarGen()->getVar(); auto* start = StartNode::make(qctx); - auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); - gn->setSrc(goCtx_->from.src); - gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); - gn->setEdgeProps(buildEdgeProps(false)); - gn->setInputVar(goCtx_->vidsVar); - auto* sampleLimit = buildSampleLimit(gn); + PlanNode* scan = nullptr; + if (isSimple) { + auto* gd = GetDstBySrc::make(qctx, start, goCtx_->space.id); + gd->setSrc(goCtx_->from.src); + gd->setEdgeTypes(buildEdgeTypes()); + gd->setInputVar(goCtx_->vidsVar); + gd->setColNames({kDst}); + scan = gd; + } else { + auto* gn = GetNeighbors::make(qctx, start, goCtx_->space.id); + gn->setSrc(goCtx_->from.src); + gn->setVertexProps(buildVertexProps(goCtx_->exprProps.srcTagProps())); + gn->setEdgeProps(buildEdgeProps(false)); + gn->setInputVar(goCtx_->vidsVar); + scan = gn; + } + + auto* sampleLimit = buildSampleLimit(scan); - auto* getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); + PlanNode* getDst = nullptr; + + if (isSimple) { + auto* dedup = Dedup::make(qctx, sampleLimit); + dedup->setOutputVar(goCtx_->vidsVar); + dedup->setColNames(sampleLimit->colNames()); + getDst = dedup; + } else { + getDst = PlannerUtil::extractDstFromGN(qctx, sampleLimit, goCtx_->vidsVar); + } auto* loopBody = getDst; auto* loopDep = startVidPlan.root; @@ -487,24 +555,33 @@ SubPlan GoPlanner::mToNStepsPlan(SubPlan& startVidPlan) { loopBody->setInputVar(filterInput); } - const auto& projectInput = - (loopBody != getDst) ? loopBody->outputVar() : sampleLimit->outputVar(); - loopBody = Project::make(qctx, loopBody, goCtx_->yieldExpr); - loopBody->setInputVar(projectInput); - loopBody->setColNames(std::move(goCtx_->colNames)); + if (!isSimple) { + const auto& projectInput = + (loopBody != getDst) ? loopBody->outputVar() : sampleLimit->outputVar(); + loopBody = Project::make(qctx, loopBody, goCtx_->yieldExpr); + loopBody->setInputVar(projectInput); - if (goCtx_->distinct) { - loopBody = Dedup::make(qctx, loopBody); + if (goCtx_->distinct) { + loopBody = Dedup::make(qctx, loopBody); + } } + loopBody->setColNames(std::move(goCtx_->colNames)); auto* condition = loopCondition(goCtx_->steps.nSteps(), sampleLimit->outputVar()); auto* loop = Loop::make(qctx, loopDep, loopBody, condition); auto* dc = DataCollect::make(qctx, DataCollect::DCKind::kMToN); dc->addDep(loop); - dc->setMToN(goCtx_->steps); + if (isSimple) { + StepClause newStep(goCtx_->steps.mSteps() + 1, goCtx_->steps.nSteps() + 1); + dc->setMToN(newStep); + dc->setInputVars({loopBody->outputVar()}); + } else { + dc->setMToN(goCtx_->steps); + dc->setInputVars({loopBody->outputVar()}); + } + dc->setDistinct(goCtx_->distinct); - dc->setInputVars({loopBody->outputVar()}); dc->setColNames(loopBody->colNames()); SubPlan subPlan; @@ -519,6 +596,10 @@ StatusOr GoPlanner::transform(AstContext* astCtx) { auto qctx = goCtx_->qctx; goCtx_->joinInput = goCtx_->from.fromType != FromType::kInstantExpr; goCtx_->joinDst = !goCtx_->exprProps.dstTagProps().empty(); + goCtx_->isSimple = isSimpleCase(); + if (goCtx_->isSimple) { + goCtx_->yieldExpr->columns()[0]->setExpr(InputPropertyExpression::make(qctx->objPool(), kDst)); + } SubPlan startPlan = PlannerUtil::buildStart(qctx, goCtx_->from, goCtx_->vidsVar); @@ -541,5 +622,45 @@ StatusOr GoPlanner::transform(AstContext* astCtx) { return nStepsPlan(startPlan); } +bool GoPlanner::isSimpleCase() { + if (goCtx_->joinInput || goCtx_->joinDst || goCtx_->filter || !goCtx_->distinct) { + return false; + } + if (goCtx_->yieldExpr->columns().size() != 1 || + goCtx_->yieldExpr->columns()[0]->expr()->kind() != Expression::Kind::kEdgeDst) { + return false; + } + + auto expr = static_cast(goCtx_->yieldExpr->columns()[0]->expr()); + if (expr->sym() != "*" && goCtx_->over.edgeTypes.size() != 1) { + return false; + } + return true; +} + +std::vector GoPlanner::buildEdgeTypes() { + switch (goCtx_->over.direction) { + case storage::cpp2::EdgeDirection::IN_EDGE: { + std::vector edgeTypes; + for (auto edgeType : goCtx_->over.edgeTypes) { + edgeTypes.emplace_back(-edgeType); + } + return edgeTypes; + } + case storage::cpp2::EdgeDirection::OUT_EDGE: { + return goCtx_->over.edgeTypes; + } + case storage::cpp2::EdgeDirection::BOTH: { + std::vector edgeTypes; + for (auto edgeType : goCtx_->over.edgeTypes) { + edgeTypes.emplace_back(edgeType); + edgeTypes.emplace_back(-edgeType); + } + return edgeTypes; + } + } + return {}; +} + } // namespace graph } // namespace nebula diff --git a/src/graph/planner/ngql/GoPlanner.h b/src/graph/planner/ngql/GoPlanner.h index 49fc2f259c9..9217666ad42 100644 --- a/src/graph/planner/ngql/GoPlanner.h +++ b/src/graph/planner/ngql/GoPlanner.h @@ -89,6 +89,9 @@ class GoPlanner final : public Planner { // Get step sample/limit number Expression* stepSampleLimit(); + bool isSimpleCase(); + std::vector buildEdgeTypes(); + private: GoPlanner() = default; diff --git a/src/graph/planner/plan/PlanNode.cpp b/src/graph/planner/plan/PlanNode.cpp index d0d5398cf7b..9e8dc0b0228 100644 --- a/src/graph/planner/plan/PlanNode.cpp +++ b/src/graph/planner/plan/PlanNode.cpp @@ -300,6 +300,8 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "Argument"; case Kind::kRollUpApply: return "RollUpApply"; + case Kind::kGetDstBySrc: + return "GetDstBySrc"; // 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 0a56b7d0517..8ee84207edd 100644 --- a/src/graph/planner/plan/PlanNode.h +++ b/src/graph/planner/plan/PlanNode.h @@ -180,6 +180,8 @@ class PlanNode { kShowQueries, kKillQuery, + + kGetDstBySrc, }; bool isQueryNode() const { diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index a03841b8d4d..4c5301e543b 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -102,6 +102,26 @@ void GetNeighbors::cloneMembers(const GetNeighbors& g) { } } +std::unique_ptr GetDstBySrc::explain() const { + auto desc = Explore::explain(); + addDescription("src", src_ ? src_->toString() : "", desc.get()); + addDescription("edgeTypes", folly::toJson(util::toJson(edgeTypes_)), desc.get()); + return desc; +} + +PlanNode* GetDstBySrc::clone() const { + auto* newGV = GetDstBySrc::make(qctx_, nullptr, space_); + newGV->cloneMembers(*this); + return newGV; +} + +void GetDstBySrc::cloneMembers(const GetDstBySrc& gd) { + Explore::cloneMembers(gd); + + src_ = gd.src()->clone(); + edgeTypes_ = gd.edgeTypes_; +} + std::unique_ptr GetVertices::explain() const { auto desc = Explore::explain(); addDescription("src", src_ ? src_->toString() : "", desc.get()); diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index c10cf992f9b..4e219d4ea88 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -259,6 +259,55 @@ class GetNeighbors : public Explore { bool random_{false}; }; +// Get Edge dst id by src id +class GetDstBySrc : public Explore { + public: + static GetDstBySrc* make(QueryContext* qctx, + PlanNode* input, + GraphSpaceID space, + Expression* src = nullptr, + std::vector edgeTypes = {}) { + return qctx->objPool()->makeAndAdd( + qctx, Kind::kGetDstBySrc, input, space, src, std::move(edgeTypes)); + } + + Expression* src() const { + return src_; + } + + void setSrc(Expression* src) { + src_ = src; + } + + const std::vector& edgeTypes() const { + return edgeTypes_; + } + + void setEdgeTypes(std::vector edgeTypes) { + edgeTypes_ = std::move(edgeTypes); + } + + PlanNode* clone() const override; + std::unique_ptr explain() const override; + + protected: + friend ObjectPool; + GetDstBySrc(QueryContext* qctx, + Kind kind, + PlanNode* input, + GraphSpaceID space, + Expression* src, + std::vector edgeTypes) + : Explore(qctx, kind, input, space), src_(src), edgeTypes_(std::move(edgeTypes)) {} + + void cloneMembers(const GetDstBySrc&); + + private: + // vertices may be parsing from runtime. + Expression* src_{nullptr}; + std::vector edgeTypes_; +}; + // Get property with given vertex keys. class GetVertices : public Explore { public: diff --git a/src/graph/validator/test/QueryValidatorTest.cpp b/src/graph/validator/test/QueryValidatorTest.cpp index 49ea0c03d16..a7777c5f843 100644 --- a/src/graph/validator/test/QueryValidatorTest.cpp +++ b/src/graph/validator/test/QueryValidatorTest.cpp @@ -751,10 +751,7 @@ TEST_F(QueryValidatorTest, GoMToN) { PK::kLoop, PK::kStart, PK::kDedup, - PK::kProject, - PK::kDedup, - PK::kProject, - PK::kGetNeighbors, + PK::kGetDstBySrc, PK::kStart, }; EXPECT_TRUE(checkResult(query, expected)); @@ -766,10 +763,7 @@ TEST_F(QueryValidatorTest, GoMToN) { PK::kLoop, PK::kStart, PK::kDedup, - PK::kProject, - PK::kDedup, - PK::kProject, - PK::kGetNeighbors, + PK::kGetDstBySrc, PK::kStart, }; EXPECT_TRUE(checkResult(query, expected)); @@ -802,10 +796,7 @@ TEST_F(QueryValidatorTest, GoMToN) { PK::kLoop, PK::kStart, PK::kDedup, - PK::kProject, - PK::kDedup, - PK::kProject, - PK::kGetNeighbors, + PK::kGetDstBySrc, PK::kStart, }; EXPECT_TRUE(checkResult(query, expected)); @@ -817,10 +808,7 @@ TEST_F(QueryValidatorTest, GoMToN) { PK::kLoop, PK::kStart, PK::kDedup, - PK::kProject, - PK::kDedup, - PK::kProject, - PK::kGetNeighbors, + PK::kGetDstBySrc, PK::kStart, }; EXPECT_TRUE(checkResult(query, expected)); diff --git a/src/storage/GraphStorageLocalServer.cpp b/src/storage/GraphStorageLocalServer.cpp index 5e318285fc5..d43b5fe616e 100644 --- a/src/storage/GraphStorageLocalServer.cpp +++ b/src/storage/GraphStorageLocalServer.cpp @@ -44,6 +44,11 @@ folly::Future GraphStorageLocalServer::future_getNei LOCAL_RETURN_FUTURE(cpp2::GetNeighborsResponse, future_getNeighbors); } +folly::Future GraphStorageLocalServer::future_getDstBySrc( + const cpp2::GetDstBySrcRequest& request) { + LOCAL_RETURN_FUTURE(cpp2::GetDstBySrcResponse, future_getDstBySrc); +} + folly::Future GraphStorageLocalServer::future_addVertices( const cpp2::AddVerticesRequest& request) { LOCAL_RETURN_FUTURE(cpp2::ExecResponse, future_addVertices); diff --git a/src/storage/GraphStorageLocalServer.h b/src/storage/GraphStorageLocalServer.h index 3ab374b84bd..fb11f618397 100644 --- a/src/storage/GraphStorageLocalServer.h +++ b/src/storage/GraphStorageLocalServer.h @@ -29,6 +29,8 @@ class GraphStorageLocalServer final : public boost::noncopyable, public nebula:: public: folly::Future future_getNeighbors( const cpp2::GetNeighborsRequest& request); + folly::Future future_getDstBySrc( + const cpp2::GetDstBySrcRequest& request); folly::Future future_addVertices(const cpp2::AddVerticesRequest& request); folly::Future future_chainAddEdges(const cpp2::AddEdgesRequest& request); folly::Future future_addEdges(const cpp2::AddEdgesRequest& request); diff --git a/tests/tck/features/go/SimpleCase.feature b/tests/tck/features/go/SimpleCase.feature new file mode 100644 index 00000000000..9f774f04f75 --- /dev/null +++ b/tests/tck/features/go/SimpleCase.feature @@ -0,0 +1,68 @@ +# Copyright (c) 2021 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. +Feature: Simple case + + Background: + Given a graph with space named "nba" + + Scenario: go 1 steps yield distinct dst id + When profiling query: + """ + GO FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) as dst | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 2 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 3 | Aggregate | 2 | | + | 2 | Dedup | 1 | | + | 1 | GetDstBySrc | 0 | | + | 0 | Start | | | + + Scenario: go m steps yield distinct dst id + When profiling query: + """ + GO 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) AS dst | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 22 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 7 | Aggregate | 6 | | + | 6 | Dedup | 5 | | + | 5 | GetDstBySrc | 4 | | + | 4 | Loop | 0 | {"loopBody": "3"} | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + + Scenario: go m to n steps yield distinct dst id + When profiling query: + """ + GO 1 to 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) AS dst | YIELD count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 41 | + And the execution plan should be: + | id | name | dependencies | operator info | + | 6 | Aggregate | 5 | | + | 5 | DataCollect | 4 | | + | 4 | Loop | 0 | {"loopBody": "3"} | + | 3 | Dedup | 2 | | + | 2 | GetDstBySrc | 1 | | + | 1 | Start | | | + | 0 | Start | | | + + Scenario: k-hop neighbors + When profiling query: + """ + $v1 = GO 1 to 3 STEPS FROM "Tony Parker" OVER serve BIDIRECT YIELD DISTINCT id($$) as dst; $v2 = GO from $v1.dst OVER serve BIDIRECT YIELD DISTINCT id($$) as dst; (Yield $v2.dst as id minus yield $v1.dst as id) | yield count(*) + """ + Then the result should be, in any order, with relax comparison: + | count(*) | + | 28 |