diff --git a/src/storage/CommonUtils.h b/src/storage/CommonUtils.h index fba3ea6f6b5..2826dc654d1 100644 --- a/src/storage/CommonUtils.h +++ b/src/storage/CommonUtils.h @@ -241,6 +241,10 @@ struct RuntimeContext { // used for update bool insert_ = false; + // some times, one line is filter out but still return (has edge) + // and some time, this line is just removed from the return result + bool filterInvalidResultOut = false; + ResultStatus resultStat_{ResultStatus::NORMAL}; }; diff --git a/src/storage/exec/FilterNode.h b/src/storage/exec/FilterNode.h index b70df4d389b..d8d24dd88ae 100644 --- a/src/storage/exec/FilterNode.h +++ b/src/storage/exec/FilterNode.h @@ -14,6 +14,11 @@ namespace nebula { namespace storage { +enum class FilterMode { + TAG_AND_EDGE = 0, + TAG_ONLY = 1, +}; + /* FilterNode will receive the result from upstream, check whether tag data or edge data could pass the expression filter. FilterNode can only accept one upstream @@ -59,27 +64,48 @@ class FilterNode : public IterateNode { return nebula::cpp2::ErrorCode::SUCCEEDED; } + void setFilterMode(FilterMode mode) { + mode_ = mode; + } + private: - // return true when the value iter points to a value which can filter bool check() override { - if (filterExp_ != nullptr) { - expCtx_->reset(this->reader(), this->key().str()); - // result is false when filter out - auto result = filterExp_->eval(*expCtx_); - // NULL is always false - auto ret = result.toBool(); - if (ret.isBool() && ret.getBool()) { - return true; - } - return false; + if (filterExp_ == nullptr) { + return true; } - return true; + switch (mode_) { + case FilterMode::TAG_AND_EDGE: + return checkTagAndEdge(); + case FilterMode::TAG_ONLY: + return checkTagOnly(); + default: + return checkTagAndEdge(); + } + } + + bool checkTagOnly() { + auto result = filterExp_->eval(*expCtx_); + // NULL is always false + auto ret = result.toBool(); + return ret.isBool() && ret.getBool(); + } + + // return true when the value iter points to a value which can filter + bool checkTagAndEdge() { + expCtx_->reset(this->reader(), this->key().str()); + // result is false when filter out + auto result = filterExp_->eval(*expCtx_); + // NULL is always false + auto ret = result.toBool(); + return ret.isBool() && ret.getBool(); } private: RuntimeContext* context_; StorageExpressionContext* expCtx_; Expression* filterExp_; + FilterMode mode_{FilterMode::TAG_AND_EDGE}; + int32_t callCheck{0}; }; } // namespace storage diff --git a/src/storage/exec/GetNeighborsNode.h b/src/storage/exec/GetNeighborsNode.h index a983a9c66b7..5a5c17f9dab 100644 --- a/src/storage/exec/GetNeighborsNode.h +++ b/src/storage/exec/GetNeighborsNode.h @@ -82,7 +82,12 @@ class GetNeighborsNode : public QueryNode { row[1].setList(agg->mutableResult().moveList()); } - resultDataSet_->rows.emplace_back(std::move(row)); + // only set filterInvalidResultOut = true in TagOnly mode + // so if it it an edge, this test is always true + if (!context_->filterInvalidResultOut || context_->resultStat_ == ResultStatus::NORMAL) { + resultDataSet_->rows.emplace_back(std::move(row)); + } + return nebula::cpp2::ErrorCode::SUCCEEDED; } @@ -90,6 +95,9 @@ class GetNeighborsNode : public QueryNode { GetNeighborsNode() = default; virtual nebula::cpp2::ErrorCode iterateEdges(std::vector& row) { + if (edgeContext_->propContexts_.empty()) { + return nebula::cpp2::ErrorCode::SUCCEEDED; + } int64_t edgeRowCount = 0; nebula::List list; for (; upstream_->valid(); upstream_->next(), ++edgeRowCount) { diff --git a/src/storage/exec/MultiTagNode.h b/src/storage/exec/MultiTagNode.h new file mode 100644 index 00000000000..ba2ba3ade92 --- /dev/null +++ b/src/storage/exec/MultiTagNode.h @@ -0,0 +1,118 @@ +/* Copyright (c) 2021 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + */ +#pragma once + +#include "common/base/Base.h" +#include "storage/context/StorageExpressionContext.h" +#include "storage/exec/EdgeNode.h" +#include "storage/exec/StorageIterator.h" +#include "storage/exec/TagNode.h" + +namespace nebula { +namespace storage { + +// MultiTagNode is a replacement of HashJoinNode +// in execution of "go over" +// if Graph don't pass any Edge prop +class MultiTagNode : public IterateNode { + public: + using RelNode::doExecute; + + MultiTagNode(RuntimeContext* context, + const std::vector& tagNodes, + StorageExpressionContext* expCtx) + : context_(context), tagNodes_(tagNodes), expCtx_(expCtx) { + IterateNode::name_ = "MultiTagNode"; + } + + nebula::cpp2::ErrorCode doExecute(PartitionID partId, const VertexID& vId) override { + auto ret = RelNode::doExecute(partId, vId); + if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) { + return ret; + } + + if (expCtx_ != nullptr) { + expCtx_->clear(); + } + result_.setList(nebula::List()); + auto& result = result_.mutableList(); + if (context_->resultStat_ == ResultStatus::ILLEGAL_DATA) { + return nebula::cpp2::ErrorCode::E_INVALID_DATA; + } + + // add result of each tag node to tagResult + for (auto* tagNode : tagNodes_) { + if (context_->isPlanKilled()) { + return nebula::cpp2::ErrorCode::E_PLAN_IS_KILLED; + } + ret = tagNode->collectTagPropsIfValid( + [&result](const std::vector*) -> nebula::cpp2::ErrorCode { + result.values.emplace_back(Value()); + return nebula::cpp2::ErrorCode::SUCCEEDED; + }, + [this, &result, tagNode]( + folly::StringPiece key, + RowReader* reader, + const std::vector* props) -> nebula::cpp2::ErrorCode { + nebula::List list; + list.reserve(props->size()); + const auto& tagName = tagNode->getTagName(); + for (const auto& prop : *props) { + VLOG(2) << "Collect prop " << prop.name_; + auto value = QueryUtils::readVertexProp( + key, context_->vIdLen(), context_->isIntId(), reader, prop); + if (!value.ok()) { + return nebula::cpp2::ErrorCode::E_TAG_PROP_NOT_FOUND; + } + if (prop.filtered_ && expCtx_ != nullptr) { + expCtx_->setTagProp(tagName, prop.name_, value.value()); + } + if (prop.returned_) { + list.emplace_back(std::move(value).value()); + } + } + result.values.emplace_back(std::move(list)); + return nebula::cpp2::ErrorCode::SUCCEEDED; + }); + if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) { + return ret; + } + } + + return nebula::cpp2::ErrorCode::SUCCEEDED; + } + + bool valid() const override { + auto ret = tagNodes_.back()->valid(); + return ret; + } + + void next() override { + tagNodes_.back()->next(); + } + + folly::StringPiece key() const override { + LOG(FATAL) << "not allowed to do this"; + return ""; + } + + folly::StringPiece val() const override { + LOG(FATAL) << "not allowed to do this"; + return ""; + } + + RowReader* reader() const override { + LOG(FATAL) << "not allowed to do this"; + return nullptr; + } + + private: + RuntimeContext* context_; + std::vector tagNodes_; + StorageExpressionContext* expCtx_; +}; + +} // namespace storage +} // namespace nebula diff --git a/src/storage/query/GetNeighborsProcessor.cpp b/src/storage/query/GetNeighborsProcessor.cpp index 42e65f8d033..017e60d6d78 100644 --- a/src/storage/query/GetNeighborsProcessor.cpp +++ b/src/storage/query/GetNeighborsProcessor.cpp @@ -11,6 +11,7 @@ #include "storage/exec/FilterNode.h" #include "storage/exec/GetNeighborsNode.h" #include "storage/exec/HashJoinNode.h" +#include "storage/exec/MultiTagNode.h" #include "storage/exec/TagNode.h" namespace nebula { @@ -79,6 +80,7 @@ void GetNeighborsProcessor::runInSingleThread(const cpp2::GetNeighborsRequest& r auto plan = buildPlan(&contexts_.front(), &expCtxs_.front(), &resultDataSet_, limit, random); std::unordered_set failedParts; for (const auto& partEntry : req.get_parts()) { + contexts_.front().resultStat_ = ResultStatus::NORMAL; auto partId = partEntry.first; for (const auto& row : partEntry.second) { CHECK_GE(row.values.size(), 1); @@ -184,24 +186,32 @@ StoragePlan GetNeighborsProcessor::buildPlan(RuntimeContext* context, bool random) { /* The StoragePlan looks like this: - +--------+---------+ - | GetNeighborsNode | - +--------+---------+ - | - +--------+---------+ - | AggregateNode | - +--------+---------+ - | - +--------+---------+ - | FilterNode | - +--------+---------+ - | - +--------+---------+ - +-->+ HashJoinNode +<----+ - | +------------------+ | - +--------+---------+ +---------+--------+ - | TagNodes | | EdgeNodes | - +------------------+ +------------------+ + +------------------+ or, if there is no edge: + | GetNeighborsNode | + +--------+---------+ +-----------------+ + | |GetNeighborsNode | + +--------+---------+ +--------+--------+ + | AggregateNode | | + +--------+---------+ +------+------+ + | |AggregateNode| + +--------+---------+ +------+------+ + | FilterNode | | + +--------+---------+ +-----+----+ + | |FilterNode| + +--------+---------+ +-----+----+ + +-->+ HashJoinNode +<----+ | + | +------------------+ | +------+-----+ ++--------+---------+ +---------+--------+ |HashJoinNode| +| TagNodes | | EdgeNodes | +------+-----+ ++------------------+ +------------------+ | + +------+-----+ + |MultiTagNode| + +------+-----+ + | + +----+---+ + |TagNodes| + +--------+ + */ StoragePlan plan; std::vector tags; @@ -217,23 +227,39 @@ StoragePlan GetNeighborsProcessor::buildPlan(RuntimeContext* context, plan.addNode(std::move(edge)); } - auto hashJoin = - std::make_unique(context, tags, edges, &tagContext_, &edgeContext_, expCtx); - for (auto* tag : tags) { - hashJoin->addDependency(tag); - } - for (auto* edge : edges) { - hashJoin->addDependency(edge); + IterateNode* upstream = nullptr; + IterateNode* join = nullptr; + if (!edges.empty()) { + auto hashJoin = + std::make_unique(context, tags, edges, &tagContext_, &edgeContext_, expCtx); + for (auto* tag : tags) { + hashJoin->addDependency(tag); + } + for (auto* edge : edges) { + hashJoin->addDependency(edge); + } + join = hashJoin.get(); + upstream = hashJoin.get(); + plan.addNode(std::move(hashJoin)); + } else { + context->filterInvalidResultOut = true; + auto groupNode = std::make_unique(context, tags, expCtx); + for (auto* tag : tags) { + groupNode->addDependency(tag); + } + join = groupNode.get(); + upstream = groupNode.get(); + plan.addNode(std::move(groupNode)); } - IterateNode* join = hashJoin.get(); - IterateNode* upstream = hashJoin.get(); - plan.addNode(std::move(hashJoin)); if (filter_) { auto filter = std::make_unique>(context, upstream, expCtx, filter_->clone()); filter->addDependency(upstream); upstream = filter.get(); + if (edges.empty()) { + filter.get()->setFilterMode(FilterMode::TAG_ONLY); + } plan.addNode(std::move(filter)); } @@ -302,8 +328,7 @@ nebula::cpp2::ErrorCode GetNeighborsProcessor::buildTagContext(const cpp2::Trave // If the list is not given, no prop will be returned. return nebula::cpp2::ErrorCode::SUCCEEDED; } - auto returnProps = - (*req.vertex_props_ref()).empty() ? buildAllTagProps() : *req.vertex_props_ref(); + auto returnProps = *req.vertex_props_ref(); auto ret = handleVertexProps(returnProps); if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) { @@ -320,8 +345,7 @@ nebula::cpp2::ErrorCode GetNeighborsProcessor::buildEdgeContext(const cpp2::Trav // If the list is not given, no prop will be returned. return nebula::cpp2::ErrorCode::SUCCEEDED; } - auto returnProps = (*req.edge_props_ref()).empty() ? buildAllEdgeProps(*req.edge_direction_ref()) - : *req.edge_props_ref(); + auto returnProps = *req.edge_props_ref(); auto ret = handleEdgeProps(returnProps); if (ret != nebula::cpp2::ErrorCode::SUCCEEDED) { return ret; diff --git a/src/storage/test/GetNeighborsTest.cpp b/src/storage/test/GetNeighborsTest.cpp index 3bcd3115442..cd9c5eb6b9c 100644 --- a/src/storage/test/GetNeighborsTest.cpp +++ b/src/storage/test/GetNeighborsTest.cpp @@ -49,6 +49,28 @@ TEST(GetNeighborsTest, PropertyTest) { // vId, stat, player, serve, expr QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 5); } + { + LOG(INFO) << "OneOutEdgeMultiProperty"; + std::vector vertices = {"Tim Duncan"}; + std::vector over = {serve}; + std::vector>> tags; + std::vector>> edges; + tags.emplace_back(player, std::vector{"name", "age", "avgScore"}); + bool retNoneProps = false; + auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges, retNoneProps); + + auto* processor = GetNeighborsProcessor::instance(env, nullptr, threadPool.get()); + auto fut = processor->getFuture(); + processor->process(req); + auto resp = std::move(fut).get(); + + ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); + // vId, stat, player, expr + size_t expectRowCount = 1; + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, expectRowCount, expectColumnCount); + } { LOG(INFO) << "OneOutEdgeKeyInProperty"; std::vector vertices = {"Tim Duncan"}; @@ -164,8 +186,10 @@ TEST(GetNeighborsTest, PropertyTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, serve, teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 6); + // vId, stat, player, expr + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColumnCount); } { LOG(INFO) << "InEdgeReturnAllProperty"; @@ -183,8 +207,10 @@ TEST(GetNeighborsTest, PropertyTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, - teammate, - serve, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 6); + // vId, stat, player, expr + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColumnCount); } { LOG(INFO) << "InOutEdgeReturnAllProperty"; @@ -203,7 +229,9 @@ TEST(GetNeighborsTest, PropertyTest) { ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); // vId, stat, player, - teammate, - serve, serve, teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 8); + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColumnCount); } { LOG(INFO) << "InEdgeReturnAllProperty"; @@ -222,7 +250,9 @@ TEST(GetNeighborsTest, PropertyTest) { ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); // vId, stat, player, - teammate, - serve, serve, teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 8); + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColumnCount); } { LOG(INFO) << "Nullable"; @@ -385,6 +415,26 @@ TEST(GetNeighborsTest, GoFromMultiVerticesTest) { // vId, stat, player, serve, expr QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 2, 5); } + { + LOG(INFO) << "NoOutEdgeMultiProperty"; + std::vector vertices = {"Tim Duncan", "Tony Parker"}; + std::vector over = {serve}; + std::vector>> tags; + std::vector>> edges; + tags.emplace_back(player, std::vector{"name", "age", "avgScore"}); + auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges); + + auto* processor = GetNeighborsProcessor::instance(env, nullptr, threadPool.get()); + auto fut = processor->getFuture(); + processor->process(req); + auto resp = std::move(fut).get(); + + ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); + // vId, stat, player, serve, expr + size_t expectColumnCount = 4; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 2, expectColumnCount); + } { LOG(INFO) << "OneInEdgeMultiProperty"; std::vector vertices = {"Spurs", "Rockets"}; @@ -935,6 +985,7 @@ TEST(GetNeighborsTest, TtlTest) { TagID player = 1; TagID team = 2; EdgeType serve = 101; + EdgeType teammate = 102; { LOG(INFO) << "OutEdgeReturnAllProperty"; @@ -988,9 +1039,10 @@ TEST(GetNeighborsTest, TtlTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 10); + // vId, stat, expr + size_t expectColsNum = 3; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColsNum); } sleep(FLAGS_mock_ttl_duration + 1); { @@ -1048,7 +1100,7 @@ TEST(GetNeighborsTest, TtlTest) { { LOG(INFO) << "GoFromPlayerOverAll"; std::vector vertices = {"Tim Duncan"}; - std::vector over = {}; + std::vector over = {serve, teammate}; std::vector>> tags; std::vector>> edges; auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges); @@ -1060,20 +1112,15 @@ TEST(GetNeighborsTest, TtlTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr + // vId, stat, expr ASSERT_EQ(1, (*resp.vertices_ref()).rows.size()); - ASSERT_EQ(10, (*resp.vertices_ref()).rows[0].values.size()); + ASSERT_EQ(3, (*resp.vertices_ref()).rows[0].values.size()); + for (auto& s : resp.vertices_ref().value().colNames) { + LOG(INFO) << "colName: " << s; + } ASSERT_EQ("Tim Duncan", (*resp.vertices_ref()).rows[0].values[0].getStr()); ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[1].empty()); // stat - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[2].empty()); // player expired - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[3].empty()); // team not exists - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[4].empty()); // general tag not exists - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[5].isList()); // - teammate valid - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[6].empty()); // - serve expired - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[7].empty()); // + serve expired - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[8].isList()); // + teammate valid - ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[9].empty()); // expr + ASSERT_TRUE((*resp.vertices_ref()).rows[0].values[2].empty()); // expr } FLAGS_mock_ttl_col = false; } @@ -1267,9 +1314,8 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 10); + // vId, stat, expr + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 3); } { LOG(INFO) << "GoFromTeamOverAll"; @@ -1286,9 +1332,10 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 10); + // vId, stat, expr + size_t expectColCnt = 3; + QueryTestUtils::checkResponse( + *resp.vertices_ref(), vertices, over, tags, edges, 1, expectColCnt); } { LOG(INFO) << "GoFromPlayerOverInEdge"; @@ -1305,8 +1352,8 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - serve, - teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 8); + // vId, stat, expr + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 3); } { LOG(INFO) << "GoFromPlayerOverOutEdge"; @@ -1323,8 +1370,8 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, + serve, + teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 8); + // vId, stat, expr + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 3); } { LOG(INFO) << "GoFromMultiPlayerOverAll"; @@ -1341,9 +1388,8 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 3, 10); + // vId, stat, _expr + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 3, 3); } { LOG(INFO) << "GoFromMultiTeamOverAll"; @@ -1360,9 +1406,8 @@ TEST(GetNeighborsTest, GoOverAllTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 3, 10); + // vId, stat, _expr + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 3, 3); } } @@ -1391,9 +1436,93 @@ TEST(GetNeighborsTest, MultiVersionTest) { auto resp = std::move(fut).get(); ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); - // vId, stat, player, team, general tag, - teammate, - serve, + serve, + - // teammate, expr - QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 10); + // vId, stat, player, team + QueryTestUtils::checkResponse(*resp.vertices_ref(), vertices, over, tags, edges, 1, 3); + } +} + +TEST(GetNeighborsTest, MultiTagNodeTest) { + fs::TempDir rootPath("/tmp/GetNeighborsTest.XXXXXX"); + mock::MockCluster cluster; + cluster.initStorageKV(rootPath.path()); + auto* env = cluster.storageEnv_.get(); + auto totalParts = cluster.getTotalParts(); + ASSERT_EQ(true, QueryTestUtils::mockVertexData(env, totalParts)); + ASSERT_EQ(true, QueryTestUtils::mockEdgeData(env, totalParts)); + auto threadPool = std::make_shared(4); + + TagID player = 1; + // TagID team = 2; + EdgeType serve = 101; + + { + LOG(INFO) << "negative no edge prop RelExp "; + std::vector vertices = {"Tracy McGrady"}; + std::vector over = {serve}; + std::vector>> tags; + std::vector>> edges; + tags.emplace_back(player, std::vector{"name", "age", "avgScore"}); + auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges); + + { + const auto& exp = *RelationalExpression::makeGT( + pool, + SourcePropertyExpression::make(pool, folly::to(player), "avgScore"), + ConstantExpression::make(pool, Value(9999))); + req.traverse_spec_ref()->filter_ref() = Expression::encode(exp); + } + + auto* processor = GetNeighborsProcessor::instance(env, nullptr, threadPool.get()); + auto fut = processor->getFuture(); + processor->process(req); + auto resp = std::move(fut).get(); + + EXPECT_EQ(0, (*resp.result_ref()).failed_parts.size()); + // vId, stat, player, serve, expr + nebula::DataSet expected; + expected.colNames = {kVid, "_stats", "_tag:1:name:age:avgScore", "_expr"}; + // nebula::Row row({"Tracy McGrady", Value()}); + nebula::Row row({""}); + expected.rows.emplace_back(std::move(row)); + EXPECT_EQ(expected.colNames, resp.vertices_ref().value().colNames); + EXPECT_TRUE(resp.vertices_ref().value().rows.empty()); + } + { + LOG(INFO) << "no edge prop RelExp multi result"; + std::vector vertices = { + "Tracy McGrady", "Dwight Howard", "Shaquille O'Neal", "Yao Ming"}; + std::vector over = {serve}; + std::vector>> tags; + std::vector>> edges; + tags.emplace_back(player, std::vector{"name", "age", "avgScore"}); + auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges); + + { + const auto& exp = *RelationalExpression::makeGT( + pool, + SourcePropertyExpression::make(pool, folly::to(player), "avgScore"), + ConstantExpression::make(pool, Value(19))); + req.traverse_spec_ref()->filter_ref() = Expression::encode(exp); + } + + auto* processor = GetNeighborsProcessor::instance(env, nullptr, threadPool.get()); + auto fut = processor->getFuture(); + processor->process(req); + auto resp = std::move(fut).get(); + + ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); + // vId, stat, player, serve, expr + nebula::DataSet expected; + expected.colNames = {kVid, "_stats", "_tag:1:name:age:avgScore", "_expr"}; + nebula::Row row1( + {"Shaquille O'Neal", Value(), nebula::List({"Shaquille O'Neal", 48, 23.7}), Value()}); + nebula::Row row2( + {"Tracy McGrady", Value(), nebula::List({"Tracy McGrady", 41, 19.6}), Value()}); + expected.rows.emplace_back(std::move(row1)); + expected.rows.emplace_back(std::move(row2)); + ASSERT_EQ(expected.colNames, resp.vertices_ref().value().colNames); + ASSERT_EQ(expected.rows, resp.vertices_ref().value().rows); + ASSERT_EQ(expected, *resp.vertices_ref()); } } @@ -1453,6 +1582,39 @@ TEST(GetNeighborsTest, FilterTest) { expected.rows.emplace_back(std::move(row)); ASSERT_EQ(expected, *resp.vertices_ref()); } + { + LOG(INFO) << "positive no edge prop RelExp"; + std::vector vertices = {"Tracy McGrady"}; + std::vector over = {serve}; + std::vector>> tags; + std::vector>> edges; + tags.emplace_back(player, std::vector{"name", "age", "avgScore"}); + auto req = QueryTestUtils::buildRequest(totalParts, vertices, over, tags, edges); + + { + const auto& exp = *RelationalExpression::makeGT( + pool, + SourcePropertyExpression::make(pool, folly::to(player), "avgScore"), + ConstantExpression::make(pool, Value(18))); + // (*req.traverse_spec_ref()).set_filter(Expression::encode(exp)); + req.traverse_spec_ref()->filter_ref() = Expression::encode(exp); + } + + auto* processor = GetNeighborsProcessor::instance(env, nullptr, threadPool.get()); + auto fut = processor->getFuture(); + processor->process(req); + auto resp = std::move(fut).get(); + + ASSERT_EQ(0, (*resp.result_ref()).failed_parts.size()); + // vId, stat, player, serve, expr + nebula::DataSet expected; + expected.colNames = {kVid, "_stats", "_tag:1:name:age:avgScore", "_expr"}; + nebula::Row row({"Tracy McGrady", Value(), nebula::List({"Tracy McGrady", 41, 19.6}), Value()}); + expected.rows.emplace_back(std::move(row)); + EXPECT_EQ(expected.colNames, resp.vertices_ref().value().colNames); + EXPECT_EQ(expected.rows, resp.vertices_ref().value().rows); + EXPECT_EQ(expected, *resp.vertices_ref()); + } { LOG(INFO) << "ArithExpression"; std::vector vertices = {"Tracy McGrady"};