diff --git a/src/common/utils/NebulaKeyUtils.h b/src/common/utils/NebulaKeyUtils.h index 62379df1a0d..c30835c6152 100644 --- a/src/common/utils/NebulaKeyUtils.h +++ b/src/common/utils/NebulaKeyUtils.h @@ -36,6 +36,13 @@ enum class NebulaSystemKeyType : uint32_t { kSystemPart = 0x00000002, }; +enum class NebulaBoundValueType : uint8_t { + kMax = 0x0001, + kMin = 0x0002, + kSubtraction = 0x0003, + kAddition = 0x0004, +}; + /** * This class supply some utils for transition between Vertex/Edge and key in kvstore. * */ @@ -248,6 +255,107 @@ class NebulaKeyUtils final { return rawKey.subpiece(0, rawKey.size() - sizeof(int64_t)); } + // Only int and double are supported + static std::string boundVariant(nebula::cpp2::SupportedType type, + NebulaBoundValueType op, + const VariantType& v = 0L) { + switch (op) { + case NebulaBoundValueType::kMax : { + return std::string(8, '\377'); + } + case NebulaBoundValueType::kMin : { + return std::string(8, '\0'); + } + case NebulaBoundValueType::kSubtraction : { + std::string str; + if (type == nebula::cpp2::SupportedType::INT) { + str = encodeInt64(boost::get(v)); + } else { + str = encodeDouble(boost::get(v)); + } + std::vector bytes(str.begin(), str.end()); + + for (size_t i = bytes.size();; i--) { + if (i > 0) { + if (bytes[i-1]-- != 0) break; + } else { + return std::string(bytes.size(), '\0'); + } + } + return std::string(bytes.begin(), bytes.end()); + } + case NebulaBoundValueType::kAddition : { + std::string str; + if (type == nebula::cpp2::SupportedType::INT) { + str = encodeInt64(boost::get(v)); + } else { + str = encodeDouble(boost::get(v)); + } + std::vector bytes(str.begin(), str.end()); + for (size_t i = bytes.size();; i--) { + if (i > 0) { + if (bytes[i-1]++ != 255) break; + } else { + return std::string(bytes.size(), '\377'); + } + } + return std::string(bytes.begin(), bytes.end()); + } + } + return ""; + } + + static bool checkAndCastVariant(nebula::cpp2::SupportedType sType, + VariantType& v) { + nebula::cpp2::SupportedType type = nebula::cpp2::SupportedType::UNKNOWN; + switch (v.which()) { + case VAR_INT64: { + type = nebula::cpp2::SupportedType::INT; + break; + } + case VAR_DOUBLE: { + type = nebula::cpp2::SupportedType::DOUBLE; + break; + } + case VAR_BOOL: { + type = nebula::cpp2::SupportedType::BOOL; + break; + } + case VAR_STR: { + type = nebula::cpp2::SupportedType::STRING; + break; + } + default: + return false; + } + if (sType != type) { + switch (sType) { + case nebula::cpp2::SupportedType::INT: + case nebula::cpp2::SupportedType::TIMESTAMP: { + v = Expression::toInt(v); + break; + } + case nebula::cpp2::SupportedType::BOOL: { + v = Expression::toBool(v); + break; + } + case nebula::cpp2::SupportedType::FLOAT: + case nebula::cpp2::SupportedType::DOUBLE: { + v = Expression::toDouble(v); + break; + } + case nebula::cpp2::SupportedType::STRING: { + v = Expression::toString(v); + break; + } + default: { + return false; + } + } + } + return true; + } + static std::string encodeVariant(const VariantType& v) { switch (v.which()) { case VAR_INT64: diff --git a/src/graph/LookupExecutor.cpp b/src/graph/LookupExecutor.cpp index 145a7e149eb..e460d569a0d 100644 --- a/src/graph/LookupExecutor.cpp +++ b/src/graph/LookupExecutor.cpp @@ -5,6 +5,7 @@ */ #include "graph/LookupExecutor.h" +#include namespace nebula { namespace graph { @@ -157,7 +158,7 @@ Status LookupExecutor::optimize() { if (!status.ok()) { break; } - status = findValidIndex(); + status = findOptimalIndex(); if (!status.ok()) { break; } @@ -240,77 +241,225 @@ Status LookupExecutor::checkFilter() { return Status::OK(); } -Status -LookupExecutor::findValidIndex() { - std::vector> indexes; - std::set filterCols; - for (auto& filter : filters_) { - filterCols.insert(filter.first); - } - /** - * step 1 : found out all valid indexes. for example : - * tag (col1 , col2, col3) - * index1 on tag (col1, col2) - * index2 on tag (col2, col1) - * index3 on tag (col3) - * - * where where clause is below : - * col1 > 1 and col2 > 1 --> index1 and index2 are valid. - * col1 > 1 --> index1 is valid. - * col2 > 1 --> index2 is valid. - * col3 > 1 --> index3 is valid. - */ - for (auto& index : indexes_) { - bool matching = true; - size_t filterNum = 1; +Status LookupExecutor::findOptimalIndex() { + // The rule of priority is '==' --> '< > <= >=' --> '!=' + // Step 1 : find out all valid indexes for where condition. + auto validIndexes = findValidIndex(); + if (validIndexes.empty()) { + LOG(ERROR) << "No valid index found"; + return Status::IndexNotFound(); + } + // Step 2 : find optimal indexes for equal condition. + auto indexesEq = findIndexForEqualScan(validIndexes); + if (indexesEq.size() == 1) { + index_ = indexesEq[0]->get_index_id(); + return Status::OK(); + } + // Step 3 : find optimal indexes for range condition. + auto indexesRange = findIndexForRangeScan(indexesEq); + + // At this stage, all the optimizations are done. + // Because the storage layer only needs one. So return first one of indexesRange. + index_ = indexesRange[0]->get_index_id(); + return Status::OK(); +} + +std::vector> LookupExecutor::findValidIndexWithStr() { + std::vector> validIndexes; + // Because the string type is a variable-length field, + // the WHERE condition must cover all fields in index for performance. + + // Maybe there are duplicate fields in the WHERE condition, + // so need to using std::set remove duplicate field at here, for example : + // where col1 > 1 and col1 < 5, the field col1 will appear twice in filters_. + std::set cols; + for (const auto& filter : filters_) { + cols.emplace(filter.first); + } + for (const auto& index : indexes_) { + if (index->get_fields().size() != cols.size()) { + continue; + } + bool allColsHint = true; for (const auto& field : index->get_fields()) { - auto it = std::find_if(filterCols.begin(), filterCols.end(), - [field](const auto &name) { - return field.get_name() == name; + auto it = std::find_if(cols.begin(), cols.end(), + [field](const auto &col) { + return field.get_name() == col; }); - if (it == filterCols.end()) { - matching = false; + if (it == cols.end()) { + allColsHint = false; break; } - if (filterNum++ == filterCols.size()) { + } + if (allColsHint) { + validIndexes.emplace_back(index); + } + } + if (validIndexes.empty()) { + LOG(WARNING) << "The WHERE condition contains fields of string type, " + << "So the WHERE condition must cover all fields in index."; + } + return validIndexes; +} + +std::vector> LookupExecutor::findValidIndexNoStr() { + std::vector> validIndexes; + // Find indexes for match all fields by where condition. + // Non-string type fields do not need to involve all fields + for (const auto& index : indexes_) { + bool allColsHint = true; + const auto& fields = index->get_fields(); + // If index including string type fields, skip this index. + auto stringField = std::find_if(fields.begin(), fields.end(), [](const auto &f) { + return f.get_type().get_type() == nebula::cpp2::SupportedType::STRING; + }); + if (stringField != fields.end()) { + continue; + } + for (const auto& filter : filters_) { + auto it = std::find_if(fields.begin(), fields.end(), + [filter](const auto &field) { + return field.get_name() == filter.first; + }); + if (it == fields.end()) { + allColsHint = false; break; } } - if (!matching || index->get_fields().size() < filterCols.size()) { - continue; + if (allColsHint) { + validIndexes.emplace_back(index); } - indexes.emplace_back(index); } + // If the first field of the index does not match any condition, the index is invalid. + // remove it from validIndexes. + if (!validIndexes.empty()) { + auto index = validIndexes.begin(); + while (index != validIndexes.end()) { + const auto& fields = index->get()->get_fields(); + auto it = std::find_if(filters_.begin(), filters_.end(), + [fields](const auto &filter) { + return filter.first == fields[0].get_name(); + }); + if (it == filters_.end()) { + validIndexes.erase(index); + } else { + index++; + } + } + } + return validIndexes; +} - if (indexes.empty()) { - return Status::IndexNotFound(); +std::vector> LookupExecutor::findValidIndex() { + // Check contains string type field from where condition + auto *sm = ectx()->schemaManager(); + auto schema = isEdge_ + ? sm->getEdgeSchema(spaceId_, tagOrEdge_) + : sm->getTagSchema(spaceId_, tagOrEdge_); + if (schema == nullptr) { + LOG(ERROR) << "No schema found : " << from_; + return {}; } - /** - * step 2 , if have multiple valid indexes, get the best one. - * for example : if where clause is : - * col1 > 1 and col2 > 1 --> index1 and index2 are valid. get one of these at random. - * col1 > 1 and col2 == 1 --> index1 and index2 are valid. - * but need choose one for storage layer. - * here index2 is chosen because col2 have a equivalent value. - */ - std::map indexHint; + // Check conditions whether contains string type for where condition. + // Different optimization rules: + // Contains string type : Conditions need to match all the index columns. for example + // where c1 == 'a' and c2 == 'b' + // index1 (c1, c2) is valid + // index2 (c1, c2, c3) is invalid. + // so index1 should be hit. + // + // Not contains string type : Conditions only needs to match the first N columns of the index. + // for example : where c1 == 1 and c2 == 2 + // index1 (c1, c2) is valid. + // index2 (c1, c2, c3) is valid too. + // so index1 and index2 should be hit. + bool hasStringCol = false; + for (const auto& filter : filters_) { + auto type = schema->getFieldType(filter.first); + if (type.get_type() == nebula::cpp2::SupportedType::STRING) { + hasStringCol = true; + break; + } + } + return hasStringCol ? findValidIndexWithStr() : findValidIndexNoStr(); +} + +std::vector> LookupExecutor::findIndexForEqualScan( + const std::vector>& indexes) { + std::vector>> eqIndexHint; for (auto& index : indexes) { int32_t hintCount = 0; for (const auto& field : index->get_fields()) { auto it = std::find_if(filters_.begin(), filters_.end(), - [field](const auto &rel) { - return rel.second == RelationalExpression::EQ; + [field](const auto &filter) { + return filter.first == field.get_name(); }); if (it == filters_.end()) { break; } - ++hintCount; + if (it->second == RelationalExpression::Operator::EQ) { + ++hintCount; + } else { + break; + } } - indexHint[hintCount] = index->get_index_id(); + eqIndexHint.emplace_back(hintCount, index); } - index_ = indexHint.rbegin()->second; - return Status::OK(); + // Sort the priorityIdxs for equivalent condition. + std::vector> priorityIdxs; + auto comp = [] (std::pair>& lhs, + std::pair>& rhs) { + return lhs.first > rhs.first; + }; + std::sort(eqIndexHint.begin(), eqIndexHint.end(), comp); + // Get the index with the highest hit rate from eqIndexHint. + int32_t maxHint = eqIndexHint[0].first; + for (const auto& hint : eqIndexHint) { + if (hint.first < maxHint) { + break; + } + priorityIdxs.emplace_back(hint.second); + } + return priorityIdxs; +} + +std::vector> LookupExecutor::findIndexForRangeScan( + const std::vector>& indexes) { + std::map> rangeIndexHint; + for (const auto& index : indexes) { + int32_t hintCount = 0; + for (const auto& field : index->get_fields()) { + auto fi = std::find_if(filters_.begin(), filters_.end(), + [field](const auto &rel) { + return rel.first == field.get_name(); + }); + if (fi == filters_.end()) { + break; + } + if (fi->second == RelationalExpression::Operator::EQ) { + continue; + } + if (fi->second == RelationalExpression::Operator::GE || + fi->second == RelationalExpression::Operator::GT || + fi->second == RelationalExpression::Operator::LE || + fi->second == RelationalExpression::Operator::LT) { + hintCount++; + } else { + break; + } + } + rangeIndexHint[hintCount] = index; + } + std::vector> priorityIdxs; + int32_t maxHint = rangeIndexHint.rbegin()->first; + for (auto iter = rangeIndexHint.rbegin(); iter != rangeIndexHint.rend(); iter++) { + if (iter->first < maxHint) { + break; + } + priorityIdxs.emplace_back(iter->second); + } + return priorityIdxs; } void LookupExecutor::lookUp() { diff --git a/src/graph/LookupExecutor.h b/src/graph/LookupExecutor.h index 32fe66f9f4d..1cbf25e3021 100644 --- a/src/graph/LookupExecutor.h +++ b/src/graph/LookupExecutor.h @@ -16,6 +16,9 @@ namespace graph { using RpcResponse = storage::StorageRpcResponse; class LookupExecutor final : public TraverseExecutor { + FRIEND_TEST(LookupTest, OptimizerTest); + FRIEND_TEST(LookupTest, OptimizerWithStringFieldTest); + public: LookupExecutor(Sentence *sentence, ExecutionContext *ectx); @@ -50,7 +53,19 @@ class LookupExecutor final : public TraverseExecutor { Status checkFilter(); - Status findValidIndex(); + std::vector> findValidIndexWithStr(); + + std::vector> findValidIndexNoStr(); + + std::vector> findValidIndex(); + + std::vector> findIndexForEqualScan( + const std::vector>& indexes); + + std::vector> findIndexForRangeScan( + const std::vector>& indexes); + + Status findOptimalIndex(); void lookUp(); diff --git a/src/graph/test/IndexTest.cpp b/src/graph/test/IndexTest.cpp index 0ab925e3477..3321d8b3375 100644 --- a/src/graph/test/IndexTest.cpp +++ b/src/graph/test/IndexTest.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2018 vesoft inc. All rights reserved. +/* 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. @@ -39,11 +39,7 @@ TEST_F(IndexTest, TagIndex) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE TAG person(name string, age int, gender string, email string)"; - code = client->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - - query = "CREATE TAG course(teacher string, score double)"; + query = "CREATE TAG tag_1(col1 string, col2 int, col3 double, col4 timestamp)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -51,13 +47,13 @@ TEST_F(IndexTest, TagIndex) { // Single Tag Single Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX single_person_index ON person(name)"; + 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); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX duplicate_person_index ON person(name)"; + std::string query = "CREATE TAG INDEX duplicate_tag_index_1 ON tag_1(col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } @@ -71,49 +67,49 @@ TEST_F(IndexTest, TagIndex) { // Property not exist { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX single_person_index ON person(phone)"; + std::string query = "CREATE TAG INDEX single_tag_index ON tag_1(col5)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } // Property is empty { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX single_person_index ON person()"; + 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_person_index ON person(name, email)"; + 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 duplicate_person_index ON person(name, email)"; + std::string query = "CREATE TAG INDEX duplicate_person_index ON tag_1(col2, col3)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX duplicate_index ON person(name, name)"; + std::string query = "CREATE TAG INDEX duplicate_index ON tag_1(col2, col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX disorder_person_index ON person(email, name)"; + 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; - auto query = "INSERT VERTEX person(name, age, gender, email) VALUES " - "uuid(\"Tim\"): (\"Tim\", 18, \"M\", \"tim@ve.com\"), " - "uuid(\"Tony\"): (\"Tony\", 18, \"M\", \"tony@ve.com\"), " - "uuid(\"May\"): (\"May\", 18, \"F\", \"may@ve.com\"), " - "uuid(\"Tom\"): (\"Tom\", 18, \"M\", \"tom@ve.com\")"; + auto query = "INSERT VERTEX tag_1(col1, col2, col3, col4) VALUES " + "uuid(\"Tim\"): (\"Tim\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"Tony\"): (\"Tony\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"May\"): (\"May\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"Tom\"): (\"Tom\", 18, 11.11, \"2000-10-10 10:00:00\")"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -121,28 +117,29 @@ TEST_F(IndexTest, TagIndex) { // Rebuild Tag Index { cpp2::ExecutionResponse resp; - std::string query = "REBUILD TAG INDEX single_person_index OFFLINE"; + std::string query = "REBUILD TAG INDEX single_tag_index OFFLINE"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD TAG INDEX single_person_index"; + std::string query = "REBUILD TAG INDEX single_tag_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD TAG INDEX multi_person_index OFFLINE"; + std::string query = "REBUILD TAG INDEX multi_tag_index OFFLINE"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD TAG INDEX multi_person_index"; + std::string query = "REBUILD TAG INDEX multi_tag_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } + sleep(FLAGS_heartbeat_interval_secs + 1); // Show Tag Index Status { cpp2::ExecutionResponse resp; @@ -160,31 +157,31 @@ TEST_F(IndexTest, TagIndex) { // Describe Tag Index { cpp2::ExecutionResponse resp; - std::string query = "DESCRIBE TAG INDEX multi_person_index"; + std::string query = "DESCRIBE TAG INDEX multi_tag_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "DESC TAG INDEX multi_person_index"; + query = "DESC TAG INDEX multi_tag_index"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected{ - {"name", "string"}, - {"email", "string"}, + {"col2", "int"}, + {"col3", "double"}, }; ASSERT_TRUE(verifyResult(resp, expected)); } // Show Create Tag Indexes { cpp2::ExecutionResponse resp; - std::string query = "SHOW CREATE TAG INDEX multi_person_index"; + std::string query = "SHOW CREATE TAG INDEX multi_tag_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::string createTagIndex = "CREATE TAG INDEX `multi_person_index` " - "ON `person`(`name`, `email`)"; + std::string createTagIndex = "CREATE TAG INDEX `multi_tag_index` " + "ON `tag_1`(`col2`, `col3`)"; std::vector> expected{ - {"multi_person_index", createTagIndex}, + {"multi_tag_index", createTagIndex}, }; ASSERT_TRUE(verifyResult(resp, expected)); - query = "DROP TAG INDEX multi_person_index;" + createTagIndex; + query = "DROP TAG INDEX multi_tag_index;" + createTagIndex; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -195,19 +192,19 @@ TEST_F(IndexTest, TagIndex) { auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected{ - {"single_person_index"}, - {"multi_person_index"}, - {"disorder_person_index"}, + {"single_tag_index"}, + {"multi_tag_index"}, + {"disorder_tag_index"}, }; ASSERT_TRUE(verifyResult(resp, expected, true, {0})); } // Drop Tag Index { cpp2::ExecutionResponse resp; - std::string query = "DROP TAG INDEX multi_person_index"; + std::string query = "DROP TAG INDEX multi_tag_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "DESCRIBE TAG INDEX multi_person_index"; + query = "DESCRIBE TAG INDEX multi_tag_index"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } @@ -245,11 +242,7 @@ TEST_F(IndexTest, EdgeIndex) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE EDGE friend(degree string, start_time int)"; - code = client->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - - query = "CREATE EDGE transfer(amount double, bank string)"; + query = "CREATE EDGE edge_1(col1 string, col2 int, col3 double, col4 timestamp)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -257,70 +250,70 @@ TEST_F(IndexTest, EdgeIndex) { // Single Edge Single Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX single_friend_index ON friend(degree)"; + std::string query = "CREATE EDGE INDEX single_edge_index ON edge_1(col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX duplicate_friend_index ON friend(degree)"; + std::string query = "CREATE EDGE INDEX duplicate_edge_1_index ON edge_1(col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } // Edge not exist { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX single_friend_index ON friendship(name)"; + std::string query = "CREATE EDGE INDEX single_edge_index ON edge_1_ship(name)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } // Property not exist { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX single_friend_index ON friend(startTime)"; + std::string query = "CREATE EDGE INDEX single_edge_index ON edge_1(startTime)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } // Property is empty { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX single_friend_index ON friend()"; + std::string query = "CREATE EDGE INDEX single_edge_index ON edge_1()"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); } // Single EDGE Multi Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX multi_friend_index ON friend(degree, start_time)"; + std::string query = "CREATE EDGE INDEX multi_edge_1_index ON edge_1(col2, col3)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX duplicate_friend_index " - "ON friend(degree, start_time)"; + std::string query = "CREATE EDGE INDEX duplicate_edge_1_index " + "ON edge_1(col2, col3)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX duplicate_index ON friend(degree, degree)"; + std::string query = "CREATE EDGE INDEX duplicate_index ON edge_1(col2, col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX disorder_friend_index ON friend(start_time, degree)"; + std::string query = "CREATE EDGE INDEX disorder_edge_1_index ON edge_1(col3, col2)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto query = "INSERT EDGE friend(degree, start_time) VALUES " - "uuid(\"Tim\") -> uuid(\"May\"): (\"Good\", 18), " - "uuid(\"Tim\") -> uuid(\"Tony\"): (\"Good\", 18), " - "uuid(\"Tony\") -> uuid(\"May\"): (\"Like\", 18), " - "uuid(\"May\") -> uuid(\"Tim\"): (\"Like\", 18)"; + auto query = "INSERT EDGE edge_1(col1, col2, col3, col4) VALUES " + "uuid(\"Tim\") -> uuid(\"May\"): (\"Good\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"Tim\") -> uuid(\"Tony\"): (\"Good\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"Tony\") -> uuid(\"May\"): (\"Like\", 18, 11.11, \"2000-10-10 10:00:00\"), " + "uuid(\"May\") -> uuid(\"Tim\"): (\"Like\", 18, 11.11, \"2000-10-10 10:00:00\")"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -328,28 +321,29 @@ TEST_F(IndexTest, EdgeIndex) { // Rebuild EDGE Index { cpp2::ExecutionResponse resp; - std::string query = "REBUILD EDGE INDEX single_friend_index OFFLINE"; + std::string query = "REBUILD EDGE INDEX single_edge_index OFFLINE"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD EDGE INDEX single_friend_index"; + std::string query = "REBUILD EDGE INDEX single_edge_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD EDGE INDEX multi_friend_index OFFLINE"; + std::string query = "REBUILD EDGE INDEX multi_edge_1_index OFFLINE"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "REBUILD EDGE INDEX multi_friend_index"; + std::string query = "REBUILD EDGE INDEX multi_edge_1_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } + sleep(FLAGS_heartbeat_interval_secs + 1); // Show EDGE Index Status { cpp2::ExecutionResponse resp; @@ -367,31 +361,31 @@ TEST_F(IndexTest, EdgeIndex) { // Describe Edge Index { cpp2::ExecutionResponse resp; - std::string query = "DESCRIBE EDGE INDEX multi_friend_index"; + std::string query = "DESCRIBE EDGE INDEX multi_edge_1_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "DESC EDGE INDEX multi_friend_index"; + query = "DESC EDGE INDEX multi_edge_1_index"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected{ - {"degree", "string"}, - {"start_time", "int"}, + {"col2", "int"}, + {"col3", "double"}, }; ASSERT_TRUE(verifyResult(resp, expected)); } // Show Create Edge Index { cpp2::ExecutionResponse resp; - std::string query = "SHOW CREATE EDGE INDEX multi_friend_index"; + std::string query = "SHOW CREATE EDGE INDEX multi_edge_1_index"; auto code = client->execute(query, resp); - std::string createEdgeIndex = "CREATE EDGE INDEX `multi_friend_index` ON " - "`friend`(`degree`, `start_time`)"; + std::string createEdgeIndex = "CREATE EDGE INDEX `multi_edge_1_index` ON " + "`edge_1`(`col2`, `col3`)"; ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected{ - {"multi_friend_index", createEdgeIndex}, + {"multi_edge_1_index", createEdgeIndex}, }; ASSERT_TRUE(verifyResult(resp, expected)); - query = "DROP EDGE INDEX multi_friend_index;" + createEdgeIndex; + query = "DROP EDGE INDEX multi_edge_1_index;" + createEdgeIndex; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -402,19 +396,19 @@ TEST_F(IndexTest, EdgeIndex) { auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected{ - {"single_friend_index"}, - {"multi_friend_index"}, - {"disorder_friend_index"}, + {"single_edge_index"}, + {"multi_edge_1_index"}, + {"disorder_edge_1_index"}, }; ASSERT_TRUE(verifyResult(resp, expected, true, {0})); } // Drop Edge Index { cpp2::ExecutionResponse resp; - std::string query = "DROP EDGE INDEX multi_friend_index"; + std::string query = "DROP EDGE INDEX multi_edge_1_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "DESCRIBE EDGE INDEX multi_friend_index"; + query = "DESCRIBE EDGE INDEX multi_edge_1_index"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } @@ -452,7 +446,7 @@ TEST_F(IndexTest, TagIndexTTL) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE TAG person_ttl(name string, age int, gender int, email string)"; + query = "CREATE TAG person_ttl(number int, age int, gender int, email string)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -526,7 +520,7 @@ TEST_F(IndexTest, TagIndexTTL) { auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } - { + { cpp2::ExecutionResponse resp; std::string query = "DROP TAG INDEX single_person_ttl_index_second"; auto code = client->execute(query, resp); @@ -535,7 +529,7 @@ TEST_F(IndexTest, TagIndexTTL) { { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG person_ttl_2(name string, age int, gender string) " + std::string query = "CREATE TAG person_ttl_2(number int, age int, gender string) " "ttl_duration = 200, ttl_col = \"age\""; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); @@ -543,7 +537,7 @@ TEST_F(IndexTest, TagIndexTTL) { // Tag with ttl cannot create index on not ttl col { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX person_ttl_2_index ON person_ttl_2(name)"; + std::string query = "CREATE TAG INDEX person_ttl_2_index ON person_ttl_2(number)"; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } @@ -564,7 +558,7 @@ TEST_F(IndexTest, TagIndexTTL) { // Create index, succeed { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX person_ttl_2_index ON person_ttl_2(name)"; + std::string query = "CREATE TAG INDEX person_ttl_2_index ON person_ttl_2(number)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -596,90 +590,90 @@ TEST_F(IndexTest, EdgeIndexTTL) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE EDGE friend_ttl(degree int, start_time int)"; + query = "CREATE EDGE edge_1_ttl(degree int, start_time int)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Single Edge Single Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX single_friend_ttl_index ON friend_ttl(start_time)"; + std::string query = "CREATE EDGE INDEX single_edge_1_ttl_index ON edge_1_ttl(start_time)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Alter edge add ttl property on index col, failed { cpp2::ExecutionResponse resp; - std::string query = "ALTER edge friend_ttl ttl_duration = 100, ttl_col = \"start_time\""; + std::string query = "ALTER edge edge_1_ttl ttl_duration = 100, ttl_col = \"start_time\""; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Alter edge add ttl property on not index col, failed { cpp2::ExecutionResponse resp; - std::string query = "ALTER edge friend_ttl ttl_duration = 100, ttl_col = \"degree\""; + std::string query = "ALTER edge edge_1_ttl ttl_duration = 100, ttl_col = \"degree\""; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Drop index { cpp2::ExecutionResponse resp; - std::string query = "DROP EDGE INDEX single_friend_ttl_index"; + std::string query = "DROP EDGE INDEX single_edge_1_ttl_index"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Alter edge add ttl property on index col, succeed { cpp2::ExecutionResponse resp; - std::string query = "ALTER edge friend_ttl ttl_duration = 100, ttl_col = \"start_time\""; + std::string query = "ALTER edge edge_1_ttl ttl_duration = 100, ttl_col = \"start_time\""; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Alter edge add ttl property on not index col, succeed { cpp2::ExecutionResponse resp; - std::string query = "ALTER edge friend_ttl ttl_duration = 100, ttl_col = \"degree\""; + std::string query = "ALTER edge edge_1_ttl ttl_duration = 100, ttl_col = \"degree\""; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Edge with ttl to create index on ttl col, failed { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_second ON friend_ttl(degree)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_second ON edge_1_ttl(degree)"; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Edge with ttl to create index on no ttl col, failed { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_second ON friend_ttl(start_time)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_second ON edge_1_ttl(start_time)"; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Drop ttl propery { cpp2::ExecutionResponse resp; - std::string query = "ALTER EDGE friend_ttl ttl_col = \"\""; + std::string query = "ALTER EDGE edge_1_ttl ttl_col = \"\""; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Edge without ttl to create index, succeed { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_second ON friend_ttl(start_time)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_second ON edge_1_ttl(start_time)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Drop index { cpp2::ExecutionResponse resp; - std::string query = "DROP EDGE INDEX friend_ttl_index_second"; + std::string query = "DROP EDGE INDEX edge_1_ttl_index_second"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE friend_ttl_2(degree int, start_time int) " + std::string query = "CREATE EDGE edge_1_ttl_2(degree int, start_time int) " "ttl_duration = 200, ttl_col = \"start_time\""; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); @@ -687,41 +681,41 @@ TEST_F(IndexTest, EdgeIndexTTL) { // Edge with ttl cannot create index on not ttl col { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_2 ON friend_ttl_2(degree)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_2 ON edge_1_ttl_2(degree)"; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Edge with ttl cannot create index on ttl col { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_2 ON friend_ttl_2(start_time)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_2 ON edge_1_ttl_2(start_time)"; auto code = client->execute(query, resp); ASSERT_NE(cpp2::ErrorCode::SUCCEEDED, code); } // Drop ttl col { cpp2::ExecutionResponse resp; - std::string query = "ALTER EDGE friend_ttl_2 DROP (start_time)"; + std::string query = "ALTER EDGE edge_1_ttl_2 DROP (start_time)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Create index, succeed { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX friend_ttl_index_2 ON friend_ttl_2(degree)"; + std::string query = "CREATE EDGE INDEX edge_1_ttl_index_2 ON edge_1_ttl_2(degree)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { // Drop index - { - cpp2::ExecutionResponse resp; - std::string query = "DROP EDGE INDEX friend_ttl_index_2"; - auto code = client->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - } + { + cpp2::ExecutionResponse resp; + std::string query = "DROP EDGE INDEX edge_1_ttl_index_2"; + auto code = client->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } cpp2::ExecutionResponse resp; - std::string query = "DROP EDGE friend_ttl_2"; + std::string query = "DROP EDGE edge_1_ttl_2"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -746,7 +740,7 @@ TEST_F(IndexTest, AlterTag) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE TAG person(name string, age int, gender string, email string)"; + query = "CREATE TAG tag_1(col1 bool, col2 int, col3 double, col4 timestamp)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -754,21 +748,21 @@ TEST_F(IndexTest, AlterTag) { // Single Tag Single Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX single_person_index ON person(name)"; + std::string query = "CREATE TAG INDEX single_person_index ON tag_1(col1)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } sleep(FLAGS_heartbeat_interval_secs + 1); { cpp2::ExecutionResponse resp; - auto query = "INSERT VERTEX person(name, age, gender, email) VALUES " - "100: (\"Tim\", 18, \"M\", \"tim@ve.com\")"; + auto query = "INSERT VERTEX tag_1(col1, col2, col3, col4) VALUES " + "100: (true, 18, 1.1, \"2000-10-10 10:00:00\")"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto query = "ALTER TAG person ADD (col1 int)"; + auto query = "ALTER TAG tag_1 ADD (col5 int)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -776,35 +770,35 @@ TEST_F(IndexTest, AlterTag) { // Single Tag Single Field { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX single_person_index2 ON person(col1)"; + std::string query = "CREATE TAG INDEX single_person_index2 ON tag_1(col5)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } sleep(FLAGS_heartbeat_interval_secs + 1); { cpp2::ExecutionResponse resp; - auto query = "INSERT VERTEX person(name, age, gender, email, col1) VALUES " - "100:(\"Tim\", 18, \"M\", \"tim@ve.com\", 5)"; + auto query = "INSERT VERTEX tag_1(col1, col2, col3, col4, col5) VALUES " + "100:(true, 18, 1.1, \"2000-10-10 10:00:00\", 5)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON person WHERE person.col1 == 5 YIELD person.col1, person.name"; + auto query = "LOOKUP ON tag_1 WHERE tag_1.col5 == 5 YIELD tag_1.col5, tag_1.col1"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {100, 5, "Tim"}, + std::vector> expected = { + {100, 5, true}, }; ASSERT_TRUE(verifyResult(resp, expected)); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON person where person.name == \"Tim\" YIELD person.name, person.col1"; + auto query = "LOOKUP ON tag_1 where tag_1.col1 == true YIELD tag_1.col1, tag_1.col5"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {100, "Tim", 5}, + std::vector> expected = { + {100, true, 5}, }; ASSERT_TRUE(verifyResult(resp, expected)); } @@ -841,14 +835,14 @@ TEST_F(IndexTest, RebuildTagIndexStatusInfo) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE TAG tag_status(name string)"; + query = "CREATE TAG tag_status(col1 int)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } sleep(FLAGS_heartbeat_interval_secs + 1); { cpp2::ExecutionResponse resp; - std::string query = "CREATE TAG INDEX tag_index_status ON tag_status(name)"; + std::string query = "CREATE TAG INDEX tag_index_status ON tag_status(col1)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -905,14 +899,14 @@ TEST_F(IndexTest, RebuildEdgeIndexStatusInfo) { code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - query = "CREATE EDGE edge_status(name string)"; + query = "CREATE EDGE edge_status(col1 int)"; code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } sleep(FLAGS_heartbeat_interval_secs + 1); { cpp2::ExecutionResponse resp; - std::string query = "CREATE EDGE INDEX edge_index_status ON edge_status(name)"; + std::string query = "CREATE EDGE INDEX edge_index_status ON edge_status(col1)"; auto code = client->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } diff --git a/src/graph/test/LookupTest.cpp b/src/graph/test/LookupTest.cpp index e8eec4a19da..3f8c65c0cf1 100644 --- a/src/graph/test/LookupTest.cpp +++ b/src/graph/test/LookupTest.cpp @@ -5,6 +5,7 @@ */ #include "graph/test/LookupTestBase.h" +#include "graph/LookupExecutor.h" namespace nebula { namespace graph { @@ -26,9 +27,9 @@ TEST_F(LookupTest, SimpleVertex) { { cpp2::ExecutionResponse resp; auto query = "INSERT VERTEX lookup_tag_1(col1, col2, col3) VALUES " - "200:(\"col1_200\", \"col2_200\", \"col3_200\"), " - "201:(\"col1_201\", \"col2_201\", \"col3_201\"), " - "202:(\"col1_202\", \"col2_202\", \"col3_202\")"; + "200:(200, 200, 200), " + "201:(201, 201, 201), " + "202:(202, 202, 202)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -37,13 +38,13 @@ TEST_F(LookupTest, SimpleVertex) { * kPrimary == kPrimary */ cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_1 WHERE col1 == \"col1_200\""; + auto query = "LOOKUP ON lookup_tag_1 WHERE col1 == 200"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == \"col1\""; + auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == 300"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = {}; @@ -51,7 +52,7 @@ TEST_F(LookupTest, SimpleVertex) { } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == \"col1_200\""; + auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == 200"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = { @@ -65,12 +66,12 @@ TEST_F(LookupTest, SimpleVertex) { } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == \"col1_200\" " + auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col1 == 200 " "YIELD lookup_tag_1.col1, lookup_tag_1.col2, lookup_tag_1.col3"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {200, "col1_200", "col2_200", "col3_200"}, + std::vector> expected = { + {200, 200, 200, 200}, }; ASSERT_TRUE(verifyResult(resp, expected)); std::vector cols = { @@ -87,8 +88,8 @@ TEST_F(LookupTest, SimpleEdge) { { cpp2::ExecutionResponse resp; auto query = "INSERT EDGE lookup_edge_1(col1, col2, col3) VALUES " - "200 -> 201@0:(\"col1_200_1\", \"col2_200_1\", \"col3_200_1\"), " - "200 -> 202@0:(\"col1_200_2\", \"col2_200_2\", \"col3_200_2\")"; + "200 -> 201@0:(201, 201, 201), " + "200 -> 202@0:(202, 202, 202)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -97,13 +98,13 @@ TEST_F(LookupTest, SimpleEdge) { * kPrimary == kPrimary */ cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_1 WHERE col1 == \"col1_200_1\""; + auto query = "LOOKUP ON lookup_edge_1 WHERE col1 == 201"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == \"col1\""; + auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == 300"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = {}; @@ -111,7 +112,7 @@ TEST_F(LookupTest, SimpleEdge) { } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == \"col1_200_1\""; + auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == 201"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = { @@ -125,13 +126,13 @@ TEST_F(LookupTest, SimpleEdge) { } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == \"col1_200_1\" " + auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col1 == 201 " "YIELD lookup_edge_1.col1, lookup_edge_1.col2, lookup_edge_1.col3"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = { - {200, 201, 0, "col1_200_1", "col2_200_1", "col3_200_1"}, + int64_t, int64_t, int64_t>> expected = { + {200, 201, 0, 201, 201, 201}, }; ASSERT_TRUE(verifyResult(resp, expected)); std::vector cols = { @@ -150,15 +151,15 @@ TEST_F(LookupTest, VertexIndexHint) { { cpp2::ExecutionResponse resp; auto query = "INSERT VERTEX lookup_tag_1(col1, col2, col3) VALUES " - "200:(\"col1_200\", \"col2_200\", \"col3_200\"), " - "201:(\"col1_201\", \"col2_201\", \"col3_201\"), " - "202:(\"col1_202\", \"col2_202\", \"col3_202\")"; + "200:(200, 200, 200), " + "201:(201, 201, 201), " + "202:(202, 202, 202)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == \"col2_200\""; + auto query = "LOOKUP ON lookup_tag_1 WHERE lookup_tag_1.col2 == 200"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = { @@ -171,7 +172,7 @@ TEST_F(LookupTest, VertexIndexHint) { */ { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col1 == \"col2_200\""; + auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col1 == true"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } @@ -181,14 +182,14 @@ TEST_F(LookupTest, EdgeIndexHint) { { cpp2::ExecutionResponse resp; auto query = "INSERT EDGE lookup_edge_1(col1, col2, col3) VALUES " - "200 -> 201@0:(\"col1_200_1\", \"col2_200_1\", \"col3_200_1\"), " - "200 -> 202@0:(\"col1_200_2\", \"col2_200_2\", \"col3_200_2\")"; + "200 -> 201@0:(201, 201, 201), " + "200 -> 202@0:(202, 202, 202)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col2 == \"col2_200_1\""; + auto query = "LOOKUP ON lookup_edge_1 WHERE lookup_edge_1.col2 == 201"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = { @@ -201,7 +202,7 @@ TEST_F(LookupTest, EdgeIndexHint) { */ { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col1 == \"col2_200\""; + auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col1 == 200"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } @@ -211,12 +212,12 @@ TEST_F(LookupTest, VertexConditionScan) { { cpp2::ExecutionResponse resp; auto query = "INSERT VERTEX lookup_tag_2(col1, col2, col3, col4) VALUES " - "220:(\"col1_220\", 100, 100.5, true), " - "221:(\"col1_221\", 200, 200.5, true), " - "222:(\"col1_222\", 300, 300.5, true), " - "223:(\"col1_223\", 400, 400.5, true), " - "224:(\"col1_224\", 500, 500.5, true), " - "225:(\"col1_225\", 600, 600.5, true)"; + "220:(true, 100, 100.5, true), " + "221:(true, 200, 200.5, true), " + "222:(true, 300, 300.5, true), " + "223:(true, 400, 400.5, true), " + "224:(true, 500, 500.5, true), " + "225:(true, 600, 600.5, true)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -235,11 +236,7 @@ TEST_F(LookupTest, VertexConditionScan) { auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col2 == 100 " "OR lookup_tag_2.col2 == 200"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {220, 221} - }; - ASSERT_TRUE(verifyResult(resp, expected)); + ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; @@ -276,14 +273,19 @@ TEST_F(LookupTest, VertexConditionScan) { auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col2 >= 100 " "AND lookup_tag_2.col4 == true"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {220, 221, 222, 223, 224, 225} + }; + ASSERT_TRUE(verifyResult(resp, expected)); } { cpp2::ExecutionResponse resp; auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col2 >= 100 " "AND lookup_tag_2.col4 != true"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_FALSE(resp.__isset.rows); } { cpp2::ExecutionResponse resp; @@ -344,17 +346,6 @@ TEST_F(LookupTest, VertexConditionScan) { std::vector> expected = {}; ASSERT_TRUE(verifyResult(resp, expected)); } - { - cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col3 == 100.5 " - "OR lookup_tag_2.col3 == 200.5"; - auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {220, 221} - }; - ASSERT_TRUE(verifyResult(resp, expected)); - } { cpp2::ExecutionResponse resp; auto query = "LOOKUP ON lookup_tag_2 WHERE lookup_tag_2.col3 >= 100.5 " @@ -372,23 +363,23 @@ TEST_F(LookupTest, EdgeConditionScan) { { cpp2::ExecutionResponse resp; auto query = "INSERT VERTEX lookup_tag_2(col1, col2, col3, col4) VALUES " - "220:(\"col1_220\", 100, 100.5, true), " - "221:(\"col1_221\", 200, 200.5, true), " - "222:(\"col1_222\", 300, 300.5, true), " - "223:(\"col1_223\", 400, 400.5, true), " - "224:(\"col1_224\", 500, 500.5, true), " - "225:(\"col1_225\", 600, 600.5, true)"; + "220:(true, 100, 100.5, true), " + "221:(true, 200, 200.5, true), " + "222:(true, 300, 300.5, true), " + "223:(true, 400, 400.5, true), " + "224:(true, 500, 500.5, true), " + "225:(true, 600, 600.5, true)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; auto query = "INSERT EDGE lookup_edge_2(col1, col2, col3, col4) VALUES " - "220 -> 221@0:(\"col1_220\", 100, 100.5, true), " - "220 -> 222@0:(\"col1_221\", 200, 200.5, true), " - "220 -> 223@0:(\"col1_222\", 300, 300.5, true), " - "220 -> 224@0:(\"col1_223\", 400, 400.5, true), " - "220 -> 225@0:(\"col1_224\", 500, 500.5, true)"; + "220 -> 221@0:(true, 100, 100.5, true), " + "220 -> 222@0:(true, 200, 200.5, true), " + "220 -> 223@0:(true, 300, 300.5, true), " + "220 -> 224@0:(true, 400, 400.5, true), " + "220 -> 225@0:(true, 500, 500.5, true)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -407,12 +398,7 @@ TEST_F(LookupTest, EdgeConditionScan) { auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col2 == 100 " "OR lookup_edge_2.col2 == 200"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {220, 221, 0}, - {220, 222, 0} - }; - ASSERT_TRUE(verifyResult(resp, expected)); + ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); } { cpp2::ExecutionResponse resp; @@ -459,14 +445,23 @@ TEST_F(LookupTest, EdgeConditionScan) { auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col2 >= 100 " "AND lookup_edge_2.col4 == true"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = { + {220, 221, 0}, + {220, 222, 0}, + {220, 223, 0}, + {220, 224, 0}, + {220, 225, 0} + }; + ASSERT_TRUE(verifyResult(resp, expected)); } { cpp2::ExecutionResponse resp; auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col2 >= 100 " "AND lookup_edge_2.col4 != true"; auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_FALSE(resp.__isset.rows); } { cpp2::ExecutionResponse resp; @@ -533,18 +528,6 @@ TEST_F(LookupTest, EdgeConditionScan) { std::vector> expected = {}; ASSERT_TRUE(verifyResult(resp, expected)); } - { - cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col3 == 100.5 " - "OR lookup_edge_2.col3 == 200.5"; - auto code = client_->execute(query, resp); - ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); - std::vector> expected = { - {220, 221, 0}, - {220, 222, 0} - }; - ASSERT_TRUE(verifyResult(resp, expected)); - } { cpp2::ExecutionResponse resp; auto query = "LOOKUP ON lookup_edge_2 WHERE lookup_edge_2.col3 >= 100.5 " @@ -564,12 +547,12 @@ TEST_F(LookupTest, FunctionExprTest) { { cpp2::ExecutionResponse resp; auto query = "INSERT VERTEX lookup_tag_2(col1, col2, col3, col4) VALUES " - "220:(\"col1_220\", 100, 100.5, true), " - "221:(\"col1_221\", 200, 200.5, true), " - "222:(\"col1_222\", 300, 300.5, true), " - "223:(\"col1_223\", 400, 400.5, true), " - "224:(\"col1_224\", 500, 500.5, true), " - "225:(\"col1_225\", 600, 600.5, true)"; + "220:(true, 100, 100.5, true), " + "221:(true, 200, 200.5, true), " + "222:(true, 300, 300.5, true), " + "223:(true, 400, 400.5, true), " + "224:(true, 500, 500.5, true), " + "225:(true, 600, 600.5, true)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } @@ -690,42 +673,42 @@ TEST_F(LookupTest, FunctionExprTest) { TEST_F(LookupTest, YieldClauseTest) { { cpp2::ExecutionResponse resp; - auto stmt = "CREATE TAG student(name string, age int)"; + auto stmt = "CREATE TAG student(number int, age int)"; auto code = client_->execute(stmt, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto stmt = "CREATE TAG INDEX student_index ON student(name, age)"; + auto stmt = "CREATE TAG INDEX student_index ON student(number, age)"; auto code = client_->execute(stmt, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } { cpp2::ExecutionResponse resp; - auto stmt = "CREATE TAG teacher(name string, age int)"; + auto stmt = "CREATE TAG teacher(number int, age int)"; auto code = client_->execute(stmt, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } sleep(FLAGS_heartbeat_interval_secs + 1); { cpp2::ExecutionResponse resp; - auto query = "INSERT VERTEX student(name, age), teacher(name, age) VALUES " - "220:(\"student_1\", 20, \"teacher_1\", 30), " - "221:(\"student_2\", 22, \"teacher_1\", 32)"; + auto query = "INSERT VERTEX student(number, age), teacher(number, age) VALUES " + "220:(1, 20, 1, 30), " + "221:(2, 22, 2, 32)"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); } // Invalid tag name in yield clause. { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON student WHERE student.name == \"student_1\" YIELD teacher.age"; + auto query = "LOOKUP ON student WHERE student.number == 1 YIELD teacher.age"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); } // Invalid tag name in yield clause. and Alias is same with tag name. { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON student WHERE student.name == \"student_1\" YIELD teacher.age" + auto query = "LOOKUP ON student WHERE student.number == 1 YIELD teacher.age" " as student_name"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); @@ -733,13 +716,13 @@ TEST_F(LookupTest, YieldClauseTest) { // Invalid tag name in where clause. { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON student WHERE teacher.name == \"student_1\" YIELD student.age"; + auto query = "LOOKUP ON student WHERE teacher.number == 1 YIELD student.age"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code); } { cpp2::ExecutionResponse resp; - auto query = "LOOKUP ON student WHERE student.name == \"student_1\" YIELD student.age"; + auto query = "LOOKUP ON student WHERE student.number == 1 YIELD student.age"; auto code = client_->execute(query, resp); ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); std::vector> expected = {{220, 20}}; @@ -747,5 +730,398 @@ TEST_F(LookupTest, YieldClauseTest) { } } +TEST_F(LookupTest, OptimizerTest) { + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG t1(c1 int, c2 int, c3 int, c4 int, c5 int)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i1 ON t1(c1, c2)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i2 ON t1(c2, c1)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i3 ON t1(c3)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i4 ON t1(c1, c2, c3, c4)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i5 ON t1(c1, c2, c3, c5)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + sleep(FLAGS_heartbeat_interval_secs + 1); + + auto mc = gEnv->metaClient(); + auto indexes = mc->getTagIndexesFromCache(1); + ASSERT_TRUE(indexes.ok()); + auto ectx = std::make_unique(nullptr, + gEnv->schemaManager(), + nullptr, + nullptr, + nullptr, + nullptr); + auto executor = std::make_unique(nullptr, ectx.get()); + std::map expected; + { + executor->indexes_ = indexes.value(); + executor->spaceId_ = 1; + auto tagID = mc->getTagIDByNameFromCache(1, "t1"); + ASSERT_TRUE(tagID.ok()); + executor->tagOrEdge_ = tagID.value(); + for (int8_t i = 1; i <= 5; i++) { + auto indexName = folly::stringPrintf("i%d", i); + auto index = std::find_if(executor->indexes_.begin(), executor->indexes_.end(), + [indexName](const auto &idx) { + return idx->get_index_name() == indexName; + }); + if (index != executor->indexes_.end()) { + expected[index->get()->get_index_name()] = index->get()->get_index_id(); + } + } + } + // tag (c1 , c2, c3, c4, c5) + // i1 on tag (c1, c2) + // i2 on tag (c2, c1) + // i3 on tag (c3) + // i4 on tag (c1, c2, c3, c4) + // i5 on tag (c1, c2, c3, c5) + { + // "LOOKUP on t1 WHERE t1.c1 == 1"; expected i1 or i4 + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i1"] == executor->index_ || + expected["i4"] == executor->index_ || + expected["i5"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c1 == 1 and c2 > 1"; expected i1 or i4 or i5 + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::GT); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i1"] == executor->index_ || + expected["i4"] == executor->index_ || + expected["i5"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c1 > 1 and c2 == 1"; expected i2 + executor->filters_.emplace_back("c1", RelationalExpression::Operator::GT); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i2"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c1 > 1 and c2 == 1 and c3 == 1"; expected i4 or i5 + executor->filters_.emplace_back("c1", RelationalExpression::Operator::GT); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i4"] == executor->index_ || expected["i5"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c3 > 1"; expected i3 + executor->filters_.emplace_back("c3", RelationalExpression::Operator::GT); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i3"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c3 > 1 and c1 > 1"; expected i4 or i5. + executor->filters_.emplace_back("c3", RelationalExpression::Operator::GT); + executor->filters_.emplace_back("c1", RelationalExpression::Operator::GT); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i4"] == executor->index_ || expected["i5"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c4 > 1"; No invalid index found. + executor->filters_.emplace_back("c4", RelationalExpression::Operator::GT); + ASSERT_FALSE(executor->findOptimalIndex().ok()); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c2 > 1 and c3 > 1"; No invalid index found. + executor->filters_.emplace_back("c2", RelationalExpression::Operator::GT); + executor->filters_.emplace_back("c3", RelationalExpression::Operator::GT); + ASSERT_FALSE(executor->findOptimalIndex().ok()); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c2 > 1 and c1 != 1"; expected i2. + executor->filters_.emplace_back("c2", RelationalExpression::Operator::GT); + executor->filters_.emplace_back("c1", RelationalExpression::Operator::NE); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i2"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE t1.c2 != 1. + executor->filters_.emplace_back("c2", RelationalExpression::Operator::NE); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i2"] == executor->index_); + executor->filters_.clear(); + } + { + for (int8_t i = 1; i <= 5; i++) { + cpp2::ExecutionResponse resp; + auto stmt = folly::stringPrintf("DROP TAG INDEX i%d", i); + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + } +} + +TEST_F(LookupTest, OptimizerWithStringFieldTest) { + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG t1_str(c1 int, c2 int, c3 string, c4 string)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i1_str ON t1_str(c1, c2)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i2_str ON t1_str(c4)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i3_str ON t1_str(c3, c1)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i4_str ON t1_str(c3, c4)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i5_str ON t1_str(c1, c2, c3, c4)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + sleep(FLAGS_heartbeat_interval_secs + 1); + + auto mc = gEnv->metaClient(); + auto indexes = mc->getTagIndexesFromCache(1); + ASSERT_TRUE(indexes.ok()); + auto ectx = std::make_unique(nullptr, + gEnv->schemaManager(), + nullptr, + nullptr, + nullptr, + nullptr); + auto executor = std::make_unique(nullptr, ectx.get()); + std::map expected; + { + executor->indexes_ = indexes.value(); + executor->spaceId_ = 1; + auto tagID = mc->getTagIDByNameFromCache(1, "t1_str"); + ASSERT_TRUE(tagID.ok()); + executor->tagOrEdge_ = tagID.value(); + for (int8_t i = 1; i <= 5; i++) { + auto indexName = folly::stringPrintf("i%d_str", i); + auto index = std::find_if(executor->indexes_.begin(), executor->indexes_.end(), + [indexName](const auto &idx) { + return idx->get_index_name() == indexName; + }); + if (index != executor->indexes_.end()) { + expected[index->get()->get_index_name()] = index->get()->get_index_id(); + } + } + } + { + // "LOOKUP on t1_str WHERE c1 == 1"; expected i1_str + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + LOG(INFO) << "index is : " << executor->index_; + ASSERT_TRUE(expected["i1_str"] == executor->index_ ); + executor->filters_.clear(); + } + { + // "LOOKUP on t1_str WHERE c1 == 1 and tc2 > 1"; expected i1_str + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::GT); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i1_str"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE c3 == "a". No invalid index found. + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + ASSERT_FALSE(executor->findOptimalIndex().ok()); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE c4 == "a". expected i2_str + executor->filters_.emplace_back("c4", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i2_str"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE c3 == "a" and c4 == "a". expected i4_str + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c4", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i4_str"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE c3 == "a" and c1 == 1. expected i3_str + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i3_str"] == executor->index_); + executor->filters_.clear(); + } + { + // "LOOKUP on t1 WHERE c3 == "a" and c2 == 1 and c1 == 1. No invalid index found. + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + ASSERT_FALSE(executor->findOptimalIndex().ok()); + } + { + // "LOOKUP on t1 WHERE + // c4 == "a" and c3 == "a" and c2 == 1 and c1 == 1. No invalid index found. + executor->filters_.emplace_back("c4", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c3", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c2", RelationalExpression::Operator::EQ); + executor->filters_.emplace_back("c1", RelationalExpression::Operator::EQ); + ASSERT_TRUE(executor->findOptimalIndex().ok()); + ASSERT_TRUE(expected["i5_str"] == executor->index_); + executor->filters_.clear(); + } + { + for (int8_t i = 1; i <= 5; i++) { + cpp2::ExecutionResponse resp; + auto stmt = folly::stringPrintf("DROP TAG INDEX i%d_str", i); + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + } +} + +TEST_F(LookupTest, StringFieldTest) { + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG tag_with_str(c1 int, c2 string, c3 string)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i1_with_str ON tag_with_str(c1, c2)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i2_with_str ON tag_with_str(c2, c3)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + { + cpp2::ExecutionResponse resp; + auto stmt = "CREATE TAG INDEX i3_with_str ON tag_with_str(c1, c2, c3)"; + auto code = client_->execute(stmt, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + sleep(FLAGS_heartbeat_interval_secs + 1); + { + cpp2::ExecutionResponse resp; + auto query = "INSERT VERTEX tag_with_str(c1, c2, c3) VALUES " + "1:(1, \"c1_row1\", \"c2_row1\"), " + "2:(2, \"c1_row2\", \"c2_row2\"), " + "3:(3, \"abc\", \"abc\"), " + "4:(4, \"abc\", \"abc\"), " + "5:(5, \"ab\", \"cabc\"), " + "6:(5, \"abca\", \"bc\")"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + } + // No valid index found, the where condition need refer to all index fields. + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE tag_with_str.c1 == 1"; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::E_EXECUTION_ERROR, code); + } + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE " + "tag_with_str.c1 == 1 and tag_with_str.c2 == \"ccc\""; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + ASSERT_TRUE(!resp.__isset.rows || resp.get_rows()->size() == 0); + } + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE " + "tag_with_str.c1 == 1 and tag_with_str.c2 == \"c1_row1\""; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = {{1}}; + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE " + "tag_with_str.c1 == 5 and tag_with_str.c2 == \"ab\""; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = {{5}}; + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE " + "tag_with_str.c2 == \"abc\" and tag_with_str.c3 == \"abc\""; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = {{3}, {4}}; + ASSERT_TRUE(verifyResult(resp, expected)); + } + { + cpp2::ExecutionResponse resp; + auto query = "LOOKUP ON tag_with_str WHERE " + "tag_with_str.c1 == 5 and tag_with_str.c2 == \"abca\" " + "and tag_with_str.c3 == \"bc\""; + auto code = client_->execute(query, resp); + ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code); + std::vector> expected = {{6}}; + ASSERT_TRUE(verifyResult(resp, expected)); + } +} } // namespace graph } // namespace nebula diff --git a/src/graph/test/LookupTestBase.h b/src/graph/test/LookupTestBase.h index 7ad23b40680..3aebaa94a33 100644 --- a/src/graph/test/LookupTestBase.h +++ b/src/graph/test/LookupTestBase.h @@ -74,7 +74,7 @@ AssertionResult LookupTestBase::prepareSchema() { } { cpp2::ExecutionResponse resp; - std::string cmd = "CREATE TAG lookup_tag_1(col1 string, col2 string, col3 string)"; + std::string cmd = "CREATE TAG lookup_tag_1(col1 int, col2 int, col3 int)"; auto code = client_->execute(cmd, resp); if (cpp2::ErrorCode::SUCCEEDED != code) { return TestError() << "Do cmd:" << cmd << " failed"; @@ -82,7 +82,7 @@ AssertionResult LookupTestBase::prepareSchema() { } { cpp2::ExecutionResponse resp; - std::string cmd = "CREATE TAG lookup_tag_2(col1 string, col2 int, col3 double, col4 bool)"; + std::string cmd = "CREATE TAG lookup_tag_2(col1 bool, col2 int, col3 double, col4 bool)"; auto code = client_->execute(cmd, resp); if (cpp2::ErrorCode::SUCCEEDED != code) { return TestError() << "Do cmd:" << cmd << " failed"; @@ -90,7 +90,7 @@ AssertionResult LookupTestBase::prepareSchema() { } { cpp2::ExecutionResponse resp; - std::string cmd = "CREATE EDGE lookup_edge_1(col1 string, col2 string, col3 string)"; + std::string cmd = "CREATE EDGE lookup_edge_1(col1 int, col2 int, col3 int)"; auto code = client_->execute(cmd, resp); if (cpp2::ErrorCode::SUCCEEDED != code) { return TestError() << "Do cmd:" << cmd << " failed"; @@ -98,7 +98,7 @@ AssertionResult LookupTestBase::prepareSchema() { } { cpp2::ExecutionResponse resp; - std::string cmd = "CREATE EDGE lookup_edge_2(col1 string," + std::string cmd = "CREATE EDGE lookup_edge_2(col1 bool," "col2 int, col3 double, col4 bool)"; auto code = client_->execute(cmd, resp); if (cpp2::ErrorCode::SUCCEEDED != code) { diff --git a/src/graph/test/TestEnv.h b/src/graph/test/TestEnv.h index 489142d302a..c12b2ed9179 100644 --- a/src/graph/test/TestEnv.h +++ b/src/graph/test/TestEnv.h @@ -50,6 +50,10 @@ class TestEnv : public ::testing::Environment { return storageRootPath_.path(); } + meta::SchemaManager* schemaManager() { + return storageServer_->schemaMan_.get(); + } + private: nebula::fs::TempDir metaRootPath_{"/tmp/MetaTest.XXXXXX"}; nebula::fs::TempDir storageRootPath_{"/tmp/StorageTest.XXXXXX"}; diff --git a/src/storage/BaseProcessor.h b/src/storage/BaseProcessor.h index 8d6e19e4ffa..d8608583261 100644 --- a/src/storage/BaseProcessor.h +++ b/src/storage/BaseProcessor.h @@ -65,9 +65,6 @@ class BaseProcessor { void doRemove(GraphSpaceID spaceId, PartitionID partId, std::vector keys); - kvstore::ResultCode doRange(GraphSpaceID spaceId, PartitionID partId, std::string start, - std::string end, std::unique_ptr* iter); - kvstore::ResultCode doRange(GraphSpaceID spaceId, PartitionID partId, const std::string& start, const std::string& end, std::unique_ptr* iter); @@ -80,7 +77,7 @@ class BaseProcessor { std::unique_ptr* iter); kvstore::ResultCode doPrefix(GraphSpaceID spaceId, PartitionID partId, - std::string prefix, + std::string&& prefix, std::unique_ptr* iter) = delete; kvstore::ResultCode doRangeWithPrefix(GraphSpaceID spaceId, PartitionID partId, diff --git a/src/storage/index/IndexExecutor.h b/src/storage/index/IndexExecutor.h index d1bf4fa305c..275dc84703f 100644 --- a/src/storage/index/IndexExecutor.h +++ b/src/storage/index/IndexExecutor.h @@ -48,6 +48,11 @@ class IndexExecutor : public BaseProcessor **/ cpp2::ErrorCode buildExecutionPlan(const std::string& filter); + std::pair makeScanPair(PartitionID partId, IndexID indexId); + + std::pair + normalizeScanPair(const nebula::cpp2::ColumnDef& field, const ScanBound& item); + /** * Details Scan index part as one by one. **/ diff --git a/src/storage/index/IndexExecutor.inl b/src/storage/index/IndexExecutor.inl index f0735d53ea5..f15289d57f5 100644 --- a/src/storage/index/IndexExecutor.inl +++ b/src/storage/index/IndexExecutor.inl @@ -37,7 +37,9 @@ cpp2::ErrorCode IndexExecutor::buildExecutionPlan(const std::string& filte if (ret != cpp2::ErrorCode::SUCCEEDED) { return ret; } - buildPolicy(); + if (!buildPolicy()) { + return cpp2::ErrorCode::E_INVALID_FILTER; + } return ret; } @@ -115,16 +117,108 @@ cpp2::ErrorCode IndexExecutor::checkReturnColumns(const std::vector +std::pair +IndexExecutor::makeScanPair(PartitionID partId, IndexID indexId) { + std::string beginStr = NebulaKeyUtils::indexPrefix(partId, indexId); + std::string endStr = NebulaKeyUtils::indexPrefix(partId, indexId); + const auto& fields = index_->get_fields(); + std::vector colsLen; + for (const auto& field : fields) { + auto item = scanItems_.find(field.get_name()); + if (item == scanItems_.end()) { + break; + } + // here need check the value type, maybe different data types appear. + // for example: + // index (c1 double) + // where c1 > abs(1) , FunctionCallExpression->eval(abs(1)) + // should be cast type from int to double. + bool suc = true; + if (item->second.beginBound_.rel_ != RelationType::kNull) { + suc = NebulaKeyUtils::checkAndCastVariant(field.get_type().type, + item->second.beginBound_.val_); + } + if (suc == true && item->second.endBound_.rel_ != RelationType::kNull) { + suc = NebulaKeyUtils::checkAndCastVariant(field.get_type().type, + item->second.endBound_.val_); + } + if (!suc) { + VLOG(1) << "Unknown VariantType"; + return {}; + } + auto pair = normalizeScanPair(field, (*item).second); + if (field.get_type().type == nebula::cpp2::SupportedType::STRING) { + if (pair.first != pair.second) { + VLOG(1) << "String type field does not allow range scan : " << field.get_name(); + return {}; + } + colsLen.emplace_back(pair.first.size()); + } + beginStr.append(pair.first); + endStr.append(pair.second); + } + for (auto len : colsLen) { + beginStr.append(reinterpret_cast(&len), sizeof(int32_t)); + endStr.append(reinterpret_cast(&len), sizeof(int32_t)); + } + return std::make_pair(beginStr, endStr); +} + +template +std::pair +IndexExecutor::normalizeScanPair(const nebula::cpp2::ColumnDef& field, + const ScanBound& item) { + std::string begin, end; + auto type = field.get_type().type; + // if begin == end, means the scan is equivalent scan. + if (item.beginBound_.rel_ == RelationType::kEQRel && + item.endBound_.rel_ == RelationType::kEQRel && + item.beginBound_.rel_ != RelationType::kNull && + item.endBound_.rel_ != RelationType::kNull && + item.beginBound_.val_ == item.endBound_.val_) { + begin = end = NebulaKeyUtils::encodeVariant(item.beginBound_.val_); + return std::make_pair(begin, end); + } + // normalize begin and end value using ScanItem. for example : + // 1 < c1 < 5 --> ScanItem:{(GT, 1), (LT, 5)} --> {2, 5} + // 1 <= c1 <= 5 --> ScanItem:{(GE, 1), (LE, 5)} --> {1, 6} + // c1 > 5 --> ScanItem:{(GT, 5), (NULL)} --> {6, max} + // c1 >= 5 --> ScanItem:{(GE, 5), (NULL)} --> {5, max} + // c1 < 6 --> ScanItem:{(NULL), (LT, 6)} --> {min, 6} + // c1 <= 6 --> ScanItem:{(NULL), (LE, 6)} --> {min, 7} + if (item.beginBound_.rel_ == RelationType::kNull) { + begin = NebulaKeyUtils::boundVariant(type, NebulaBoundValueType::kMin); + } else if (item.beginBound_.rel_ == RelationType::kGTRel) { + begin = NebulaKeyUtils::boundVariant(type, NebulaBoundValueType::kAddition, + item.beginBound_.val_); + } else { + begin = NebulaKeyUtils::encodeVariant(item.beginBound_.val_); + } + + if (item.endBound_.rel_ == RelationType::kNull) { + end = NebulaKeyUtils::boundVariant(type, NebulaBoundValueType::kMax); + } else if (item.endBound_.rel_ == RelationType::kLTRel) { + end = NebulaKeyUtils::encodeVariant(item.endBound_.val_); + } else { + end = NebulaKeyUtils::boundVariant(type, NebulaBoundValueType::kAddition, + item.endBound_.val_); + } + return std::make_pair(begin, end); +} + template kvstore::ResultCode IndexExecutor::executeExecutionPlan(PartitionID part) { - std::string prefix = NebulaKeyUtils::indexPrefix(part, index_->get_index_id()) - .append(prefix_); std::unique_ptr iter; std::vector keys; - auto ret = this->kvstore_->prefix(spaceId_, - part, - prefix, - &iter); + auto pair = makeScanPair(part, index_->get_index_id()); + if (pair.first.empty() || pair.second.empty()) { + return kvstore::ResultCode::ERR_KEY_NOT_FOUND; + } + auto ret = (pair.first == pair.second) + ? this->doPrefix(spaceId_, part, pair.first, &iter) + : this->doRange(spaceId_, part, pair.first, pair.second, &iter); if (ret != nebula::kvstore::SUCCEEDED) { return ret; } diff --git a/src/storage/index/IndexPolicyMaker.cpp b/src/storage/index/IndexPolicyMaker.cpp index d5a16d51ddc..cc4caf80fa4 100644 --- a/src/storage/index/IndexPolicyMaker.cpp +++ b/src/storage/index/IndexPolicyMaker.cpp @@ -41,41 +41,54 @@ cpp2::ErrorCode IndexPolicyMaker::decodeExpression(const std::string &filter) { return code; } -void IndexPolicyMaker::buildPolicy() { - prefix_.reserve(256); - decltype(operatorList_.size()) hintNum = 0; - bool hasStr = false; +bool IndexPolicyMaker::buildPolicy() { + bool nextCol = true; for (auto& col : index_->get_fields()) { - auto it = std::find_if(operatorList_.begin(), operatorList_.end(), - [&col] (const auto& tup) { - return col.get_name() == std::get<0>(tup);; - }); - if (it != operatorList_.end()) { - if (std::get<2>(*it) == RelationalExpression::Operator::EQ) { + auto itr = operatorList_.begin(); + while (nextCol && itr != operatorList_.end()) { + if (col.get_name() == std::get<0>(*itr)) { /** * TODO sky : drop the sub-exp from root expression tree. */ - hintNum++; - auto v = std::get<1>(*it); - hasStr = (v.which() == VAR_STR); - prefix_.append(NebulaKeyUtils::encodeVariant(v)); + if (std::get<2>(*itr) == RelationalExpression::Operator::NE) { + // The build policy will be interrupted when '!=' expression occur. + // And '!=' expression filtering will also be done in the result set. + requiredFilter_ = true; + nextCol = false; + break; + } + if (!writeScanItem(col.get_name(), *itr)) { + return false; + } + // Delete operator item if hint. + operatorList_.erase(itr); } else { - break; + ++itr; } - } else { + } + /** + * If index field does not hit the filter condition, + * means there is no need to loop the index fields. for example : + * index (c1, c2, c3) + * where c1 > 1 and c3 == 1 + * Field c2 is missing from the operatorList_, + * So we just need using c1 to range scan and filter c3. + */ + auto exist = scanItems_.find(col.get_name()); + if (exist == scanItems_.end()) { break; } } - if (optimizedPolicy_ && hintNum == operatorList_.size() && !hasStr) { - requiredFilter_ = false; + // re-check operatorList_. + // if operatorList_ is not empty, that means there are still fields to filter + if (!requiredFilter_ && operatorList_.size() > 0) { + requiredFilter_ = true; } + return true; } cpp2::ErrorCode IndexPolicyMaker::traversalExpression(const Expression *expr) { cpp2::ErrorCode code = cpp2::ErrorCode::SUCCEEDED; - if (!optimizedPolicy_) { - return code; - } Getters getters; /** @@ -90,12 +103,9 @@ cpp2::ErrorCode IndexPolicyMaker::traversalExpression(const Expression *expr) { }; switch (expr->kind()) { case nebula::Expression::kLogical : { + // OR logical expression is not allowed in graph layer. + // Make sure all logical expression is 'AND' at here. auto* lExpr = dynamic_cast(expr); - if (lExpr->op() == LogicalExpression::Operator::XOR) { - return cpp2::ErrorCode::E_INVALID_FILTER; - } else if (lExpr->op() == LogicalExpression::Operator::OR) { - optimizedPolicy_ = false; - } auto* left = lExpr->left(); traversalExpression(left); auto* right = lExpr->right(); @@ -108,6 +118,7 @@ cpp2::ErrorCode IndexPolicyMaker::traversalExpression(const Expression *expr) { auto* rExpr = dynamic_cast(expr); auto* left = rExpr->left(); auto* right = rExpr->right(); + RelationalExpression::Operator op; if (left->kind() == nebula::Expression::kAliasProp) { auto* aExpr = dynamic_cast(left); prop = *aExpr->prop(); @@ -117,24 +128,19 @@ cpp2::ErrorCode IndexPolicyMaker::traversalExpression(const Expression *expr) { return cpp2::ErrorCode::E_INVALID_FILTER; } v = value.value(); + op = rExpr->op(); } else if (right->kind() == nebula::Expression::kAliasProp) { + auto* aExpr = dynamic_cast(right); + prop = *aExpr->prop(); auto value = left->eval(getters); if (!value.ok()) { VLOG(1) << "Can't evaluate the expression " << left->toString(); return cpp2::ErrorCode::E_INVALID_FILTER; } v = value.value(); - auto* aExpr = dynamic_cast(right); - prop = *aExpr->prop(); - } else { - optimizedPolicy_ = false; - break; + op = reversalRelationalExprOP(rExpr->op()); } - operatorList_.emplace_back(std::make_tuple(std::move(prop), std::move(v), rExpr->op())); - break; - } - case nebula::Expression::kFunctionCall : { - optimizedPolicy_ = false; + operatorList_.emplace_back(std::make_tuple(std::move(prop), std::move(v), op)); break; } default : { @@ -151,6 +157,120 @@ bool IndexPolicyMaker::exprEval(Getters &getters) { } return true; } + +RelationType IndexPolicyMaker::toRel(RelationalExpression::Operator op) { + switch (op) { + case RelationalExpression::Operator::LT : + return RelationType::kLTRel; + case RelationalExpression::Operator::LE : + return RelationType::kLERel; + case RelationalExpression::Operator::GT : + return RelationType::kGTRel; + case RelationalExpression::Operator::GE: + return RelationType::kGERel; + case RelationalExpression::Operator::EQ : + return RelationType::kEQRel; + default : + return RelationType::kNull; + } +} + +RelationalExpression::Operator +IndexPolicyMaker::reversalRelationalExprOP(RelationalExpression::Operator op) { + switch (op) { + case RelationalExpression::Operator::LT: { + return RelationalExpression::Operator::GT; + } + case RelationalExpression::Operator::LE: { + return RelationalExpression::Operator::GE; + } + case RelationalExpression::Operator::GT: { + return RelationalExpression::Operator::LT; + } + case RelationalExpression::Operator::GE: { + return RelationalExpression::Operator::LE; + } + default : { + return op; + } + } +} + +bool IndexPolicyMaker::writeScanItem(const std::string& prop, const OperatorItem& item) { + auto op = std::get<2>(item); + switch (op) { + // for example col > 1, means the operator is GT. if col >= 1 ,means the opertor is GE. + // if operator is GT or GE . the 1 should a begin value. + case RelationalExpression::Operator::GE : + case RelationalExpression::Operator::GT : { + auto v = scanItems_.find(prop); + if (v == scanItems_.end()) { + // if the field did not exist in scanItems_, add an new one. + // default value is invalid VariantType. + scanItems_[prop] = ScanBound(Bound(toRel(op), std::get<1>(item)), Bound()); + } else { + if (v->second.beginBound_.rel_ == RelationType::kNull) { + // if value is invalid VariantType, reset it. + v->second.beginBound_ = Bound(toRel(op), std::get<1>(item)); + } else if (v->second.beginBound_.val_ < std::get<1>(item)) { + // This might be the case where c1 > 1 and c1 > 5 , so the 5 should be save. + v->second.beginBound_.val_ = std::get<1>(item); + } else if (v->second.beginBound_.rel_ == RelationType::kEQRel) { + // If this field appears in scanItems_ , + // means that the filter conditions are wrong, for example : + // c1 == 1 and c1 > 2 + VLOG(1) << "Repeated conditional expression for field : " << prop; + return false; + } + } + break; + } + // if col < 1, means the operator is LT. if col <= 1 ,means the opertor is LE. + // if operator is LT or LE . the 1 should a end value. + case RelationalExpression::Operator::LE : + case RelationalExpression::Operator::LT : { + auto v = scanItems_.find(prop); + if (v == scanItems_.end()) { + scanItems_[prop] = ScanBound(Bound(), Bound(toRel(op), std::get<1>(item))); + } else { + if (v->second.endBound_.rel_ == RelationType::kNull) { + v->second.endBound_ = Bound(toRel(op), std::get<1>(item)); + } else if (v->second.endBound_.val_ > std::get<1>(item)) { + // This might be the case where c1 < 1 and c1 < 5 , so the 1 should be save. + v->second.endBound_.val_ = std::get<1>(item); + } else if (v->second.endBound_.rel_ == RelationType::kEQRel) { + // If this field appears in scanItems_ , + // means that the filter conditions are wrong, for example : + // c1 == 1 and c1 < 2 + VLOG(1) << "Repeated conditional expression for field : " << prop; + return false; + } + } + break; + } + case RelationalExpression::Operator::EQ: { + auto v = scanItems_.find(prop); + if (v == scanItems_.end()) { + scanItems_[prop] = ScanBound(Bound(toRel(op), std::get<1>(item)), + Bound(toRel(op), std::get<1>(item))); + } else { + // If this field appears in scanItems_ , + // means that the filter conditions are wrong, for example : + // c1 == 1 and c1 == 2 + VLOG(1) << "Repeated conditional expression for field : " << prop; + return false; + } + } + case RelationalExpression::Operator::NE : { + break; + } + default : { + VLOG(1) << "Unknown operation of RelationalExpression. column : " << prop; + return false; + } + } + return true; +} } // namespace storage } // namespace nebula diff --git a/src/storage/index/IndexPolicyMaker.h b/src/storage/index/IndexPolicyMaker.h index b9339fb5786..9dc126c4935 100644 --- a/src/storage/index/IndexPolicyMaker.h +++ b/src/storage/index/IndexPolicyMaker.h @@ -22,6 +22,40 @@ namespace storage { */ using OperatorItem = std::tuple; +enum RelationType : uint8_t { + kGTRel, + kGERel, + kLTRel, + kLERel, + kEQRel, + kNull, +}; + +struct Bound { + RelationType rel_; + VariantType val_; + Bound() { + rel_ = RelationType::kNull; + } + Bound(RelationType rel, const VariantType& val) { + rel_ = rel; + val_ = val; + } +}; + +struct ScanBound { + Bound beginBound_; + Bound endBound_; + ScanBound() { + beginBound_ = Bound(); + endBound_ = Bound(); + } + ScanBound(const Bound& begin, const Bound& end) { + beginBound_ = begin; + endBound_ = end; + } +}; + class IndexPolicyMaker { public: virtual ~IndexPolicyMaker() = default; @@ -48,13 +82,15 @@ class IndexPolicyMaker { * execution policy according to PolicyType. * In this method, it is best to use as many index columns as possible. **/ - void buildPolicy(); + bool buildPolicy(); /** * Details Evaluate filter conditions. */ bool exprEval(Getters &getters); + static RelationType toRel(RelationalExpression::Operator op); + private: cpp2::ErrorCode decodeExpression(const std::string &filter); @@ -64,16 +100,20 @@ class IndexPolicyMaker { cpp2::ErrorCode traversalExpression(const Expression *expr); + RelationalExpression::Operator reversalRelationalExprOP(RelationalExpression::Operator op); + + bool writeScanItem(const std::string& prop, const OperatorItem& item); + protected: meta::SchemaManager* schemaMan_{nullptr}; meta::IndexManager* indexMan_{nullptr}; std::unique_ptr expCtx_{nullptr}; std::unique_ptr exp_{nullptr}; - std::string prefix_; std::shared_ptr index_{nullptr}; - bool optimizedPolicy_{true}; - bool requiredFilter_{true}; + bool requiredFilter_{false}; std::vector operatorList_; + // map + std::map scanItems_; }; } // namespace storage } // namespace nebula diff --git a/src/storage/test/IndexScanTest.cpp b/src/storage/test/IndexScanTest.cpp index 5e21e69bff1..f92e023bf59 100644 --- a/src/storage/test/IndexScanTest.cpp +++ b/src/storage/test/IndexScanTest.cpp @@ -1014,17 +1014,42 @@ TEST(IndexScanTest, EdgeStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_0 == "ABC" + * where col_0 == "ABC" and col_1 == "ABC" and col_2 == "ABC" */ + + std::string abc("ABC"); auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("101"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c0("ABC"); - auto* pe0 = new PrimaryExpression(c0); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupEdgesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(abc); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("101"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(abc); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("101"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(abc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + + auto resp = checkLookupEdgesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); EXPECT_EQ(3, resp.get_edges()->size()); @@ -1047,17 +1072,42 @@ TEST(IndexScanTest, EdgeStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_0 == "AB" + * where col_0 == "AB" and col_1 == "CAB" and col_2 == "CABC" */ + std::string ab("AB"); auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("101"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c0("AB"); - auto* pe0 = new PrimaryExpression(c0); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupEdgesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(ab); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + std::string cab("CAB"); + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("101"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(cab); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + std::string cabc("CABC"); + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("101"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(cabc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + auto resp = checkLookupEdgesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); EXPECT_EQ(3, resp.get_edges()->size()); @@ -1080,30 +1130,45 @@ TEST(IndexScanTest, EdgeStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_1 == "CAB" + * where col_0 == "ABCA" and col_1 == "BC" and col_2 == "ABC" */ - auto* col0 = new std::string("col_1"); + std::string abca("ABCA"); + auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("101"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c1("CAB"); - auto* pe0 = new PrimaryExpression(c1); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupEdgesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(abca); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + std::string bc("BC"); + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("101"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(bc); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + std::string abc("ABC"); + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("101"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(abc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + auto resp = checkLookupEdgesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); - EXPECT_EQ(3, resp.get_edges()->size()); - RowWriter ewriter(nullptr); - ewriter << "AB" << "CAB" << "CABC"; - auto eval = ewriter.encode(); - for (const auto& row : *resp.get_edges()) { - EXPECT_EQ(eval, row.get_props()); - EXPECT_EQ(1, row.get_key().get_src()); - EXPECT_EQ(10, row.get_key().get_dst()); - EXPECT_EQ(101, row.get_key().get_edge_type()); - EXPECT_EQ(0, row.get_key().get_ranking()); - } + EXPECT_EQ(0, resp.get_edges()->size()); } } @@ -1116,17 +1181,42 @@ TEST(IndexScanTest, VertexStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_0 == "ABC" + * where col_0 == "ABC" and col_1 == "ABC" and col_2 == "ABC" */ + + std::string abc("ABC"); auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("3001"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c0("ABC"); - auto* pe0 = new PrimaryExpression(c0); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupVerticesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(abc); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("3001"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(abc); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("3001"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(abc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + + auto resp = checkLookupVerticesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); EXPECT_EQ(3, resp.get_vertices()->size()); @@ -1146,17 +1236,43 @@ TEST(IndexScanTest, VertexStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_0 == "AB" + * where col_0 == "AB" and col_1 == "CAB" and col_2 == "CABC" */ + std::string ab("AB"); auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("3001"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c0("AB"); - auto* pe0 = new PrimaryExpression(c0); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupVerticesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(ab); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + std::string cab("CAB"); + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("3001"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(cab); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + std::string cabc("CABC"); + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("3001"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(cabc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + + auto resp = checkLookupVerticesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); EXPECT_EQ(3, resp.get_vertices()->size()); @@ -1176,27 +1292,46 @@ TEST(IndexScanTest, VertexStringTest) { * "AB" << "CAB" << "CABC" * "ABC" << "ABC" << "ABC" * - * where col_1 == "CAB" + * where col_0 == "ABCA" and col_1 == "BC" and col_2 == "ABC" */ - auto* col0 = new std::string("col_1"); + std::string abca("ABCA"); + auto* col0 = new std::string("col_0"); auto* alias0 = new std::string("3001"); auto* ape0 = new AliasPropertyExpression(new std::string(""), alias0, col0); - std::string c1("CAB"); - auto* pe0 = new PrimaryExpression(c1); - auto r1 = std::make_unique(ape0, - RelationalExpression::Operator::EQ, - pe0); - auto resp = checkLookupVerticesString(Expression::encode(r1.get())); + auto* pe0 = new PrimaryExpression(abca); + auto* r1 = new RelationalExpression(ape0, + RelationalExpression::Operator::EQ, + pe0); + + std::string bc("BC"); + auto* col1 = new std::string("col_1"); + auto* alias1 = new std::string("3001"); + auto* ape1 = new AliasPropertyExpression(new std::string(""), alias1, col1); + auto* pe1 = new PrimaryExpression(bc); + auto* r2 = new RelationalExpression(ape1, + RelationalExpression::Operator::EQ, + pe1); + + std::string abc("ABC"); + auto* col2 = new std::string("col_2"); + auto* alias2 = new std::string("3001"); + auto* ape2 = new AliasPropertyExpression(new std::string(""), alias2, col2); + auto* pe2 = new PrimaryExpression(abc); + auto* r3 = new RelationalExpression(ape2, + RelationalExpression::Operator::EQ, + pe2); + auto* le1 = new LogicalExpression(r1, + LogicalExpression::AND, + r2); + + auto logExp = std::make_unique(le1, + LogicalExpression::AND, + r3); + + auto resp = checkLookupVerticesString(Expression::encode(logExp.get())); EXPECT_EQ(0, resp.result.failed_codes.size()); EXPECT_EQ(3, resp.get_schema()->get_columns().size()); - EXPECT_EQ(3, resp.get_vertices()->size()); - RowWriter ewriter(nullptr); - ewriter << "AB" << "CAB" << "CABC"; - auto eval = ewriter.encode(); - for (const auto& row : *resp.get_vertices()) { - EXPECT_EQ(eval, row.get_props()); - EXPECT_EQ(100, row.get_vertex_id()); - } + EXPECT_EQ(0, resp.get_vertices()->size()); } }