From b5c49230d705b348dfce790e6c29fc96685cf6af Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Mon, 29 May 2023 19:24:42 +0800 Subject: [PATCH 1/6] stash --- src/common/expression/Expression.cpp | 20 +- src/common/expression/Expression.h | 6 +- .../expression/TextSearchExpression.cpp | 51 +- src/common/expression/TextSearchExpression.h | 97 +- .../fulltext/elasticsearch/ESAdapter.cpp | 114 +- .../plugin/fulltext/elasticsearch/ESAdapter.h | 155 +- .../plugin/fulltext/test/CMakeLists.txt | 32 +- .../fulltext/test/ElasticsearchTest.cpp | 1384 +++++++++-------- src/graph/context/ast/QueryAstContext.h | 1 - .../query/FulltextIndexScanExecutor.cpp | 56 +- .../query/FulltextIndexScanExecutor.h | 3 +- src/graph/planner/ngql/LookupPlanner.cpp | 3 +- src/graph/planner/plan/Query.cpp | 2 +- src/graph/planner/plan/Query.h | 13 +- src/graph/util/ExpressionUtils.cpp | 6 +- src/graph/util/FTIndexUtils.cpp | 182 ++- src/graph/util/test/CMakeLists.txt | 2 +- src/graph/validator/LookupValidator.cpp | 80 +- src/interface/meta.thrift | 1 + .../processors/index/FTIndexProcessor.cpp | 2 +- src/parser/parser.yy | 153 +- src/parser/scanner.lex | 6 +- src/storage/query/QueryBaseProcessor-inl.h | 6 +- 23 files changed, 1070 insertions(+), 1305 deletions(-) diff --git a/src/common/expression/Expression.cpp b/src/common/expression/Expression.cpp index 478bdbe3c71..ebaab8bd960 100644 --- a/src/common/expression/Expression.cpp +++ b/src/common/expression/Expression.cpp @@ -512,10 +512,8 @@ Expression* Expression::decode(ObjectPool* pool, Expression::Decoder& decoder) { exp->resetFrom(decoder); return exp; } - case Expression::Kind::kTSPrefix: - case Expression::Kind::kTSWildcard: - case Expression::Kind::kTSRegexp: - case Expression::Kind::kTSFuzzy: { + case Expression::Kind::kESMATCH: + case Expression::Kind::kESQUERY: { LOG(FATAL) << "Should not decode text search expression"; return exp; } @@ -721,17 +719,11 @@ std::ostream& operator<<(std::ostream& os, Expression::Kind kind) { case Expression::Kind::kPathBuild: os << "PathBuild"; break; - case Expression::Kind::kTSPrefix: - os << "Prefix"; + case Expression::Kind::kESMATCH: + os << "ESMatch"; break; - case Expression::Kind::kTSWildcard: - os << "Wildcard"; - break; - case Expression::Kind::kTSRegexp: - os << "Regexp"; - break; - case Expression::Kind::kTSFuzzy: - os << "Fuzzy"; + case Expression::Kind::kESQUERY: + os << "ESQuery"; break; case Expression::Kind::kListComprehension: os << "ListComprehension"; diff --git a/src/common/expression/Expression.h b/src/common/expression/Expression.h index 31b4dcfac73..07f41ebf3d4 100644 --- a/src/common/expression/Expression.h +++ b/src/common/expression/Expression.h @@ -96,10 +96,8 @@ class Expression { kPathBuild, // text or key word search expression - kTSPrefix, - kTSWildcard, - kTSRegexp, - kTSFuzzy, + kESMATCH, + kESQUERY, kAggregate, kIsNull, diff --git a/src/common/expression/TextSearchExpression.cpp b/src/common/expression/TextSearchExpression.cpp index a8e556def24..6ca92a20314 100644 --- a/src/common/expression/TextSearchExpression.cpp +++ b/src/common/expression/TextSearchExpression.cpp @@ -10,29 +10,34 @@ namespace nebula { bool TextSearchArgument::operator==(const TextSearchArgument& rhs) const { - return val_ == rhs.val_ && op_ == rhs.op_ && fuzziness_ == rhs.fuzziness_ && - limit_ == rhs.limit_ && timeout_ == rhs.timeout_; + return index_ == rhs.index_ && query_ == rhs.query_ && props_ == rhs.props_; } std::string TextSearchArgument::toString() const { std::string buf; buf.reserve(64); - buf = from_ + "." + prop_ + ", "; - buf += "\"" + val_ + "\""; - if (fuzziness_ == -1) { - buf += ", AUTO, "; - buf += ((op_ == "or") ? "OR" : "AND"); - } else if (fuzziness_ > -1) { - buf += ", "; - buf += folly::stringPrintf("%d, ", fuzziness_); - buf += ((op_ == "or") ? "OR" : "AND"); + if (!index_.empty()) { + buf += "\"", index_ + "\", "; } - if (limit_ != -1) { - buf += folly::stringPrintf(", %d", limit_); + buf += "\"" + query_ + "\""; + if (!props_.empty()) { + buf += ", ["; + for (size_t i = 0; i < props_.size(); i++) { + buf += props_[i]; + if (i != props_.size() - 1) { + buf += ", "; + } + } + buf += "]"; } - if (timeout_ != -1) { - buf += folly::stringPrintf(", %d", timeout_); + + if (count_ != 0) { + buf += ", " + std::to_string(count_); + if (offset_) { + buf += ", " + std::to_string(offset_); + } } + return buf; } @@ -49,20 +54,12 @@ std::string TextSearchExpression::toString() const { std::string buf; buf.reserve(64); switch (kind_) { - case Kind::kTSWildcard: { - buf = "WILDCARD("; - break; - } - case Kind::kTSPrefix: { - buf = "PREFIX("; - break; - } - case Kind::kTSFuzzy: { - buf = "FUZZY("; + case Kind::kESMATCH: { + buf = "ES_MATCH("; break; } - case Kind::kTSRegexp: { - buf = "REGEXP("; + case Kind::kESQUERY: { + buf = "ES_QUERY("; break; } default: { diff --git a/src/common/expression/TextSearchExpression.h b/src/common/expression/TextSearchExpression.h index b4239d91933..71dc24e7eb5 100644 --- a/src/common/expression/TextSearchExpression.h +++ b/src/common/expression/TextSearchExpression.h @@ -14,60 +14,34 @@ namespace nebula { class TextSearchArgument final { public: static TextSearchArgument* make(ObjectPool* pool, - const std::string& from, - const std::string& prop, - const std::string& val) { - return pool->makeAndAdd(from, prop, val); + const std::string& index, + const std::string& query, + const std::vector& props, + int64_t count, + int64_t offset) { + return pool->makeAndAdd(index, query, props, count, offset); } ~TextSearchArgument() = default; - void setVal(const std::string& val) { - val_ = val; + std::string& index() { + return index_; } - const std::string& from() { - return from_; + std::string& query() { + return query_; } - const std::string& prop() { - return prop_; + std::vector& props() { + return props_; } - const std::string& val() const { - return val_; + int64_t& offset() { + return offset_; } - void setOP(const std::string& op) { - op_ = op; - } - - const std::string& op() const { - return op_; - } - - void setFuzziness(int32_t fuzz) { - fuzziness_ = fuzz; - } - - int32_t fuzziness() { - return fuzziness_; - } - - void setLimit(int32_t limit) { - limit_ = limit; - } - - int32_t limit() { - return limit_; - } - - void setTimeout(int32_t timeout) { - timeout_ = timeout; - } - - int32_t timeout() { - return timeout_; + int64_t& count() { + return count_; } bool operator==(const TextSearchArgument& rhs) const; @@ -76,35 +50,29 @@ class TextSearchArgument final { private: friend ObjectPool; - TextSearchArgument(const std::string& from, const std::string& prop, const std::string& val) - : from_(from), prop_(prop), val_(val) {} + TextSearchArgument(const std::string& index, + const std::string& query, + const std::vector& props, + int64_t count, + int64_t offset) + : index_(index), query_(query), props_(props), count_(count), offset_(offset) {} private: - std::string from_; - std::string prop_; - std::string val_; - std::string op_; - int32_t fuzziness_{-2}; - int32_t limit_{10000}; - int32_t timeout_{-1}; + std::string index_; + std::string query_; + std::vector props_; + int64_t count_ = 0; + int64_t offset_ = 0; }; class TextSearchExpression : public Expression { public: - static TextSearchExpression* makePrefix(ObjectPool* pool, TextSearchArgument* arg) { - return pool->makeAndAdd(pool, Kind::kTSPrefix, arg); - } - - static TextSearchExpression* makeWildcard(ObjectPool* pool, TextSearchArgument* arg) { - return pool->makeAndAdd(pool, Kind::kTSWildcard, arg); - } - - static TextSearchExpression* makeRegexp(ObjectPool* pool, TextSearchArgument* arg) { - return pool->makeAndAdd(pool, Kind::kTSRegexp, arg); + static TextSearchExpression* makeMatch(ObjectPool* pool, TextSearchArgument* arg) { + return pool->makeAndAdd(pool, Kind::kESMATCH, arg); } - static TextSearchExpression* makeFuzzy(ObjectPool* pool, TextSearchArgument* arg) { - return pool->makeAndAdd(pool, Kind::kTSFuzzy, arg); + static TextSearchExpression* makeQuery(ObjectPool* pool, TextSearchArgument* arg) { + return pool->makeAndAdd(pool, Kind::kESQUERY, arg); } static TextSearchExpression* make(ObjectPool* pool, Kind kind, TextSearchArgument* arg) { @@ -125,7 +93,8 @@ class TextSearchExpression : public Expression { std::string toString() const override; Expression* clone() const override { - auto arg = TextSearchArgument::make(pool_, arg_->from(), arg_->prop(), arg_->val()); + auto arg = TextSearchArgument::make( + pool_, arg_->index(), arg_->query(), arg_->props(), arg_->count(), arg_->offset()); return TextSearchExpression::make(pool_, kind_, arg); } diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp index fc6dceb18c6..9cd7b022a05 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp @@ -13,12 +13,9 @@ namespace nebula::plugin { using namespace fmt::literals; // NOLINT -ESQueryResult::Item::Item(const std::string& v, const std::string& t) : vid(v), text(t) {} -ESQueryResult::Item::Item(const std::string& s, - const std::string& d, - int64_t r, - const std::string& t) - : src(s), dst(d), rank(r), text(t) {} +ESQueryResult::Item::Item(const std::string& v, double sc) : vid(v), score(sc) {} +ESQueryResult::Item::Item(const std::string& s, const std::string& d, int64_t r, double sc) + : src(s), dst(d), rank(r), score(sc) {} void ESBulk::put(const std::string& indexName, const std::string& vid, @@ -68,30 +65,39 @@ void ESAdapter::setClients(std::vector&& clients) { clients_ = std::move(clients); } -Status ESAdapter::createIndex(const std::string& name) { - folly::dynamic mappings = folly::parseJson(R"( +Status ESAdapter::createIndex(const std::string& name, + const std::vector& fields, + const std::string& analyzer) { + folly::dynamic obj = folly::parseJson(R"( { "mappings":{ "properties":{ - "vid": { + "@vid": { "type": "keyword" }, - "src": { + "@src": { "type": "keyword" }, - "dst": { + "@dst": { "type": "keyword" }, - "rank": { + "@rank": { "type": "long" - }, - "text": { - "type": "keyword" } } } } )"); + auto& mappings = obj["mappings"]; + for (auto& field : fields) { + folly::dynamic f = folly::dynamic::object(); + f["type"] = "text"; + if (!analyzer.empty()) { + f["analyzer"] = analyzer; + } + mappings["properties"][field] = std::move(f); + } + auto result = randomClient().createIndex(name, mappings); if (!result.ok()) { return result.status(); @@ -181,70 +187,46 @@ Status ESAdapter::bulk(const ESBulk& bulk, bool refresh) { return Status::Error(folly::toJson(resp)); } -StatusOr ESAdapter::prefix(const std::string& index, - const std::string& pattern, - int64_t size, - int64_t timeout) { +StatusOr ESAdapter::multiMatch(const std::string& index, + const std::string& query, + const std::vector& fields, + int64_t from, + int64_t size) { folly::dynamic body = folly::dynamic::object(); body["query"] = folly::dynamic::object(); - body["query"]["prefix"] = folly::dynamic::object(); - body["query"]["prefix"]["text"] = pattern; - if (size > 0) { - body["size"] = size; - body["from"] = 0; + body["query"]["multi_match"] = folly::dynamic::object(); + body["query"]["multi_match"]["query"] = query; + body["query"]["multi_match"]["fields"] = folly::dynamic::array(); + for (auto& field : fields) { + body["query"]["multi_match"]["fields"].push_back(field); } - return ESAdapter::query(index, body, timeout); -} - -StatusOr ESAdapter::wildcard(const std::string& index, - const std::string& pattern, - int64_t size, - int64_t timeout) { - folly::dynamic body = folly::dynamic::object(); - body["query"] = folly::dynamic::object(); - body["query"]["wildcard"] = folly::dynamic::object("text", pattern); if (size > 0) { body["size"] = size; - body["from"] = 0; + body["from"] = from; } - return ESAdapter::query(index, body, timeout); + return ESAdapter::query(index, body, 2000); } -StatusOr ESAdapter::regexp(const std::string& index, - const std::string& pattern, - int64_t size, - int64_t timeout) { +StatusOr ESAdapter::queryString(const std::string& index, + const std::string& query, + const std::vector& fields, + int64_t from, + int64_t size) { folly::dynamic body = folly::dynamic::object(); body["query"] = folly::dynamic::object(); - body["query"]["regexp"] = folly::dynamic::object("text", pattern); - if (size > 0) { - body["size"] = size; - body["from"] = 0; + body["query"]["query_string"] = folly::dynamic::object(); + body["query"]["query_string"]["query"] = query; + body["query"]["query_string"]["fields"] = folly::dynamic::array(); + for (auto& field : fields) { + body["query"]["query_string"]["fields"].push_back(field); } - return ESAdapter::query(index, body, timeout); -} - -StatusOr ESAdapter::fuzzy(const std::string& index, - const std::string& pattern, - const std::string& fuzziness, - int64_t size, - int64_t timeout) { - folly::dynamic body = folly::dynamic::object(); - body["query"] = folly::dynamic::object(); - body["query"]["fuzzy"] = folly::dynamic::object(); - body["query"]["fuzzy"]["text"] = folly::dynamic::object("value", pattern)("fuzziness", fuzziness); if (size > 0) { body["size"] = size; - body["from"] = 0; + body["from"] = from; } - return ESAdapter::query(index, body, timeout); + return ESAdapter::query(index, body, 2000); } -// StatusOr ESAdapter::term(const std::string& index,const std::vector& -// words){ -// folly::dynamic query = folly::dynamic::object("query",folly::dynam) -// } - StatusOr ESAdapter::match_all(const std::string& index) { folly::dynamic body = folly::dynamic::object(); body["query"] = folly::dynamic::object(); @@ -266,7 +248,11 @@ StatusOr ESAdapter::query(const std::string& index, for (auto& hit : hits) { auto source = hit["_source"]; ESQueryResult::Item item; - item.text = source["text"].getString(); + if (hit["_score"].isInt()) { + item.score = hit["_score"].getInt(); + } else if (hit["_score"].isDouble()) { + item.score = hit["_score"].getDouble(); + } item.src = source["src"].getString(); item.dst = source["dst"].getString(); item.rank = source["rank"].getInt(); diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h index b1db86d2868..b1d6e15bd45 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h @@ -17,18 +17,18 @@ namespace nebula::plugin { struct ESQueryResult { struct Item { Item() = default; - Item(const std::string& vid, const std::string& text); - Item(const std::string& src, const std::string& dst, int64_t rank, const std::string& text); + Item(const std::string& vid, double score); + Item(const std::string& src, const std::string& dst, int64_t rank, double score); std::string vid; // for vertex std::string src, dst; // for edge int64_t rank = 0; // for edge - std::string text; + double score; bool operator==(const Item& item) const { return vid == item.vid && src == item.src && dst == item.dst && rank == item.rank && - text == item.text; + score == item.score; } std::ostream& operator<<(std::ostream& out) { - out << fmt::format("vid={}, src={}, dst={}, rank={}, text={}", vid, src, dst, rank, text); + out << fmt::format("vid={}, src={}, dst={}, rank={}, score={}", vid, src, dst, rank, score); return out; } }; @@ -67,143 +67,26 @@ class ESAdapter { } virtual ~ESAdapter() = default; virtual void setClients(std::vector&& clients); - virtual Status createIndex(const std::string& name); + virtual Status createIndex(const std::string& name, + const std::vector& fields, + const std::string& analyzer); virtual Status dropIndex(const std::string& name); virtual Status clearIndex(const std::string& name, bool refresh = false); virtual StatusOr isIndexExist(const std::string& name); virtual Status bulk(const ESBulk& bulk, bool refresh = false); - /** - * @brief - * - * Query: - * - * GET /_search - * { - * "query": { - * "prefix": { - * "text": { - * "value": $pattern - * } - * } - * } - * } - * - * @param index - * @param pattern - * @return StatusOr - */ - virtual StatusOr prefix(const std::string& index, - const std::string& pattern, - int64_t size = -1, - int64_t timeout = -1); - - /** - * @brief - * - * - * Query: - * - * GET /_search - * { - * "query": { - * "fuzzy": { - * "text": { - * "value": $pattern - * } - * } - * } - * } - * - * @param index - * @param pattern - * @return StatusOr - */ - virtual StatusOr fuzzy(const std::string& index, - const std::string& pattern, - const std::string& fuzziness, - int64_t size = -1, - int64_t timeout = -1); - - /** - * @brief - * - * Query: - * - * GET /_search - * { - * "query": { - * "regexp": { - * "text": { - * "value": $pattern - * } - * } - * } - * } - * - * @param index - * @param pattern - * @return StatusOr - */ - virtual StatusOr regexp(const std::string& index, - const std::string& pattern, - int64_t size = -1, - int64_t timeout = -1); - - /** - * @brief - * - * Query: - * - * GET /_search - * { - * "query": { - * "wildcard": { - * "text": { - * "value": $pattern - * } - * } - * } - * } - * - * @param index - * @param pattern - * @return StatusOr - */ - virtual StatusOr wildcard(const std::string& index, - const std::string& pattern, - int64_t size = -1, - int64_t timeout = -1); - - // /** - // * @brief - // * - // * Query: - // * - // * GET /_search - // * { - // * "query": { - // * "bool": { - // * "must": [ - // * { - // * "term": { - // * "text": { - // * "value": $words[i] - // * } - // * } - // * }, - // * ... - // * ] - // * } - // * } - // * } - // * - // * @param index - // * @param words - // * @return StatusOr - // */ - // StatusOr term(const std::string& index, const std::vector& words); + virtual StatusOr multiMatch(const std::string& index, + const std::string& query, + const std::vector& fields, + int64_t from, + int64_t size); + + virtual StatusOr queryString(const std::string& index, + const std::string& query, + const std::vector& fields, + int64_t from, + int64_t size); virtual StatusOr match_all(const std::string& index); StatusOr query(const std::string& index, diff --git a/src/common/plugin/fulltext/test/CMakeLists.txt b/src/common/plugin/fulltext/test/CMakeLists.txt index 2cf09e201a0..48025e78b68 100644 --- a/src/common/plugin/fulltext/test/CMakeLists.txt +++ b/src/common/plugin/fulltext/test/CMakeLists.txt @@ -2,19 +2,19 @@ # # This source code is licensed under Apache 2.0 License. -nebula_add_test( - NAME - es_test - SOURCES - ElasticsearchTest.cpp - OBJECTS - $ - $ - $ - LIBRARIES - gtest - gtest_main - gmock - curl - ${PROXYGEN_LIBRARIES} -) +# nebula_add_test( +# NAME +# es_test +# SOURCES +# ElasticsearchTest.cpp +# OBJECTS +# $ +# $ +# $ +# LIBRARIES +# gtest +# gtest_main +# gmock +# curl +# ${PROXYGEN_LIBRARIES} +# ) diff --git a/src/common/plugin/fulltext/test/ElasticsearchTest.cpp b/src/common/plugin/fulltext/test/ElasticsearchTest.cpp index ce8ea5ad15b..4e265db1541 100644 --- a/src/common/plugin/fulltext/test/ElasticsearchTest.cpp +++ b/src/common/plugin/fulltext/test/ElasticsearchTest.cpp @@ -19,696 +19,698 @@ using ::testing::_; using ::testing::Eq; using ::testing::Return; -class MockHttpClient : public HttpClient { - public: - MOCK_METHOD(HttpResponse, get, (const std::string& url), (override)); - MOCK_METHOD(HttpResponse, - get, - (const std::string& url, const std::vector& headers), - (override)); - MOCK_METHOD(HttpResponse, - get, - (const std::string& url, - const std::vector& headers, - const std::string&, - const std::string&), - (override)); - MOCK_METHOD(HttpResponse, - post, - (const std::string& url, - const std::vector& headers, - const std::string& body), - (override)); - MOCK_METHOD(HttpResponse, - post, - (const std::string& url, - const std::vector& headers, - const std::string& body, - const std::string&, - const std::string&), - (override)); - MOCK_METHOD(HttpResponse, - delete_, - (const std::string& url, const std::vector& headers), - (override)); - MOCK_METHOD(HttpResponse, - delete_, - (const std::string& url, - const std::vector& headers, - const std::string&, - const std::string&), - (override)); - MOCK_METHOD(HttpResponse, - put, - (const std::string& url, - const std::vector& headers, - const std::string& body), - (override)); - MOCK_METHOD(HttpResponse, - put, - (const std::string& url, - const std::vector& headers, - const std::string& body, - const std::string&, - const std::string&), - (override)); -}; - -HttpResponse operator"" _http_resp(const char* s, size_t) { - HttpResponse resp; - folly::StringPiece text(s); - text = folly::trimWhitespace(text); - std::string curlResult = text.split_step("\n\n").toString(); - std::regex curlReg(R"(curl: \((\d+)\)( (.*))?)"); - std::smatch matchResult; - CHECK(std::regex_match(curlResult, matchResult, curlReg)); - resp.curlCode = CURLcode(std::stoi(matchResult[1].str())); - if (matchResult.size() > 2) { - resp.curlMessage = matchResult[3]; - } - if (!text.empty()) { - folly::StringPiece header = text.split_step("\n\n"); - resp.header = header.toString(); - } - if (!text.empty()) { - resp.body = text.toString(); - } - return resp; -} - -class ESTest : public ::testing::Test { - public: - void SetUp() override { - queryResultResp_.body = queryResultBody; - } - - protected: - HttpResponse queryResultResp_ = R"( -curl: (0) - -HTTP/1.1 200 OK -content-type: application/json; charset=UTF-8 -content-length: 78 - -)"_http_resp; - HttpResponse normalSuccessResp_ = R"( -curl: (0) - -HTTP/1.1 200 OK -content-type: application/json; charset=UTF-8 -content-length: 78 - -{"acknowledged":true,"shards_acknowledged":true,"index":"nebula_test_index_1"} -)"_http_resp; - - HttpResponse esErrorResp_ = R"( -curl: (0) - -HTTP/1.1 400 Bad Request -content-type: application/json; charset=UTF-8 -content-length: 417 - -{"error":{"reason":"mock error"},"status":400} -)"_http_resp; - - HttpResponse curlErrorResp_ = R"( -curl: (7) mock error message - -)"_http_resp; -}; - -TEST_F(ESTest, createIndex) { - MockHttpClient mockHttpClient; - EXPECT_CALL(mockHttpClient, - put("http://127.0.0.1:9200/nebula_index_1", - std::vector{"Content-Type: application/json"}, - _, - "", - "")) - .Times(3) - .WillOnce(Return(normalSuccessResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - std::vector clients; - clients.push_back(client); - plugin::ESAdapter adapter(std::move(clients)); - { - auto result = adapter.createIndex("nebula_index_1"); - ASSERT_TRUE(result.ok()); - } - { - auto result = adapter.createIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.createIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, dropIndex) { - MockHttpClient mockHttpClient; - EXPECT_CALL(mockHttpClient, - delete_("http://127.0.0.1:9200/nebula_index_1", - std::vector{"Content-Type: application/json"}, - "", - "")) - .Times(3) - .WillOnce(Return(normalSuccessResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.dropIndex("nebula_index_1"); - ASSERT_TRUE(result.ok()); - } - { - auto result = adapter.dropIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.dropIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, getIndex) { - MockHttpClient mockHttpClient; - HttpResponse indexExistResp = R"( -curl: (0) - -HTTP/1.1 200 OK -content-type: application/json; charset=UTF-8 -content-length: 78 - -{"nebula_index_1":{}} -)"_http_resp; - - HttpResponse indexNotExistResp = R"( -curl: (0) - -HTTP/1.1 404 OK -content-type: application/json; charset=UTF-8 -content-length: 78 - -{"error":{"reason":"mock error"},"status":404} -)"_http_resp; - - EXPECT_CALL(mockHttpClient, - get("http://127.0.0.1:9200/nebula_index_1", - std::vector{"Content-Type: application/json"}, - "", - "")) - .Times(4) - .WillOnce(Return(indexExistResp)) - .WillOnce(Return(indexNotExistResp)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.isIndexExist("nebula_index_1"); - ASSERT_TRUE(result.ok()); - ASSERT_TRUE(result.value()); - } - { - auto result = adapter.isIndexExist("nebula_index_1"); - ASSERT_TRUE(result.ok()); - ASSERT_FALSE(result.value()); - } - { - auto result = adapter.isIndexExist("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.isIndexExist("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, clearIndex) { - MockHttpClient mockHttpClient; - auto clearSuccessResp_ = R"( -curl: (0) - -HTTP/1.1 200 OK -content-type: application/json; charset=UTF-8 -content-length: 78 - -{"failures":[]} -)"_http_resp; - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=false", - std::vector{"Content-Type: application/json"}, - _, - "", - "")) - .Times(2) - .WillOnce(Return(clearSuccessResp_)) - .WillOnce(Return(esErrorResp_)); - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=true", - std::vector{"Content-Type: application/json"}, - _, - "", - "")) - .Times(1) - .WillOnce(Return(curlErrorResp_)); - - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.clearIndex("nebula_index_1"); - ASSERT_TRUE(result.ok()); - } - { - auto result = adapter.clearIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.clearIndex("nebula_index_1", true); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, prefix) { - folly::dynamic prefixBody = folly::parseJson(R"( - { - "query":{ - "prefix":{ - "text":"abc" - } - } - } - )"); - MockHttpClient mockHttpClient; - - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_search", - std::vector{"Content-Type: application/json"}, - folly::toJson(prefixBody), - std::string(""), - std::string(""))) - .Times(3) - .WillOnce(Return(queryResultResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); - ASSERT_TRUE(result.ok()); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); - } - { - auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, wildcard) { - folly::dynamic body = folly::parseJson(R"( - { - "query":{ - "wildcard":{ - "text":"abc" - } - } - } - )"); - MockHttpClient mockHttpClient; - - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_search", - std::vector{"Content-Type: application/json"}, - folly::toJson(body), - "", - "")) - .Times(3) - .WillOnce(Return(queryResultResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); - ASSERT_TRUE(result.ok()); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); - } - { - auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, regexp) { - folly::dynamic body = folly::parseJson(R"( - { - "query":{ - "regexp":{ - "text":"abc" - } - } - } - )"); - MockHttpClient mockHttpClient; - - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_search", - std::vector{"Content-Type: application/json"}, - folly::toJson(body), - "", - "")) - .Times(3) - .WillOnce(Return(queryResultResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); - ASSERT_TRUE(result.ok()); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); - } - { - auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, bulk) { - MockHttpClient mockHttpClient; - - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/_bulk?refresh=true", - std::vector{"Content-Type: application/x-ndjson"}, - _, - "", - "")) // TODO(hs.zhang): Matcher - .Times(2) - .WillOnce(Return(queryResultResp_)) - .WillOnce(Return(esErrorResp_)); - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/_bulk?refresh=false", - std::vector{"Content-Type: application/x-ndjson"}, - _, - "", - "")) // TODO(hs.zhang): Matcher - .Times(1) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - plugin::ESBulk bulk; - bulk.put("nebula_index_1", "1", "", "", 0, "vertex text"); - bulk.delete_("nebula_index_2", "", "a", "b", 10); - { - auto result = adapter.bulk(bulk, true); - ASSERT_TRUE(result.ok()); - } - { - auto result = adapter.bulk(bulk, true); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.bulk(bulk, false); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); - } -} - -TEST_F(ESTest, fuzzy) { - // folly::dynamic body = folly::parseJson(R"( - // { - // "query":{ - // "fuzzy":{ - // "text":{ - // "fuzziness": "2", - // "value": "abc" - // } - // } - // } - // } - // )"); - MockHttpClient mockHttpClient; - - EXPECT_CALL(mockHttpClient, - post("http://127.0.0.1:9200/nebula_index_1/_search", - std::vector{"Content-Type: application/json"}, - _, - "", - "")) - .Times(3) - .WillOnce(Return(queryResultResp_)) - .WillOnce(Return(esErrorResp_)) - .WillOnce(Return(curlErrorResp_)); - plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); - ASSERT_TRUE(result.ok()); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); - } - { - auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); - } - { - auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); - } -} - -DEFINE_string(es_address, "192.168.8.211:9200", "address of elasticsearch"); -class RealESTest : public ::testing::Test {}; - -TEST_F(RealESTest, DISABLED_CREATE_DROP_INDEX) { - plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.createIndex("nebula_index_1"); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.createIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_NE(result.message().find("resource_already_exists_exception"), std::string::npos); - } - { - auto result = adapter.dropIndex("nebula_index_1"); - ASSERT_TRUE(result.ok()); - } - { - auto result = adapter.dropIndex("nebula_index_1"); - ASSERT_FALSE(result.ok()); - ASSERT_NE(result.message().find("index_not_found_exception"), std::string::npos); - } -} - -TEST_F(RealESTest, DISABLED_QUERY) { - plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); - std::string indexName = "nebula_index_2"; - plugin::ESAdapter adapter(std::vector({client})); - { - auto result = adapter.createIndex(indexName); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - plugin::ESBulk bulk; - bulk.put(indexName, "1", "", "", 0, "abc"); - bulk.put(indexName, "2", "", "", 0, "abcd"); - auto result = adapter.bulk(bulk, true); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.prefix(indexName, "a", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); - } - { - auto result = adapter.regexp(indexName, "a.*", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); - } - { - auto result = adapter.prefix(indexName, "abcd", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 1); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("2", "abcd")); - } - { - plugin::ESBulk bulk; - bulk.put(indexName, "2", "", "", 0, "NebulaGraph is a graph database"); - bulk.put(indexName, "3", "", "", 0, "The best graph database is NebulaGraph"); - bulk.put(indexName, "4", "", "", 0, "Nebulagraph是最好的图数据库"); - bulk.put(indexName, "5", "", "", 0, "NebulaGraph是一个图数据库"); - auto result = adapter.bulk(bulk, true); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.match_all(indexName); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 5); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); - ASSERT_EQ(esResult.items[1], - plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); - ASSERT_EQ(esResult.items[2], - plugin::ESQueryResult::Item("3", "The best graph database is NebulaGraph")); - ASSERT_EQ(esResult.items[3], plugin::ESQueryResult::Item("4", "Nebulagraph是最好的图数据库")); - ASSERT_EQ(esResult.items[4], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); - } - { - auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], - plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); - } - { - auto result = adapter.regexp(indexName, "NebulaGraph.*(图数据库|database)", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], - plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); - } - { - auto result = adapter.wildcard(indexName, "Nebula?raph是*", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 2); - std::sort(esResult.items.begin(), - esResult.items.end(), - [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { - return a.vid < b.vid; - }); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("4", "Nebulagraph是最好的图数据库")); - ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); - } - { - auto result = adapter.fuzzy(indexName, "Nebulagraph is a graph Database", "2", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 1); - - ASSERT_EQ(esResult.items[0], - plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); - } - { - plugin::ESBulk bulk; - bulk.delete_(indexName, "2", "", "", 0); - auto result = adapter.bulk(bulk, true); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 1); - ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); - } - { - auto result = adapter.clearIndex(indexName, true); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.match_all(indexName); - ASSERT_TRUE(result.ok()) << result.status().message(); - auto esResult = std::move(result).value(); - ASSERT_EQ(esResult.items.size(), 0); - } - { - auto result = adapter.isIndexExist(indexName); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_TRUE(result.value()); - } - { - auto result = adapter.dropIndex(indexName); - ASSERT_TRUE(result.ok()) << result.message(); - } - { - auto result = adapter.isIndexExist(indexName); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_FALSE(result.value()); - } -} +// class MockHttpClient : public HttpClient { +// public: +// MOCK_METHOD(HttpResponse, get, (const std::string& url), (override)); +// MOCK_METHOD(HttpResponse, +// get, +// (const std::string& url, const std::vector& headers), +// (override)); +// MOCK_METHOD(HttpResponse, +// get, +// (const std::string& url, +// const std::vector& headers, +// const std::string&, +// const std::string&), +// (override)); +// MOCK_METHOD(HttpResponse, +// post, +// (const std::string& url, +// const std::vector& headers, +// const std::string& body), +// (override)); +// MOCK_METHOD(HttpResponse, +// post, +// (const std::string& url, +// const std::vector& headers, +// const std::string& body, +// const std::string&, +// const std::string&), +// (override)); +// MOCK_METHOD(HttpResponse, +// delete_, +// (const std::string& url, const std::vector& headers), +// (override)); +// MOCK_METHOD(HttpResponse, +// delete_, +// (const std::string& url, +// const std::vector& headers, +// const std::string&, +// const std::string&), +// (override)); +// MOCK_METHOD(HttpResponse, +// put, +// (const std::string& url, +// const std::vector& headers, +// const std::string& body), +// (override)); +// MOCK_METHOD(HttpResponse, +// put, +// (const std::string& url, +// const std::vector& headers, +// const std::string& body, +// const std::string&, +// const std::string&), +// (override)); +// }; + +// HttpResponse operator"" _http_resp(const char* s, size_t) { +// HttpResponse resp; +// folly::StringPiece text(s); +// text = folly::trimWhitespace(text); +// std::string curlResult = text.split_step("\n\n").toString(); +// std::regex curlReg(R"(curl: \((\d+)\)( (.*))?)"); +// std::smatch matchResult; +// CHECK(std::regex_match(curlResult, matchResult, curlReg)); +// resp.curlCode = CURLcode(std::stoi(matchResult[1].str())); +// if (matchResult.size() > 2) { +// resp.curlMessage = matchResult[3]; +// } +// if (!text.empty()) { +// folly::StringPiece header = text.split_step("\n\n"); +// resp.header = header.toString(); +// } +// if (!text.empty()) { +// resp.body = text.toString(); +// } +// return resp; +// } + +// class ESTest : public ::testing::Test { +// public: +// void SetUp() override { +// queryResultResp_.body = queryResultBody; +// } + +// protected: +// HttpResponse queryResultResp_ = R"( +// curl: (0) + +// HTTP/1.1 200 OK +// content-type: application/json; charset=UTF-8 +// content-length: 78 + +// )"_http_resp; +// HttpResponse normalSuccessResp_ = R"( +// curl: (0) + +// HTTP/1.1 200 OK +// content-type: application/json; charset=UTF-8 +// content-length: 78 + +// {"acknowledged":true,"shards_acknowledged":true,"index":"nebula_test_index_1"} +// )"_http_resp; + +// HttpResponse esErrorResp_ = R"( +// curl: (0) + +// HTTP/1.1 400 Bad Request +// content-type: application/json; charset=UTF-8 +// content-length: 417 + +// {"error":{"reason":"mock error"},"status":400} +// )"_http_resp; + +// HttpResponse curlErrorResp_ = R"( +// curl: (7) mock error message + +// )"_http_resp; +// }; + +// TEST_F(ESTest, createIndex) { +// MockHttpClient mockHttpClient; +// EXPECT_CALL(mockHttpClient, +// put("http://127.0.0.1:9200/nebula_index_1", +// std::vector{"Content-Type: application/json"}, +// _, +// "", +// "")) +// .Times(3) +// .WillOnce(Return(normalSuccessResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// std::vector clients; +// clients.push_back(client); +// plugin::ESAdapter adapter(std::move(clients)); +// { +// auto result = adapter.createIndex("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// } +// { +// auto result = adapter.createIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.createIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, dropIndex) { +// MockHttpClient mockHttpClient; +// EXPECT_CALL(mockHttpClient, +// delete_("http://127.0.0.1:9200/nebula_index_1", +// std::vector{"Content-Type: application/json"}, +// "", +// "")) +// .Times(3) +// .WillOnce(Return(normalSuccessResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.dropIndex("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// } +// { +// auto result = adapter.dropIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.dropIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, getIndex) { +// MockHttpClient mockHttpClient; +// HttpResponse indexExistResp = R"( +// curl: (0) + +// HTTP/1.1 200 OK +// content-type: application/json; charset=UTF-8 +// content-length: 78 + +// {"nebula_index_1":{}} +// )"_http_resp; + +// HttpResponse indexNotExistResp = R"( +// curl: (0) + +// HTTP/1.1 404 OK +// content-type: application/json; charset=UTF-8 +// content-length: 78 + +// {"error":{"reason":"mock error"},"status":404} +// )"_http_resp; + +// EXPECT_CALL(mockHttpClient, +// get("http://127.0.0.1:9200/nebula_index_1", +// std::vector{"Content-Type: application/json"}, +// "", +// "")) +// .Times(4) +// .WillOnce(Return(indexExistResp)) +// .WillOnce(Return(indexNotExistResp)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.isIndexExist("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// ASSERT_TRUE(result.value()); +// } +// { +// auto result = adapter.isIndexExist("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// ASSERT_FALSE(result.value()); +// } +// { +// auto result = adapter.isIndexExist("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.isIndexExist("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, clearIndex) { +// MockHttpClient mockHttpClient; +// auto clearSuccessResp_ = R"( +// curl: (0) + +// HTTP/1.1 200 OK +// content-type: application/json; charset=UTF-8 +// content-length: 78 + +// {"failures":[]} +// )"_http_resp; +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=false", +// std::vector{"Content-Type: application/json"}, +// _, +// "", +// "")) +// .Times(2) +// .WillOnce(Return(clearSuccessResp_)) +// .WillOnce(Return(esErrorResp_)); +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=true", +// std::vector{"Content-Type: application/json"}, +// _, +// "", +// "")) +// .Times(1) +// .WillOnce(Return(curlErrorResp_)); + +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.clearIndex("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// } +// { +// auto result = adapter.clearIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.clearIndex("nebula_index_1", true); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, prefix) { +// folly::dynamic prefixBody = folly::parseJson(R"( +// { +// "query":{ +// "prefix":{ +// "text":"abc" +// } +// } +// } +// )"); +// MockHttpClient mockHttpClient; + +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_search", +// std::vector{"Content-Type: application/json"}, +// folly::toJson(prefixBody), +// std::string(""), +// std::string(""))) +// .Times(3) +// .WillOnce(Return(queryResultResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); +// ASSERT_TRUE(result.ok()); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); +// } +// { +// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, wildcard) { +// folly::dynamic body = folly::parseJson(R"( +// { +// "query":{ +// "wildcard":{ +// "text":"abc" +// } +// } +// } +// )"); +// MockHttpClient mockHttpClient; + +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_search", +// std::vector{"Content-Type: application/json"}, +// folly::toJson(body), +// "", +// "")) +// .Times(3) +// .WillOnce(Return(queryResultResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); +// ASSERT_TRUE(result.ok()); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); +// } +// { +// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, regexp) { +// folly::dynamic body = folly::parseJson(R"( +// { +// "query":{ +// "regexp":{ +// "text":"abc" +// } +// } +// } +// )"); +// MockHttpClient mockHttpClient; + +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_search", +// std::vector{"Content-Type: application/json"}, +// folly::toJson(body), +// "", +// "")) +// .Times(3) +// .WillOnce(Return(queryResultResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); +// ASSERT_TRUE(result.ok()); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); +// } +// { +// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, bulk) { +// MockHttpClient mockHttpClient; + +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/_bulk?refresh=true", +// std::vector{"Content-Type: application/x-ndjson"}, +// _, +// "", +// "")) // TODO(hs.zhang): Matcher +// .Times(2) +// .WillOnce(Return(queryResultResp_)) +// .WillOnce(Return(esErrorResp_)); +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/_bulk?refresh=false", +// std::vector{"Content-Type: application/x-ndjson"}, +// _, +// "", +// "")) // TODO(hs.zhang): Matcher +// .Times(1) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// plugin::ESBulk bulk; +// bulk.put("nebula_index_1", "1", "", "", 0, "vertex text"); +// bulk.delete_("nebula_index_2", "", "a", "b", 10); +// { +// auto result = adapter.bulk(bulk, true); +// ASSERT_TRUE(result.ok()); +// } +// { +// auto result = adapter.bulk(bulk, true); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.bulk(bulk, false); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); +// } +// } + +// TEST_F(ESTest, fuzzy) { +// // folly::dynamic body = folly::parseJson(R"( +// // { +// // "query":{ +// // "fuzzy":{ +// // "text":{ +// // "fuzziness": "2", +// // "value": "abc" +// // } +// // } +// // } +// // } +// // )"); +// MockHttpClient mockHttpClient; + +// EXPECT_CALL(mockHttpClient, +// post("http://127.0.0.1:9200/nebula_index_1/_search", +// std::vector{"Content-Type: application/json"}, +// _, +// "", +// "")) +// .Times(3) +// .WillOnce(Return(queryResultResp_)) +// .WillOnce(Return(esErrorResp_)) +// .WillOnce(Return(curlErrorResp_)); +// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); +// ASSERT_TRUE(result.ok()); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); +// } +// { +// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); +// } +// { +// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); +// ASSERT_FALSE(result.ok()); +// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); +// } +// } + +// DEFINE_string(es_address, "192.168.8.211:9200", "address of elasticsearch"); +// class RealESTest : public ::testing::Test {}; + +// TEST_F(RealESTest, DISABLED_CREATE_DROP_INDEX) { +// plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.createIndex("nebula_index_1"); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.createIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_NE(result.message().find("resource_already_exists_exception"), std::string::npos); +// } +// { +// auto result = adapter.dropIndex("nebula_index_1"); +// ASSERT_TRUE(result.ok()); +// } +// { +// auto result = adapter.dropIndex("nebula_index_1"); +// ASSERT_FALSE(result.ok()); +// ASSERT_NE(result.message().find("index_not_found_exception"), std::string::npos); +// } +// } + +// TEST_F(RealESTest, DISABLED_QUERY) { +// plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); +// std::string indexName = "nebula_index_2"; +// plugin::ESAdapter adapter(std::vector({client})); +// { +// auto result = adapter.createIndex(indexName); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// plugin::ESBulk bulk; +// bulk.put(indexName, "1", "", "", 0, "abc"); +// bulk.put(indexName, "2", "", "", 0, "abcd"); +// auto result = adapter.bulk(bulk, true); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.prefix(indexName, "a", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); +// } +// { +// auto result = adapter.regexp(indexName, "a.*", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); +// } +// { +// auto result = adapter.prefix(indexName, "abcd", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 1); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("2", "abcd")); +// } +// { +// plugin::ESBulk bulk; +// bulk.put(indexName, "2", "", "", 0, "NebulaGraph is a graph database"); +// bulk.put(indexName, "3", "", "", 0, "The best graph database is NebulaGraph"); +// bulk.put(indexName, "4", "", "", 0, "Nebulagraph是最好的图数据库"); +// bulk.put(indexName, "5", "", "", 0, "NebulaGraph是一个图数据库"); +// auto result = adapter.bulk(bulk, true); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.match_all(indexName); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 5); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); +// ASSERT_EQ(esResult.items[1], +// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); +// ASSERT_EQ(esResult.items[2], +// plugin::ESQueryResult::Item("3", "The best graph database is NebulaGraph")); +// ASSERT_EQ(esResult.items[3], plugin::ESQueryResult::Item("4", +// "Nebulagraph是最好的图数据库")); ASSERT_EQ(esResult.items[4], +// plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); +// } +// { +// auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], +// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); +// } +// { +// auto result = adapter.regexp(indexName, "NebulaGraph.*(图数据库|database)", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], +// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); +// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); +// } +// { +// auto result = adapter.wildcard(indexName, "Nebula?raph是*", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 2); +// std::sort(esResult.items.begin(), +// esResult.items.end(), +// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { +// return a.vid < b.vid; +// }); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("4", +// "Nebulagraph是最好的图数据库")); ASSERT_EQ(esResult.items[1], +// plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); +// } +// { +// auto result = adapter.fuzzy(indexName, "Nebulagraph is a graph Database", "2", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 1); + +// ASSERT_EQ(esResult.items[0], +// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); +// } +// { +// plugin::ESBulk bulk; +// bulk.delete_(indexName, "2", "", "", 0); +// auto result = adapter.bulk(bulk, true); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 1); +// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); +// } +// { +// auto result = adapter.clearIndex(indexName, true); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.match_all(indexName); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// auto esResult = std::move(result).value(); +// ASSERT_EQ(esResult.items.size(), 0); +// } +// { +// auto result = adapter.isIndexExist(indexName); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// ASSERT_TRUE(result.value()); +// } +// { +// auto result = adapter.dropIndex(indexName); +// ASSERT_TRUE(result.ok()) << result.message(); +// } +// { +// auto result = adapter.isIndexExist(indexName); +// ASSERT_TRUE(result.ok()) << result.status().message(); +// ASSERT_FALSE(result.value()); +// } +// } } // namespace nebula diff --git a/src/graph/context/ast/QueryAstContext.h b/src/graph/context/ast/QueryAstContext.h index db981658b82..d69a3f39ef5 100644 --- a/src/graph/context/ast/QueryAstContext.h +++ b/src/graph/context/ast/QueryAstContext.h @@ -122,7 +122,6 @@ struct LookupContext final : public AstContext { // fulltext index bool isFulltextIndex{false}; - std::string fulltextIndex; Expression* fulltextExpr{nullptr}; // order by diff --git a/src/graph/executor/query/FulltextIndexScanExecutor.cpp b/src/graph/executor/query/FulltextIndexScanExecutor.cpp index 42fd35309bc..fa81bebedfe 100644 --- a/src/graph/executor/query/FulltextIndexScanExecutor.cpp +++ b/src/graph/executor/query/FulltextIndexScanExecutor.cpp @@ -21,7 +21,7 @@ folly::Future FulltextIndexScanExecutor::execute() { } esAdapter_ = std::move(esAdapterResult).value(); auto* ftIndexScan = asNode(node()); - auto esQueryResult = accessFulltextIndex(ftIndexScan->index(), ftIndexScan->searchExpression()); + auto esQueryResult = accessFulltextIndex(ftIndexScan->searchExpression()); if (!esQueryResult.ok()) { LOG(ERROR) << esQueryResult.status().message(); return esQueryResult.status(); @@ -32,12 +32,14 @@ folly::Future FulltextIndexScanExecutor::execute() { if (ftIndexScan->isEdge()) { DataSet edges({kSrc, kRank, kDst}); for (auto& item : esResultValue.items) { + // TODO(hs.zhang): return item.score edges.emplace_back(Row({item.src, item.rank, item.dst})); } finish(ResultBuilder().value(Value(std::move(edges))).iter(Iterator::Kind::kProp).build()); } else { DataSet vertices({kVid}); for (auto& item : esResultValue.items) { + // TODO(hs.zhang): return item.score vertices.emplace_back(Row({item.vid})); } finish(ResultBuilder().value(Value(std::move(vertices))).iter(Iterator::Kind::kProp).build()); @@ -67,45 +69,31 @@ folly::Future FulltextIndexScanExecutor::execute() { } StatusOr FulltextIndexScanExecutor::accessFulltextIndex( - const std::string& index, TextSearchExpression* tsExpr) { + TextSearchExpression* tsExpr) { std::function()> execFunc; plugin::ESAdapter& esAdapter = esAdapter_; switch (tsExpr->kind()) { - case Expression::Kind::kTSFuzzy: { - std::string pattern = tsExpr->arg()->val(); - int fuzziness = tsExpr->arg()->fuzziness(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, fuzziness, size, timeout]() { - return esAdapter.fuzzy( - index, pattern, fuzziness < 0 ? "AUTO" : std::to_string(fuzziness), size, timeout); + case Expression::Kind::kESMATCH: { + auto arg = tsExpr->arg(); + auto index = arg->index(); + auto query = arg->query(); + auto props = arg->props(); + auto count = arg->count(); + auto offset = arg->offset(); + execFunc = [=, &esAdapter]() { + return esAdapter.multiMatch(index, query, props, offset, count); }; break; } - case Expression::Kind::kTSPrefix: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.prefix(index, pattern, size, timeout); - }; - break; - } - case Expression::Kind::kTSRegexp: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.regexp(index, pattern, size, timeout); - }; - break; - } - case Expression::Kind::kTSWildcard: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.wildcard(index, pattern, size, timeout); + case Expression::Kind::kESQUERY: { + auto arg = tsExpr->arg(); + auto index = arg->index(); + auto query = arg->query(); + auto props = arg->props(); + auto count = arg->count(); + auto offset = arg->offset(); + execFunc = [=, &esAdapter]() { + return esAdapter.queryString(index, query, props, offset, count); }; break; } diff --git a/src/graph/executor/query/FulltextIndexScanExecutor.h b/src/graph/executor/query/FulltextIndexScanExecutor.h index 7c05f443a3d..e199635f633 100644 --- a/src/graph/executor/query/FulltextIndexScanExecutor.h +++ b/src/graph/executor/query/FulltextIndexScanExecutor.h @@ -19,8 +19,7 @@ class FulltextIndexScanExecutor final : public Executor { folly::Future execute() override; private: - StatusOr accessFulltextIndex(const std::string& index, - TextSearchExpression* expr); + StatusOr accessFulltextIndex(TextSearchExpression* expr); bool isIntVidType(const SpaceInfo& space) const { return (*space.spaceDesc.vid_type_ref()).type == nebula::cpp2::PropertyType::INT64; diff --git a/src/graph/planner/ngql/LookupPlanner.cpp b/src/graph/planner/ngql/LookupPlanner.cpp index 41fbc9fa0dc..3f757072499 100644 --- a/src/graph/planner/ngql/LookupPlanner.cpp +++ b/src/graph/planner/ngql/LookupPlanner.cpp @@ -30,8 +30,7 @@ StatusOr LookupPlanner::transform(AstContext* astCtx) { SubPlan plan; if (lookupCtx->isFulltextIndex) { auto expr = static_cast(lookupCtx->fulltextExpr); - auto fulltextIndexScan = - FulltextIndexScan::make(qctx, lookupCtx->fulltextIndex, expr, lookupCtx->isEdge); + auto fulltextIndexScan = FulltextIndexScan::make(qctx, expr, lookupCtx->isEdge); plan.tail = fulltextIndexScan; plan.root = fulltextIndexScan; diff --git a/src/graph/planner/plan/Query.cpp b/src/graph/planner/plan/Query.cpp index 6dd9c037f48..ef174124bbe 100644 --- a/src/graph/planner/plan/Query.cpp +++ b/src/graph/planner/plan/Query.cpp @@ -1059,7 +1059,7 @@ PlanNode* PatternApply::clone() const { } PlanNode* FulltextIndexScan::clone() const { - auto ret = FulltextIndexScan::make(qctx_, index_, searchExpr_, isEdge_); + auto ret = FulltextIndexScan::make(qctx_, searchExpr_, isEdge_); ret->cloneMembers(*this); return ret; } diff --git a/src/graph/planner/plan/Query.h b/src/graph/planner/plan/Query.h index 784a22d18b7..36a251c7ee3 100644 --- a/src/graph/planner/plan/Query.h +++ b/src/graph/planner/plan/Query.h @@ -753,13 +753,9 @@ class IndexScan : public Explore { class FulltextIndexScan : public Explore { public: static FulltextIndexScan* make(QueryContext* qctx, - const std::string& index, TextSearchExpression* searchExpr, bool isEdge) { - return qctx->objPool()->makeAndAdd(qctx, index, searchExpr, isEdge); - } - const std::string& index() const { - return index_; + return qctx->objPool()->makeAndAdd(qctx, searchExpr, isEdge); } TextSearchExpression* searchExpression() const { @@ -776,15 +772,10 @@ class FulltextIndexScan : public Explore { protected: friend ObjectPool; - FulltextIndexScan(QueryContext* qctx, - const std::string& index, - TextSearchExpression* searchExpr, - bool isEdge) + FulltextIndexScan(QueryContext* qctx, TextSearchExpression* searchExpr, bool isEdge) : Explore(qctx, Kind::kFulltextIndexScan, nullptr, 0, false, -1, nullptr, {}), - index_(index), searchExpr_(searchExpr), isEdge_(isEdge) {} - std::string index_; TextSearchExpression* searchExpr_{nullptr}; bool isEdge_{false}; }; diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index 29d95db9755..d39b4fbffe2 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -1557,10 +1557,8 @@ bool ExpressionUtils::checkExprDepth(const Expression *expr) { case Expression::Kind::kUUID: case Expression::Kind::kPathBuild: case Expression::Kind::kColumn: - case Expression::Kind::kTSPrefix: - case Expression::Kind::kTSWildcard: - case Expression::Kind::kTSRegexp: - case Expression::Kind::kTSFuzzy: + case Expression::Kind::kESMATCH: + case Expression::Kind::kESQUERY: case Expression::Kind::kAggregate: case Expression::Kind::kSubscriptRange: case Expression::Kind::kVersionedVar: diff --git a/src/graph/util/FTIndexUtils.cpp b/src/graph/util/FTIndexUtils.cpp index ab64e060d30..372d0762cc7 100644 --- a/src/graph/util/FTIndexUtils.cpp +++ b/src/graph/util/FTIndexUtils.cpp @@ -14,10 +14,8 @@ namespace graph { bool FTIndexUtils::needTextSearch(const Expression* expr) { switch (expr->kind()) { - case Expression::Kind::kTSFuzzy: - case Expression::Kind::kTSPrefix: - case Expression::Kind::kTSRegexp: - case Expression::Kind::kTSWildcard: { + case Expression::Kind::kESMATCH: + case Expression::Kind::kESQUERY: { return true; } default: @@ -44,96 +42,96 @@ StatusOr<::nebula::plugin::ESAdapter> FTIndexUtils::getESAdapter(meta::MetaClien return ::nebula::plugin::ESAdapter(std::move(clients)); } -StatusOr FTIndexUtils::rewriteTSFilter(ObjectPool* pool, - bool isEdge, - Expression* expr, - const std::string& index, - ::nebula::plugin::ESAdapter& esAdapter) { - auto vRet = textSearch(expr, index, esAdapter); - if (!vRet.ok()) { - return vRet.status(); - } - auto result = std::move(vRet).value(); - if (result.items.empty()) { - return nullptr; - } - auto tsArg = static_cast(expr)->arg(); - Expression* propExpr; - if (isEdge) { - propExpr = EdgePropertyExpression::make(pool, tsArg->from(), tsArg->prop()); - } else { - propExpr = TagPropertyExpression::make(pool, tsArg->from(), tsArg->prop()); - } - std::vector rels; - for (auto& item : result.items) { - auto constExpr = ConstantExpression::make(pool, Value(item.text)); - rels.emplace_back(RelationalExpression::makeEQ(pool, propExpr, constExpr)); - } - if (rels.size() == 1) { - return rels.front(); - } - return ExpressionUtils::pushOrs(pool, rels); -} +// StatusOr FTIndexUtils::rewriteTSFilter(ObjectPool* pool, +// bool isEdge, +// Expression* expr, +// const std::string& index, +// ::nebula::plugin::ESAdapter& esAdapter) { +// auto vRet = textSearch(expr, index, esAdapter); +// if (!vRet.ok()) { +// return vRet.status(); +// } +// auto result = std::move(vRet).value(); +// if (result.items.empty()) { +// return nullptr; +// } +// auto tsArg = static_cast(expr)->arg(); +// Expression* propExpr; +// if (isEdge) { +// propExpr = EdgePropertyExpression::make(pool, tsArg->from(), tsArg->prop()); +// } else { +// propExpr = TagPropertyExpression::make(pool, tsArg->from(), tsArg->prop()); +// } +// std::vector rels; +// for (auto& item : result.items) { +// auto constExpr = ConstantExpression::make(pool, Value(item.text)); +// rels.emplace_back(RelationalExpression::makeEQ(pool, propExpr, constExpr)); +// } +// if (rels.size() == 1) { +// return rels.front(); +// } +// return ExpressionUtils::pushOrs(pool, rels); +// } -StatusOr FTIndexUtils::textSearch( - Expression* expr, const std::string& index, ::nebula::plugin::ESAdapter& esAdapter) { - auto tsExpr = static_cast(expr); - std::function()> execFunc; - switch (tsExpr->kind()) { - case Expression::Kind::kTSFuzzy: { - std::string pattern = tsExpr->arg()->val(); - int fuzziness = tsExpr->arg()->fuzziness(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, fuzziness, size, timeout]() { - return esAdapter.fuzzy( - index, pattern, fuzziness < 0 ? "AUTO" : std::to_string(fuzziness), size, timeout); - }; - break; - } - case Expression::Kind::kTSPrefix: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.prefix(index, pattern, size, timeout); - }; - break; - } - case Expression::Kind::kTSRegexp: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.regexp(index, pattern, size, timeout); - }; - break; - } - case Expression::Kind::kTSWildcard: { - std::string pattern = tsExpr->arg()->val(); - int64_t size = tsExpr->arg()->limit(); - int64_t timeout = tsExpr->arg()->timeout(); - execFunc = [&index, pattern, &esAdapter, size, timeout]() { - return esAdapter.wildcard(index, pattern, size, timeout); - }; - break; - } - default: { - return Status::SemanticError("text search expression error"); - } - } +// StatusOr FTIndexUtils::textSearch( +// Expression* expr, const std::string& index, ::nebula::plugin::ESAdapter& esAdapter) { +// auto tsExpr = static_cast(expr); +// std::function()> execFunc; +// switch (tsExpr->kind()) { +// case Expression::Kind::kESMATCH: { +// std::string pattern = tsExpr->arg()->val(); +// int fuzziness = tsExpr->arg()->fuzziness(); +// int64_t size = tsExpr->arg()->limit(); +// int64_t timeout = tsExpr->arg()->timeout(); +// execFunc = [&index, pattern, &esAdapter, fuzziness, size, timeout]() { +// return esAdapter.multiMatch( +// index, pattern, fuzziness < 0 ? "AUTO" : std::to_string(fuzziness), size, timeout); +// }; +// break; +// } +// case Expression::Kind::kESQUERY: { +// std::string pattern = tsExpr->arg()->val(); +// int64_t size = tsExpr->arg()->limit(); +// int64_t timeout = tsExpr->arg()->timeout(); +// execFunc = [&index, pattern, &esAdapter, size, timeout]() { +// return esAdapter.prefix(index, pattern, size, timeout); +// }; +// break; +// } +// case Expression::Kind::kTSRegexp: { +// std::string pattern = tsExpr->arg()->val(); +// int64_t size = tsExpr->arg()->limit(); +// int64_t timeout = tsExpr->arg()->timeout(); +// execFunc = [&index, pattern, &esAdapter, size, timeout]() { +// return esAdapter.regexp(index, pattern, size, timeout); +// }; +// break; +// } +// case Expression::Kind::kTSWildcard: { +// std::string pattern = tsExpr->arg()->val(); +// int64_t size = tsExpr->arg()->limit(); +// int64_t timeout = tsExpr->arg()->timeout(); +// execFunc = [&index, pattern, &esAdapter, size, timeout]() { +// return esAdapter.wildcard(index, pattern, size, timeout); +// }; +// break; +// } +// default: { +// return Status::SemanticError("text search expression error"); +// } +// } - auto retryCnt = FLAGS_ft_request_retry_times > 0 ? FLAGS_ft_request_retry_times : 1; - StatusOr result; - while (retryCnt-- > 0) { - result = execFunc(); - if (!result.ok()) { - continue; - } - break; - } - return result; -} +// auto retryCnt = FLAGS_ft_request_retry_times > 0 ? FLAGS_ft_request_retry_times : 1; +// StatusOr result; +// while (retryCnt-- > 0) { +// result = execFunc(); +// if (!result.ok()) { +// continue; +// } +// break; +// } +// return result; +// } } // namespace graph } // namespace nebula diff --git a/src/graph/util/test/CMakeLists.txt b/src/graph/util/test/CMakeLists.txt index 09aa0bd8d6a..819bb4e5168 100644 --- a/src/graph/util/test/CMakeLists.txt +++ b/src/graph/util/test/CMakeLists.txt @@ -27,7 +27,7 @@ nebula_add_test( SOURCES ExpressionUtilsTest.cpp IdGeneratorTest.cpp - FTindexUtilsTest.cpp + # FTindexUtilsTest.cpp OBJECTS $ $ diff --git a/src/graph/validator/LookupValidator.cpp b/src/graph/validator/LookupValidator.cpp index ddfde70b894..73f513e6567 100644 --- a/src/graph/validator/LookupValidator.cpp +++ b/src/graph/validator/LookupValidator.cpp @@ -213,12 +213,46 @@ Status LookupValidator::validateWhere() { lookupCtx_->isFulltextIndex = true; lookupCtx_->fulltextExpr = filter; auto tsExpr = static_cast(filter); - auto prop = tsExpr->arg()->prop(); + auto arg = tsExpr->arg(); + std::string& index = arg->index(); auto metaClient = qctx_->getMetaClient(); - auto tsi = metaClient->getFTIndexFromCache(spaceId(), schemaId(), prop); - NG_RETURN_IF_ERROR(tsi); - auto tsName = tsi.value().first; - lookupCtx_->fulltextIndex = tsName; + meta::cpp2::FTIndex ftIndex; + if (index.empty()) { + auto result = metaClient->getFTIndexFromCache(spaceId(), schemaId()); + NG_RETURN_IF_ERROR(result); + auto indexes = std::move(result).value(); + if (indexes.size() == 0) { + return Status::Error("There is no ft index of schema"); + } else if (indexes.size() > 1) { + return Status::Error("There is more than one schema, one must be specified"); + } + index = indexes.begin()->first; + ftIndex = indexes.begin()->second; + } else { + // TODO(hs.zhang): Directly get `ftIndex` by `index` + auto result = metaClient->getFTIndexFromCache(spaceId(), schemaId()); + NG_RETURN_IF_ERROR(result); + auto indexes = std::move(result).value(); + auto iter = indexes.find(index); + if (iter == indexes.end()) { + return Status::Error("Index %s is not found", index.c_str()); + } + ftIndex = iter->second; + } + auto& props = arg->props(); + if (props.empty()) { + for (auto& f : *ftIndex.fields()) { + props.push_back(f); + } + } else { + std::set fields(ftIndex.fields()->begin(), ftIndex.fields()->end()); + for (auto& p : props) { + if (fields.count(p)) { + continue; + } + return Status::Error("Index %s does not include %s", index.c_str(), p.c_str()); + } + } } else { auto ret = checkFilter(filter); NG_RETURN_IF_ERROR(ret); @@ -518,15 +552,15 @@ StatusOr LookupValidator::checkConstExpr(Expression* expr, } // Check does test search contains properties search in test search expression -StatusOr LookupValidator::checkTSExpr(Expression* expr) { - auto tsExpr = static_cast(expr); - auto prop = tsExpr->arg()->prop(); - auto metaClient = qctx_->getMetaClient(); - auto tsi = metaClient->getFTIndexFromCache(spaceId(), schemaId(), prop); - NG_RETURN_IF_ERROR(tsi); - auto tsName = tsi.value().first; - return tsName; -} +// StatusOr LookupValidator::checkTSExpr(Expression* expr) { +// auto tsExpr = static_cast(expr); +// auto prop = tsExpr->arg()->prop(); +// auto metaClient = qctx_->getMetaClient(); +// auto tsi = metaClient->getFTIndexFromCache(spaceId(), schemaId(), prop); +// NG_RETURN_IF_ERROR(tsi); +// auto tsName = tsi.value().first; +// return tsName; +// } // Reverse position of operands in relational expression and keep the origin semantic. // Transform (A > B) to (B < A) @@ -612,15 +646,15 @@ Status LookupValidator::getSchemaProvider(shared_ptr } // Generate text search filter, check validity and rewrite -StatusOr LookupValidator::genTsFilter(Expression* filter) { - auto esAdapterRet = FTIndexUtils::getESAdapter(qctx_->getMetaClient()); - NG_RETURN_IF_ERROR(esAdapterRet); - auto esAdapter = std::move(esAdapterRet).value(); - auto tsIndex = checkTSExpr(filter); - NG_RETURN_IF_ERROR(tsIndex); - return FTIndexUtils::rewriteTSFilter( - qctx_->objPool(), lookupCtx_->isEdge, filter, tsIndex.value(), esAdapter); -} +// StatusOr LookupValidator::genTsFilter(Expression* filter) { +// auto esAdapterRet = FTIndexUtils::getESAdapter(qctx_->getMetaClient()); +// NG_RETURN_IF_ERROR(esAdapterRet); +// auto esAdapter = std::move(esAdapterRet).value(); +// auto tsIndex = checkTSExpr(filter); +// NG_RETURN_IF_ERROR(tsIndex); +// return FTIndexUtils::rewriteTSFilter( +// qctx_->objPool(), lookupCtx_->isEdge, filter, tsIndex.value(), esAdapter); +// } } // namespace graph } // namespace nebula diff --git a/src/interface/meta.thrift b/src/interface/meta.thrift index 3e6433cca80..c452c68705f 100644 --- a/src/interface/meta.thrift +++ b/src/interface/meta.thrift @@ -1045,6 +1045,7 @@ struct FTIndex { 1: common.GraphSpaceID space_id, 2: common.SchemaID depend_schema, 3: list fields, + 4: binary analyzer, } struct CreateFTIndexReq { diff --git a/src/meta/processors/index/FTIndexProcessor.cpp b/src/meta/processors/index/FTIndexProcessor.cpp index 278dee1e3ee..aadeaeecc11 100644 --- a/src/meta/processors/index/FTIndexProcessor.cpp +++ b/src/meta/processors/index/FTIndexProcessor.cpp @@ -125,7 +125,7 @@ void CreateFTIndexProcessor::process(const cpp2::CreateFTIndexReq& req) { HttpClient::instance(), protocol, client.get_host().toRawString(), user, password); } plugin::ESAdapter esAdapter(std::move(esClients)); - auto createIndexresult = esAdapter.createIndex(name); + auto createIndexresult = esAdapter.createIndex(name, index.get_fields(), index.get_analyzer()); if (!createIndexresult.ok()) { LOG(ERROR) << createIndexresult.message(); handleErrorCode(nebula::cpp2::ErrorCode::E_ACCESS_ES_FAILURE); diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 24747e3ecac..b0aaff1ff01 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -64,6 +64,7 @@ using namespace nebula; int64_t intval; double doubleval; std::string *strval; + std::pair *intintval; nebula::meta::cpp2::GeoShape geo_shape; nebula::meta::cpp2::ColumnTypeDef *type; nebula::Expression *expr; @@ -153,8 +154,6 @@ using namespace nebula; nebula::IndexFieldList *index_field_list; CaseList *case_list; nebula::TextSearchArgument *text_search_argument; - nebula::TextSearchArgument *base_text_search_argument; - nebula::TextSearchArgument *fuzzy_text_search_argument; nebula::meta::cpp2::ServiceClient *service_client_item; nebula::ServiceClientList *service_client_list; nebula::QueryUniqueIdentifier *query_unique_identifier; @@ -164,7 +163,7 @@ using namespace nebula; %destructor {} // Expression related memory will be managed by object pool %destructor {} -%destructor {} +%destructor {} %destructor {} %destructor { delete $$; } <*> @@ -204,7 +203,7 @@ using namespace nebula; %token KW_CASE KW_THEN KW_ELSE KW_END %token KW_GROUP KW_ZONE KW_GROUPS KW_ZONES KW_INTO KW_NEW %token KW_LISTENER KW_ELASTICSEARCH KW_FULLTEXT KW_HTTPS KW_HTTP -%token KW_AUTO KW_FUZZY KW_PREFIX KW_REGEXP KW_WILDCARD +%token KW_AUTO KW_ES_QUERY KW_ES_MATCH %token KW_TEXT KW_SEARCH KW_CLIENTS KW_SIGN KW_SERVICE KW_TEXT_SEARCH %token KW_ANY KW_SINGLE KW_NONE %token KW_REDUCE @@ -339,8 +338,6 @@ using namespace nebula; %type match_step_range %type match_order_by %type text_search_argument -%type base_text_search_argument -%type fuzzy_text_search_argument %type service_client_item %type service_client_list @@ -410,6 +407,8 @@ using namespace nebula; %type opt_with_properties %type opt_ignore_existed_index +%type text_search_limit + // Define precedence and associativity of tokens. // Associativity: // The associativity of an operator op determines how repeated uses of the operator nest: @@ -547,10 +546,8 @@ unreserved_keyword | KW_STATS { $$ = new std::string("stats"); } | KW_STATUS { $$ = new std::string("status"); } | KW_AUTO { $$ = new std::string("auto"); } - | KW_FUZZY { $$ = new std::string("fuzzy"); } - | KW_PREFIX { $$ = new std::string("prefix"); } - | KW_REGEXP { $$ = new std::string("regexp"); } - | KW_WILDCARD { $$ = new std::string("wildcard"); } + | KW_ES_QUERY { $$ = new std::string("es_query"); } + | KW_ES_MATCH { $$ = new std::string("es_match"); } | KW_TEXT { $$ = new std::string("text"); } | KW_SEARCH { $$ = new std::string("search"); } | KW_CLIENTS { $$ = new std::string("clients"); } @@ -2071,119 +2068,57 @@ sign_out_service_sentence } ; -base_text_search_argument - : name_label DOT name_label COMMA STRING { - auto arg = TextSearchArgument::make(qctx->objPool(), *$1, *$3, *$5); - $$ = arg; - delete $1; - delete $3; - delete $5; +text_search_limit + : %empty { + $$ = new std::pair(0, 0); + } + | COMMA legal_integer { + $$ = new std::pair($2, 0); + } + | COMMA legal_integer COMMA legal_integer{ + $$ = new std::pair($2, $4); } ; -fuzzy_text_search_argument - : base_text_search_argument COMMA KW_AUTO COMMA KW_AND { - $$ = $1; - $$->setFuzziness(-1); - $$->setOP("and"); - } - | base_text_search_argument COMMA KW_AUTO COMMA KW_OR { - $$ = $1; - $$->setFuzziness(-1); - $$->setOP("or"); - } - | base_text_search_argument COMMA legal_integer COMMA KW_AND { - if ($3 != 0 && $3 != 1 && $3 != 2) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); - } - $$ = $1; - $$->setFuzziness($3); - $$->setOP("and"); - } - | base_text_search_argument COMMA legal_integer COMMA KW_OR { - if ($3 != 0 && $3 != 1 && $3 != 2) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); - } - $$ = $1; - $$->setFuzziness($3); - $$->setOP("or"); - } - text_search_argument - : base_text_search_argument { - $$ = $1; - } - | fuzzy_text_search_argument { - $$ = $1; - } - | base_text_search_argument COMMA legal_integer { - if ($3 < 1) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + : STRING COMMA STRING COMMA L_BRACKET name_label_list R_BRACKET text_search_limit{ + std::vector props; + for(auto& p:$6->labels()){ + props.push_back(*p); } - $$ = $1; - $$->setLimit($3); + delete $6; + auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, props, $8->first, $8->second); + delete $8; + $$ = args; } - | base_text_search_argument COMMA legal_integer COMMA legal_integer { - if ($3 < 1) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); - } - if ($5 < 1) { - throw nebula::GraphParser::syntax_error(@5, "Out of range:"); - } - $$ = $1; - $$->setLimit($3); - $$->setTimeout($5); + | STRING COMMA STRING text_search_limit{ + auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, {}, $4->first, $4->second); + delete $4; + $$ = args; } - | fuzzy_text_search_argument COMMA legal_integer { - if ($3 < 1) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + | STRING COMMA L_BRACKET name_label_list R_BRACKET text_search_limit{ + std::vector props; + for(auto& p:$4->labels()){ + props.push_back(*p); } - $$ = $1; - $$->setLimit($3); + delete $4; + auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, props, $6->first, $6->second); + delete $6; + $$ = args; } - | fuzzy_text_search_argument COMMA legal_integer COMMA legal_integer { - if ($3 < 1) { - throw nebula::GraphParser::syntax_error(@3, "Out of range:"); - } - if ($5 < 1) { - throw nebula::GraphParser::syntax_error(@5, "Out of range:"); - } - $$ = $1; - $$->setLimit($3); - $$->setTimeout($5); + | STRING text_search_limit{ + auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, {}, $2->first, $2->second); + delete $2; + $$ = args; } ; text_search_expression - : KW_PREFIX L_PAREN text_search_argument R_PAREN { - if (!$3->op().empty()) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - if ($3->fuzziness() != -2) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - $$ = TextSearchExpression::makePrefix(qctx->objPool(), $3); - } - | KW_WILDCARD L_PAREN text_search_argument R_PAREN { - if (!$3->op().empty()) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - if ($3->fuzziness() != -2) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - $$ = TextSearchExpression::makeWildcard(qctx->objPool(), $3); - } - | KW_REGEXP L_PAREN text_search_argument R_PAREN { - if (!$3->op().empty()) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - if ($3->fuzziness() != -2) { - throw nebula::GraphParser::syntax_error(@3, "argument error:"); - } - $$ = TextSearchExpression::makeRegexp(qctx->objPool(), $3); + : KW_ES_MATCH L_PAREN text_search_argument R_PAREN KW_FROM{ + $$ = TextSearchExpression::makeMatch(qctx->objPool(), $3); } - | KW_FUZZY L_PAREN text_search_argument R_PAREN { - $$ = TextSearchExpression::makeFuzzy(qctx->objPool(), $3); + | KW_ES_QUERY L_PAREN text_search_argument R_PAREN { + $$ = TextSearchExpression::makeQuery(qctx->objPool(), $3); } ; diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 283302135ab..5ddac905b8d 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -284,10 +284,8 @@ LABEL_FULL_WIDTH {CN_EN_FULL_WIDTH}{CN_EN_NUM_FULL_WIDTH}* "HTTPS" { return TokenType::KW_HTTPS; } "FULLTEXT" { return TokenType::KW_FULLTEXT; } "AUTO" { return TokenType::KW_AUTO; } -"FUZZY" { return TokenType::KW_FUZZY; } -"PREFIX" { return TokenType::KW_PREFIX; } -"REGEXP" { return TokenType::KW_REGEXP; } -"WILDCARD" { return TokenType::KW_WILDCARD; } +"ES_MATCH" { return TokenType::KW_ES_MATCH; } +"ES_QUERY" { return TokenType::KW_ES_QUERY; } "TEXT" { return TokenType::KW_TEXT; } "SEARCH" { return TokenType::KW_SEARCH; } "CLIENTS" { return TokenType::KW_CLIENTS; } diff --git a/src/storage/query/QueryBaseProcessor-inl.h b/src/storage/query/QueryBaseProcessor-inl.h index a2ae8a34d33..7456716763c 100644 --- a/src/storage/query/QueryBaseProcessor-inl.h +++ b/src/storage/query/QueryBaseProcessor-inl.h @@ -628,10 +628,8 @@ nebula::cpp2::ErrorCode QueryBaseProcessor::checkExp( case Expression::Kind::kUUID: case Expression::Kind::kPathBuild: case Expression::Kind::kColumn: - case Expression::Kind::kTSPrefix: - case Expression::Kind::kTSWildcard: - case Expression::Kind::kTSRegexp: - case Expression::Kind::kTSFuzzy: + case Expression::Kind::kESQUERY: + case Expression::Kind::kESMATCH: case Expression::Kind::kAggregate: case Expression::Kind::kSubscriptRange: case Expression::Kind::kVersionedVar: From b5b76051c708bf7f0965d788d40f2f79c195af78 Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Wed, 31 May 2023 14:06:47 +0800 Subject: [PATCH 2/6] real ft-search --- src/common/expression/Expression.cpp | 4 - src/common/expression/Expression.h | 1 - .../expression/TextSearchExpression.cpp | 14 +- src/common/expression/TextSearchExpression.h | 21 +- .../fulltext/elasticsearch/ESAdapter.cpp | 28 +- .../plugin/fulltext/elasticsearch/ESAdapter.h | 5 - .../plugin/fulltext/test/CMakeLists.txt | 32 +- .../fulltext/test/ElasticsearchTest.cpp | 1015 ++++++----------- src/graph/executor/maintain/FTIndexExecutor.h | 2 +- .../query/FulltextIndexScanExecutor.cpp | 12 - src/graph/util/ExpressionUtils.cpp | 1 - src/graph/util/FTIndexUtils.cpp | 91 -- src/graph/util/test/CMakeLists.txt | 1 - src/graph/util/test/FTindexUtilsTest.cpp | 162 --- src/graph/validator/LookupValidator.cpp | 20 - src/graph/validator/MaintainValidator.cpp | 10 +- src/graph/validator/MaintainValidator.h | 1 - src/parser/MaintainSentences.cpp | 3 +- src/parser/MaintainSentences.h | 23 +- src/parser/parser.yy | 63 +- src/parser/scanner.lex | 1 - src/parser/test/ParserTest.cpp | 131 +-- src/storage/query/QueryBaseProcessor-inl.h | 1 - .../FulltextIndexQuery1.feature | 107 -- .../FulltextIndexQuery2.feature | 84 -- 25 files changed, 408 insertions(+), 1425 deletions(-) delete mode 100644 src/graph/util/test/FTindexUtilsTest.cpp delete mode 100644 tests/tck/features/fulltext_index/FulltextIndexQuery1.feature delete mode 100644 tests/tck/features/fulltext_index/FulltextIndexQuery2.feature diff --git a/src/common/expression/Expression.cpp b/src/common/expression/Expression.cpp index ebaab8bd960..88fc42bd602 100644 --- a/src/common/expression/Expression.cpp +++ b/src/common/expression/Expression.cpp @@ -512,7 +512,6 @@ Expression* Expression::decode(ObjectPool* pool, Expression::Decoder& decoder) { exp->resetFrom(decoder); return exp; } - case Expression::Kind::kESMATCH: case Expression::Kind::kESQUERY: { LOG(FATAL) << "Should not decode text search expression"; return exp; @@ -719,9 +718,6 @@ std::ostream& operator<<(std::ostream& os, Expression::Kind kind) { case Expression::Kind::kPathBuild: os << "PathBuild"; break; - case Expression::Kind::kESMATCH: - os << "ESMatch"; - break; case Expression::Kind::kESQUERY: os << "ESQuery"; break; diff --git a/src/common/expression/Expression.h b/src/common/expression/Expression.h index 07f41ebf3d4..2749305cf6d 100644 --- a/src/common/expression/Expression.h +++ b/src/common/expression/Expression.h @@ -96,7 +96,6 @@ class Expression { kPathBuild, // text or key word search expression - kESMATCH, kESQUERY, kAggregate, diff --git a/src/common/expression/TextSearchExpression.cpp b/src/common/expression/TextSearchExpression.cpp index 6ca92a20314..1ee06c027f6 100644 --- a/src/common/expression/TextSearchExpression.cpp +++ b/src/common/expression/TextSearchExpression.cpp @@ -17,7 +17,7 @@ std::string TextSearchArgument::toString() const { std::string buf; buf.reserve(64); if (!index_.empty()) { - buf += "\"", index_ + "\", "; + buf += "\"" + index_ + "\", "; } buf += "\"" + query_ + "\""; if (!props_.empty()) { @@ -30,14 +30,6 @@ std::string TextSearchArgument::toString() const { } buf += "]"; } - - if (count_ != 0) { - buf += ", " + std::to_string(count_); - if (offset_) { - buf += ", " + std::to_string(offset_); - } - } - return buf; } @@ -54,10 +46,6 @@ std::string TextSearchExpression::toString() const { std::string buf; buf.reserve(64); switch (kind_) { - case Kind::kESMATCH: { - buf = "ES_MATCH("; - break; - } case Kind::kESQUERY: { buf = "ES_QUERY("; break; diff --git a/src/common/expression/TextSearchExpression.h b/src/common/expression/TextSearchExpression.h index 71dc24e7eb5..02f820051a7 100644 --- a/src/common/expression/TextSearchExpression.h +++ b/src/common/expression/TextSearchExpression.h @@ -16,10 +16,8 @@ class TextSearchArgument final { static TextSearchArgument* make(ObjectPool* pool, const std::string& index, const std::string& query, - const std::vector& props, - int64_t count, - int64_t offset) { - return pool->makeAndAdd(index, query, props, count, offset); + const std::vector& props) { + return pool->makeAndAdd(index, query, props); } ~TextSearchArgument() = default; @@ -52,10 +50,8 @@ class TextSearchArgument final { friend ObjectPool; TextSearchArgument(const std::string& index, const std::string& query, - const std::vector& props, - int64_t count, - int64_t offset) - : index_(index), query_(query), props_(props), count_(count), offset_(offset) {} + const std::vector& props) + : index_(index), query_(query), props_(props) {} private: std::string index_; @@ -67,10 +63,6 @@ class TextSearchArgument final { class TextSearchExpression : public Expression { public: - static TextSearchExpression* makeMatch(ObjectPool* pool, TextSearchArgument* arg) { - return pool->makeAndAdd(pool, Kind::kESMATCH, arg); - } - static TextSearchExpression* makeQuery(ObjectPool* pool, TextSearchArgument* arg) { return pool->makeAndAdd(pool, Kind::kESQUERY, arg); } @@ -93,8 +85,9 @@ class TextSearchExpression : public Expression { std::string toString() const override; Expression* clone() const override { - auto arg = TextSearchArgument::make( - pool_, arg_->index(), arg_->query(), arg_->props(), arg_->count(), arg_->offset()); + auto arg = TextSearchArgument::make(pool_, arg_->index(), arg_->query(), arg_->props()); + arg->count() = arg_->count(); + arg->offset() = arg_->offset(); return TextSearchExpression::make(pool_, kind_, arg); } diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp index 9cd7b022a05..c6a1bd1b0a5 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp @@ -72,16 +72,16 @@ Status ESAdapter::createIndex(const std::string& name, { "mappings":{ "properties":{ - "@vid": { + "vid": { "type": "keyword" }, - "@src": { + "src": { "type": "keyword" }, - "@dst": { + "dst": { "type": "keyword" }, - "@rank": { + "rank": { "type": "long" } } @@ -187,26 +187,6 @@ Status ESAdapter::bulk(const ESBulk& bulk, bool refresh) { return Status::Error(folly::toJson(resp)); } -StatusOr ESAdapter::multiMatch(const std::string& index, - const std::string& query, - const std::vector& fields, - int64_t from, - int64_t size) { - folly::dynamic body = folly::dynamic::object(); - body["query"] = folly::dynamic::object(); - body["query"]["multi_match"] = folly::dynamic::object(); - body["query"]["multi_match"]["query"] = query; - body["query"]["multi_match"]["fields"] = folly::dynamic::array(); - for (auto& field : fields) { - body["query"]["multi_match"]["fields"].push_back(field); - } - if (size > 0) { - body["size"] = size; - body["from"] = from; - } - return ESAdapter::query(index, body, 2000); -} - StatusOr ESAdapter::queryString(const std::string& index, const std::string& query, const std::vector& fields, diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h index b1d6e15bd45..4f2625dc649 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h @@ -76,11 +76,6 @@ class ESAdapter { virtual Status bulk(const ESBulk& bulk, bool refresh = false); - virtual StatusOr multiMatch(const std::string& index, - const std::string& query, - const std::vector& fields, - int64_t from, - int64_t size); virtual StatusOr queryString(const std::string& index, const std::string& query, diff --git a/src/common/plugin/fulltext/test/CMakeLists.txt b/src/common/plugin/fulltext/test/CMakeLists.txt index 48025e78b68..2cf09e201a0 100644 --- a/src/common/plugin/fulltext/test/CMakeLists.txt +++ b/src/common/plugin/fulltext/test/CMakeLists.txt @@ -2,19 +2,19 @@ # # This source code is licensed under Apache 2.0 License. -# nebula_add_test( -# NAME -# es_test -# SOURCES -# ElasticsearchTest.cpp -# OBJECTS -# $ -# $ -# $ -# LIBRARIES -# gtest -# gtest_main -# gmock -# curl -# ${PROXYGEN_LIBRARIES} -# ) +nebula_add_test( + NAME + es_test + SOURCES + ElasticsearchTest.cpp + OBJECTS + $ + $ + $ + LIBRARIES + gtest + gtest_main + gmock + curl + ${PROXYGEN_LIBRARIES} +) diff --git a/src/common/plugin/fulltext/test/ElasticsearchTest.cpp b/src/common/plugin/fulltext/test/ElasticsearchTest.cpp index 4e265db1541..4ec9dc6405b 100644 --- a/src/common/plugin/fulltext/test/ElasticsearchTest.cpp +++ b/src/common/plugin/fulltext/test/ElasticsearchTest.cpp @@ -19,698 +19,327 @@ using ::testing::_; using ::testing::Eq; using ::testing::Return; -// class MockHttpClient : public HttpClient { -// public: -// MOCK_METHOD(HttpResponse, get, (const std::string& url), (override)); -// MOCK_METHOD(HttpResponse, -// get, -// (const std::string& url, const std::vector& headers), -// (override)); -// MOCK_METHOD(HttpResponse, -// get, -// (const std::string& url, -// const std::vector& headers, -// const std::string&, -// const std::string&), -// (override)); -// MOCK_METHOD(HttpResponse, -// post, -// (const std::string& url, -// const std::vector& headers, -// const std::string& body), -// (override)); -// MOCK_METHOD(HttpResponse, -// post, -// (const std::string& url, -// const std::vector& headers, -// const std::string& body, -// const std::string&, -// const std::string&), -// (override)); -// MOCK_METHOD(HttpResponse, -// delete_, -// (const std::string& url, const std::vector& headers), -// (override)); -// MOCK_METHOD(HttpResponse, -// delete_, -// (const std::string& url, -// const std::vector& headers, -// const std::string&, -// const std::string&), -// (override)); -// MOCK_METHOD(HttpResponse, -// put, -// (const std::string& url, -// const std::vector& headers, -// const std::string& body), -// (override)); -// MOCK_METHOD(HttpResponse, -// put, -// (const std::string& url, -// const std::vector& headers, -// const std::string& body, -// const std::string&, -// const std::string&), -// (override)); -// }; - -// HttpResponse operator"" _http_resp(const char* s, size_t) { -// HttpResponse resp; -// folly::StringPiece text(s); -// text = folly::trimWhitespace(text); -// std::string curlResult = text.split_step("\n\n").toString(); -// std::regex curlReg(R"(curl: \((\d+)\)( (.*))?)"); -// std::smatch matchResult; -// CHECK(std::regex_match(curlResult, matchResult, curlReg)); -// resp.curlCode = CURLcode(std::stoi(matchResult[1].str())); -// if (matchResult.size() > 2) { -// resp.curlMessage = matchResult[3]; -// } -// if (!text.empty()) { -// folly::StringPiece header = text.split_step("\n\n"); -// resp.header = header.toString(); -// } -// if (!text.empty()) { -// resp.body = text.toString(); -// } -// return resp; -// } - -// class ESTest : public ::testing::Test { -// public: -// void SetUp() override { -// queryResultResp_.body = queryResultBody; -// } - -// protected: -// HttpResponse queryResultResp_ = R"( -// curl: (0) - -// HTTP/1.1 200 OK -// content-type: application/json; charset=UTF-8 -// content-length: 78 - -// )"_http_resp; -// HttpResponse normalSuccessResp_ = R"( -// curl: (0) - -// HTTP/1.1 200 OK -// content-type: application/json; charset=UTF-8 -// content-length: 78 - -// {"acknowledged":true,"shards_acknowledged":true,"index":"nebula_test_index_1"} -// )"_http_resp; - -// HttpResponse esErrorResp_ = R"( -// curl: (0) - -// HTTP/1.1 400 Bad Request -// content-type: application/json; charset=UTF-8 -// content-length: 417 - -// {"error":{"reason":"mock error"},"status":400} -// )"_http_resp; - -// HttpResponse curlErrorResp_ = R"( -// curl: (7) mock error message - -// )"_http_resp; -// }; - -// TEST_F(ESTest, createIndex) { -// MockHttpClient mockHttpClient; -// EXPECT_CALL(mockHttpClient, -// put("http://127.0.0.1:9200/nebula_index_1", -// std::vector{"Content-Type: application/json"}, -// _, -// "", -// "")) -// .Times(3) -// .WillOnce(Return(normalSuccessResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// std::vector clients; -// clients.push_back(client); -// plugin::ESAdapter adapter(std::move(clients)); -// { -// auto result = adapter.createIndex("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// } -// { -// auto result = adapter.createIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.createIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, dropIndex) { -// MockHttpClient mockHttpClient; -// EXPECT_CALL(mockHttpClient, -// delete_("http://127.0.0.1:9200/nebula_index_1", -// std::vector{"Content-Type: application/json"}, -// "", -// "")) -// .Times(3) -// .WillOnce(Return(normalSuccessResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.dropIndex("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// } -// { -// auto result = adapter.dropIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.dropIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, getIndex) { -// MockHttpClient mockHttpClient; -// HttpResponse indexExistResp = R"( -// curl: (0) - -// HTTP/1.1 200 OK -// content-type: application/json; charset=UTF-8 -// content-length: 78 - -// {"nebula_index_1":{}} -// )"_http_resp; - -// HttpResponse indexNotExistResp = R"( -// curl: (0) - -// HTTP/1.1 404 OK -// content-type: application/json; charset=UTF-8 -// content-length: 78 - -// {"error":{"reason":"mock error"},"status":404} -// )"_http_resp; - -// EXPECT_CALL(mockHttpClient, -// get("http://127.0.0.1:9200/nebula_index_1", -// std::vector{"Content-Type: application/json"}, -// "", -// "")) -// .Times(4) -// .WillOnce(Return(indexExistResp)) -// .WillOnce(Return(indexNotExistResp)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.isIndexExist("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// ASSERT_TRUE(result.value()); -// } -// { -// auto result = adapter.isIndexExist("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// ASSERT_FALSE(result.value()); -// } -// { -// auto result = adapter.isIndexExist("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.isIndexExist("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, clearIndex) { -// MockHttpClient mockHttpClient; -// auto clearSuccessResp_ = R"( -// curl: (0) - -// HTTP/1.1 200 OK -// content-type: application/json; charset=UTF-8 -// content-length: 78 - -// {"failures":[]} -// )"_http_resp; -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=false", -// std::vector{"Content-Type: application/json"}, -// _, -// "", -// "")) -// .Times(2) -// .WillOnce(Return(clearSuccessResp_)) -// .WillOnce(Return(esErrorResp_)); -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=true", -// std::vector{"Content-Type: application/json"}, -// _, -// "", -// "")) -// .Times(1) -// .WillOnce(Return(curlErrorResp_)); - -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.clearIndex("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// } -// { -// auto result = adapter.clearIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.clearIndex("nebula_index_1", true); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, prefix) { -// folly::dynamic prefixBody = folly::parseJson(R"( -// { -// "query":{ -// "prefix":{ -// "text":"abc" -// } -// } -// } -// )"); -// MockHttpClient mockHttpClient; - -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_search", -// std::vector{"Content-Type: application/json"}, -// folly::toJson(prefixBody), -// std::string(""), -// std::string(""))) -// .Times(3) -// .WillOnce(Return(queryResultResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); -// ASSERT_TRUE(result.ok()); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); -// } -// { -// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.prefix("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, wildcard) { -// folly::dynamic body = folly::parseJson(R"( -// { -// "query":{ -// "wildcard":{ -// "text":"abc" -// } -// } -// } -// )"); -// MockHttpClient mockHttpClient; - -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_search", -// std::vector{"Content-Type: application/json"}, -// folly::toJson(body), -// "", -// "")) -// .Times(3) -// .WillOnce(Return(queryResultResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); -// ASSERT_TRUE(result.ok()); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); -// } -// { -// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.wildcard("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, regexp) { -// folly::dynamic body = folly::parseJson(R"( -// { -// "query":{ -// "regexp":{ -// "text":"abc" -// } -// } -// } -// )"); -// MockHttpClient mockHttpClient; - -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_search", -// std::vector{"Content-Type: application/json"}, -// folly::toJson(body), -// "", -// "")) -// .Times(3) -// .WillOnce(Return(queryResultResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); -// ASSERT_TRUE(result.ok()); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); -// } -// { -// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.regexp("nebula_index_1", "abc", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, bulk) { -// MockHttpClient mockHttpClient; - -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/_bulk?refresh=true", -// std::vector{"Content-Type: application/x-ndjson"}, -// _, -// "", -// "")) // TODO(hs.zhang): Matcher -// .Times(2) -// .WillOnce(Return(queryResultResp_)) -// .WillOnce(Return(esErrorResp_)); -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/_bulk?refresh=false", -// std::vector{"Content-Type: application/x-ndjson"}, -// _, -// "", -// "")) // TODO(hs.zhang): Matcher -// .Times(1) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// plugin::ESBulk bulk; -// bulk.put("nebula_index_1", "1", "", "", 0, "vertex text"); -// bulk.delete_("nebula_index_2", "", "a", "b", 10); -// { -// auto result = adapter.bulk(bulk, true); -// ASSERT_TRUE(result.ok()); -// } -// { -// auto result = adapter.bulk(bulk, true); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.bulk(bulk, false); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); -// } -// } - -// TEST_F(ESTest, fuzzy) { -// // folly::dynamic body = folly::parseJson(R"( -// // { -// // "query":{ -// // "fuzzy":{ -// // "text":{ -// // "fuzziness": "2", -// // "value": "abc" -// // } -// // } -// // } -// // } -// // )"); -// MockHttpClient mockHttpClient; - -// EXPECT_CALL(mockHttpClient, -// post("http://127.0.0.1:9200/nebula_index_1/_search", -// std::vector{"Content-Type: application/json"}, -// _, -// "", -// "")) -// .Times(3) -// .WillOnce(Return(queryResultResp_)) -// .WillOnce(Return(esErrorResp_)) -// .WillOnce(Return(curlErrorResp_)); -// plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); -// ASSERT_TRUE(result.ok()); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "vertex text")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("a", "b", 10, "edge text")); -// } -// { -// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); -// } -// { -// auto result = adapter.fuzzy("nebula_index_1", "abc", "2", -1, -1); -// ASSERT_FALSE(result.ok()); -// ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); -// } -// } - -// DEFINE_string(es_address, "192.168.8.211:9200", "address of elasticsearch"); -// class RealESTest : public ::testing::Test {}; - -// TEST_F(RealESTest, DISABLED_CREATE_DROP_INDEX) { -// plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.createIndex("nebula_index_1"); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.createIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_NE(result.message().find("resource_already_exists_exception"), std::string::npos); -// } -// { -// auto result = adapter.dropIndex("nebula_index_1"); -// ASSERT_TRUE(result.ok()); -// } -// { -// auto result = adapter.dropIndex("nebula_index_1"); -// ASSERT_FALSE(result.ok()); -// ASSERT_NE(result.message().find("index_not_found_exception"), std::string::npos); -// } -// } - -// TEST_F(RealESTest, DISABLED_QUERY) { -// plugin::ESClient client(HttpClient::instance(), "http", FLAGS_es_address, "", ""); -// std::string indexName = "nebula_index_2"; -// plugin::ESAdapter adapter(std::vector({client})); -// { -// auto result = adapter.createIndex(indexName); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// plugin::ESBulk bulk; -// bulk.put(indexName, "1", "", "", 0, "abc"); -// bulk.put(indexName, "2", "", "", 0, "abcd"); -// auto result = adapter.bulk(bulk, true); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.prefix(indexName, "a", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); -// } -// { -// auto result = adapter.regexp(indexName, "a.*", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("2", "abcd")); -// } -// { -// auto result = adapter.prefix(indexName, "abcd", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 1); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("2", "abcd")); -// } -// { -// plugin::ESBulk bulk; -// bulk.put(indexName, "2", "", "", 0, "NebulaGraph is a graph database"); -// bulk.put(indexName, "3", "", "", 0, "The best graph database is NebulaGraph"); -// bulk.put(indexName, "4", "", "", 0, "Nebulagraph是最好的图数据库"); -// bulk.put(indexName, "5", "", "", 0, "NebulaGraph是一个图数据库"); -// auto result = adapter.bulk(bulk, true); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.match_all(indexName); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 5); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("1", "abc")); -// ASSERT_EQ(esResult.items[1], -// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); -// ASSERT_EQ(esResult.items[2], -// plugin::ESQueryResult::Item("3", "The best graph database is NebulaGraph")); -// ASSERT_EQ(esResult.items[3], plugin::ESQueryResult::Item("4", -// "Nebulagraph是最好的图数据库")); ASSERT_EQ(esResult.items[4], -// plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); -// } -// { -// auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], -// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); -// } -// { -// auto result = adapter.regexp(indexName, "NebulaGraph.*(图数据库|database)", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], -// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); -// ASSERT_EQ(esResult.items[1], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); -// } -// { -// auto result = adapter.wildcard(indexName, "Nebula?raph是*", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 2); -// std::sort(esResult.items.begin(), -// esResult.items.end(), -// [](const plugin::ESQueryResult::Item& a, const plugin::ESQueryResult::Item& b) { -// return a.vid < b.vid; -// }); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("4", -// "Nebulagraph是最好的图数据库")); ASSERT_EQ(esResult.items[1], -// plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); -// } -// { -// auto result = adapter.fuzzy(indexName, "Nebulagraph is a graph Database", "2", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 1); - -// ASSERT_EQ(esResult.items[0], -// plugin::ESQueryResult::Item("2", "NebulaGraph is a graph database")); -// } -// { -// plugin::ESBulk bulk; -// bulk.delete_(indexName, "2", "", "", 0); -// auto result = adapter.bulk(bulk, true); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.prefix(indexName, "NebulaGraph", -1, -1); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 1); -// ASSERT_EQ(esResult.items[0], plugin::ESQueryResult::Item("5", "NebulaGraph是一个图数据库")); -// } -// { -// auto result = adapter.clearIndex(indexName, true); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.match_all(indexName); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// auto esResult = std::move(result).value(); -// ASSERT_EQ(esResult.items.size(), 0); -// } -// { -// auto result = adapter.isIndexExist(indexName); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// ASSERT_TRUE(result.value()); -// } -// { -// auto result = adapter.dropIndex(indexName); -// ASSERT_TRUE(result.ok()) << result.message(); -// } -// { -// auto result = adapter.isIndexExist(indexName); -// ASSERT_TRUE(result.ok()) << result.status().message(); -// ASSERT_FALSE(result.value()); -// } -// } +class MockHttpClient : public HttpClient { + public: + MOCK_METHOD(HttpResponse, get, (const std::string& url), (override)); + MOCK_METHOD(HttpResponse, + get, + (const std::string& url, const std::vector& headers), + (override)); + MOCK_METHOD(HttpResponse, + get, + (const std::string& url, + const std::vector& headers, + const std::string&, + const std::string&), + (override)); + MOCK_METHOD(HttpResponse, + post, + (const std::string& url, + const std::vector& headers, + const std::string& body), + (override)); + MOCK_METHOD(HttpResponse, + post, + (const std::string& url, + const std::vector& headers, + const std::string& body, + const std::string&, + const std::string&), + (override)); + MOCK_METHOD(HttpResponse, + delete_, + (const std::string& url, const std::vector& headers), + (override)); + MOCK_METHOD(HttpResponse, + delete_, + (const std::string& url, + const std::vector& headers, + const std::string&, + const std::string&), + (override)); + MOCK_METHOD(HttpResponse, + put, + (const std::string& url, + const std::vector& headers, + const std::string& body), + (override)); + MOCK_METHOD(HttpResponse, + put, + (const std::string& url, + const std::vector& headers, + const std::string& body, + const std::string&, + const std::string&), + (override)); +}; + +HttpResponse operator"" _http_resp(const char* s, size_t) { + HttpResponse resp; + folly::StringPiece text(s); + text = folly::trimWhitespace(text); + std::string curlResult = text.split_step("\n\n").toString(); + std::regex curlReg(R"(curl: \((\d+)\)( (.*))?)"); + std::smatch matchResult; + CHECK(std::regex_match(curlResult, matchResult, curlReg)); + resp.curlCode = CURLcode(std::stoi(matchResult[1].str())); + if (matchResult.size() > 2) { + resp.curlMessage = matchResult[3]; + } + if (!text.empty()) { + folly::StringPiece header = text.split_step("\n\n"); + resp.header = header.toString(); + } + if (!text.empty()) { + resp.body = text.toString(); + } + return resp; +} + +class ESTest : public ::testing::Test { + public: + void SetUp() override { + queryResultResp_.body = queryResultBody; + } + + protected: + HttpResponse queryResultResp_ = R"( +curl: (0) + +HTTP/1.1 200 OK +content-type: application/json; charset=UTF-8 +content-length: 78 + +)"_http_resp; + HttpResponse normalSuccessResp_ = R"( +curl: (0) + +HTTP/1.1 200 OK +content-type: application/json; charset=UTF-8 +content-length: 78 + +{"acknowledged":true,"shards_acknowledged":true,"index":"nebula_test_index_1"} +)"_http_resp; + + HttpResponse esErrorResp_ = R"( +curl: (0) + +HTTP/1.1 400 Bad Request +content-type: application/json; charset=UTF-8 +content-length: 417 + +{"error":{"reason":"mock error"},"status":400} +)"_http_resp; + + HttpResponse curlErrorResp_ = R"( +curl: (7) mock error message + +)"_http_resp; +}; + +TEST_F(ESTest, createIndex) { + MockHttpClient mockHttpClient; + EXPECT_CALL(mockHttpClient, + put("http://127.0.0.1:9200/nebula_index_1", + std::vector{"Content-Type: application/json"}, + _, + "", + "")) + .Times(3) + .WillOnce(Return(normalSuccessResp_)) + .WillOnce(Return(esErrorResp_)) + .WillOnce(Return(curlErrorResp_)); + plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); + std::vector clients; + clients.push_back(client); + plugin::ESAdapter adapter(std::move(clients)); + { + auto result = adapter.createIndex("nebula_index_1", {"a", "b", "c"}, ""); + ASSERT_TRUE(result.ok()); + } + { + auto result = adapter.createIndex("nebula_index_1", {"a", "b", "c"}, ""); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); + } + { + auto result = adapter.createIndex("nebula_index_1", {"a", "b", "c"}, ""); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); + } +} + +TEST_F(ESTest, dropIndex) { + MockHttpClient mockHttpClient; + EXPECT_CALL(mockHttpClient, + delete_("http://127.0.0.1:9200/nebula_index_1", + std::vector{"Content-Type: application/json"}, + "", + "")) + .Times(3) + .WillOnce(Return(normalSuccessResp_)) + .WillOnce(Return(esErrorResp_)) + .WillOnce(Return(curlErrorResp_)); + plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); + plugin::ESAdapter adapter(std::vector({client})); + { + auto result = adapter.dropIndex("nebula_index_1"); + ASSERT_TRUE(result.ok()); + } + { + auto result = adapter.dropIndex("nebula_index_1"); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); + } + { + auto result = adapter.dropIndex("nebula_index_1"); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); + } +} + +TEST_F(ESTest, getIndex) { + MockHttpClient mockHttpClient; + HttpResponse indexExistResp = R"( +curl: (0) + +HTTP/1.1 200 OK +content-type: application/json; charset=UTF-8 +content-length: 78 + +{"nebula_index_1":{}} +)"_http_resp; + + HttpResponse indexNotExistResp = R"( +curl: (0) + +HTTP/1.1 404 OK +content-type: application/json; charset=UTF-8 +content-length: 78 + +{"error":{"reason":"mock error"},"status":404} +)"_http_resp; + + EXPECT_CALL(mockHttpClient, + get("http://127.0.0.1:9200/nebula_index_1", + std::vector{"Content-Type: application/json"}, + "", + "")) + .Times(4) + .WillOnce(Return(indexExistResp)) + .WillOnce(Return(indexNotExistResp)) + .WillOnce(Return(esErrorResp_)) + .WillOnce(Return(curlErrorResp_)); + plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); + plugin::ESAdapter adapter(std::vector({client})); + { + auto result = adapter.isIndexExist("nebula_index_1"); + ASSERT_TRUE(result.ok()); + ASSERT_TRUE(result.value()); + } + { + auto result = adapter.isIndexExist("nebula_index_1"); + ASSERT_TRUE(result.ok()); + ASSERT_FALSE(result.value()); + } + { + auto result = adapter.isIndexExist("nebula_index_1"); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.status().message(), R"({"reason":"mock error"})"); + } + { + auto result = adapter.isIndexExist("nebula_index_1"); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.status().message(), R"(curl error(7):mock error message)"); + } +} + +TEST_F(ESTest, clearIndex) { + MockHttpClient mockHttpClient; + auto clearSuccessResp_ = R"( +curl: (0) + +HTTP/1.1 200 OK +content-type: application/json; charset=UTF-8 +content-length: 78 + +{"failures":[]} +)"_http_resp; + EXPECT_CALL(mockHttpClient, + post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=false", + std::vector{"Content-Type: application/json"}, + _, + "", + "")) + .Times(2) + .WillOnce(Return(clearSuccessResp_)) + .WillOnce(Return(esErrorResp_)); + EXPECT_CALL(mockHttpClient, + post("http://127.0.0.1:9200/nebula_index_1/_delete_by_query?refresh=true", + std::vector{"Content-Type: application/json"}, + _, + "", + "")) + .Times(1) + .WillOnce(Return(curlErrorResp_)); + + plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); + plugin::ESAdapter adapter(std::vector({client})); + { + auto result = adapter.clearIndex("nebula_index_1"); + ASSERT_TRUE(result.ok()); + } + { + auto result = adapter.clearIndex("nebula_index_1"); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); + } + { + auto result = adapter.clearIndex("nebula_index_1", true); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); + } +} + +TEST_F(ESTest, bulk) { + MockHttpClient mockHttpClient; + + EXPECT_CALL(mockHttpClient, + post("http://127.0.0.1:9200/_bulk?refresh=true", + std::vector{"Content-Type: application/x-ndjson"}, + _, + "", + "")) // TODO(hs.zhang): Matcher + .Times(2) + .WillOnce(Return(queryResultResp_)) + .WillOnce(Return(esErrorResp_)); + EXPECT_CALL(mockHttpClient, + post("http://127.0.0.1:9200/_bulk?refresh=false", + std::vector{"Content-Type: application/x-ndjson"}, + _, + "", + "")) // TODO(hs.zhang): Matcher + .Times(1) + .WillOnce(Return(curlErrorResp_)); + plugin::ESClient client(mockHttpClient, "http", "127.0.0.1:9200", "", ""); + plugin::ESAdapter adapter(std::vector({client})); + plugin::ESBulk bulk; + bulk.put("nebula_index_1", "1", "", "", 0, "vertex text"); + bulk.delete_("nebula_index_2", "", "a", "b", 10); + { + auto result = adapter.bulk(bulk, true); + ASSERT_TRUE(result.ok()); + } + { + auto result = adapter.bulk(bulk, true); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"({"reason":"mock error"})"); + } + { + auto result = adapter.bulk(bulk, false); + ASSERT_FALSE(result.ok()); + ASSERT_EQ(result.message(), R"(curl error(7):mock error message)"); + } +} } // namespace nebula diff --git a/src/graph/executor/maintain/FTIndexExecutor.h b/src/graph/executor/maintain/FTIndexExecutor.h index cbe65faddf3..fe5124f4faa 100644 --- a/src/graph/executor/maintain/FTIndexExecutor.h +++ b/src/graph/executor/maintain/FTIndexExecutor.h @@ -6,7 +6,7 @@ #define GRAPH_EXECUTOR_ADMIN_SHOW_FT_INDEXES_EXECUTOR_H_ #include "graph/executor/Executor.h" -// full-text indexes are used to do prefix, wildcard, regexp, and fuzzy search on a string property. +// full-text indexes are used to do "query_string" search on a string property. // you can use the WHERE clause to specify the search strings in LOOKUP statements. namespace nebula { namespace graph { diff --git a/src/graph/executor/query/FulltextIndexScanExecutor.cpp b/src/graph/executor/query/FulltextIndexScanExecutor.cpp index fa81bebedfe..761731a1db4 100644 --- a/src/graph/executor/query/FulltextIndexScanExecutor.cpp +++ b/src/graph/executor/query/FulltextIndexScanExecutor.cpp @@ -73,18 +73,6 @@ StatusOr FulltextIndexScanExecutor::accessFulltextIndex( std::function()> execFunc; plugin::ESAdapter& esAdapter = esAdapter_; switch (tsExpr->kind()) { - case Expression::Kind::kESMATCH: { - auto arg = tsExpr->arg(); - auto index = arg->index(); - auto query = arg->query(); - auto props = arg->props(); - auto count = arg->count(); - auto offset = arg->offset(); - execFunc = [=, &esAdapter]() { - return esAdapter.multiMatch(index, query, props, offset, count); - }; - break; - } case Expression::Kind::kESQUERY: { auto arg = tsExpr->arg(); auto index = arg->index(); diff --git a/src/graph/util/ExpressionUtils.cpp b/src/graph/util/ExpressionUtils.cpp index d39b4fbffe2..9c2edbfa67f 100644 --- a/src/graph/util/ExpressionUtils.cpp +++ b/src/graph/util/ExpressionUtils.cpp @@ -1557,7 +1557,6 @@ bool ExpressionUtils::checkExprDepth(const Expression *expr) { case Expression::Kind::kUUID: case Expression::Kind::kPathBuild: case Expression::Kind::kColumn: - case Expression::Kind::kESMATCH: case Expression::Kind::kESQUERY: case Expression::Kind::kAggregate: case Expression::Kind::kSubscriptRange: diff --git a/src/graph/util/FTIndexUtils.cpp b/src/graph/util/FTIndexUtils.cpp index 372d0762cc7..84e5a3d60b8 100644 --- a/src/graph/util/FTIndexUtils.cpp +++ b/src/graph/util/FTIndexUtils.cpp @@ -14,7 +14,6 @@ namespace graph { bool FTIndexUtils::needTextSearch(const Expression* expr) { switch (expr->kind()) { - case Expression::Kind::kESMATCH: case Expression::Kind::kESQUERY: { return true; } @@ -42,96 +41,6 @@ StatusOr<::nebula::plugin::ESAdapter> FTIndexUtils::getESAdapter(meta::MetaClien return ::nebula::plugin::ESAdapter(std::move(clients)); } -// StatusOr FTIndexUtils::rewriteTSFilter(ObjectPool* pool, -// bool isEdge, -// Expression* expr, -// const std::string& index, -// ::nebula::plugin::ESAdapter& esAdapter) { -// auto vRet = textSearch(expr, index, esAdapter); -// if (!vRet.ok()) { -// return vRet.status(); -// } -// auto result = std::move(vRet).value(); -// if (result.items.empty()) { -// return nullptr; -// } -// auto tsArg = static_cast(expr)->arg(); -// Expression* propExpr; -// if (isEdge) { -// propExpr = EdgePropertyExpression::make(pool, tsArg->from(), tsArg->prop()); -// } else { -// propExpr = TagPropertyExpression::make(pool, tsArg->from(), tsArg->prop()); -// } -// std::vector rels; -// for (auto& item : result.items) { -// auto constExpr = ConstantExpression::make(pool, Value(item.text)); -// rels.emplace_back(RelationalExpression::makeEQ(pool, propExpr, constExpr)); -// } -// if (rels.size() == 1) { -// return rels.front(); -// } -// return ExpressionUtils::pushOrs(pool, rels); -// } - -// StatusOr FTIndexUtils::textSearch( -// Expression* expr, const std::string& index, ::nebula::plugin::ESAdapter& esAdapter) { -// auto tsExpr = static_cast(expr); -// std::function()> execFunc; -// switch (tsExpr->kind()) { -// case Expression::Kind::kESMATCH: { -// std::string pattern = tsExpr->arg()->val(); -// int fuzziness = tsExpr->arg()->fuzziness(); -// int64_t size = tsExpr->arg()->limit(); -// int64_t timeout = tsExpr->arg()->timeout(); -// execFunc = [&index, pattern, &esAdapter, fuzziness, size, timeout]() { -// return esAdapter.multiMatch( -// index, pattern, fuzziness < 0 ? "AUTO" : std::to_string(fuzziness), size, timeout); -// }; -// break; -// } -// case Expression::Kind::kESQUERY: { -// std::string pattern = tsExpr->arg()->val(); -// int64_t size = tsExpr->arg()->limit(); -// int64_t timeout = tsExpr->arg()->timeout(); -// execFunc = [&index, pattern, &esAdapter, size, timeout]() { -// return esAdapter.prefix(index, pattern, size, timeout); -// }; -// break; -// } -// case Expression::Kind::kTSRegexp: { -// std::string pattern = tsExpr->arg()->val(); -// int64_t size = tsExpr->arg()->limit(); -// int64_t timeout = tsExpr->arg()->timeout(); -// execFunc = [&index, pattern, &esAdapter, size, timeout]() { -// return esAdapter.regexp(index, pattern, size, timeout); -// }; -// break; -// } -// case Expression::Kind::kTSWildcard: { -// std::string pattern = tsExpr->arg()->val(); -// int64_t size = tsExpr->arg()->limit(); -// int64_t timeout = tsExpr->arg()->timeout(); -// execFunc = [&index, pattern, &esAdapter, size, timeout]() { -// return esAdapter.wildcard(index, pattern, size, timeout); -// }; -// break; -// } -// default: { -// return Status::SemanticError("text search expression error"); -// } -// } - -// auto retryCnt = FLAGS_ft_request_retry_times > 0 ? FLAGS_ft_request_retry_times : 1; -// StatusOr result; -// while (retryCnt-- > 0) { -// result = execFunc(); -// if (!result.ok()) { -// continue; -// } -// break; -// } -// return result; -// } } // namespace graph } // namespace nebula diff --git a/src/graph/util/test/CMakeLists.txt b/src/graph/util/test/CMakeLists.txt index 819bb4e5168..797576eecf0 100644 --- a/src/graph/util/test/CMakeLists.txt +++ b/src/graph/util/test/CMakeLists.txt @@ -27,7 +27,6 @@ nebula_add_test( SOURCES ExpressionUtilsTest.cpp IdGeneratorTest.cpp - # FTindexUtilsTest.cpp OBJECTS $ $ diff --git a/src/graph/util/test/FTindexUtilsTest.cpp b/src/graph/util/test/FTindexUtilsTest.cpp deleted file mode 100644 index 41dff6a0dd4..00000000000 --- a/src/graph/util/test/FTindexUtilsTest.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) 2022 vesoft inc. All rights reserved. -// -// This source code is licensed under Apache 2.0 License. - -#include "common/plugin/fulltext/elasticsearch/ESAdapter.h" -#include "glog/logging.h" -#include "gmock/gmock.h" -#include "graph/util/ExpressionUtils.h" -#include "graph/util/FTIndexUtils.h" -#include "gtest/gtest.h" - -DECLARE_uint32(ft_request_retry_times); - -using ::testing::_; -using ::testing::Return; - -namespace nebula::graph { - -class MockESAdapter : public plugin::ESAdapter { - public: - MOCK_METHOD(StatusOr, - fuzzy, - (const std::string&, const std::string&, const std::string&, int64_t, int64_t), - (override)); - MOCK_METHOD(StatusOr, - prefix, - (const std::string&, const std::string&, int64_t, int64_t), - (override)); - MOCK_METHOD(StatusOr, - regexp, - (const std::string&, const std::string&, int64_t, int64_t), - (override)); - MOCK_METHOD(StatusOr, - wildcard, - (const std::string&, const std::string&, int64_t, int64_t), - (override)); -}; - -class FTIndexUtilsTest : public ::testing::Test { - public: - protected: - ObjectPool pool_; - Expression* eq(Expression* left, Expression* right) { - return RelationalExpression::makeEQ(&pool_, left, right); - } - Expression* or_(const std::vector& exprs) { - return ExpressionUtils::pushOrs(&pool_, exprs); - } - Expression* tagProp(const std::string& tag, const std::string& prop) { - return TagPropertyExpression::make(&pool_, tag, prop); - } - Expression* edgeProp(const std::string& edge, const std::string& prop) { - return EdgePropertyExpression::make(&pool_, edge, prop); - } - template - Expression* constant(const T& value) { - return ConstantExpression::make(&pool_, Value(value)); - } -}; - -TEST_F(FTIndexUtilsTest, rewriteTSFilter) { - ObjectPool pool; - std::string indexName = "test_index"; - std::string tagName = "tag1"; - std::string edgeName = "edge1"; - std::string propName = "prop1"; - using Item = plugin::ESQueryResult::Item; - std::vector items = {Item("1", "abc"), Item("2", "abc1"), Item("3", "abc")}; - plugin::ESQueryResult esResult; - esResult.items = items; - { - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, prefix(indexName, "prefix_pattern", 10000, -1)) - .WillOnce(Return(esResult)); - auto argument = TextSearchArgument::make(&pool, tagName, propName, "prefix_pattern"); - auto expr = TextSearchExpression::makePrefix(&pool, argument); - auto expect = or_({eq(tagProp(tagName, propName), constant("abc")), - eq(tagProp(tagName, propName), constant("abc1")), - eq(tagProp(tagName, propName), constant("abc"))}); - auto result = FTIndexUtils::rewriteTSFilter(&pool, false, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(*result.value(), *expect) << result.value()->toString() << "\t" << expect->toString(); - } - { - plugin::ESQueryResult emptyEsResult; - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, prefix(indexName, "prefix_pattern", 10000, -1)) - .WillOnce(Return(emptyEsResult)); - auto argument = TextSearchArgument::make(&pool, tagName, propName, "prefix_pattern"); - auto expr = TextSearchExpression::makePrefix(&pool, argument); - auto result = FTIndexUtils::rewriteTSFilter(&pool, false, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(result.value(), nullptr); - } - { - Status status = Status::Error("mock error"); - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, prefix(indexName, "prefix_pattern", 10000, -1)) - .Times(FLAGS_ft_request_retry_times) - .WillRepeatedly(Return(status)); - auto argument = TextSearchArgument::make(&pool, tagName, propName, "prefix_pattern"); - auto expr = TextSearchExpression::makePrefix(&pool, argument); - auto result = FTIndexUtils::rewriteTSFilter(&pool, false, expr, indexName, mockESAdapter); - ASSERT_FALSE(result.ok()); - ASSERT_EQ(result.status().message(), "mock error"); - } - { - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, wildcard(indexName, "wildcard_pattern", 10000, -1)) - .WillOnce(Return(esResult)); - auto argument = TextSearchArgument::make(&pool, edgeName, propName, "wildcard_pattern"); - auto expr = TextSearchExpression::makeWildcard(&pool, argument); - auto expect = or_({eq(edgeProp(edgeName, propName), constant("abc")), - eq(edgeProp(edgeName, propName), constant("abc1")), - eq(edgeProp(edgeName, propName), constant("abc"))}); - auto result = FTIndexUtils::rewriteTSFilter(&pool, true, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(*result.value(), *expect) << result.value()->toString() << "\t" << expect->toString(); - } - { - plugin::ESQueryResult singleEsResult; - singleEsResult.items = {Item("a", "b", 1, "edge text")}; - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, wildcard(indexName, "wildcard_pattern", 10000, -1)) - .WillOnce(Return(singleEsResult)); - auto argument = TextSearchArgument::make(&pool, edgeName, propName, "wildcard_pattern"); - auto expr = TextSearchExpression::makeWildcard(&pool, argument); - auto expect = eq(edgeProp(edgeName, propName), constant("edge text")); - auto result = FTIndexUtils::rewriteTSFilter(&pool, true, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(*result.value(), *expect) << result.value()->toString() << "\t" << expect->toString(); - } - { - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, regexp(indexName, "regexp_pattern", 10000, -1)) - .WillOnce(Return(esResult)); - auto argument = TextSearchArgument::make(&pool, edgeName, propName, "regexp_pattern"); - auto expr = TextSearchExpression::makeRegexp(&pool, argument); - auto expect = or_({eq(edgeProp(edgeName, propName), constant("abc")), - eq(edgeProp(edgeName, propName), constant("abc1")), - eq(edgeProp(edgeName, propName), constant("abc"))}); - auto result = FTIndexUtils::rewriteTSFilter(&pool, true, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(*result.value(), *expect) << result.value()->toString() << "\t" << expect->toString(); - } - { - MockESAdapter mockESAdapter; - EXPECT_CALL(mockESAdapter, fuzzy(indexName, "fuzzy_pattern", "1", 10000, -1)) - .WillOnce(Return(esResult)); - auto argument = TextSearchArgument::make(&pool, tagName, propName, "fuzzy_pattern"); - argument->setFuzziness(1); - auto expr = TextSearchExpression::makeFuzzy(&pool, argument); - auto expect = or_({eq(tagProp(tagName, propName), constant("abc")), - eq(tagProp(tagName, propName), constant("abc1")), - eq(tagProp(tagName, propName), constant("abc"))}); - auto result = FTIndexUtils::rewriteTSFilter(&pool, false, expr, indexName, mockESAdapter); - ASSERT_TRUE(result.ok()) << result.status().message(); - ASSERT_EQ(*result.value(), *expect) << result.value()->toString() << "\t" << expect->toString(); - } -} - -} // namespace nebula::graph diff --git a/src/graph/validator/LookupValidator.cpp b/src/graph/validator/LookupValidator.cpp index 73f513e6567..dcff26f13c9 100644 --- a/src/graph/validator/LookupValidator.cpp +++ b/src/graph/validator/LookupValidator.cpp @@ -551,16 +551,6 @@ StatusOr LookupValidator::checkConstExpr(Expression* expr, return expr; } -// Check does test search contains properties search in test search expression -// StatusOr LookupValidator::checkTSExpr(Expression* expr) { -// auto tsExpr = static_cast(expr); -// auto prop = tsExpr->arg()->prop(); -// auto metaClient = qctx_->getMetaClient(); -// auto tsi = metaClient->getFTIndexFromCache(spaceId(), schemaId(), prop); -// NG_RETURN_IF_ERROR(tsi); -// auto tsName = tsi.value().first; -// return tsName; -// } // Reverse position of operands in relational expression and keep the origin semantic. // Transform (A > B) to (B < A) @@ -645,16 +635,6 @@ Status LookupValidator::getSchemaProvider(shared_ptr return Status::OK(); } -// Generate text search filter, check validity and rewrite -// StatusOr LookupValidator::genTsFilter(Expression* filter) { -// auto esAdapterRet = FTIndexUtils::getESAdapter(qctx_->getMetaClient()); -// NG_RETURN_IF_ERROR(esAdapterRet); -// auto esAdapter = std::move(esAdapterRet).value(); -// auto tsIndex = checkTSExpr(filter); -// NG_RETURN_IF_ERROR(tsIndex); -// return FTIndexUtils::rewriteTSFilter( -// qctx_->objPool(), lookupCtx_->isEdge, filter, tsIndex.value(), esAdapter); -// } } // namespace graph } // namespace nebula diff --git a/src/graph/validator/MaintainValidator.cpp b/src/graph/validator/MaintainValidator.cpp index 95bde20293c..155cd7d836d 100644 --- a/src/graph/validator/MaintainValidator.cpp +++ b/src/graph/validator/MaintainValidator.cpp @@ -599,10 +599,7 @@ Status AddHostsIntoZoneValidator::toPlan() { Status CreateFTIndexValidator::validateImpl() { auto sentence = static_cast(sentence_); folly::StringPiece name = folly::StringPiece(*sentence->indexName()); - if (!name.startsWith(kFulltextIndexNamePrefix)) { - return Status::SyntaxError("Index name must begin with \"%s\"", - kFulltextIndexNamePrefix.c_str()); - } + if (name.size() > kFulltextIndexNameLength) { return Status::SyntaxError(fmt::format("Fulltext index name's length must less equal than {}", kFulltextIndexNameLength)); @@ -639,7 +636,10 @@ Status CreateFTIndexValidator::validateImpl() { } index_.space_id_ref() = space.id; index_.depend_schema_ref() = std::move(id); - index_.fields_ref()->push_back(sentence->field()); + for (auto &f : sentence->fields()) { + index_.fields_ref()->push_back(f); + } + index_.analyzer_ref() = sentence->analyzer(); return Status::OK(); } diff --git a/src/graph/validator/MaintainValidator.h b/src/graph/validator/MaintainValidator.h index c055b1795fa..de4be76c177 100644 --- a/src/graph/validator/MaintainValidator.h +++ b/src/graph/validator/MaintainValidator.h @@ -419,7 +419,6 @@ class CreateFTIndexValidator final : public Validator { private: meta::cpp2::FTIndex index_; - const std::string kFulltextIndexNamePrefix = "nebula_"; const size_t kFulltextIndexNameLength = 256; }; diff --git a/src/parser/MaintainSentences.cpp b/src/parser/MaintainSentences.cpp index 7167a536f49..7e8318b75a8 100644 --- a/src/parser/MaintainSentences.cpp +++ b/src/parser/MaintainSentences.cpp @@ -494,8 +494,7 @@ std::string CreateFTIndexSentence::toString() const { buf += " ON "; buf += *schemaName_; buf += "("; - std::vector fieldDefs; - fieldDefs.emplace_back(field()); + std::vector fieldDefs = fields(); std::string fields; folly::join(", ", fieldDefs, fields); buf += fields; diff --git a/src/parser/MaintainSentences.h b/src/parser/MaintainSentences.h index 1125cc1dd59..c5db24715e3 100644 --- a/src/parser/MaintainSentences.h +++ b/src/parser/MaintainSentences.h @@ -1123,11 +1123,15 @@ class CreateFTIndexSentence final : public Sentence { CreateFTIndexSentence(bool isEdge, std::string *indexName, std::string *schemaName, - std::string *field) { + NameLabelList *fields, + std::string *analyzer) { isEdge_ = isEdge; indexName_.reset(indexName); schemaName_.reset(schemaName); - field_.reset(field); + for (auto &f : fields->labels()) { + fields_.push_back(*f); + } + analyzer_.reset(analyzer); kind_ = Kind::kCreateFTIndex; } @@ -1144,15 +1148,24 @@ class CreateFTIndexSentence final : public Sentence { return schemaName_.get(); } - std::string field() const { - return *field_; + std::vector fields() const { + return fields_; + } + + std::string analyzer() const { + if (analyzer_ == nullptr) { + return ""; + } else { + return *analyzer_; + } } private: bool isEdge_; std::unique_ptr indexName_; std::unique_ptr schemaName_; - std::unique_ptr field_; + std::vector fields_; + std::unique_ptr analyzer_; }; class DropFTIndexSentence final : public Sentence { public: diff --git a/src/parser/parser.yy b/src/parser/parser.yy index b0aaff1ff01..210f7394fe8 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -64,7 +64,6 @@ using namespace nebula; int64_t intval; double doubleval; std::string *strval; - std::pair *intintval; nebula::meta::cpp2::GeoShape geo_shape; nebula::meta::cpp2::ColumnTypeDef *type; nebula::Expression *expr; @@ -203,7 +202,7 @@ using namespace nebula; %token KW_CASE KW_THEN KW_ELSE KW_END %token KW_GROUP KW_ZONE KW_GROUPS KW_ZONES KW_INTO KW_NEW %token KW_LISTENER KW_ELASTICSEARCH KW_FULLTEXT KW_HTTPS KW_HTTP -%token KW_AUTO KW_ES_QUERY KW_ES_MATCH +%token KW_AUTO KW_ES_QUERY KW_ANALYZER %token KW_TEXT KW_SEARCH KW_CLIENTS KW_SIGN KW_SERVICE KW_TEXT_SEARCH %token KW_ANY KW_SINGLE KW_NONE %token KW_REDUCE @@ -227,7 +226,7 @@ using namespace nebula; %token DOUBLE %token STRING VARIABLE LABEL IPV4 -%type name_label unreserved_keyword predicate_name +%type name_label unreserved_keyword predicate_name opt_analyzer %type expression expression_internal %type property_expression %type vertex_prop_expression @@ -407,8 +406,6 @@ using namespace nebula; %type opt_with_properties %type opt_ignore_existed_index -%type text_search_limit - // Define precedence and associativity of tokens. // Associativity: // The associativity of an operator op determines how repeated uses of the operator nest: @@ -547,7 +544,6 @@ unreserved_keyword | KW_STATUS { $$ = new std::string("status"); } | KW_AUTO { $$ = new std::string("auto"); } | KW_ES_QUERY { $$ = new std::string("es_query"); } - | KW_ES_MATCH { $$ = new std::string("es_match"); } | KW_TEXT { $$ = new std::string("text"); } | KW_SEARCH { $$ = new std::string("search"); } | KW_CLIENTS { $$ = new std::string("clients"); } @@ -2068,56 +2064,38 @@ sign_out_service_sentence } ; -text_search_limit - : %empty { - $$ = new std::pair(0, 0); - } - | COMMA legal_integer { - $$ = new std::pair($2, 0); - } - | COMMA legal_integer COMMA legal_integer{ - $$ = new std::pair($2, $4); - } - ; text_search_argument - : STRING COMMA STRING COMMA L_BRACKET name_label_list R_BRACKET text_search_limit{ + : STRING COMMA STRING COMMA L_BRACKET name_label_list R_BRACKET{ std::vector props; for(auto& p:$6->labels()){ props.push_back(*p); } delete $6; - auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, props, $8->first, $8->second); - delete $8; + auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, props); $$ = args; } - | STRING COMMA STRING text_search_limit{ - auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, {}, $4->first, $4->second); - delete $4; + | STRING COMMA STRING { + auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, {}); $$ = args; } - | STRING COMMA L_BRACKET name_label_list R_BRACKET text_search_limit{ + | STRING COMMA L_BRACKET name_label_list R_BRACKET { std::vector props; for(auto& p:$4->labels()){ props.push_back(*p); } delete $4; - auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, props, $6->first, $6->second); - delete $6; + auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, props); $$ = args; - } - | STRING text_search_limit{ - auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, {}, $2->first, $2->second); - delete $2; + } + | STRING { + auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, {}); $$ = args; } ; text_search_expression - : KW_ES_MATCH L_PAREN text_search_argument R_PAREN KW_FROM{ - $$ = TextSearchExpression::makeMatch(qctx->objPool(), $3); - } - | KW_ES_QUERY L_PAREN text_search_argument R_PAREN { + : KW_ES_QUERY L_PAREN text_search_argument R_PAREN { $$ = TextSearchExpression::makeQuery(qctx->objPool(), $3); } ; @@ -2649,12 +2627,21 @@ create_edge_index_sentence } ; +opt_analyzer + : %empty { + $$ = nullptr; + } + | KW_USE KW_ANALYZER ASSIGN STRING { + $$ = $4; + } + ; + create_fulltext_index_sentence - : KW_CREATE KW_FULLTEXT KW_TAG KW_INDEX name_label KW_ON name_label L_PAREN name_label R_PAREN { - $$ = new CreateFTIndexSentence(false, $5, $7, $9); + : KW_CREATE KW_FULLTEXT KW_TAG KW_INDEX name_label KW_ON name_label L_PAREN name_label_list R_PAREN opt_analyzer { + $$ = new CreateFTIndexSentence(false, $5, $7, $9, $11); } - | KW_CREATE KW_FULLTEXT KW_EDGE KW_INDEX name_label KW_ON name_label L_PAREN name_label R_PAREN { - $$ = new CreateFTIndexSentence(true, $5, $7, $9); + | KW_CREATE KW_FULLTEXT KW_EDGE KW_INDEX name_label KW_ON name_label L_PAREN name_label_list R_PAREN opt_analyzer { + $$ = new CreateFTIndexSentence(true, $5, $7, $9, $11); } ; diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index 5ddac905b8d..a84f92782c1 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -284,7 +284,6 @@ LABEL_FULL_WIDTH {CN_EN_FULL_WIDTH}{CN_EN_NUM_FULL_WIDTH}* "HTTPS" { return TokenType::KW_HTTPS; } "FULLTEXT" { return TokenType::KW_FULLTEXT; } "AUTO" { return TokenType::KW_AUTO; } -"ES_MATCH" { return TokenType::KW_ES_MATCH; } "ES_QUERY" { return TokenType::KW_ES_QUERY; } "TEXT" { return TokenType::KW_TEXT; } "SEARCH" { return TokenType::KW_SEARCH; } diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 9bc976cc1a0..bc2df895f6d 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -2929,139 +2929,24 @@ TEST_F(ParserTest, Zone) { TEST_F(ParserTest, FullText) { { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\")"; + std::string query = "LOOKUP ON t1 WHERE ES_QUERY(\"abc\", \"qwerty\", [a,b,c,d])"; auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\")"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\")"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\")"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1, 2)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", 1, 2)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", 1, 2)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 1, 2)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", AUTO, AND)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", AUTO, OR)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, AND)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1, 1)"; - auto result = parse(query); - ASSERT_TRUE(result.ok()) << result.status(); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, -1, 1)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1, -1)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", -1, OR, 1, 1)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); - } - { - std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 4, OR, 1, 1)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); - } - { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", -1, 2)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); - } - { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1, -2)"; - auto result = parse(query); - ASSERT_FALSE(result.ok()); + EXPECT_TRUE(result.ok()) << result.status(); } { - std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", AUTO, AND)"; + std::string query = "LOOKUP ON t1 WHERE ES_QUERY(\"qwerty\", [a,b,c,d])"; auto result = parse(query); - ASSERT_FALSE(result.ok()); + EXPECT_TRUE(result.ok()) << result.status(); } { - std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", AUTO, AND)"; + std::string query = "LOOKUP ON t1 WHERE ES_QUERY(\"index1\", \"qwerty\")"; auto result = parse(query); - ASSERT_FALSE(result.ok()); + EXPECT_TRUE(result.ok()) << result.status(); } { - std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", AUTO, AND)"; + std::string query = "LOOKUP ON t1 WHERE ES_QUERY(\"qwerty\")"; auto result = parse(query); - ASSERT_FALSE(result.ok()); + EXPECT_TRUE(result.ok()) << result.status(); } } diff --git a/src/storage/query/QueryBaseProcessor-inl.h b/src/storage/query/QueryBaseProcessor-inl.h index 7456716763c..11db18e9e96 100644 --- a/src/storage/query/QueryBaseProcessor-inl.h +++ b/src/storage/query/QueryBaseProcessor-inl.h @@ -629,7 +629,6 @@ nebula::cpp2::ErrorCode QueryBaseProcessor::checkExp( case Expression::Kind::kPathBuild: case Expression::Kind::kColumn: case Expression::Kind::kESQUERY: - case Expression::Kind::kESMATCH: case Expression::Kind::kAggregate: case Expression::Kind::kSubscriptRange: case Expression::Kind::kVersionedVar: diff --git a/tests/tck/features/fulltext_index/FulltextIndexQuery1.feature b/tests/tck/features/fulltext_index/FulltextIndexQuery1.feature deleted file mode 100644 index f8ad3645bc4..00000000000 --- a/tests/tck/features/fulltext_index/FulltextIndexQuery1.feature +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright (c) 2022 vesoft inc. All rights reserved. -# -# This source code is licensed under Apache 2.0 License. -Feature: FulltextIndexTest - - Background: - Given an empty graph - And create a space with following options: - | partition_num | 1 | - | replica_factor | 1 | - | vid_type | INT64 | - And add listeners to space - - @ft_index - Scenario: fulltext query1 - When executing query: - """ - CREATE TAG tag1(prop1 string,prop2 string); - CREATE EDGE edge1(prop1 string); - """ - Then the execution should be successful - And wait 3 seconds - When executing query: - """ - CREATE FULLTEXT TAG INDEX nebula_index_tag1_prop1 on tag1(prop1); - CREATE FULLTEXT TAG INDEX nebula_index_tag1_prop2 on tag1(prop2); - CREATE FULLTEXT EDGE INDEX nebula_index_edge1_prop1 on edge1(prop1); - """ - Then the execution should be successful - And wait 5 seconds - When executing query: - """ - INSERT VERTEX tag1(prop1,prop2) VALUES 1:("abc","nebula graph"); - INSERT VERTEX tag1(prop1,prop2) VALUES 2:("abcde","nebula-graph"); - INSERT VERTEX tag1(prop1,prop2) VALUES 3:("bcd","nebula database"); - INSERT VERTEX tag1(prop1,prop2) VALUES 4:("zyx","Nebula"); - INSERT VERTEX tag1(prop1,prop2) VALUES 5:("cba","neBula"); - INSERT VERTEX tag1(prop1,prop2) VALUES 6:("abcxyz","nebula graph"); - INSERT VERTEX tag1(prop1,prop2) VALUES 7:("xyz","nebula graph"); - INSERT VERTEX tag1(prop1,prop2) VALUES 8:("123456","nebula graph"); - """ - Then the execution should be successful - When executing query: - """ - INSERT EDGE edge1(prop1) VALUES 1->2@1:("一个可靠的分布式"); - INSERT EDGE edge1(prop1) VALUES 2->3@3:("性能高效的图数据库"); - INSERT EDGE edge1(prop1) VALUES 3->4@5:("高性能"); - INSERT EDGE edge1(prop1) VALUES 4->5@7:("高吞吐"); - INSERT EDGE edge1(prop1) VALUES 5->6@9:("低延时"); - INSERT EDGE edge1(prop1) VALUES 6->7@11:("易扩展"); - INSERT EDGE edge1(prop1) VALUES 7->8@13:("线性扩缩容"); - INSERT EDGE edge1(prop1) VALUES 8->1@15:("安全稳定"); - """ - Then the execution should be successful - And wait 10 seconds - When executing query: - """ - LOOKUP ON tag1 where prefix(tag1.prop1,"abc") YIELD id(vertex) as id, tag1.prop1 as prop1, tag1.prop2 as prop2 - """ - Then the result should be, in any order: - | id | prop1 | prop2 | - | 1 | "abc" | "nebula graph" | - | 2 | "abcde" | "nebula-graph" | - | 6 | "abcxyz" | "nebula graph" | - When executing query: - """ - LOOKUP ON tag1 where prefix(tag1.prop2,"nebula") YIELD id(vertex) as id, tag1.prop1 as prop1, tag1.prop2 as prop2 - """ - Then the result should be, in any order: - | id | prop1 | prop2 | - | 1 | "abc" | "nebula graph" | - | 2 | "abcde" | "nebula-graph" | - | 3 | "bcd" | "nebula database" | - | 6 | "abcxyz" | "nebula graph" | - | 7 | "xyz" | "nebula graph" | - | 8 | "123456" | "nebula graph" | - When executing query: - """ - LOOKUP ON edge1 where prefix(edge1.prop1,"高") YIELD src(edge) as src,dst(edge) as dst,rank(edge) as rank, edge1.prop1 as prop1 - """ - Then the result should be, in any order: - | src | dst | rank | prop1 | - | 3 | 4 | 5 | "高性能" | - | 4 | 5 | 7 | "高吞吐" | - When executing query: - """ - LOOKUP ON tag1 where regexp(tag1.prop2,"neBula.*") YIELD id(vertex) as id, tag1.prop1 as prop1 - """ - Then the result should be, in any order: - | id | prop1 | - | 5 | "cba" | - When executing query: - """ - LOOKUP ON edge1 where wildcard(edge1.prop1,"高??") YIELD src(edge) as src,dst(edge) as dst,rank(edge) as rank, edge1.prop1 as prop1 - """ - Then the result should be, in any order: - | src | dst | rank | prop1 | - | 3 | 4 | 5 | "高性能" | - | 4 | 5 | 7 | "高吞吐" | - When executing query: - """ - LOOKUP ON tag1 where fuzzy(edge1.prop2,"nebula") YIELD tag1.prop2 as prop2 - """ - Then the result should be, in any order: - | prop2 | - | "Nebula" | - | "neBula" | diff --git a/tests/tck/features/fulltext_index/FulltextIndexQuery2.feature b/tests/tck/features/fulltext_index/FulltextIndexQuery2.feature deleted file mode 100644 index 40a7bd90cec..00000000000 --- a/tests/tck/features/fulltext_index/FulltextIndexQuery2.feature +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) 2022 vesoft inc. All rights reserved. -# -# This source code is licensed under Apache 2.0 License. -Feature: FulltextIndexTest - - Background: - Given an empty graph - And create a space with following options: - | partition_num | 1 | - | replica_factor | 1 | - | vid_type | FIXED_STRING(30) | - And add listeners to space - - @ft_index - Scenario: fulltext query2 - When executing query: - """ - CREATE TAG tag2(prop1 string,prop2 string); - CREATE EDGE edge2(prop1 string); - """ - Then the execution should be successful - And wait 3 seconds - When executing query: - """ - CREATE FULLTEXT TAG INDEX nebula_index_tag2_prop1 on tag2(prop1); - CREATE FULLTEXT TAG INDEX nebula_index_tag2_prop2 on tag2(prop2); - CREATE FULLTEXT EDGE INDEX nebula_index_edge2_prop1 on edge2(prop1); - """ - Then the execution should be successful - And wait 5 seconds - When executing query: - """ - INSERT VERTEX tag2(prop1,prop2) VALUES "1":("abc","nebula graph"); - INSERT VERTEX tag2(prop1,prop2) VALUES "2":("abcde","nebula-graph"); - INSERT VERTEX tag2(prop1,prop2) VALUES "3":("bcd","nebula database"); - INSERT VERTEX tag2(prop1,prop2) VALUES "4":("zyx","Nebula"); - INSERT VERTEX tag2(prop1,prop2) VALUES "5":("cba","neBula"); - INSERT VERTEX tag2(prop1,prop2) VALUES "6":("abcxyz","nebula graph"); - INSERT VERTEX tag2(prop1,prop2) VALUES "7":("xyz","nebula graph"); - INSERT VERTEX tag2(prop1,prop2) VALUES "8":("123456","nebula graph"); - """ - Then the execution should be successful - When executing query: - """ - INSERT EDGE edge2(prop1) VALUES "1"->"2"@1:("一个可靠的分布式"); - INSERT EDGE edge2(prop1) VALUES "2"->"3"@3:("性能高效的图数据库"); - INSERT EDGE edge2(prop1) VALUES "3"->"4"@5:("高性能"); - INSERT EDGE edge2(prop1) VALUES "4"->"5"@7:("高吞吐"); - INSERT EDGE edge2(prop1) VALUES "5"->"6"@9:("低延时"); - INSERT EDGE edge2(prop1) VALUES "6"->"7"@11:("易扩展"); - INSERT EDGE edge2(prop1) VALUES "7"->"8"@13:("线性扩缩容"); - INSERT EDGE edge2(prop1) VALUES "8"->"1"@15:("安全稳定"); - """ - Then the execution should be successful - And wait 10 seconds - When executing query: - """ - LOOKUP ON tag2 where prefix(tag2.prop1,"abc") YIELD id(vertex) as id, tag2.prop1 as prop1, tag2.prop2 as prop2 - """ - Then the result should be, in any order: - | id | prop1 | prop2 | - | "1" | "abc" | "nebula graph" | - | "2" | "abcde" | "nebula-graph" | - | "6" | "abcxyz" | "nebula graph" | - When executing query: - """ - LOOKUP ON tag2 where prefix(tag2.prop2,"nebula") YIELD id(vertex) as id, tag2.prop1 as prop1, tag2.prop2 as prop2 - """ - Then the result should be, in any order: - | id | prop1 | prop2 | - | "1" | "abc" | "nebula graph" | - | "2" | "abcde" | "nebula-graph" | - | "3" | "bcd" | "nebula database" | - | "6" | "abcxyz" | "nebula graph" | - | "7" | "xyz" | "nebula graph" | - | "8" | "123456" | "nebula graph" | - When executing query: - """ - LOOKUP ON edge2 where prefix(edge2.prop1,"高") YIELD src(edge) as src,dst(edge) as dst,rank(edge) as rank, edge2.prop1 as prop1 - """ - Then the result should be, in any order: - | src | dst | rank | prop1 | - | "3" | "4" | 5 | "高性能" | - | "4" | "5" | 7 | "高吞吐" | From 37b41268f686e90d963054ebb285bd1e9f3d0950 Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Wed, 31 May 2023 14:10:06 +0800 Subject: [PATCH 3/6] fmt --- src/common/plugin/fulltext/elasticsearch/ESAdapter.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h index 4f2625dc649..19229f8306a 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.h +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.h @@ -76,7 +76,6 @@ class ESAdapter { virtual Status bulk(const ESBulk& bulk, bool refresh = false); - virtual StatusOr queryString(const std::string& index, const std::string& query, const std::vector& fields, From dcc5dfcf5f24a84034d2ed102520d4eb95f9ed79 Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:47:31 +0800 Subject: [PATCH 4/6] fix bug --- src/clients/meta/MetaClient.cpp | 2 +- src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clients/meta/MetaClient.cpp b/src/clients/meta/MetaClient.cpp index 4ee228c51bc..93a97b83b91 100644 --- a/src/clients/meta/MetaClient.cpp +++ b/src/clients/meta/MetaClient.cpp @@ -990,7 +990,7 @@ Status MetaClient::handleResponse(const RESP& resp) { case nebula::cpp2::ErrorCode::E_HOST_CAN_NOT_BE_ADDED: return Status::Error("Could not add a host, which is not a storage and not expired either"); default: - return Status::Error("Unknown error!"); + return Status::Error("Unknown error %d!", static_cast(resp.get_code())); } } diff --git a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp index c6a1bd1b0a5..a7dd540c298 100644 --- a/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp +++ b/src/common/plugin/fulltext/elasticsearch/ESAdapter.cpp @@ -98,7 +98,7 @@ Status ESAdapter::createIndex(const std::string& name, mappings["properties"][field] = std::move(f); } - auto result = randomClient().createIndex(name, mappings); + auto result = randomClient().createIndex(name, obj); if (!result.ok()) { return result.status(); } From 424a1d0ded6d354557d1a40e12b000fbf10b1e44 Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:35:14 +0800 Subject: [PATCH 5/6] fix bug --- tests/tck/features/yield/yield.IntVid.feature | 5 ----- tests/tck/features/yield/yield.feature | 5 ----- 2 files changed, 10 deletions(-) diff --git a/tests/tck/features/yield/yield.IntVid.feature b/tests/tck/features/yield/yield.IntVid.feature index 7955f856c6b..d1b04750451 100644 --- a/tests/tck/features/yield/yield.IntVid.feature +++ b/tests/tck/features/yield/yield.IntVid.feature @@ -110,11 +110,6 @@ Feature: Yield Sentence Then the result should be, in any order: | [1,2,3][0..1] | | [1] | - When executing query: - """ - YIELD prefix(edge1.prop1,"高") - """ - Then a SyntaxError should be raised at runtime: syntax error near `(edge1.p' When executing query: """ YIELD (INT)"1" diff --git a/tests/tck/features/yield/yield.feature b/tests/tck/features/yield/yield.feature index d6f092cb00d..5d6b5bc05eb 100644 --- a/tests/tck/features/yield/yield.feature +++ b/tests/tck/features/yield/yield.feature @@ -110,11 +110,6 @@ Feature: Yield Sentence Then the result should be, in any order: | [1,2,3][0..1] | | [1] | - When executing query: - """ - YIELD prefix(edge1.prop1,"高") - """ - Then a SyntaxError should be raised at runtime: syntax error near `(edge1.p' When executing query: """ YIELD (int)"1" From 56d4165c702149fdbebd6e1bc7b26cf397344d50 Mon Sep 17 00:00:00 2001 From: "hs.zhang" <22708345+cangfengzhs@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:49:36 +0800 Subject: [PATCH 6/6] fix memleak --- src/parser/parser.yy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 210f7394fe8..3ecc955b835 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -2073,10 +2073,14 @@ text_search_argument } delete $6; auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, props); + delete $1; + delete $3; $$ = args; } | STRING COMMA STRING { auto args = TextSearchArgument::make(qctx->objPool(), *$1, *$3, {}); + delete $1; + delete $3; $$ = args; } | STRING COMMA L_BRACKET name_label_list R_BRACKET { @@ -2086,10 +2090,12 @@ text_search_argument } delete $4; auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, props); + delete $1; $$ = args; } | STRING { auto args = TextSearchArgument::make(qctx->objPool(), "", *$1, {}); + delete $1; $$ = args; } ;