diff --git a/src/context/QueryContext.h b/src/context/QueryContext.h index b809b92305f4c9d0ab7cf1e51412ad828983d3d9..b318bc80c477713f89c809c733993874a173a77f 100644 --- a/src/context/QueryContext.h +++ b/src/context/QueryContext.h @@ -106,6 +106,10 @@ public: return sm_; } + meta::IndexManager* indexMng() const { + return im_; + } + storage::GraphStorageClient* getStorageClient() const { return storageClient_; } diff --git a/src/mock/MetaCache.cpp b/src/mock/MetaCache.cpp index 2ab26cde7a215c59554c8185cc5fa93679298b26..d04a2b2bc08f3bb126a935131782215314c79dcc 100644 --- a/src/mock/MetaCache.cpp +++ b/src/mock/MetaCache.cpp @@ -266,11 +266,98 @@ Status MetaCache::AlterEdge(const meta::cpp2::AlterEdgeReq &req) { return alterSchemaProp(schema, prop); } -Status MetaCache::createTagIndex(const meta::cpp2::CreateTagIndexReq&) { +Status MetaCache::createTagIndex(const meta::cpp2::CreateTagIndexReq& req, IndexID &indexId) { + folly::RWSpinLock::WriteHolder holder(lock_); + CHECK_SPACE_ID(req.get_space_id()); + auto ifNotExists = req.get_if_not_exists(); + auto tagName = req.get_tag_name(); + auto indexName = req.get_index_name(); + auto &tagIndexes = spaceIter->second.tagIndexes_; + auto findIndexIter = tagIndexes.find(indexName); + if (ifNotExists && findIndexIter != tagIndexes.end()) { + indexId = findIndexIter->second.get_index_id(); + return Status::OK(); + } + if (findIndexIter != tagIndexes.end()) { + LOG(INFO) << "\tIndex already exist"; + return Status::Error("Index already exist"); + } + + if (req.get_fields().empty()) { + return Status::Error("column is empty"); + } + + auto &tagSchemas = spaceIter->second.tagSchemas_; + auto findTagIter = tagSchemas.find(tagName); + if (findTagIter == tagSchemas.end()) { + return Status::Error("Tag Not found"); + } + auto tagId = findTagIter->second.get_tag_id(); + + indexId = ++id_; + meta::cpp2::IndexItem indexItem; + meta::cpp2::SchemaID schemaID; + schemaID.set_tag_id(tagId); + indexItem.set_schema_id(std::move(schemaID)); + indexItem.set_schema_name(tagName); + indexItem.set_index_id(indexId); + indexItem.set_index_name(indexName); + + std::vector<meta::cpp2::ColumnDef> columns; + for (auto &field : req.get_fields()) { + meta::cpp2::ColumnDef column; + column.set_name(std::move(field)); + columns.emplace_back(std::move(column)); + } + indexItem.set_fields(std::move(columns)); + tagIndexes[indexName] = std::move(indexItem); return Status::OK(); } -Status MetaCache::createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq&) { +Status MetaCache::createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq& req, IndexID &indexId) { + folly::RWSpinLock::WriteHolder holder(lock_); + CHECK_SPACE_ID(req.get_space_id()); + auto ifNotExists = req.get_if_not_exists(); + auto edgeName = req.get_edge_name(); + auto indexName = req.get_index_name(); + auto &edgeIndexes = spaceIter->second.edgeIndexes_; + auto findIndexIter = edgeIndexes.find(indexName); + if (ifNotExists && findIndexIter != edgeIndexes.end()) { + indexId = findIndexIter->second.get_index_id(); + return Status::OK(); + } + if (findIndexIter != edgeIndexes.end()) { + return Status::Error("Index already exist"); + } + + if (req.get_fields().empty()) { + return Status::Error("column is empty"); + } + + auto &edgeSchemas = spaceIter->second.edgeSchemas_; + auto findEdgeIter = edgeSchemas.find(edgeName); + if (findEdgeIter == edgeSchemas.end()) { + return Status::Error("Edge Not found"); + } + auto edgeType = findEdgeIter->second.get_edge_type(); + + indexId = ++id_; + meta::cpp2::IndexItem indexItem; + meta::cpp2::SchemaID schemaID; + schemaID.set_edge_type(edgeType); + indexItem.set_schema_id(std::move(schemaID)); + indexItem.set_schema_name(edgeName); + indexItem.set_index_id(indexId); + indexItem.set_index_name(indexName); + + std::vector<meta::cpp2::ColumnDef> columns; + for (auto &field : req.get_fields()) { + meta::cpp2::ColumnDef column; + column.set_name(std::move(field)); + columns.emplace_back(std::move(column)); + } + indexItem.set_fields(std::move(columns)); + edgeIndexes[indexName] = std::move(indexItem); return Status::OK(); } diff --git a/src/mock/MetaCache.h b/src/mock/MetaCache.h index c71a4ce4b26baa2fb91ccbfec3f2c61781130e3d..b99c121f802cbd4f72e53e11f5a56f1ca88eeb3b 100644 --- a/src/mock/MetaCache.h +++ b/src/mock/MetaCache.h @@ -50,9 +50,9 @@ public: Status AlterEdge(const meta::cpp2::AlterEdgeReq &req); - Status createTagIndex(const meta::cpp2::CreateTagIndexReq &req); + Status createTagIndex(const meta::cpp2::CreateTagIndexReq &req, IndexID &indexId); - Status createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq &req); + Status createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq &req, IndexID &indexId); Status dropTagIndex(const meta::cpp2::DropTagIndexReq &req); diff --git a/src/mock/MockMetaServiceHandler.cpp b/src/mock/MockMetaServiceHandler.cpp index 67d0e6532b2fc11bc3bef9eb7f6f043567c78702..74d4b4e055c891d9724559f4596a96ba6fb9b345 100644 --- a/src/mock/MockMetaServiceHandler.cpp +++ b/src/mock/MockMetaServiceHandler.cpp @@ -357,8 +357,23 @@ MockMetaServiceHandler::future_listEdges(const meta::cpp2::ListEdgesReq& req) { } folly::Future<meta::cpp2::ExecResp> -MockMetaServiceHandler::future_createTagIndex(const meta::cpp2::CreateTagIndexReq&) { - RETURN_SUCCESSED(); +MockMetaServiceHandler::future_createTagIndex(const meta::cpp2::CreateTagIndexReq& req) { + folly::Promise<meta::cpp2::ExecResp> promise; + auto future = promise.getFuture(); + meta::cpp2::ExecResp resp; + IndexID indexId = 0; + auto status = MetaCache::instance().createTagIndex(req, indexId); + if (!status.ok()) { + resp.set_code(meta::cpp2::ErrorCode::E_UNKNOWN); + promise.setValue(std::move(resp)); + return future; + } + resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED); + meta::cpp2::ID id; + id.set_index_id(indexId); + resp.set_id(std::move(id)); + promise.setValue(std::move(resp)); + return future; } folly::Future<meta::cpp2::ExecResp> diff --git a/src/mock/test/CMakeLists.txt b/src/mock/test/CMakeLists.txt index b2cf9f28225bfbe2df0ac689c6171819bff900c5..88f56a44dec75000dde3e5a274f647ddceb44d64 100644 --- a/src/mock/test/CMakeLists.txt +++ b/src/mock/test/CMakeLists.txt @@ -84,6 +84,22 @@ nebula_add_test( gtest_main ) +nebula_add_test( + NAME + index_test + SOURCES + IndexTest.cpp + OBJECTS + ${GRAPH_TEST_LIB} + LIBRARIES + ${THRIFT_LIBRARIES} + proxygenhttpserver + proxygenlib + wangle + gtest + gtest_main +) + nebula_add_test( NAME acl_test diff --git a/src/mock/test/IndexTest.cpp b/src/mock/test/IndexTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c1cb0eb72e05f10c8013e0f024f976b692af434a --- /dev/null +++ b/src/mock/test/IndexTest.cpp @@ -0,0 +1,119 @@ +/* Copyright (c) 2020 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 "mock/test/TestEnv.h" +#include "mock/test/TestBase.h" +#include <gtest/gtest.h> + +DECLARE_int32(heartbeat_interval_secs); + +namespace nebula { +namespace graph { + +class IndexTest : public TestBase { +protected: + void SetUp() override { + TestBase::SetUp(); + } + + void TearDown() override { + TestBase::TearDown(); + } + + static void SetUpTestCase() { + client_ = gEnv->getGraphClient(); + ASSERT_NE(nullptr, client_); + } + + static void TearDownTestCase() { + client_.reset(); + } + +protected: + static std::unique_ptr<GraphClient> client_; +}; + +std::unique_ptr<GraphClient> IndexTest::client_{nullptr}; + +TEST_F(IndexTest, TagIndex) { + ASSERT_NE(nullptr, client_); + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE SPACE tag_index_space(partition_num=1, replica_factor=1)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + sleep(FLAGS_heartbeat_interval_secs + 1); + { + cpp2::ExecutionResponse resp; + std::string query = "USE tag_index_space"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE TAG tag_1(col1 string, col2 int, col3 double, col4 timestamp)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + sleep(FLAGS_heartbeat_interval_secs + 1); + + { + cpp2::ExecutionResponse resp; + std::string query = "SHOW TAGS;"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector<std::string> colNames = {"Name"}; + ASSERT_TRUE(verifyColNames(resp, colNames)); + std::vector<std::vector<Value>> values = {{"tag_1"}}; + ASSERT_TRUE(verifyValues(resp, values)); + } + + // Single Tag Single Field + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE TAG INDEX single_tag_index ON tag_1(col2)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + // Property is empty + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE TAG INDEX single_tag_index ON tag_1()"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); + } + // Single Tag Multi Field + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE TAG INDEX multi_tag_index ON tag_1(col2, col3)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + std::string query = "CREATE TAG INDEX disorder_tag_index ON tag_1(col3, col2)"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + std::string query = "DROP TAG INDEX IF EXISTS not_exists_tag_index"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + std::string query = "DROP SPACE tag_index_space"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } +} + + +} // namespace graph +} // namespace nebula + diff --git a/src/planner/Maintain.cpp b/src/planner/Maintain.cpp index d73d0eb27efbfbf231b53bda008749439aa3b6a9..14911fafdb0ac55ff415ed20d865a0fdbe8a57c1 100644 --- a/src/planner/Maintain.cpp +++ b/src/planner/Maintain.cpp @@ -17,7 +17,7 @@ namespace graph { std::unique_ptr<cpp2::PlanNodeDescription> CreateSchemaNode::explain() const { auto desc = SingleInputNode::explain(); addDescription("name", name_, desc.get()); - addDescription("ifNotExists", folly::to<std::string>(ifNotExists_), desc.get()); + addDescription("ifNotExists", util::toJson(ifNotExists_), desc.get()); addDescription("schema", folly::toJson(util::toJson(schema_)), desc.get()); return desc; } @@ -49,7 +49,7 @@ std::unique_ptr<cpp2::PlanNodeDescription> CreateIndexNode::explain() const { addDescription("schemaName", schemaName_, desc.get()); addDescription("indexName", indexName_, desc.get()); addDescription("fields", folly::toJson(util::toJson(fields_)), desc.get()); - addDescription("ifNotExists", folly::to<std::string>(ifNotExists_), desc.get()); + addDescription("ifNotExists", util::toJson(ifNotExists_), desc.get()); return desc; } diff --git a/tests/maintain/test_index.py b/tests/maintain/test_index.py new file mode 100644 index 0000000000000000000000000000000000000000..bce5fb509ce30c1619f1edc4c22e4c1f4dad2749 --- /dev/null +++ b/tests/maintain/test_index.py @@ -0,0 +1,107 @@ +# --coding:utf-8-- +# +# Copyright (c) 2020 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. + + +import time + +from tests.common.nebula_test_suite import NebulaTestSuite +from tests.common.nebula_test_suite import T_EMPTY, T_NULL + +class TestIndex(NebulaTestSuite): + + @classmethod + def prepare(self): + resp = self.client.execute('CREATE SPACE index_space(partition_num=9)') + self.check_resp_succeeded(resp) + + time.sleep(self.delay) + resp = self.client.execute('USE index_space') + self.check_resp_succeeded(resp) + + resp = self.client.execute('CREATE TAG tag_1(col1 string, col2 int, col3 double, col4 timestamp)') + self.check_resp_succeeded(resp) + + resp = self.client.execute('CREATE EDGE edge_1(col1 string, col2 int, col3 double, col4 timestamp)') + self.check_resp_succeeded(resp) + time.sleep(self.delay) + + @classmethod + def cleanup(self): + resp = self.execute('DROP SPACE index_space;') + self.check_resp_succeeded(resp) + + def test_tag_index(self): + # Single Tag Single Field + resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1(col2)') + self.check_resp_succeeded(resp) + + # Duplicate Index + resp = self.client.execute('CREATE TAG INDEX duplicate_tag_index_1 ON tag_1(col2)') + self.check_resp_failed(resp) + + # Tag not exist + resp = self.client.execute('CREATE TAG INDEX single_person_index ON student(name)') + self.check_resp_failed(resp) + + # Property not exist + resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1(col5)') + self.check_resp_failed(resp) + + # Property is empty + resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1()') + self.check_resp_failed(resp) + + # Single Tag Multi Field + resp = self.client.execute('CREATE TAG INDEX multi_tag_index ON tag_1(col2, col3)') + self.check_resp_succeeded(resp) + + # Duplicate Index + resp = self.client.execute('CREATE TAG INDEX duplicate_person_index ON tag_1(col2, col3)') + self.check_resp_failed(resp) + + # Duplicate Field + resp = self.client.execute('CREATE TAG INDEX duplicate_index ON tag_1(col2, col2)') + self.check_resp_failed(resp) + + resp = self.client.execute('CREATE TAG INDEX disorder_tag_index ON tag_1(col3, col2)') + self.check_resp_succeeded(resp) + + def test_edge_index(self): + # Single Tag Single Field + resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1(col2)') + self.check_resp_succeeded(resp) + + # Duplicate Index + resp = self.client.execute('CREATE EDGE INDEX duplicate_edge_1_index ON edge_1(col2)') + self.check_resp_failed(resp) + + # Edge not exist + resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1_ship(name)') + self.check_resp_failed(resp) + + # Property not exist + resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1(startTime)') + self.check_resp_failed(resp) + + # Property is empty + resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1()') + self.check_resp_failed(resp) + + # Single Edge Multi Field + resp = self.client.execute('CREATE EDGE INDEX multi_edge_1_index ON edge_1(col2, col3)') + self.check_resp_succeeded(resp) + + # Duplicate Index + resp = self.client.execute('CREATE EDGE INDEX duplicate_edge_1_index ON edge_1(col2, col3)') + self.check_resp_failed(resp) + + # Duplicate Field + resp = self.client.execute('CREATE EDGE INDEX duplicate_index ON edge_1(col2, col2)') + self.check_resp_failed(resp) + + resp = self.client.execute('CREATE EDGE INDEX disorder_edge_1_index ON edge_1(col3, col2)') + self.check_resp_succeeded(resp)