diff --git a/src/dataman/RowSetReader.h b/src/dataman/RowSetReader.h index 05a15d0ef88..b167cacf3e6 100644 --- a/src/dataman/RowSetReader.h +++ b/src/dataman/RowSetReader.h @@ -59,8 +59,8 @@ class RowSetReader { virtual ~RowSetReader() = default; - meta::SchemaProviderIf const* schema() const { - return schema_.get(); + std::shared_ptr schema() const { + return schema_; } Iterator begin() const noexcept; diff --git a/src/graph/CMakeLists.txt b/src/graph/CMakeLists.txt index 2e67a65e4a4..8b3e1a78ccd 100644 --- a/src/graph/CMakeLists.txt +++ b/src/graph/CMakeLists.txt @@ -33,6 +33,7 @@ add_library( DescribeSpaceExecutor.cpp ShowExecutor.cpp YieldExecutor.cpp + OrderByExecutor.cpp SchemaHelper.cpp ) add_dependencies( diff --git a/src/graph/Executor.cpp b/src/graph/Executor.cpp index 9e9722716dd..e1da6870071 100644 --- a/src/graph/Executor.cpp +++ b/src/graph/Executor.cpp @@ -31,6 +31,7 @@ #include "graph/DescribeSpaceExecutor.h" #include "graph/DropSpaceExecutor.h" #include "graph/YieldExecutor.h" +#include "graph/OrderByExecutor.h" namespace nebula { namespace graph { @@ -102,6 +103,9 @@ std::unique_ptr Executor::makeExecutor(Sentence *sentence) { case Sentence::Kind::kYield: executor = std::make_unique(sentence, ectx()); break; + case Sentence::Kind::kOrderBy: + executor = std::make_unique(sentence, ectx()); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/graph/InterimResult.cpp b/src/graph/InterimResult.cpp index c64749e3618..4a77f91a180 100644 --- a/src/graph/InterimResult.cpp +++ b/src/graph/InterimResult.cpp @@ -40,6 +40,61 @@ std::vector InterimResult::getVIDs(const std::string &col) const { return result; } +std::vector InterimResult::getRows() const { + DCHECK(rsReader_ != nullptr); + auto schema = rsReader_->schema(); + auto columnCnt = schema->getNumFields(); + std::vector rows; + folly::StringPiece piece; + using nebula::cpp2::SupportedType; + auto rowIter = rsReader_->begin(); + while (rowIter) { + std::vector row; + row.reserve(columnCnt); + auto fieldIter = schema->begin(); + while (fieldIter) { + auto type = fieldIter->getType().type; + auto field = fieldIter->getName(); + row.emplace_back(); + switch (type) { + case SupportedType::VID: { + int64_t v; + auto rc = rowIter->getVid(field, v); + CHECK(rc == ResultType::SUCCEEDED); + row.back().set_integer(v); + break; + } + case SupportedType::DOUBLE: { + double v; + auto rc = rowIter->getDouble(field, v); + CHECK(rc == ResultType::SUCCEEDED); + row.back().set_double_precision(v); + break; + } + case SupportedType::BOOL: { + bool v; + auto rc = rowIter->getBool(field, v); + CHECK(rc == ResultType::SUCCEEDED); + row.back().set_bool_val(v); + break; + } + case SupportedType::STRING: { + auto rc = rowIter->getString(field, piece); + CHECK(rc == ResultType::SUCCEEDED); + row.back().set_str(piece.toString()); + break; + } + default: + LOG(FATAL) << "Unknown Type: " << static_cast(type); + } + ++fieldIter; + } + rows.emplace_back(); + rows.back().set_columns(std::move(row)); + ++rowIter; + } + return rows; +} } // namespace graph } // namespace nebula diff --git a/src/graph/InterimResult.h b/src/graph/InterimResult.h index 0d0899f01c5..bf81509c8f5 100644 --- a/src/graph/InterimResult.h +++ b/src/graph/InterimResult.h @@ -32,8 +32,13 @@ class InterimResult final { explicit InterimResult(std::unique_ptr rsWriter); explicit InterimResult(std::vector vids); + std::shared_ptr schema() const { + return rsReader_->schema(); + } + std::vector getVIDs(const std::string &col) const; + std::vector getRows() const; // TODO(dutor) iterating interfaces on rows and columns private: diff --git a/src/graph/OrderByExecutor.cpp b/src/graph/OrderByExecutor.cpp new file mode 100644 index 00000000000..8a3ddb2c502 --- /dev/null +++ b/src/graph/OrderByExecutor.cpp @@ -0,0 +1,191 @@ +/* Copyright (c) 2019 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "base/Base.h" +#include "graph/OrderByExecutor.h" + +namespace nebula { +namespace graph { +namespace cpp2 { + +bool ColumnValue::operator < (const ColumnValue& rhs) const { + DCHECK_EQ(type_, rhs.type_); + auto& lhs = *this; + switch (lhs.type_) { + case Type::bool_val: + { + return lhs.value_.bool_val < rhs.value_.bool_val; + } + case Type::integer: + { + return lhs.value_.integer < rhs.value_.integer; + } + case Type::id: + { + return lhs.value_.id < rhs.value_.id; + } + case Type::single_precision: + { + return lhs.value_.single_precision < rhs.value_.single_precision; + } + case Type::double_precision: + { + return lhs.value_.double_precision < rhs.value_.double_precision; + } + case Type::str: + { + return lhs.value_.str < rhs.value_.str; + } + case Type::timestamp: + { + return lhs.value_.timestamp < rhs.value_.timestamp; + } + case Type::year: + { + return lhs.value_.year < rhs.value_.year; + } + case Type::month: + { + return lhs.value_.month < rhs.value_.month; + } + case Type::date: + { + return lhs.value_.date < rhs.value_.date; + } + case Type::datetime: + { + return lhs.value_.datetime < rhs.value_.datetime; + } + default: + { + return false; + } + } + return false; +} +} // namespace cpp2 + +OrderByExecutor::OrderByExecutor(Sentence *sentence, ExecutionContext *ectx) + : TraverseExecutor(ectx) { + sentence_ = static_cast(sentence); +} + +Status OrderByExecutor::prepare() { + return Status::OK(); +} + +void OrderByExecutor::feedResult(std::unique_ptr result) { + if (result == nullptr) { + return; + } + DCHECK(sentence_ != nullptr); + inputs_ = std::move(result); + rows_ = inputs_->getRows(); + + auto schema = inputs_->schema(); + auto factors = sentence_->factors(); + sortFactors_.reserve(factors.size()); + for (auto &factor : factors) { + auto expr = static_cast(factor->expr()); + folly::StringPiece field = *(expr->prop()); + auto fieldIndex = schema->getFieldIndex(field); + if (fieldIndex == -1) { + LOG(INFO) << "Field(" << field << ") not exist in input schema."; + continue; + } + auto pair = std::make_pair(schema->getFieldIndex(field), factor->orderType()); + sortFactors_.emplace_back(std::move(pair)); + } +} + +void OrderByExecutor::execute() { + FLOG_INFO("Executing Order By: %s", sentence_->toString().c_str()); + auto comparator = [this] (cpp2::RowValue& lhs, cpp2::RowValue& rhs) { + const auto &lhsColumns = lhs.get_columns(); + const auto &rhsColumns = rhs.get_columns(); + for (auto &factor : this->sortFactors_) { + auto fieldIndex = factor.first; + auto orderType = factor.second; + if (lhsColumns[fieldIndex] == rhsColumns[fieldIndex]) { + continue; + } + + if (orderType == OrderFactor::OrderType::ASCEND) { + return lhsColumns[fieldIndex] < rhsColumns[fieldIndex]; + } else if (orderType == OrderFactor::OrderType::DESCEND) { + return lhsColumns[fieldIndex] > rhsColumns[fieldIndex]; + } else { + LOG(FATAL) << "Unkown Order Type: " << orderType; + } + } + return false; + }; + + if (!sortFactors_.empty()) { + std::sort(rows_.begin(), rows_.end(), comparator); + } + + if (onResult_) { + onResult_(setupInterimResult()); + } + DCHECK(onFinish_); + onFinish_(); +} + +std::unique_ptr OrderByExecutor::setupInterimResult() { + if (rows_.empty()) { + return nullptr; + } + + auto schema = inputs_->schema(); + auto rsWriter = std::make_unique(schema); + using Type = cpp2::ColumnValue::Type; + for (auto &row : rows_) { + RowWriter writer(schema); + auto columns = row.get_columns(); + for (auto &column : columns) { + switch (column.getType()) { + case Type::integer: + writer << column.get_integer(); + break; + case Type::double_precision: + writer << column.get_double_precision(); + break; + case Type::bool_val: + writer << column.get_bool_val(); + break; + case Type::str: + writer << column.get_str(); + break; + default: + LOG(FATAL) << "Not Support: " << column.getType(); + } + } + rsWriter->addRow(writer); + } + + return std::make_unique(std::move(rsWriter)); +} + +void OrderByExecutor::setupResponse(cpp2::ExecutionResponse &resp) { + if (rows_.empty()) { + return; + } + + auto schema = inputs_->schema(); + std::vector columnNames; + columnNames.reserve(schema->getNumFields()); + auto field = schema->begin(); + while (field) { + columnNames.emplace_back(field->getName()); + ++field; + } + resp.set_column_names(std::move(columnNames)); + resp.set_rows(std::move(rows_)); +} + +} // namespace graph +} // namespace nebula diff --git a/src/graph/OrderByExecutor.h b/src/graph/OrderByExecutor.h new file mode 100644 index 00000000000..da51caab544 --- /dev/null +++ b/src/graph/OrderByExecutor.h @@ -0,0 +1,43 @@ +/* Copyright (c) 2019 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef GRAPH_ORDERBYEXECUTOR_H_ +#define GRAPH_ORDERBYEXECUTOR_H_ + +#include "base/Base.h" +#include "graph/TraverseExecutor.h" + +namespace nebula { +namespace graph { + +class OrderByExecutor final : public TraverseExecutor { +public: + OrderByExecutor(Sentence *sentence, ExecutionContext *ectx); + + const char* name() const override { + return "OrderByExecutor"; + } + + Status MUST_USE_RESULT prepare() override; + + void execute() override; + + void feedResult(std::unique_ptr result) override; + + void setupResponse(cpp2::ExecutionResponse &resp) override; + +private: + std::unique_ptr setupInterimResult(); + +private: + OrderBySentence *sentence_{nullptr}; + std::unique_ptr inputs_; + std::vector rows_; + std::vector> sortFactors_; +}; +} // namespace graph +} // namespace nebula +#endif // GRAPH_ORDERBYEXECUTOR_H_ diff --git a/src/graph/TraverseExecutor.cpp b/src/graph/TraverseExecutor.cpp index 02ea70baa65..3bb535e9e7d 100644 --- a/src/graph/TraverseExecutor.cpp +++ b/src/graph/TraverseExecutor.cpp @@ -9,6 +9,7 @@ #include "parser/TraverseSentences.h" #include "graph/GoExecutor.h" #include "graph/PipeExecutor.h" +#include "graph/OrderByExecutor.h" namespace nebula { namespace graph { @@ -30,6 +31,9 @@ TraverseExecutor::makeTraverseExecutor(Sentence *sentence, ExecutionContext *ect case Sentence::Kind::kPipe: executor = std::make_unique(sentence, ectx); break; + case Sentence::Kind::kOrderBy: + executor = std::make_unique(sentence, ectx); + break; case Sentence::Kind::kUnknown: LOG(FATAL) << "Sentence kind unknown"; break; diff --git a/src/graph/test/CMakeLists.txt b/src/graph/test/CMakeLists.txt index ef65c9fb06e..1f3eab0718b 100644 --- a/src/graph/test/CMakeLists.txt +++ b/src/graph/test/CMakeLists.txt @@ -126,3 +126,23 @@ nebula_link_libraries( gtest ) nebula_add_test(data_test) + +add_executable( + order_by_test + TestMain.cpp + TestEnv.cpp + TestBase.cpp + OrderByTest.cpp + $ + $ + ${GRAPH_TEST_LIBS} +) + +nebula_link_libraries( + order_by_test + ${THRIFT_LIBRARIES} + ${ROCKSDB_LIBRARIES} + wangle + gtest +) +nebula_add_test(order_by_test) diff --git a/src/graph/test/GoTest.cpp b/src/graph/test/GoTest.cpp index a81d7fd6365..9b748e892b6 100644 --- a/src/graph/test/GoTest.cpp +++ b/src/graph/test/GoTest.cpp @@ -7,6 +7,7 @@ #include "base/Base.h" #include "graph/test/TestEnv.h" #include "graph/test/TestBase.h" +#include "graph/test/TraverseTestBase.h" #include "meta/test/TestUtils.h" DECLARE_int32(load_data_interval_secs); @@ -14,775 +15,19 @@ DECLARE_int32(load_data_interval_secs); namespace nebula { namespace graph { -class GoTest : public TestBase { +class GoTest : public TraverseTestBase { protected: void SetUp() override { - TestBase::SetUp(); + TraverseTestBase::SetUp(); // ... } void TearDown() override { // ... - TestBase::TearDown(); - } - - static void SetUpTestCase() { - client_ = gEnv->getClient(); - storagePort_ = gEnv->storageServerPort(); - - ASSERT_NE(nullptr, client_); - - ASSERT_TRUE(prepareSchema()); - - ASSERT_TRUE(prepareData()); - } - - static void TearDownTestCase() { - ASSERT_TRUE(removeData()); - client_.reset(); - } - - static AssertionResult prepareSchema(); - - static AssertionResult prepareData(); - - static AssertionResult removeData(); - - class Player final { - public: - Player(std::string name, int64_t age) { - name_ = std::move(name); - age_ = age; - vid_ = std::hash()(name_); - } - - Player(const Player&) = default; - Player(Player&&) = default; - Player& operator=(const Player&) = default; - Player& operator=(Player&&) = default; - - const std::string& name() const { - return name_; - } - - int64_t vid() const { - return vid_; - } - - int64_t age() const { - return age_; - } - - Player& serve(std::string team, int64_t startYear, int64_t endYear) { - serves_.emplace_back(std::move(team), startYear, endYear); - return *this; - } - - Player& like(std::string player, int64_t likeness) { - likes_.emplace_back(std::move(player), likeness); - return *this; - } - - const auto& serves() const { - return serves_; - } - - const auto& likes() const { - return likes_; - } - - private: - using Serve = std::tuple; - using Like = std::tuple; - std::string name_; - int64_t vid_{0}; - int64_t age_{0}; - std::vector serves_; - std::vector likes_; - }; - - template - class VertexHolder final { - public: - using KeyGetter = std::function; - using Container = std::unordered_map; - using InternalIterator = typename Container::iterator; - VertexHolder(KeyGetter getter, std::initializer_list vertices) { - getter_ = std::move(getter); - for (auto &vertex : vertices) { - vertices_.emplace(getter_(vertex), std::move(vertex)); - } - } - - const Vertex& operator[](const Key &key) const { - auto iter = vertices_.find(key); - CHECK(iter != vertices_.end()) << "Vertex not exist, key: " << key; - return iter->second; - } - - Vertex& operator[](const Key &key) { - auto iter = vertices_.find(key); - CHECK(iter != vertices_.end()) << "Vertex not exist, key: " << key; - return iter->second; - } - - class Iterator final { - public: - explicit Iterator(InternalIterator iter) { - iter_ = iter; - } - - Iterator& operator++() { - ++iter_; - return *this; - } - - Vertex& operator*() { - return iter_->second; - } - - Vertex* operator->() { - return &iter_->second; - } - - bool operator==(const Iterator &rhs) const { - return iter_ == rhs.iter_; - } - - bool operator!=(const Iterator &rhs) const { - return !(*this == rhs); - } - - private: - InternalIterator iter_; - }; - - Iterator begin() { - return Iterator(vertices_.begin()); - } - - Iterator end() { - return Iterator(vertices_.end()); - } - - private: - KeyGetter getter_; - Container vertices_; - }; - - - class Team final { - public: - explicit Team(std::string name) { - name_ = std::move(name); - vid_ = std::hash()(name_); - } - - const std::string& name() const { - return name_; - } - - int64_t vid() const { - return vid_; - } - - private: - std::string name_; - int64_t vid_{0}; - }; - -protected: - static uint16_t storagePort_; - static std::unique_ptr client_; - static VertexHolder players_; - static VertexHolder teams_; -}; - -uint16_t GoTest::storagePort_ = 0; - -std::unique_ptr GoTest::client_; - -GoTest::VertexHolder GoTest::players_ = { - [] (const auto &player) { return player.name(); }, { - Player{"Tim Duncan", 42/*, 1997*/}, - Player{"Tony Parker", 36}, - Player{"LaMarcus Aldridge", 33}, - Player{"Rudy Gay", 32}, - Player{"Marco Belinelli", 32}, - Player{"Danny Green", 31}, - Player{"Kyle Anderson", 25}, - Player{"Aron Baynes", 32}, - Player{"Boris Diaw", 36}, - Player{"Tiago Splitter", 34}, - Player{"Cory Joseph", 27}, - Player{"David West", 38}, - Player{"Jonathon Simmons", 29}, - Player{"Dejounte Murray", 29}, - Player{"Tracy McGrady", 39}, - Player{"Kobe Bryant", 40}, - Player{"LeBron James", 34}, - Player{"Stephen Curry", 31}, - Player{"Russell Westbrook", 30}, - Player{"Kevin Durant", 30}, - Player{"James Harden", 29}, - Player{"Chris Paul", 33}, - Player{"DeAndre Jordan", 30}, - Player{"Ricky Rubio", 28}, - - Player{"Rajon Rondo", 33}, - Player{"Manu Ginobili", 41}, - Player{"Kyrie Irving", 26}, - Player{"Vince Carter", 42}, - Player{"Carmelo Anthony", 34}, - Player{"Dwyane Wade", 37}, - Player{"Joel Embiid", 25}, - Player{"Paul George", 28}, - Player{"Giannis Antetokounmpo", 24}, - Player{"Yao Ming", 38}, - Player{"Blake Griffin", 30}, - Player{"Damian Lillard", 28}, - Player{"Steve Nash", 45}, - Player{"Dirk Nowitzki", 40}, - Player{"Paul Gasol", 38}, - Player{"Marc Gasol", 34}, - Player{"Grant Hill", 46}, - Player{"Ray Allen", 43}, - Player{"Klay Thompson", 29}, - Player{"Kristaps Porzingis", 23}, - Player{"Shaquile O'Neal", 47}, - Player{"JaVale McGee", 31}, - Player{"Dwight Howard", 33}, - Player{"Amar'e Stoudemire", 36}, - Player{"Jason Kidd", 45}, - Player{"Ben Simmons", 22}, - Player{"Luka Doncic", 20}, - } -}; - -GoTest::VertexHolder GoTest::teams_ = { - [] (const auto &team) { return team.name(); }, { - Team{"Warriors"}, - Team{"Nuggets"}, - Team{"Rockets"}, - Team{"Trail Blazers"}, - Team{"Spurs"}, - Team{"Thunders"}, - Team{"Jazz"}, - Team{"Clippers"}, - Team{"Kings"}, - Team{"Timberwolves"}, - Team{"Lakers"}, - Team{"Pelicans"}, - Team{"Grizzlies"}, - Team{"Mavericks"}, - Team{"Suns"}, - - Team{"Hornets"}, - Team{"Cavaliers"}, - Team{"Celtics"}, - Team{"Raptors"}, - Team{"76ers"}, - Team{"Pacers"}, - Team{"Bulls"}, - Team{"Hawks"}, - Team{"Knicks"}, - Team{"Pistons"}, - Team{"Bucks"}, - Team{"Magic"}, - Team{"Nets"}, - Team{"Wizards"}, - Team{"Heat"}, + TraverseTestBase::TearDown(); } }; - -// static -AssertionResult GoTest::prepareSchema() { - { - cpp2::ExecutionResponse resp; - std::string host = folly::stringPrintf("127.0.0.1:%u", storagePort_); - std::string cmd = "ADD HOSTS " + host; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - meta::TestUtils::registerHB(network::NetworkUtils::toHosts(host).value()); - } - { - cpp2::ExecutionResponse resp; - std::string cmd = "CREATE SPACE nba(partition_num=1, replica_factor=1)"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - sleep(FLAGS_load_data_interval_secs + 1); - { - cpp2::ExecutionResponse resp; - std::string cmd = "USE nba"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - { - cpp2::ExecutionResponse resp; - std::string cmd = "CREATE TAG player(name string, age int)"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - { - cpp2::ExecutionResponse resp; - std::string cmd = "CREATE TAG team(name string)"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - { - cpp2::ExecutionResponse resp; - std::string cmd = "CREATE EDGE serve(start_year int, end_year int)"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - { - cpp2::ExecutionResponse resp; - std::string cmd = "CREATE EDGE like(likeness int)"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - sleep(FLAGS_load_data_interval_secs + 1); - return TestOK(); -} - - -AssertionResult GoTest::prepareData() { - // TODO(dutor) Maybe we should move these data into some kind of testing resources, later. - players_["Tim Duncan"].serve("Spurs", 1997, 2016) - .like("Tony Parker", 95) - .like("Manu Ginobili", 95); - - players_["Tony Parker"].serve("Spurs", 1999, 2018) - .serve("Hornets", 2018, 2019) - .like("Tim Duncan", 95) - .like("Manu Ginobili", 95) - .like("LaMarcus Aldridge", 90); - - players_["Manu Ginobili"].serve("Spurs", 2002, 2018) - .like("Tim Duncan", 90); - - players_["LaMarcus Aldridge"].serve("Trail Blazers", 2006, 2015) - .serve("Spurs", 2015, 2019) - .like("Tony Parker", 75) - .like("Tim Duncan", 75); - - players_["Rudy Gay"].serve("Grizzlies", 2006, 2013) - .serve("Raptors", 2013, 2013) - .serve("Kings", 2013, 2017) - .serve("Spurs", 2017, 2019) - .like("LaMarcus Aldridge", 70); - - players_["Marco Belinelli"].serve("Warriors", 2007, 2009) - .serve("Raptors", 2009, 2010) - .serve("Hornets", 2010, 2012) - .serve("Bulls", 2012, 2013) - .serve("Spurs", 2013, 2015) - .serve("Kings", 2015, 2016) - .serve("Hornets", 2016, 2017) - .serve("Hawks", 2017, 2018) - .serve("76ers", 2018, 2018) - .serve("Spurs", 2018, 2019) - .like("Tony Parker", 50) - .like("Tim Duncan", 55) - .like("Danny Green", 60); - - players_["Danny Green"].serve("Cavaliers", 2009, 2010) - .serve("Spurs", 2010, 2018) - .serve("Raptors", 2018, 2019) - .like("Marco Belinelli", 83) - .like("Tim Duncan", 70) - .like("LeBron James", 80); - - players_["Kyle Anderson"].serve("Spurs", 2014, 2018) - .serve("Grizzlies", 2018, 2019); - - players_["Aron Baynes"].serve("Spurs", 2013, 2015) - .serve("Pistons", 2015, 2017) - .serve("Celtics", 2017, 2019) - .like("Tim Duncan", 80); - - players_["Boris Diaw"].serve("Hawks", 2003, 2005) - .serve("Suns", 2005, 2008) - .serve("Hornets", 2008, 2012) - .serve("Spurs", 2012, 2016) - .serve("Jazz", 2016, 2017) - .like("Tony Parker", 80) - .like("Tim Duncan", 80); - - players_["Tiago Splitter"].serve("Spurs", 2010, 2015) - .serve("Hawks", 2015, 2017) - .serve("76ers", 2017, 2017) - .like("Tim Duncan", 80) - .like("Manu Ginobili", 90); - - players_["Cory Joseph"].serve("Spurs", 2011, 2015) - .serve("Raptors", 2015, 2017) - .serve("Pacers", 2017, 2019); - - players_["David West"].serve("Hornets", 2003, 2011) - .serve("Pacers", 2011, 2015) - .serve("Spurs", 2015, 2016) - .serve("Warriors", 2016, 2018); - - players_["Jonathon Simmons"].serve("Spurs", 2015, 2017) - .serve("Magic", 2017, 2019) - .serve("76ers", 2019, 2019); - - players_["Dejounte Murray"].serve("Spurs", 2016, 2019) - .like("Tim Duncan", 99) - .like("Tony Parker", 99) - .like("Manu Ginobili", 99) - .like("Marco Belinelli", 99) - .like("Danny Green", 99) - .like("LeBron James", 99) - .like("Russell Westbrook", 99) - .like("Chris Paul", 99) - .like("Kyle Anderson", 99) - .like("Kevin Durant", 99) - .like("James Harden", 99) - .like("Tony Parker", 99); - - players_["Tracy McGrady"].serve("Raptors", 1997, 2000) - .serve("Magic", 2000, 2004) - .serve("Rockets", 2004, 2010) - .serve("Spurs", 2013, 2013) - .like("Kobe Bryant", 90) - .like("Grant Hill", 90) - .like("Rudy Gay", 90); - - players_["Kobe Bryant"].serve("Lakers", 1996, 2016); - - players_["LeBron James"].serve("Cavaliers", 2003, 2010) - .serve("Heat", 2010, 2014) - .serve("Cavaliers", 2014, 2018) - .serve("Lakers", 2018, 2019) - .like("Ray Allen", 100); - - players_["Stephen Curry"].serve("Warriors", 2009, 2019); - - players_["Russell Westbrook"].serve("Thunders", 2008, 2019) - .like("Paul George", 90) - .like("James Harden", 90); - - players_["Kevin Durant"].serve("Thunders", 2007, 2016) - .serve("Warriors", 2016, 2019); - - players_["James Harden"].serve("Thunders", 2009, 2012) - .serve("Rockets", 2012, 2019) - .like("Russell Westbrook", 80); - - players_["Chris Paul"].serve("Hornets", 2005, 2011) - .serve("Clippers", 2011, 2017) - .serve("Rockets", 2017, 2021) - .like("LeBron James", 90) - .like("Carmelo Anthony", 90) - .like("Dwyane Wade", 90); - - players_["DeAndre Jordan"].serve("Clippers", 2008, 2018) - .serve("Mavericks", 2018, 2019) - .serve("Knicks", 2019, 2019); - - players_["Ricky Rubio"].serve("Timberwolves", 2011, 2017) - .serve("Jazz", 2017, 2019); - - players_["Rajon Rondo"].serve("Celtics", 2006, 2014) - .serve("Mavericks", 2014, 2015) - .serve("Kings", 2015, 2016) - .serve("Bulls", 2016, 2017) - .serve("Pelicans", 2017, 2018) - .serve("Lakers", 2018, 2019) - .like("Ray Allen", -1); - - players_["Kyrie Irving"].serve("Cavaliers", 2011, 2017) - .serve("Celtics", 2017, 2019) - .like("LeBron James", 13); - - players_["Vince Carter"].serve("Raptors", 1998, 2004) - .serve("Nets", 2004, 2009) - .serve("Magic", 2009, 2010) - .serve("Suns", 2010, 2011) - .serve("Mavericks", 2011, 2014) - .serve("Grizzlies", 2014, 2017) - .serve("Kings", 2017, 2018) - .serve("Hawks", 2018, 2019) - .like("Tracy McGrady", 90) - .like("Jason Kidd", 70); - - players_["Carmelo Anthony"].serve("Nuggets", 2003, 2011) - .serve("Knicks", 2011, 2017) - .serve("Thunders", 2017, 2018) - .serve("Rockets", 2018, 2019) - .like("LeBron James", 90) - .like("Chris Paul", 90) - .like("Dwyane Wade", 90); - - players_["Dwyane Wade"].serve("Heat", 2003, 2016) - .serve("Bulls", 2016, 2017) - .serve("Cavaliers", 2017, 2018) - .serve("Heat", 2018, 2019) - .like("LeBron James", 90) - .like("Chris Paul", 90) - .like("Carmelo Anthony", 90); - - players_["Joel Embiid"].serve("76ers", 2014, 2019) - .like("Ben Simmons", 80); - - players_["Paul George"].serve("Pacers", 2010, 2017) - .serve("Thunders", 2017, 2019) - .like("Russell Westbrook", 95); - - players_["Giannis Antetokounmpo"].serve("Bucks", 2013, 2019); - - players_["Yao Ming"].serve("Rockets", 2002, 2011) - .like("Tracy McGrady", 90) - .like("Shaquile O'Neal", 90); - - players_["Blake Griffin"].serve("Clippers", 2009, 2018) - .serve("Pistons", 2018, 2019) - .like("Chris Paul", -1); - - players_["Damian Lillard"].serve("Trail Blazers", 2012, 2019) - .like("LaMarcus Aldridge", 80); - - players_["Steve Nash"].serve("Suns", 1996, 1998) - .serve("Mavericks", 1998, 2004) - .serve("Suns", 2004, 2012) - .serve("Lakers", 2012, 2015) - .like("Amar'e Stoudemire", 90) - .like("Dirk Nowitzki", 88) - .like("Stephen Curry", 90) - .like("Jason Kidd", 85); - - players_["Dirk Nowitzki"].serve("Mavericks", 1998, 2019) - .like("Steve Nash", 80) - .like("Jason Kidd", 80) - .like("Dwyane Wade", 10); - - players_["Paul Gasol"].serve("Grizzlies", 2001, 2008) - .serve("Lakers", 2008, 2014) - .serve("Bulls", 2014, 2016) - .serve("Spurs", 2016, 2019) - .serve("Bucks", 2019, 2020) - .like("Kobe Bryant", 90) - .like("Marc Gasol", 99); - - players_["Marc Gasol"].serve("Grizzlies", 2008, 2019) - .serve("Raptors", 2019, 2019) - .like("Paul Gasol", 99); - - players_["Grant Hill"].serve("Pistons", 1994, 2000) - .serve("Magic", 2000, 2007) - .serve("Suns", 2007, 2012) - .serve("Clippers", 2012, 2013) - .like("Tracy McGrady", 90); - - players_["Ray Allen"].serve("Bucks", 1996, 2003) - .serve("Thunders", 2003, 2007) - .serve("Celtics", 2007, 2012) - .serve("Heat", 2012, 2014) - .like("Rajon Rondo", 9); - - players_["Klay Thompson"].serve("Warriors", 2011, 2019) - .like("Stephen Curry", 90); - - players_["Kristaps Porzingis"].serve("Knicks", 2015, 2019) - .serve("Mavericks", 2019, 2020) - .like("Luka Doncic", 90); - - players_["Shaquile O'Neal"].serve("Magic", 1992, 1996) - .serve("Lakers", 1996, 2004) - .serve("Heat", 2004, 2008) - .serve("Suns", 2008, 2009) - .serve("Cavaliers", 2009, 2010) - .serve("Celtics", 2010, 2011) - .like("JaVale McGee", 100) - .like("Tim Duncan", 80); - - players_["JaVale McGee"].serve("Wizards", 2008, 2012) - .serve("Nuggets", 2012, 2015) - .serve("Mavericks", 2015, 2016) - .serve("Warriors", 2016, 2018) - .serve("Lakers", 2018, 2019); - - players_["Dwight Howard"].serve("Magic", 2004, 2012) - .serve("Lakers", 2012, 2013) - .serve("Rockets", 2013, 2016) - .serve("Hawks", 2016, 2017) - .serve("Hornets", 2017, 2018) - .serve("Wizards", 2018, 2019); - - players_["Amar'e Stoudemire"].serve("Suns", 2002, 2010) - .serve("Knicks", 2010, 2015) - .serve("Heat", 2015, 2016) - .like("Steve Nash", 90); - - players_["Jason Kidd"].serve("Mavericks", 1994, 1996) - .serve("Suns", 1996, 2001) - .serve("Nets", 2001, 2008) - .serve("Mavericks", 2008, 2012) - .serve("Knicks", 2012, 2013) - .like("Vince Carter", 80) - .like("Steve Nash", 90) - .like("Dirk Nowitzki", 85); - - players_["Ben Simmons"].serve("76ers", 2016, 2019) - .like("Joel Embiid", 80); - - players_["Luka Doncic"].serve("Mavericks", 2018, 2019) - .like("Dirk Nowitzki", 90) - .like("Kristaps Porzingis", 90) - .like("James Harden", 80); - - { - cpp2::ExecutionResponse resp; - std::string query = "USE nba"; - auto code = client_->execute(query, resp); - if (code != cpp2::ErrorCode::SUCCEEDED) { - return TestError() << "USE nba failed" - << static_cast(code); - } - } - { - // Insert vertices `player' - cpp2::ExecutionResponse resp; - std::string query; - query.reserve(1024); - query += "INSERT VERTEX player(name, age) VALUES "; - for (auto &player : players_) { - query += std::to_string(player.vid()); - query += ": "; - query += "("; - query += "\""; - query += player.name(); - query += "\""; - query += ","; - query += std::to_string(player.age()); - query += "),\n\t"; - } - query.resize(query.size() - 3); - auto code = client_->execute(query, resp); - if (code != cpp2::ErrorCode::SUCCEEDED) { - return TestError() << "Insert `players' failed: " - << static_cast(code); - } - } - { - // Insert vertices `team' - cpp2::ExecutionResponse resp; - std::string query; - query.reserve(1024); - query += "INSERT VERTEX team(name) VALUES "; - for (auto &team : teams_) { - query += std::to_string(team.vid()); - query += ": "; - query += "("; - query += "\""; - query += team.name(); - query += "\""; - query += "),\n\t"; - } - query.resize(query.size() - 3); - auto code = client_->execute(query, resp); - if (code != cpp2::ErrorCode::SUCCEEDED) { - return TestError() << "Insert `teams' failed: " - << static_cast(code); - } - } - { - // Insert edges `serve' - cpp2::ExecutionResponse resp; - std::string query; - query.reserve(1024); - query += "INSERT EDGE serve(start_year, end_year) VALUES"; - for (auto &player : players_) { - for (auto &serve : player.serves()) { - auto &team = std::get<0>(serve); - auto startYear = std::get<1>(serve); - auto endYear = std::get<2>(serve); - query += std::to_string(player.vid()); - query += " -> "; - query += std::to_string(teams_[team].vid()); - query += ": "; - query += "("; - query += std::to_string(startYear); - query += ", "; - query += std::to_string(endYear); - query += "),\n\t"; - } - } - query.resize(query.size() - 3); - auto code = client_->execute(query, resp); - if (code != cpp2::ErrorCode::SUCCEEDED) { - return TestError() << "Insert `serve' failed: " - << static_cast(code); - } - } - { - // Insert edges `like' - cpp2::ExecutionResponse resp; - std::string query; - query.reserve(1024); - query += "INSERT EDGE like(likeness) VALUES "; - for (auto &player : players_) { - for (auto &like : player.likes()) { - auto &other = std::get<0>(like); - auto likeness = std::get<1>(like); - query += std::to_string(player.vid()); - query += " -> "; - query += std::to_string(players_[other].vid()); - query += ": "; - query += "("; - query += std::to_string(likeness); - query += "),\n\t"; - } - } - query.resize(query.size() - 3); - auto code = client_->execute(query, resp); - if (code != cpp2::ErrorCode::SUCCEEDED) { - return TestError() << "Insert `like' failed: " - << static_cast(code); - } - } - return TestOK(); -} - -AssertionResult GoTest::removeData() { - { - cpp2::ExecutionResponse resp; - std::string cmd = "DROP SPACE nba"; - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - { - cpp2::ExecutionResponse resp; - std::string cmd = folly::stringPrintf("REMOVE HOSTS 127.0.0.1:%u", storagePort_); - auto code = client_->execute(cmd, resp); - if (cpp2::ErrorCode::SUCCEEDED != code) { - return TestError() << "Do cmd:" << cmd << " failed"; - } - } - - return TestOK(); -} - TEST_F(GoTest, OneStepOutBound) { { cpp2::ExecutionResponse resp; diff --git a/src/graph/test/OrderByTest.cpp b/src/graph/test/OrderByTest.cpp new file mode 100644 index 00000000000..038dd721e46 --- /dev/null +++ b/src/graph/test/OrderByTest.cpp @@ -0,0 +1,262 @@ +/* Copyright (c) 2019 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "base/Base.h" +#include "graph/test/TestEnv.h" +#include "graph/test/TestBase.h" +#include "graph/test/TraverseTestBase.h" +#include "meta/test/TestUtils.h" + +DECLARE_int32(load_data_interval_secs); + +namespace nebula { +namespace graph { + +class OrderByTest : public TraverseTestBase { +protected: + void SetUp() override { + TraverseTestBase::SetUp(); + // ... + } + + void TearDown() override { + // ... + TraverseTestBase::TearDown(); + } +}; + +TEST_F(OrderByTest, SyntaxError) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "ORDER BY "; + auto query = fmt; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); + } + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + auto *fmt = "GO FROM %ld OVER serve YIELD " + "$^[player].name as name, serve.start_year as start, $$[team].name" + "| ORDER BY $-.$$[team].name"; + auto query = folly::stringPrintf(fmt, player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); + } +} + +TEST_F(OrderByTest, NoInput) { + { + cpp2::ExecutionResponse resp; + auto *fmt = "ORDER BY $-.xx"; + auto query = fmt; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_EQ(nullptr, resp.get_rows()); + } + { + cpp2::ExecutionResponse resp; + auto &player = players_["Nobody"]; + auto *fmt = "GO FROM %ld OVER serve YIELD " + "$^[player].name as name, serve.start_year as start, $$[team].name as name" + "| ORDER BY $-.name"; + auto query = folly::stringPrintf(fmt, player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_EQ(nullptr, resp.get_rows()); + } +} + +TEST_F(OrderByTest, WrongFactor) { + std::string go = "GO FROM %ld OVER serve YIELD " + "$^[player].name as name, serve.start_year as start, $$[team].name as team"; + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + // Will not do sort if field name not exist in input schema + auto fmt = go + "| ORDER BY $-.abc"; + auto query = folly::stringPrintf(fmt.c_str(), player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {player.name(), 2003, "Hawks"}, + {player.name(), 2005, "Suns"}, + {player.name(), 2008, "Hornets"}, + {player.name(), 2012, "Spurs"}, + {player.name(), 2016, "Jazz"}, + }; + ASSERT_TRUE(verifyResult(resp, expected)); + } +} + +TEST_F(OrderByTest, SingleFactor) { + std::string go = "GO FROM %ld OVER serve YIELD " + "$^[player].name as name, serve.start_year as start, $$[team].name as team"; + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + auto fmt = go + "| ORDER BY $-.team"; + auto query = folly::stringPrintf(fmt.c_str(), player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {player.name(), 2003, "Hawks"}, + {player.name(), 2008, "Hornets"}, + {player.name(), 2016, "Jazz"}, + {player.name(), 2012, "Spurs"}, + {player.name(), 2005, "Suns"}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + auto fmt = go + "| ORDER BY $-.team"; + auto query = folly::stringPrintf(fmt.c_str(), player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {player.name(), 2008, "Hornets"}, + {player.name(), 2003, "Hawks"}, + {player.name(), 2016, "Jazz"}, + {player.name(), 2012, "Spurs"}, + {player.name(), 2005, "Suns"}, + }; + ASSERT_FALSE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + auto fmt = go + "| ORDER BY $-.team ASC"; + auto query = folly::stringPrintf(fmt.c_str(), player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {player.name(), 2003, "Hawks"}, + {player.name(), 2008, "Hornets"}, + {player.name(), 2016, "Jazz"}, + {player.name(), 2012, "Spurs"}, + {player.name(), 2005, "Suns"}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &player = players_["Boris Diaw"]; + auto fmt = go + "| ORDER BY $-.team DESC"; + auto query = folly::stringPrintf(fmt.c_str(), player.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {player.name(), 2005, "Suns"}, + {player.name(), 2012, "Spurs"}, + {player.name(), 2016, "Jazz"}, + {player.name(), 2008, "Hornets"}, + {player.name(), 2003, "Hawks"}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } +} + +TEST_F(OrderByTest, MultiFactors) { + std::string go = "GO FROM %ld,%ld OVER serve WHERE serve.start_year >= 2012 YIELD " + "$$[team].name as team, $^[player].name as player, " + "$^[player].age as age, serve.start_year as start"; + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto &aldridge = players_["LaMarcus Aldridge"]; + auto fmt = go + "| ORDER BY $-.team, $-.age"; + auto query = folly::stringPrintf(fmt.c_str(), boris.vid(), aldridge.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {"Jazz", boris.name(), 36, 2016}, + {"Spurs", aldridge.name(), 33, 2015}, + {"Spurs", boris.name(), 36, 2012}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto &aldridge = players_["LaMarcus Aldridge"]; + auto fmt = go + "| ORDER BY $-.team, $-.age DESC"; + auto query = folly::stringPrintf(fmt.c_str(), boris.vid(), aldridge.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {"Jazz", boris.name(), 36, 2016}, + {"Spurs", boris.name(), 36, 2012}, + {"Spurs", aldridge.name(), 33, 2015}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto &aldridge = players_["LaMarcus Aldridge"]; + auto fmt = go + "| ORDER BY $-.team DESC, $-.age"; + auto query = folly::stringPrintf(fmt.c_str(), boris.vid(), aldridge.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {"Spurs", aldridge.name(), 33, 2015}, + {"Spurs", boris.name(), 36, 2012}, + {"Jazz", boris.name(), 36, 2016}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto &aldridge = players_["LaMarcus Aldridge"]; + auto fmt = go + "| ORDER BY $-.team DESC, $-.age DESC"; + auto query = folly::stringPrintf(fmt.c_str(), boris.vid(), aldridge.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {"Spurs", boris.name(), 36, 2012}, + {"Spurs", aldridge.name(), 33, 2015}, + {"Jazz", boris.name(), 36, 2016}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto &aldridge = players_["LaMarcus Aldridge"]; + auto fmt = go + "| ORDER BY team DESC, age DESC"; + auto query = folly::stringPrintf(fmt.c_str(), boris.vid(), aldridge.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {"Spurs", boris.name(), 36, 2012}, + {"Spurs", aldridge.name(), 33, 2015}, + {"Jazz", boris.name(), 36, 2016}, + }; + ASSERT_TRUE(verifyResult(resp, expected, false)); + } +} + +TEST_F(OrderByTest, InterimResult) { + { + cpp2::ExecutionResponse resp; + auto &boris = players_["Boris Diaw"]; + auto *fmt = "GO FROM %ld OVER like | ORDER BY $-.id | GO FROM $-.id over serve"; + auto query = folly::stringPrintf(fmt, boris.vid()); + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + teams_["Spurs"].vid(), + teams_["Spurs"].vid(), + teams_["Hornets"].vid(), + }; + ASSERT_TRUE(verifyResult(resp, expected)); + } +} +} // namespace graph +} // namespace nebula diff --git a/src/graph/test/TestBase.h b/src/graph/test/TestBase.h index 65c98a9987e..d681053617d 100644 --- a/src/graph/test/TestBase.h +++ b/src/graph/test/TestBase.h @@ -164,7 +164,7 @@ class TestBase : public ::testing::Test { template AssertionResult verifyResult(const cpp2::ExecutionResponse &resp, - std::vector &expected) { + std::vector &expected, bool sortEnable = true) { if (resp.get_error_code() != cpp2::ErrorCode::SUCCEEDED) { auto *errmsg = resp.get_error_msg(); return TestError() << "Query failed with `" @@ -190,9 +190,10 @@ class TestBase : public ::testing::Test { return TestOK(); } - std::sort(rows.begin(), rows.end()); - std::sort(expected.begin(), expected.end()); - + if (sortEnable) { + std::sort(rows.begin(), rows.end()); + std::sort(expected.begin(), expected.end()); + } for (auto i = 0u; i < rows.size(); i++) { if (rows[i] != expected[i]) { return TestError() << rows[i] << " vs. " << expected[i]; diff --git a/src/graph/test/TraverseTestBase.h b/src/graph/test/TraverseTestBase.h new file mode 100644 index 00000000000..f10bedb72a3 --- /dev/null +++ b/src/graph/test/TraverseTestBase.h @@ -0,0 +1,792 @@ +/* Copyright (c) 2019 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef GRAPH_TEST_TRAVERSETESTBASE_H +#define GRAPH_TEST_TRAVERSETESTBASE_H + +#include "base/Base.h" +#include "graph/test/TestEnv.h" +#include "graph/test/TestBase.h" +#include "meta/test/TestUtils.h" + +DECLARE_int32(load_data_interval_secs); + +namespace nebula { +namespace graph { +class TraverseTestBase : public TestBase { +protected: + void SetUp() override { + TestBase::SetUp(); + // ... + } + + void TearDown() override { + // ... + TestBase::TearDown(); + } + + static void SetUpTestCase() { + client_ = gEnv->getClient(); + storagePort_ = gEnv->storageServerPort(); + + ASSERT_NE(nullptr, client_); + + ASSERT_TRUE(prepareSchema()); + + ASSERT_TRUE(prepareData()); + } + + static void TearDownTestCase() { + ASSERT_TRUE(removeData()); + client_.reset(); + } + + static AssertionResult prepareSchema(); + + static AssertionResult prepareData(); + + static AssertionResult removeData(); + + class Player final { + public: + Player(std::string name, int64_t age) { + name_ = std::move(name); + age_ = age; + vid_ = std::hash()(name_); + } + + Player(const Player&) = default; + Player(Player&&) = default; + Player& operator=(const Player&) = default; + Player& operator=(Player&&) = default; + + const std::string& name() const { + return name_; + } + + int64_t vid() const { + return vid_; + } + + int64_t age() const { + return age_; + } + + Player& serve(std::string team, int64_t startYear, int64_t endYear) { + serves_.emplace_back(std::move(team), startYear, endYear); + return *this; + } + + Player& like(std::string player, int64_t likeness) { + likes_.emplace_back(std::move(player), likeness); + return *this; + } + + const auto& serves() const { + return serves_; + } + + const auto& likes() const { + return likes_; + } + + private: + using Serve = std::tuple; + using Like = std::tuple; + std::string name_; + int64_t vid_{0}; + int64_t age_{0}; + std::vector serves_; + std::vector likes_; + }; + + template + class VertexHolder final { + public: + using KeyGetter = std::function; + using Container = std::unordered_map; + using InternalIterator = typename Container::iterator; + VertexHolder(KeyGetter getter, std::initializer_list vertices) { + getter_ = std::move(getter); + for (auto &vertex : vertices) { + vertices_.emplace(getter_(vertex), std::move(vertex)); + } + } + + const Vertex& operator[](const Key &key) const { + auto iter = vertices_.find(key); + CHECK(iter != vertices_.end()) << "Vertex not exist, key: " << key;; + return iter->second; + } + + Vertex& operator[](const Key &key) { + auto iter = vertices_.find(key); + CHECK(iter != vertices_.end()) << "Vertex not exist, key: " << key; + return iter->second; + } + + class Iterator final { + public: + explicit Iterator(InternalIterator iter) { + iter_ = iter; + } + + Iterator& operator++() { + ++iter_; + return *this; + } + + Vertex& operator*() { + return iter_->second; + } + + Vertex* operator->() { + return &iter_->second; + } + + bool operator==(const Iterator &rhs) const { + return iter_ == rhs.iter_; + } + + bool operator!=(const Iterator &rhs) const { + return !(*this == rhs); + } + + private: + InternalIterator iter_; + }; + + Iterator begin() { + return Iterator(vertices_.begin()); + } + + Iterator end() { + return Iterator(vertices_.end()); + } + + private: + KeyGetter getter_; + Container vertices_; + }; + + + class Team final { + public: + explicit Team(std::string name) { + name_ = std::move(name); + vid_ = std::hash()(name_); + } + + const std::string& name() const { + return name_; + } + + int64_t vid() const { + return vid_; + } + + private: + std::string name_; + int64_t vid_{0}; + }; + +protected: + static uint16_t storagePort_; + static std::unique_ptr client_; + static VertexHolder players_; + static VertexHolder teams_; +}; + +uint16_t TraverseTestBase::storagePort_ = 0; + +std::unique_ptr TraverseTestBase::client_; + +TraverseTestBase::VertexHolder TraverseTestBase::players_ = { + [] (const auto &player) { return player.name(); }, { + Player{"Tim Duncan", 42/*, 1997*/}, + Player{"Tony Parker", 36}, + Player{"LaMarcus Aldridge", 33}, + Player{"Rudy Gay", 32}, + Player{"Marco Belinelli", 32}, + Player{"Danny Green", 31}, + Player{"Kyle Anderson", 25}, + Player{"Aron Baynes", 32}, + Player{"Boris Diaw", 36}, + Player{"Tiago Splitter", 34}, + Player{"Cory Joseph", 27}, + Player{"David West", 38}, + Player{"Jonathon Simmons", 29}, + Player{"Dejounte Murray", 29}, + Player{"Tracy McGrady", 39}, + Player{"Kobe Bryant", 40}, + Player{"LeBron James", 34}, + Player{"Stephen Curry", 31}, + Player{"Russell Westbrook", 30}, + Player{"Kevin Durant", 30}, + Player{"James Harden", 29}, + Player{"Chris Paul", 33}, + Player{"DeAndre Jordan", 30}, + Player{"Ricky Rubio", 28}, + + Player{"Rajon Rondo", 33}, + Player{"Manu Ginobili", 41}, + Player{"Kyrie Irving", 26}, + Player{"Vince Carter", 42}, + Player{"Carmelo Anthony", 34}, + Player{"Dwyane Wade", 37}, + Player{"Joel Embiid", 25}, + Player{"Paul George", 28}, + Player{"Giannis Antetokounmpo", 24}, + Player{"Yao Ming", 38}, + Player{"Blake Griffin", 30}, + Player{"Damian Lillard", 28}, + Player{"Steve Nash", 45}, + Player{"Dirk Nowitzki", 40}, + Player{"Paul Gasol", 38}, + Player{"Marc Gasol", 34}, + Player{"Grant Hill", 46}, + Player{"Ray Allen", 43}, + Player{"Klay Thompson", 29}, + Player{"Kristaps Porzingis", 23}, + Player{"Shaquile O'Neal", 47}, + Player{"JaVale McGee", 31}, + Player{"Dwight Howard", 33}, + Player{"Amar'e Stoudemire", 36}, + Player{"Jason Kidd", 45}, + Player{"Ben Simmons", 22}, + Player{"Luka Doncic", 20}, + + Player{"Nobody", 0}, + } +}; + +TraverseTestBase::VertexHolder TraverseTestBase::teams_ = { + [] (const auto &team) { return team.name(); }, { + Team{"Warriors"}, + Team{"Nuggets"}, + Team{"Rockets"}, + Team{"Trail Blazers"}, + Team{"Spurs"}, + Team{"Thunders"}, + Team{"Jazz"}, + Team{"Clippers"}, + Team{"Kings"}, + Team{"Timberwolves"}, + Team{"Lakers"}, + Team{"Pelicans"}, + Team{"Grizzlies"}, + Team{"Mavericks"}, + Team{"Suns"}, + + Team{"Hornets"}, + Team{"Cavaliers"}, + Team{"Celtics"}, + Team{"Raptors"}, + Team{"76ers"}, + Team{"Pacers"}, + Team{"Bulls"}, + Team{"Hawks"}, + Team{"Knicks"}, + Team{"Pistons"}, + Team{"Bucks"}, + Team{"Magic"}, + Team{"Nets"}, + Team{"Wizards"}, + Team{"Heat"}, + } +}; + + +// static +AssertionResult TraverseTestBase::prepareSchema() { + { + cpp2::ExecutionResponse resp; + std::string host = folly::stringPrintf("127.0.0.1:%u", storagePort_); + std::string cmd = "ADD HOSTS " + host; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + meta::TestUtils::registerHB(network::NetworkUtils::toHosts(host).value()); + } + { + cpp2::ExecutionResponse resp; + std::string cmd = "CREATE SPACE nba(partition_num=1, replica_factor=1)"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + sleep(FLAGS_load_data_interval_secs + 1); + { + cpp2::ExecutionResponse resp; + std::string cmd = "USE nba"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + { + cpp2::ExecutionResponse resp; + std::string cmd = "CREATE TAG player(name string, age int)"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + { + cpp2::ExecutionResponse resp; + std::string cmd = "CREATE TAG team(name string)"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + { + cpp2::ExecutionResponse resp; + std::string cmd = "CREATE EDGE serve(start_year int, end_year int)"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + { + cpp2::ExecutionResponse resp; + std::string cmd = "CREATE EDGE like(likeness int)"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + sleep(FLAGS_load_data_interval_secs + 1); + return TestOK(); +} + + +AssertionResult TraverseTestBase::prepareData() { + // TODO(dutor) Maybe we should move these data into some kind of testing resources, later. + players_["Tim Duncan"].serve("Spurs", 1997, 2016) + .like("Tony Parker", 95) + .like("Manu Ginobili", 95); + + players_["Tony Parker"].serve("Spurs", 1999, 2018) + .serve("Hornets", 2018, 2019) + .like("Tim Duncan", 95) + .like("Manu Ginobili", 95) + .like("LaMarcus Aldridge", 90); + + players_["Manu Ginobili"].serve("Spurs", 2002, 2018) + .like("Tim Duncan", 90); + + players_["LaMarcus Aldridge"].serve("Trail Blazers", 2006, 2015) + .serve("Spurs", 2015, 2019) + .like("Tony Parker", 75) + .like("Tim Duncan", 75); + + players_["Rudy Gay"].serve("Grizzlies", 2006, 2013) + .serve("Raptors", 2013, 2013) + .serve("Kings", 2013, 2017) + .serve("Spurs", 2017, 2019) + .like("LaMarcus Aldridge", 70); + + players_["Marco Belinelli"].serve("Warriors", 2007, 2009) + .serve("Raptors", 2009, 2010) + .serve("Hornets", 2010, 2012) + .serve("Bulls", 2012, 2013) + .serve("Spurs", 2013, 2015) + .serve("Kings", 2015, 2016) + .serve("Hornets", 2016, 2017) + .serve("Hawks", 2017, 2018) + .serve("76ers", 2018, 2018) + .serve("Spurs", 2018, 2019) + .like("Tony Parker", 50) + .like("Tim Duncan", 55) + .like("Danny Green", 60); + + players_["Danny Green"].serve("Cavaliers", 2009, 2010) + .serve("Spurs", 2010, 2018) + .serve("Raptors", 2018, 2019) + .like("Marco Belinelli", 83) + .like("Tim Duncan", 70) + .like("LeBron James", 80); + + players_["Kyle Anderson"].serve("Spurs", 2014, 2018) + .serve("Grizzlies", 2018, 2019); + + players_["Aron Baynes"].serve("Spurs", 2013, 2015) + .serve("Pistons", 2015, 2017) + .serve("Celtics", 2017, 2019) + .like("Tim Duncan", 80); + + players_["Boris Diaw"].serve("Hawks", 2003, 2005) + .serve("Suns", 2005, 2008) + .serve("Hornets", 2008, 2012) + .serve("Spurs", 2012, 2016) + .serve("Jazz", 2016, 2017) + .like("Tony Parker", 80) + .like("Tim Duncan", 80); + + players_["Tiago Splitter"].serve("Spurs", 2010, 2015) + .serve("Hawks", 2015, 2017) + .serve("76ers", 2017, 2017) + .like("Tim Duncan", 80) + .like("Manu Ginobili", 90); + + players_["Cory Joseph"].serve("Spurs", 2011, 2015) + .serve("Raptors", 2015, 2017) + .serve("Pacers", 2017, 2019); + + players_["David West"].serve("Hornets", 2003, 2011) + .serve("Pacers", 2011, 2015) + .serve("Spurs", 2015, 2016) + .serve("Warriors", 2016, 2018); + + players_["Jonathon Simmons"].serve("Spurs", 2015, 2017) + .serve("Magic", 2017, 2019) + .serve("76ers", 2019, 2019); + + players_["Dejounte Murray"].serve("Spurs", 2016, 2019) + .like("Tim Duncan", 99) + .like("Tony Parker", 99) + .like("Manu Ginobili", 99) + .like("Marco Belinelli", 99) + .like("Danny Green", 99) + .like("LeBron James", 99) + .like("Russell Westbrook", 99) + .like("Chris Paul", 99) + .like("Kyle Anderson", 99) + .like("Kevin Durant", 99) + .like("James Harden", 99) + .like("Tony Parker", 99); + + players_["Tracy McGrady"].serve("Raptors", 1997, 2000) + .serve("Magic", 2000, 2004) + .serve("Rockets", 2004, 2010) + .serve("Spurs", 2013, 2013) + .like("Kobe Bryant", 90) + .like("Grant Hill", 90) + .like("Rudy Gay", 90); + + players_["Kobe Bryant"].serve("Lakers", 1996, 2016); + + players_["LeBron James"].serve("Cavaliers", 2003, 2010) + .serve("Heat", 2010, 2014) + .serve("Cavaliers", 2014, 2018) + .serve("Lakers", 2018, 2019) + .like("Ray Allen", 100); + + players_["Stephen Curry"].serve("Warriors", 2009, 2019); + + players_["Russell Westbrook"].serve("Thunders", 2008, 2019) + .like("Paul George", 90) + .like("James Harden", 90); + + players_["Kevin Durant"].serve("Thunders", 2007, 2016) + .serve("Warriors", 2016, 2019); + + players_["James Harden"].serve("Thunders", 2009, 2012) + .serve("Rockets", 2012, 2019) + .like("Russell Westbrook", 80); + + players_["Chris Paul"].serve("Hornets", 2005, 2011) + .serve("Clippers", 2011, 2017) + .serve("Rockets", 2017, 2021) + .like("LeBron James", 90) + .like("Carmelo Anthony", 90) + .like("Dwyane Wade", 90); + + players_["DeAndre Jordan"].serve("Clippers", 2008, 2018) + .serve("Mavericks", 2018, 2019) + .serve("Knicks", 2019, 2019); + + players_["Ricky Rubio"].serve("Timberwolves", 2011, 2017) + .serve("Jazz", 2017, 2019); + + players_["Rajon Rondo"].serve("Celtics", 2006, 2014) + .serve("Mavericks", 2014, 2015) + .serve("Kings", 2015, 2016) + .serve("Bulls", 2016, 2017) + .serve("Pelicans", 2017, 2018) + .serve("Lakers", 2018, 2019) + .like("Ray Allen", -1); + + players_["Kyrie Irving"].serve("Cavaliers", 2011, 2017) + .serve("Celtics", 2017, 2019) + .like("LeBron James", 13); + + players_["Vince Carter"].serve("Raptors", 1998, 2004) + .serve("Nets", 2004, 2009) + .serve("Magic", 2009, 2010) + .serve("Suns", 2010, 2011) + .serve("Mavericks", 2011, 2014) + .serve("Grizzlies", 2014, 2017) + .serve("Kings", 2017, 2018) + .serve("Hawks", 2018, 2019) + .like("Tracy McGrady", 90) + .like("Jason Kidd", 70); + + players_["Carmelo Anthony"].serve("Nuggets", 2003, 2011) + .serve("Knicks", 2011, 2017) + .serve("Thunders", 2017, 2018) + .serve("Rockets", 2018, 2019) + .like("LeBron James", 90) + .like("Chris Paul", 90) + .like("Dwyane Wade", 90); + + players_["Dwyane Wade"].serve("Heat", 2003, 2016) + .serve("Bulls", 2016, 2017) + .serve("Cavaliers", 2017, 2018) + .serve("Heat", 2018, 2019) + .like("LeBron James", 90) + .like("Chris Paul", 90) + .like("Carmelo Anthony", 90); + + players_["Joel Embiid"].serve("76ers", 2014, 2019) + .like("Ben Simmons", 80); + + players_["Paul George"].serve("Pacers", 2010, 2017) + .serve("Thunders", 2017, 2019) + .like("Russell Westbrook", 95); + + players_["Giannis Antetokounmpo"].serve("Bucks", 2013, 2019); + + players_["Yao Ming"].serve("Rockets", 2002, 2011) + .like("Tracy McGrady", 90) + .like("Shaquile O'Neal", 90); + + players_["Blake Griffin"].serve("Clippers", 2009, 2018) + .serve("Pistons", 2018, 2019) + .like("Chris Paul", -1); + + players_["Damian Lillard"].serve("Trail Blazers", 2012, 2019) + .like("LaMarcus Aldridge", 80); + + players_["Steve Nash"].serve("Suns", 1996, 1998) + .serve("Mavericks", 1998, 2004) + .serve("Suns", 2004, 2012) + .serve("Lakers", 2012, 2015) + .like("Amar'e Stoudemire", 90) + .like("Dirk Nowitzki", 88) + .like("Stephen Curry", 90) + .like("Jason Kidd", 85); + + players_["Dirk Nowitzki"].serve("Mavericks", 1998, 2019) + .like("Steve Nash", 80) + .like("Jason Kidd", 80) + .like("Dwyane Wade", 10); + + players_["Paul Gasol"].serve("Grizzlies", 2001, 2008) + .serve("Lakers", 2008, 2014) + .serve("Bulls", 2014, 2016) + .serve("Spurs", 2016, 2019) + .serve("Bucks", 2019, 2020) + .like("Kobe Bryant", 90) + .like("Marc Gasol", 99); + + players_["Marc Gasol"].serve("Grizzlies", 2008, 2019) + .serve("Raptors", 2019, 2019) + .like("Paul Gasol", 99); + + players_["Grant Hill"].serve("Pistons", 1994, 2000) + .serve("Magic", 2000, 2007) + .serve("Suns", 2007, 2012) + .serve("Clippers", 2012, 2013) + .like("Tracy McGrady", 90); + + players_["Ray Allen"].serve("Bucks", 1996, 2003) + .serve("Thunders", 2003, 2007) + .serve("Celtics", 2007, 2012) + .serve("Heat", 2012, 2014) + .like("Rajon Rondo", 9); + + players_["Klay Thompson"].serve("Warriors", 2011, 2019) + .like("Stephen Curry", 90); + + players_["Kristaps Porzingis"].serve("Knicks", 2015, 2019) + .serve("Mavericks", 2019, 2020) + .like("Luka Doncic", 90); + + players_["Shaquile O'Neal"].serve("Magic", 1992, 1996) + .serve("Lakers", 1996, 2004) + .serve("Heat", 2004, 2008) + .serve("Suns", 2008, 2009) + .serve("Cavaliers", 2009, 2010) + .serve("Celtics", 2010, 2011) + .like("JaVale McGee", 100) + .like("Tim Duncan", 80); + + players_["JaVale McGee"].serve("Wizards", 2008, 2012) + .serve("Nuggets", 2012, 2015) + .serve("Mavericks", 2015, 2016) + .serve("Warriors", 2016, 2018) + .serve("Lakers", 2018, 2019); + + players_["Dwight Howard"].serve("Magic", 2004, 2012) + .serve("Lakers", 2012, 2013) + .serve("Rockets", 2013, 2016) + .serve("Hawks", 2016, 2017) + .serve("Hornets", 2017, 2018) + .serve("Wizards", 2018, 2019); + + players_["Amar'e Stoudemire"].serve("Suns", 2002, 2010) + .serve("Knicks", 2010, 2015) + .serve("Heat", 2015, 2016) + .like("Steve Nash", 90); + + players_["Jason Kidd"].serve("Mavericks", 1994, 1996) + .serve("Suns", 1996, 2001) + .serve("Nets", 2001, 2008) + .serve("Mavericks", 2008, 2012) + .serve("Knicks", 2012, 2013) + .like("Vince Carter", 80) + .like("Steve Nash", 90) + .like("Dirk Nowitzki", 85); + + players_["Ben Simmons"].serve("76ers", 2016, 2019) + .like("Joel Embiid", 80); + + players_["Luka Doncic"].serve("Mavericks", 2018, 2019) + .like("Dirk Nowitzki", 90) + .like("Kristaps Porzingis", 90) + .like("James Harden", 80); + + { + cpp2::ExecutionResponse resp; + std::string query = "USE nba"; + auto code = client_->execute(query, resp); + if (code != cpp2::ErrorCode::SUCCEEDED) { + return TestError() << "USE nba failed" + << static_cast(code); + } + } + { + // Insert vertices `player' + cpp2::ExecutionResponse resp; + std::string query; + query.reserve(1024); + query += "INSERT VERTEX player(name, age) VALUES "; + for (auto &player : players_) { + query += std::to_string(player.vid()); + query += ": "; + query += "("; + query += "\""; + query += player.name(); + query += "\""; + query += ","; + query += std::to_string(player.age()); + query += "),\n\t"; + } + query.resize(query.size() - 3); + auto code = client_->execute(query, resp); + if (code != cpp2::ErrorCode::SUCCEEDED) { + return TestError() << "Insert `players' failed: " + << static_cast(code); + } + } + { + // Insert vertices `team' + cpp2::ExecutionResponse resp; + std::string query; + query.reserve(1024); + query += "INSERT VERTEX team(name) VALUES "; + for (auto &team : teams_) { + query += std::to_string(team.vid()); + query += ": "; + query += "("; + query += "\""; + query += team.name(); + query += "\""; + query += "),\n\t"; + } + query.resize(query.size() - 3); + auto code = client_->execute(query, resp); + if (code != cpp2::ErrorCode::SUCCEEDED) { + return TestError() << "Insert `teams' failed: " + << static_cast(code); + } + } + { + // Insert edges `serve' + cpp2::ExecutionResponse resp; + std::string query; + query.reserve(1024); + query += "INSERT EDGE serve(start_year, end_year) VALUES"; + for (auto &player : players_) { + for (auto &serve : player.serves()) { + auto &team = std::get<0>(serve); + auto startYear = std::get<1>(serve); + auto endYear = std::get<2>(serve); + query += std::to_string(player.vid()); + query += " -> "; + query += std::to_string(teams_[team].vid()); + query += ": "; + query += "("; + query += std::to_string(startYear); + query += ", "; + query += std::to_string(endYear); + query += "),\n\t"; + } + } + query.resize(query.size() - 3); + auto code = client_->execute(query, resp); + if (code != cpp2::ErrorCode::SUCCEEDED) { + return TestError() << "Insert `serve' failed: " + << static_cast(code); + } + } + { + // Insert edges `like' + cpp2::ExecutionResponse resp; + std::string query; + query.reserve(1024); + query += "INSERT EDGE like(likeness) VALUES "; + for (auto &player : players_) { + for (auto &like : player.likes()) { + auto &other = std::get<0>(like); + auto likeness = std::get<1>(like); + query += std::to_string(player.vid()); + query += " -> "; + query += std::to_string(players_[other].vid()); + query += ": "; + query += "("; + query += std::to_string(likeness); + query += "),\n\t"; + } + } + query.resize(query.size() - 3); + auto code = client_->execute(query, resp); + if (code != cpp2::ErrorCode::SUCCEEDED) { + return TestError() << "Insert `like' failed: " + << static_cast(code); + } + } + return TestOK(); +} + +AssertionResult TraverseTestBase::removeData() { + { + cpp2::ExecutionResponse resp; + std::string cmd = "DROP SPACE nba"; + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + { + cpp2::ExecutionResponse resp; + std::string cmd = folly::stringPrintf("REMOVE HOSTS 127.0.0.1:%u", storagePort_); + auto code = client_->execute(cmd, resp); + if (cpp2::ErrorCode::SUCCEEDED != code) { + return TestError() << "Do cmd:" << cmd << " failed"; + } + } + + return TestOK(); +} +} // namespace graph +} // namespace nebula + +#endif // GRAPH_TEST_TRAVERSETESTBASE_H diff --git a/src/parser/MaintainSentences.cpp b/src/parser/MaintainSentences.cpp index e3654585753..e3d5bf9d06d 100644 --- a/src/parser/MaintainSentences.cpp +++ b/src/parser/MaintainSentences.cpp @@ -210,5 +210,4 @@ std::string YieldSentence::toString() const { buf += yieldColumns_->toString(); return buf; } - } // namespace nebula diff --git a/src/parser/MaintainSentences.h b/src/parser/MaintainSentences.h index 34a978c82e6..f4773ea3341 100644 --- a/src/parser/MaintainSentences.h +++ b/src/parser/MaintainSentences.h @@ -437,7 +437,6 @@ class YieldSentence final : public Sentence { private: std::unique_ptr yieldColumns_; }; - } // namespace nebula #endif // PARSER_MAINTAINSENTENCES_H_ diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h index abeb06ccfcb..7bbebd0e91e 100644 --- a/src/parser/Sentence.h +++ b/src/parser/Sentence.h @@ -50,6 +50,7 @@ class Sentence { kGrant, kRevoke, kChangePassword, + kOrderBy, }; Kind kind() const { diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp index 88f95efe92a..195d0ecca4b 100644 --- a/src/parser/TraverseSentences.cpp +++ b/src/parser/TraverseSentences.cpp @@ -98,4 +98,31 @@ std::string AssignmentSentence::toString() const { return buf; } +std::string OrderFactor::toString() const { + switch (orderType_) { + case ASCEND: + return folly::stringPrintf("%s ASC,", expr_->toString().c_str()); + case DESCEND: + return folly::stringPrintf("%s DESC,", expr_->toString().c_str()); + default: + LOG(FATAL) << "Unkown Order Type: " << orderType_; + return ""; + } +} + +std::string OrderFactors::toString() const { + std::string buf; + buf.reserve(256); + for (auto &factor : factors_) { + buf += factor->toString(); + } + if (!buf.empty()) { + buf.resize(buf.size() - 1); + } + return buf; +} + +std::string OrderBySentence::toString() const { + return folly::stringPrintf("ORDER BY %s", orderFactors_->toString().c_str()); +} } // namespace nebula diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h index 75e4974dc6f..e937608fc1c 100644 --- a/src/parser/TraverseSentences.h +++ b/src/parser/TraverseSentences.h @@ -200,7 +200,69 @@ class AssignmentSentence final : public Sentence { std::unique_ptr sentence_; }; +class OrderFactor final { +public: + enum OrderType : uint8_t { + ASCEND, + DESCEND + }; + + OrderFactor(Expression *expr, OrderType op) { + expr_.reset(expr); + orderType_ = op; + } + + Expression* expr() { + return expr_.get(); + } + + OrderType orderType() { + return orderType_; + } + + std::string toString() const; + +private: + std::unique_ptr expr_; + OrderType orderType_; +}; + +class OrderFactors final { +public: + void addFactor(OrderFactor *factor) { + factors_.emplace_back(factor); + } + std::vector factors() { + std::vector result; + result.resize(factors_.size()); + auto get = [] (auto &factor) { return factor.get(); }; + std::transform(factors_.begin(), factors_.end(), result.begin(), get); + return result; + } + + std::string toString() const; + +private: + std::vector> factors_; +}; + +class OrderBySentence final : public Sentence { +public: + explicit OrderBySentence(OrderFactors *factors) { + orderFactors_.reset(factors); + kind_ = Kind::kOrderBy; + } + + std::vector factors() { + return orderFactors_->factors(); + } + + std::string toString() const override; + +private: + std::unique_ptr orderFactors_; +}; } // namespace nebula #endif // PARSER_TRAVERSESENTENCES_H_ diff --git a/src/parser/parser.yy b/src/parser/parser.yy index bd0b3dcbc25..6a35875b522 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -74,6 +74,8 @@ class GraphScanner; nebula::SchemaPropItem *create_schema_prop_item; nebula::SchemaPropList *alter_schema_prop_list; nebula::SchemaPropItem *alter_schema_prop_item; + nebula::OrderFactor *order_factor; + nebula::OrderFactors *order_factors; } /* destructors */ @@ -92,6 +94,7 @@ class GraphScanner; %token KW_PASSWORD KW_CHANGE KW_ROLE KW_GOD KW_ADMIN KW_GUEST KW_GRANT KW_REVOKE KW_ON %token KW_ROLES KW_BY %token KW_TTL_DURATION KW_TTL_COL +%token KW_ORDER KW_ASC /* symbols */ %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA %token PIPE OR AND LT LE GT GE EQ NE ADD SUB MUL DIV MOD NOT NEG ASSIGN @@ -146,6 +149,8 @@ class GraphScanner; %type create_schema_prop_item %type alter_schema_prop_list %type alter_schema_prop_item +%type order_factor +%type order_factors %type port @@ -168,6 +173,7 @@ class GraphScanner; %type show_sentence add_hosts_sentence remove_hosts_sentence create_space_sentence describe_space_sentence %type drop_space_sentence %type yield_sentence +%type order_by_sentence %type create_user_sentence alter_user_sentence drop_user_sentence change_password_sentence %type grant_sentence revoke_sentence %type sentence @@ -709,10 +715,53 @@ drop_edge_sentence } ; +order_factor + : input_ref_expression { + $$ = new OrderFactor($1, OrderFactor::ASCEND); + } + | input_ref_expression KW_ASC { + $$ = new OrderFactor($1, OrderFactor::ASCEND); + } + | input_ref_expression KW_DESC { + $$ = new OrderFactor($1, OrderFactor::DESCEND); + } + | LABEL { + auto inputRef = new InputPropertyExpression($1); + $$ = new OrderFactor(inputRef, OrderFactor::ASCEND); + } + | LABEL KW_ASC { + auto inputRef = new InputPropertyExpression($1); + $$ = new OrderFactor(inputRef, OrderFactor::ASCEND); + } + | LABEL KW_DESC { + auto inputRef = new InputPropertyExpression($1); + $$ = new OrderFactor(inputRef, OrderFactor::DESCEND); + } + ; + +order_factors + : order_factor { + auto factors = new OrderFactors(); + factors->addFactor($1); + $$ = factors; + } + | order_factors COMMA order_factor { + $1->addFactor($3); + $$ = $1; + } + ; + +order_by_sentence + : KW_ORDER KW_BY order_factors { + $$ = new OrderBySentence($3); + } + ; + traverse_sentence : go_sentence { $$ = $1; } | match_sentence { $$ = $1; } | find_sentence { $$ = $1; } + | order_by_sentence { $$ = $1; } ; set_sentence diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index a23b3db7c3b..ce35dfdd71c 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -96,6 +96,8 @@ BY ([Bb][Yy]) IN ([Ii][Nn]) TTL_DURATION ([Tt][Tt][Ll][_][Dd][Uu][Rr][Aa][Tt][Ii][Oo][Nn]) TTL_COL ([Tt][Tt][Ll][_][Cc][Oo][Ll]) +ORDER ([Oo][Rr][Dd][Ee][Rr]) +ASC ([Aa][Ss][Cc]) LABEL ([a-zA-Z][_a-zA-Z0-9]*) DEC ([0-9]) @@ -184,6 +186,8 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) {TTL_COL} { return TokenType::KW_TTL_COL; } {TRUE} { yylval->boolval = true; return TokenType::BOOL; } {FALSE} { yylval->boolval = false; return TokenType::BOOL; } +{ORDER} { return TokenType::KW_ORDER; } +{ASC} { return TokenType::KW_ASC; } "." { return TokenType::DOT; } "," { return TokenType::COMMA; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index ad6f5fc0429..ce47143547a 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -739,6 +739,62 @@ TEST(Parser, Annotation) { } } +TEST(Parser, Agg) { + { + GQLParser parser; + std::string query = "ORDER BY $-.id"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name | " + "ORDER BY name"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name | " + "ORDER BY $-.name"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name | " + "ORDER BY $-.name ASC"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name | " + "ORDER BY $-.name DESC"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name, friend.age as age | " + "ORDER BY $-.name ASC, $-.age DESC"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "GO FROM 1 over friend " + "YIELD friend.name as name, friend.age as age | " + "ORDER BY name ASC, age DESC"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } +} TEST(Parser, ReentrantRecoveryFromFailure) { GQLParser parser; diff --git a/src/parser/test/ScannerTest.cpp b/src/parser/test/ScannerTest.cpp index 8025d2eb6ab..b5dbe737ca6 100644 --- a/src/parser/test/ScannerTest.cpp +++ b/src/parser/test/ScannerTest.cpp @@ -317,6 +317,13 @@ TEST(Scanner, Basic) { CHECK_SEMANTIC_TYPE("TTL_COL", TokenType::KW_TTL_COL), CHECK_SEMANTIC_TYPE("ttl_col", TokenType::KW_TTL_COL), CHECK_SEMANTIC_TYPE("Ttl_col", TokenType::KW_TTL_COL), + CHECK_SEMANTIC_TYPE("ORDER", TokenType::KW_ORDER), + CHECK_SEMANTIC_TYPE("Order", TokenType::KW_ORDER), + CHECK_SEMANTIC_TYPE("order", TokenType::KW_ORDER), + CHECK_SEMANTIC_TYPE("ASC", TokenType::KW_ASC), + CHECK_SEMANTIC_TYPE("Asc", TokenType::KW_ASC), + CHECK_SEMANTIC_TYPE("asc", TokenType::KW_ASC), + CHECK_SEMANTIC_TYPE("_type", TokenType::TYPE_PROP), CHECK_SEMANTIC_TYPE("_id", TokenType::ID_PROP),