From a12d3c34d394172e0e0e8cd7401c8b640bf34d65 Mon Sep 17 00:00:00 2001 From: bright-starry-sky <56461666+bright-starry-sky@users.noreply.github.com> Date: Thu, 26 Nov 2020 16:58:26 +0800 Subject: [PATCH] Fulltext integration (#415) * fulltext * remove create ft index * fixed memory leaks * Resolved conflict * Resolve conflict * addressed yee's comments --- src/context/test/CMakeLists.txt | 1 + src/daemons/CMakeLists.txt | 1 + src/executor/CMakeLists.txt | 3 + src/executor/Executor.cpp | 12 + src/executor/admin/ShowTSClientsExecutor.cpp | 43 ++++ src/executor/admin/ShowTSClientsExecutor.h | 29 +++ .../admin/SignInTSServiceExecutor.cpp | 33 +++ src/executor/admin/SignInTSServiceExecutor.h | 29 +++ .../admin/SignOutTSServiceExecutor.cpp | 32 +++ src/executor/admin/SignOutTSServiceExecutor.h | 29 +++ src/executor/query/IndexScanExecutor.cpp | 4 + src/executor/test/CMakeLists.txt | 1 + src/optimizer/rule/IndexScanRule.cpp | 10 +- src/optimizer/rule/IndexScanRule.h | 2 + src/optimizer/test/CMakeLists.txt | 1 + src/parser/AdminSentences.cpp | 35 +++ src/parser/AdminSentences.h | 54 +++++ src/parser/Sentence.h | 4 + src/parser/parser.yy | 219 ++++++++++++++++- src/parser/scanner.lex | 12 +- src/parser/test/CMakeLists.txt | 1 + src/parser/test/ParserTest.cpp | 204 ++++++++++++++++ src/planner/Admin.h | 46 ++++ src/planner/PlanNode.cpp | 7 + src/planner/PlanNode.h | 4 + src/planner/Query.h | 9 + src/service/GraphFlags.cpp | 2 + src/service/PermissionCheck.cpp | 9 +- src/util/ExpressionUtils.cpp | 14 ++ src/util/ExpressionUtils.h | 3 + src/util/test/CMakeLists.txt | 1 + src/util/test/ExpressionUtilsTest.cpp | 19 ++ src/validator/AdminValidator.cpp | 37 +++ src/validator/AdminValidator.h | 42 ++++ src/validator/IndexScanValidator.cpp | 224 +++++++++++++++--- src/validator/IndexScanValidator.h | 34 ++- src/validator/Validator.cpp | 6 + src/validator/test/CMakeLists.txt | 1 + src/visitor/test/CMakeLists.txt | 1 + 39 files changed, 1173 insertions(+), 45 deletions(-) create mode 100644 src/executor/admin/ShowTSClientsExecutor.cpp create mode 100644 src/executor/admin/ShowTSClientsExecutor.h create mode 100644 src/executor/admin/SignInTSServiceExecutor.cpp create mode 100644 src/executor/admin/SignInTSServiceExecutor.h create mode 100644 src/executor/admin/SignOutTSServiceExecutor.cpp create mode 100644 src/executor/admin/SignOutTSServiceExecutor.h diff --git a/src/context/test/CMakeLists.txt b/src/context/test/CMakeLists.txt index 046c9a7e..d0d17267 100644 --- a/src/context/test/CMakeLists.txt +++ b/src/context/test/CMakeLists.txt @@ -28,6 +28,7 @@ SET(CONTEXT_TEST_LIBS $<TARGET_OBJECTS:common_process_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> $<TARGET_OBJECTS:util_obj> $<TARGET_OBJECTS:context_obj> $<TARGET_OBJECTS:expr_visitor_obj> diff --git a/src/daemons/CMakeLists.txt b/src/daemons/CMakeLists.txt index 7ecffcb9..e4593708 100644 --- a/src/daemons/CMakeLists.txt +++ b/src/daemons/CMakeLists.txt @@ -58,6 +58,7 @@ nebula_add_executable( $<TARGET_OBJECTS:common_conf_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> LIBRARIES proxygenhttpserver proxygenlib diff --git a/src/executor/CMakeLists.txt b/src/executor/CMakeLists.txt index a4f7f7f5..f98f916b 100644 --- a/src/executor/CMakeLists.txt +++ b/src/executor/CMakeLists.txt @@ -65,6 +65,9 @@ nebula_add_library( admin/ConfigExecutor.cpp admin/GroupExecutor.cpp admin/ZoneExecutor.cpp + admin/ShowTSClientsExecutor.cpp + admin/SignInTSServiceExecutor.cpp + admin/SignOutTSServiceExecutor.cpp ) nebula_add_subdirectory(test) diff --git a/src/executor/Executor.cpp b/src/executor/Executor.cpp index 2afdec41..897d7d59 100644 --- a/src/executor/Executor.cpp +++ b/src/executor/Executor.cpp @@ -38,6 +38,9 @@ #include "executor/admin/GroupExecutor.h" #include "executor/admin/ZoneExecutor.h" #include "executor/admin/ShowStatsExecutor.h" +#include "executor/admin/ShowTSClientsExecutor.h" +#include "executor/admin/SignInTSServiceExecutor.h" +#include "executor/admin/SignOutTSServiceExecutor.h" #include "executor/algo/BFSShortestPathExecutor.h" #include "executor/algo/ProduceSemiShortestPathExecutor.h" #include "executor/algo/ConjunctPathExecutor.h" @@ -449,6 +452,15 @@ Executor *Executor::makeExecutor(QueryContext *qctx, const PlanNode *node) { case PlanNode::Kind::kShowStats: { return pool->add(new ShowStatsExecutor(node, qctx)); } + case PlanNode::Kind::kShowTSClients: { + return pool->add(new ShowTSClientsExecutor(node, qctx)); + } + case PlanNode::Kind::kSignInTSService: { + return pool->add(new SignInTSServiceExecutor(node, qctx)); + } + case PlanNode::Kind::kSignOutTSService: { + return pool->add(new SignOutTSServiceExecutor(node, qctx)); + } case PlanNode::Kind::kUnknown: { LOG(FATAL) << "Unknown plan node kind " << static_cast<int32_t>(node->kind()); break; diff --git a/src/executor/admin/ShowTSClientsExecutor.cpp b/src/executor/admin/ShowTSClientsExecutor.cpp new file mode 100644 index 00000000..a0e27398 --- /dev/null +++ b/src/executor/admin/ShowTSClientsExecutor.cpp @@ -0,0 +1,43 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "common/interface/gen-cpp2/meta_types.h" + +#include "context/QueryContext.h" +#include "executor/admin/ShowTSClientsExecutor.h" +#include "planner/Admin.h" +#include "service/PermissionManager.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> ShowTSClientsExecutor::execute() { + SCOPED_TIMER(&execTime_); + return showTSClients(); +} + +folly::Future<Status> ShowTSClientsExecutor::showTSClients() { +return qctx() + ->getMetaClient() + ->listFTClients() + .via(runner()) + .then([this](auto &&resp) { + if (!resp.ok()) { + LOG(ERROR) << resp.status(); + return resp.status(); + } + auto value = std::move(resp).value(); + DataSet v({"Host", "Port"}); + for (const auto &client : value) { + nebula::Row r({client.host.host, client.host.port}); + v.emplace_back(std::move(r)); + } + return finish(std::move(v)); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/executor/admin/ShowTSClientsExecutor.h b/src/executor/admin/ShowTSClientsExecutor.h new file mode 100644 index 00000000..f7e1fdbf --- /dev/null +++ b/src/executor/admin/ShowTSClientsExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXECUTOR_ADMIN_SHOW_TS_CLIENTS_EXECUTOR_H_ +#define EXECUTOR_ADMIN_SHOW_TS_CLIENTS_EXECUTOR_H_ + +#include "executor/Executor.h" + +namespace nebula { +namespace graph { + +class ShowTSClientsExecutor final : public Executor { +public: + ShowTSClientsExecutor(const PlanNode *node, QueryContext *qctx) + : Executor("ShowTSClientsExecutor", node, qctx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> showTSClients(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXECUTOR_ADMIN_SHOW_TS_CLIENTS_EXECUTOR_H_ diff --git a/src/executor/admin/SignInTSServiceExecutor.cpp b/src/executor/admin/SignInTSServiceExecutor.cpp new file mode 100644 index 00000000..07ad13db --- /dev/null +++ b/src/executor/admin/SignInTSServiceExecutor.cpp @@ -0,0 +1,33 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "executor/admin/SignInTSServiceExecutor.h" +#include "planner/Admin.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> SignInTSServiceExecutor::execute() { + SCOPED_TIMER(&execTime_); + return signInTSService(); +} + +folly::Future<Status> SignInTSServiceExecutor::signInTSService() { + auto *siNode = asNode<SignInTSService>(node()); + return qctx()->getMetaClient()->signInFTService(siNode->type(), siNode->clients()) + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Sign in text service failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/executor/admin/SignInTSServiceExecutor.h b/src/executor/admin/SignInTSServiceExecutor.h new file mode 100644 index 00000000..2cfca634 --- /dev/null +++ b/src/executor/admin/SignInTSServiceExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXECUTOR_ADMIN_SIGNINTSSERVICEEXECUTOR_H_ +#define EXECUTOR_ADMIN_SIGNINTSSERVICEEXECUTOR_H_ + +#include "executor/Executor.h" + +namespace nebula { +namespace graph { + +class SignInTSServiceExecutor final : public Executor { +public: + SignInTSServiceExecutor(const PlanNode *node, QueryContext *qctx) + : Executor("SignInTSServiceExecutor", node, qctx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> signInTSService(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXECUTOR_ADMIN_SIGNINTSSERVICEEXECUTOR_H_ diff --git a/src/executor/admin/SignOutTSServiceExecutor.cpp b/src/executor/admin/SignOutTSServiceExecutor.cpp new file mode 100644 index 00000000..18cccaf2 --- /dev/null +++ b/src/executor/admin/SignOutTSServiceExecutor.cpp @@ -0,0 +1,32 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#include "executor/admin/SignOutTSServiceExecutor.h" +#include "planner/Admin.h" + +namespace nebula { +namespace graph { + +folly::Future<Status> SignOutTSServiceExecutor::execute() { + SCOPED_TIMER(&execTime_); + return signOutTSService(); +} + +folly::Future<Status> SignOutTSServiceExecutor::signOutTSService() { + return qctx()->getMetaClient()->signOutFTService() + .via(runner()) + .then([this](StatusOr<bool> resp) { + SCOPED_TIMER(&execTime_); + NG_RETURN_IF_ERROR(resp); + if (!resp.value()) { + return Status::Error("Sign out text service failed!"); + } + return Status::OK(); + }); +} + +} // namespace graph +} // namespace nebula diff --git a/src/executor/admin/SignOutTSServiceExecutor.h b/src/executor/admin/SignOutTSServiceExecutor.h new file mode 100644 index 00000000..cde08ea3 --- /dev/null +++ b/src/executor/admin/SignOutTSServiceExecutor.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2020 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License, + * attached with Common Clause Condition 1.0, found in the LICENSES directory. + */ + +#ifndef EXECUTOR_ADMIN_SIGNOUTTSSERVICEEXECUTOR_H_ +#define EXECUTOR_ADMIN_SIGNOUTTSSERVICEEXECUTOR_H_ + +#include "executor/Executor.h" + +namespace nebula { +namespace graph { + +class SignOutTSServiceExecutor final : public Executor { +public: + SignOutTSServiceExecutor(const PlanNode *node, QueryContext *qctx) + : Executor("SignInTSServiceExecutor", node, qctx) {} + + folly::Future<Status> execute() override; + +private: + folly::Future<Status> signOutTSService(); +}; + +} // namespace graph +} // namespace nebula + +#endif // EXECUTOR_ADMIN_SIGNOUTTSSERVICEEXECUTOR_H_ diff --git a/src/executor/query/IndexScanExecutor.cpp b/src/executor/query/IndexScanExecutor.cpp index 8156b5fe..69817576 100644 --- a/src/executor/query/IndexScanExecutor.cpp +++ b/src/executor/query/IndexScanExecutor.cpp @@ -23,6 +23,10 @@ folly::Future<Status> IndexScanExecutor::execute() { folly::Future<Status> IndexScanExecutor::indexScan() { GraphStorageClient* storageClient = qctx_->getStorageClient(); auto *lookup = asNode<IndexScan>(node()); + if (lookup->isEmptyResultSet()) { + DataSet dataSet({"dummy"}); + return finish(ResultBuilder().value(Value(std::move(dataSet))).finish()); + } return storageClient->lookupIndex(lookup->space(), *lookup->queryContext(), lookup->isEdge(), diff --git a/src/executor/test/CMakeLists.txt b/src/executor/test/CMakeLists.txt index 20920404..39fb0227 100644 --- a/src/executor/test/CMakeLists.txt +++ b/src/executor/test/CMakeLists.txt @@ -32,6 +32,7 @@ SET(EXEC_QUERY_TEST_OBJS $<TARGET_OBJECTS:common_encryption_obj> $<TARGET_OBJECTS:common_http_client_obj> $<TARGET_OBJECTS:common_time_utils_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> $<TARGET_OBJECTS:session_obj> $<TARGET_OBJECTS:graph_flags_obj> $<TARGET_OBJECTS:parser_obj> diff --git a/src/optimizer/rule/IndexScanRule.cpp b/src/optimizer/rule/IndexScanRule.cpp index 78fffec7..bce01b45 100644 --- a/src/optimizer/rule/IndexScanRule.cpp +++ b/src/optimizer/rule/IndexScanRule.cpp @@ -30,6 +30,10 @@ const Pattern& IndexScanRule::pattern() const { StatusOr<OptRule::TransformResult> IndexScanRule::transform(graph::QueryContext* qctx, const MatchedResult& matched) const { auto groupNode = matched.node; + if (isEmptyResultSet(groupNode)) { + return TransformResult::noTransform(); + } + auto filter = filterExpr(groupNode); IndexQueryCtx iqctx = std::make_unique<std::vector<IndexQueryContext>>(); if (filter == nullptr) { @@ -174,7 +178,7 @@ Status IndexScanRule::appendIQCtx(const IndexItem& index, #define CHECK_BOUND_VALUE(v, name) \ do { \ - if (v == Value::kNullBadType) { \ + if (v == Value::kNullBadType) { \ LOG(ERROR) << "Get bound value error. field : " << name; \ return Status::Error("Get bound value error. field : %s", name.c_str()); \ } \ @@ -605,5 +609,9 @@ IndexScanRule::findIndexForRangeScan(const std::vector<IndexItem>& indexes, return priorityIdxs; } +bool IndexScanRule::isEmptyResultSet(const OptGroupNode *groupNode) const { + auto in = static_cast<const IndexScan *>(groupNode->node()); + return in->isEmptyResultSet(); +} } // namespace opt } // namespace nebula diff --git a/src/optimizer/rule/IndexScanRule.h b/src/optimizer/rule/IndexScanRule.h index 75814751..909c29cf 100644 --- a/src/optimizer/rule/IndexScanRule.h +++ b/src/optimizer/rule/IndexScanRule.h @@ -177,6 +177,8 @@ private: std::vector<IndexItem> findIndexForRangeScan(const std::vector<IndexItem>& indexes, const FilterItems& items) const; + + bool isEmptyResultSet(const OptGroupNode *groupNode) const; }; } // namespace opt diff --git a/src/optimizer/test/CMakeLists.txt b/src/optimizer/test/CMakeLists.txt index 9c135d06..64c6e743 100644 --- a/src/optimizer/test/CMakeLists.txt +++ b/src/optimizer/test/CMakeLists.txt @@ -30,6 +30,7 @@ set(OPTIMIZER_TEST_LIB $<TARGET_OBJECTS:common_agg_function_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> $<TARGET_OBJECTS:idgenerator_obj> $<TARGET_OBJECTS:expr_visitor_obj> $<TARGET_OBJECTS:session_obj> diff --git a/src/parser/AdminSentences.cpp b/src/parser/AdminSentences.cpp index 4cce5470..bd036555 100644 --- a/src/parser/AdminSentences.cpp +++ b/src/parser/AdminSentences.cpp @@ -208,4 +208,39 @@ std::string ShowStatsSentence::toString() const { return folly::stringPrintf("SHOW STATS"); } +std::string ShowTSClientsSentence::toString() const { + return "SHOW TEXT SEARCH CLIENTS"; +} + +std::string SignInTextServiceSentence::toString() const { + std::string buf; + buf.reserve(256); + buf += "SIGN IN TEXT SERVICE "; + for (auto &client : clients_->clients()) { + buf += "("; + buf += client.get_host().host; + buf += ":"; + buf += std::to_string(client.get_host().port); + if (client.__isset.user && !client.get_user()->empty()) { + buf += ", \""; + buf += *client.get_user(); + buf += "\""; + } + if (client.__isset.pwd && !client.get_pwd()->empty()) { + buf += ", \""; + buf += *client.get_pwd(); + buf += "\""; + } + buf += ")"; + buf += ","; + } + if (!buf.empty()) { + buf.resize(buf.size() - 1); + } + return buf; +} + +std::string SignOutTextServiceSentence::toString() const { + return "SIGN OUT TEXT SERVICE"; +} } // namespace nebula diff --git a/src/parser/AdminSentences.h b/src/parser/AdminSentences.h index 00ac019f..6bd545f6 100644 --- a/src/parser/AdminSentences.h +++ b/src/parser/AdminSentences.h @@ -585,6 +585,60 @@ public: std::string toString() const override; }; +class TSClientList final { +public: + void addClient(nebula::meta::cpp2::FTClient *client) { + clients_.emplace_back(client); + } + + std::string toString() const; + + std::vector<nebula::meta::cpp2::FTClient> clients() const { + std::vector<nebula::meta::cpp2::FTClient> result; + result.reserve(clients_.size()); + for (auto &client : clients_) { + result.emplace_back(*client); + } + return result; + } + +private: + std::vector<std::unique_ptr<nebula::meta::cpp2::FTClient>> clients_; +}; + +class ShowTSClientsSentence final : public Sentence { +public: + ShowTSClientsSentence() { + kind_ = Kind::kShowTSClients; + } + std::string toString() const override; +}; + +class SignInTextServiceSentence final : public Sentence { +public: + explicit SignInTextServiceSentence(TSClientList *clients) { + kind_ = Kind::kSignInTSService; + clients_.reset(clients); + } + + std::string toString() const override; + + TSClientList* clients() const { + return clients_.get(); + } + +private: + std::unique_ptr<TSClientList> clients_; +}; + +class SignOutTextServiceSentence final : public Sentence { +public: + SignOutTextServiceSentence() { + kind_ = Kind::kSignOutTSService; + } + + std::string toString() const override; +}; } // namespace nebula #endif // PARSER_ADMINSENTENCES_H_ diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h index a6241a8c..8a7392c7 100644 --- a/src/parser/Sentence.h +++ b/src/parser/Sentence.h @@ -19,6 +19,7 @@ #include "common/expression/UUIDExpression.h" #include "common/expression/LabelExpression.h" #include "common/interface/gen-cpp2/meta_types.h" +#include "common/expression/TextSearchExpression.h" namespace nebula { @@ -77,6 +78,7 @@ public: kShowGroups, kShowZones, kShowStats, + kShowTSClients, kDeleteVertices, kDeleteEdges, kLookup, @@ -122,6 +124,8 @@ public: kAddListener, kRemoveListener, kShowListener, + kSignInTSService, + kSignOutTSService, }; Kind kind() const { diff --git a/src/parser/parser.yy b/src/parser/parser.yy index 20a9e140..cc57ce68 100644 --- a/src/parser/parser.yy +++ b/src/parser/parser.yy @@ -21,6 +21,7 @@ #include "common/expression/LabelAttributeExpression.h" #include "common/expression/VariableExpression.h" #include "common/expression/CaseExpression.h" +#include "common/expression/TextSearchExpression.h" #include "util/SchemaUtil.h" namespace nebula { @@ -67,6 +68,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; nebula::OverEdges *over_edges; nebula::OverClause *over_clause; nebula::WhereClause *where_clause; + nebula::WhereClause *lookup_where_clause; nebula::WhenClause *when_clause; nebula::YieldClause *yield_clause; nebula::YieldColumns *yield_columns; @@ -121,6 +123,11 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; nebula::meta::cpp2::IndexFieldDef *index_field; 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::FTClient *text_search_client_item; + nebula::TSClientList *text_search_client_list; } /* destructors */ @@ -163,6 +170,8 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %token KW_CASE KW_THEN KW_ELSE KW_END %token KW_GROUP KW_ZONE KW_GROUPS KW_ZONES KW_INTO %token KW_LISTENER KW_ELASTICSEARCH +%token KW_AUTO KW_FUZZY KW_PREFIX KW_REGEXP KW_WILDCARD +%token KW_TEXT KW_SEARCH KW_CLIENTS KW_SIGN KW_SERVICE KW_TEXT_SEARCH /* symbols */ %token L_PAREN R_PAREN L_BRACKET R_BRACKET L_BRACE R_BRACE COMMA @@ -197,6 +206,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type <expr> attribute_expression %type <expr> case_expression %type <expr> compound_expression +%type <expr> text_search_expression %type <argument_list> argument_list opt_argument_list %type <type> type_spec %type <step_clause> step_clause @@ -206,6 +216,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type <over_edges> over_edges %type <over_clause> over_clause %type <where_clause> where_clause +%type <lookup_where_clause> lookup_where_clause %type <when_clause> when_clause %type <yield_clause> yield_clause %type <yield_columns> yield_columns @@ -265,6 +276,11 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type <match_clause_list> reading_clauses reading_with_clause reading_with_clauses %type <match_step_range> match_step_range %type <order_factors> match_order_by +%type <text_search_argument> text_search_argument +%type <base_text_search_argument> base_text_search_argument +%type <fuzzy_text_search_argument> fuzzy_text_search_argument +%type <text_search_client_item> text_search_client_item +%type <text_search_client_list> text_search_client_list %type <intval> legal_integer unary_integer rank port job_concurrency @@ -321,6 +337,7 @@ static constexpr size_t MAX_ABS_INTEGER = 9223372036854775808ULL; %type <seq_sentences> seq_sentences %type <explain_sentence> explain_sentence %type <sentences> sentences +%type <sentence> sign_in_text_search_service_sentence sign_out_text_search_service_sentence %type <boolval> opt_if_not_exists %type <boolval> opt_if_exists @@ -450,6 +467,17 @@ unreserved_keyword | KW_ELASTICSEARCH { $$ = new std::string("elasticsearch"); } | 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_TEXT { $$ = new std::string("text"); } + | KW_SEARCH { $$ = new std::string("search"); } + | KW_CLIENTS { $$ = new std::string("clients"); } + | KW_SIGN { $$ = new std::string("sign"); } + | KW_SERVICE { $$ = new std::string("service"); } + | KW_TEXT_SEARCH { $$ = new std::string("text_search"); } ; agg_function @@ -1347,8 +1375,187 @@ match_limit } ; + +text_search_client_item + : L_PAREN host_item R_PAREN { + $$ = new nebula::meta::cpp2::FTClient(); + $$->set_host(*$2); + delete $2; + } + | L_PAREN host_item COMMA STRING COMMA STRING R_PAREN { + $$ = new nebula::meta::cpp2::FTClient(); + $$->set_host(*$2); + $$->set_user(*$4); + $$->set_pwd(*$6); + delete $2; + delete $4; + delete $6; + } + ; + +text_search_client_list + : text_search_client_item { + $$ = new TSClientList(); + $$->addClient($1); + } + | text_search_client_list COMMA text_search_client_item { + $$ = $1; + $$->addClient($3); + } + | text_search_client_list COMMA { + $$ = $1; + } + ; + +sign_in_text_search_service_sentence + : KW_SIGN KW_IN KW_TEXT KW_SERVICE text_search_client_list { + $$ = new SignInTextServiceSentence($5); + } + ; + +sign_out_text_search_service_sentence + : KW_SIGN KW_OUT KW_TEXT KW_SERVICE { + $$ = new SignOutTextServiceSentence(); + } + ; + +base_text_search_argument + : name_label DOT name_label COMMA STRING { + auto arg = new TextSearchArgument($1, $3, $5); + $$ = arg; + } + ; + +fuzzy_text_search_argument + : base_text_search_argument COMMA KW_AUTO COMMA KW_AND { + $$ = $1; + $$->setFuzziness(-1); + $$->setOP(new std::string("and")); + } + | base_text_search_argument COMMA KW_AUTO COMMA KW_OR { + $$ = $1; + $$->setFuzziness(-1); + $$->setOP(new std::string("or")); + } + | base_text_search_argument COMMA legal_integer COMMA KW_AND { + if ($3 != 0 && $3 != 1 && $3 != 2) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + $$ = $1; + $$->setFuzziness($3); + $$->setOP(new std::string("and")); + } + | base_text_search_argument COMMA legal_integer COMMA KW_OR { + if ($3 != 0 && $3 != 1 && $3 != 2) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + $$ = $1; + $$->setFuzziness($3); + $$->setOP(new std::string("or")); + } + +text_search_argument + : base_text_search_argument { + $$ = $1; + } + | fuzzy_text_search_argument { + $$ = $1; + } + | base_text_search_argument COMMA legal_integer { + if ($3 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + $$ = $1; + $$->setLimit($3); + } + | base_text_search_argument COMMA legal_integer COMMA legal_integer { + if ($3 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + if ($5 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@5, "Out of range:"); + } + $$ = $1; + $$->setLimit($3); + $$->setTimeout($5); + } + | fuzzy_text_search_argument COMMA legal_integer { + if ($3 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + $$ = $1; + $$->setLimit($3); + } + | fuzzy_text_search_argument COMMA legal_integer COMMA legal_integer { + if ($3 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@3, "Out of range:"); + } + if ($5 < 1) { + delete $1; + throw nebula::GraphParser::syntax_error(@5, "Out of range:"); + } + $$ = $1; + $$->setLimit($3); + $$->setTimeout($5); + } + ; + +text_search_expression + : KW_PREFIX L_PAREN text_search_argument R_PAREN { + if ($3->op() != nullptr) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + if ($3->fuzziness() != -2) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + $$ = new TextSearchExpression(Expression::Kind::kTSPrefix, $3); + } + | KW_WILDCARD L_PAREN text_search_argument R_PAREN { + if ($3->op() != nullptr) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + if ($3->fuzziness() != -2) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + $$ = new TextSearchExpression(Expression::Kind::kTSWildcard, $3); + } + | KW_REGEXP L_PAREN text_search_argument R_PAREN { + if ($3->op() != nullptr) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + if ($3->fuzziness() != -2) { + delete $3; + throw nebula::GraphParser::syntax_error(@3, "argument error:"); + } + $$ = new TextSearchExpression(Expression::Kind::kTSRegexp, $3); + } + | KW_FUZZY L_PAREN text_search_argument R_PAREN { + $$ = new TextSearchExpression(Expression::Kind::kTSFuzzy, $3); + } + ; + + // TODO : unfiy the text_search_expression into expression in the future + // The current version only support independent text_search_expression for lookup_sentence +lookup_where_clause + : %empty { $$ = nullptr; } + | KW_WHERE text_search_expression { $$ = new WhereClause($2); } + | KW_WHERE expression { $$ = new WhereClause($2); } + ; + lookup_sentence - : KW_LOOKUP KW_ON name_label where_clause yield_clause { + : KW_LOOKUP KW_ON name_label lookup_where_clause yield_clause { auto sentence = new LookupSentence($3); sentence->setWhereClause($4); sentence->setYieldClause($5); @@ -2383,6 +2590,9 @@ show_sentence | KW_SHOW KW_STATS { $$ = new ShowStatsSentence(); } + | KW_SHOW KW_TEXT KW_SEARCH KW_CLIENTS { + $$ = new ShowTSClientsSentence(); + } ; config_module_enum @@ -2720,7 +2930,6 @@ maintain_sentence | add_host_into_zone_sentence { $$ = $1; } | drop_host_from_zone_sentence { $$ = $1; } | show_sentence { $$ = $1; } - ; | create_user_sentence { $$ = $1; } | alter_user_sentence { $$ = $1; } | drop_user_sentence { $$ = $1; } @@ -2730,11 +2939,13 @@ maintain_sentence | get_config_sentence { $$ = $1; } | set_config_sentence { $$ = $1; } | balance_sentence { $$ = $1; } - | create_snapshot_sentence { $$ = $1; }; - | drop_snapshot_sentence { $$ = $1; }; | add_listener_sentence { $$ = $1; } | remove_listener_sentence { $$ = $1; } | list_listener_sentence { $$ = $1; } + | create_snapshot_sentence { $$ = $1; } + | drop_snapshot_sentence { $$ = $1; } + | sign_in_text_search_service_sentence { $$ = $1; } + | sign_out_text_search_service_sentence { $$ = $1; } ; return_sentence diff --git a/src/parser/scanner.lex b/src/parser/scanner.lex index c9c1d701..8d18a436 100644 --- a/src/parser/scanner.lex +++ b/src/parser/scanner.lex @@ -222,7 +222,17 @@ IP_OCTET ([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]) "INTO" { return TokenType::KW_INTO; } "LISTENER" { return TokenType::KW_LISTENER; } "ELASTICSEARCH" { return TokenType::KW_ELASTICSEARCH; } - +"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; } +"TEXT" { return TokenType::KW_TEXT; } +"SEARCH" { return TokenType::KW_SEARCH; } +"CLIENTS" { return TokenType::KW_CLIENTS; } +"SIGN" { return TokenType::KW_SIGN; } +"SERVICE" { return TokenType::KW_SERVICE; } +"TEXT_SEARCH" { return TokenType::KW_TEXT_SEARCH; } "TRUE" { yylval->boolval = true; return TokenType::BOOL; } "FALSE" { yylval->boolval = false; return TokenType::BOOL; } diff --git a/src/parser/test/CMakeLists.txt b/src/parser/test/CMakeLists.txt index db33e1d2..5c3776cf 100644 --- a/src/parser/test/CMakeLists.txt +++ b/src/parser/test/CMakeLists.txt @@ -30,6 +30,7 @@ set(PARSER_TEST_LIBS $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj> $<TARGET_OBJECTS:common_process_obj> $<TARGET_OBJECTS:common_time_utils_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> $<TARGET_OBJECTS:session_obj> $<TARGET_OBJECTS:graph_flags_obj> $<TARGET_OBJECTS:graph_auth_obj> diff --git a/src/parser/test/ParserTest.cpp b/src/parser/test/ParserTest.cpp index 7831bb8d..1cde51b3 100644 --- a/src/parser/test/ParserTest.cpp +++ b/src/parser/test/ParserTest.cpp @@ -2719,4 +2719,208 @@ TEST(Parser, Zone) { } } +TEST(Parser, FullText) { + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1, 2)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", 1, 2)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", 1, 2)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 1, 2)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", AUTO, AND)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", AUTO, OR)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, AND)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1, 1)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, -1, 1)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 0, OR, 1, -1)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", -1, OR, 1, 1)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE FUZZY(t1.c1, \"a\", 4, OR, 1, 1)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", -1, 2)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", 1, -2)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE PREFIX(t1.c1, \"a\", AUTO, AND)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE WILDCARD(t1.c1, \"a\", AUTO, AND)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } + { + GQLParser parser; + std::string query = "LOOKUP ON t1 WHERE REGEXP(t1.c1, \"a\", AUTO, AND)"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } +} + +TEST(Parser, FullTextServiceTest) { + { + GQLParser parser; + std::string query = "SIGN IN TEXT SERVICE (127.0.0.1:9200)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "SIGN IN TEXT SERVICE (127.0.0.1:9200), (127.0.0.1:9300)"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "SIGN IN TEXT SERVICE (127.0.0.1:9200, \"user\", \"password\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "SIGN IN TEXT SERVICE (127.0.0.1:9200, \"user\", \"password\"), " + "(127.0.0.1:9200, \"user\", \"password\")"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "SIGN OUT TEXT SERVICE"; + auto result = parser.parse(query); + ASSERT_TRUE(result.ok()) << result.status(); + } + { + GQLParser parser; + std::string query = "SIGN IN TEXT SERVICE (127.0.0.1:9200, \"user\")"; + auto result = parser.parse(query); + ASSERT_FALSE(result.ok()); + } +} } // namespace nebula diff --git a/src/planner/Admin.h b/src/planner/Admin.h index 3ed9dd32..6ec6396a 100644 --- a/src/planner/Admin.h +++ b/src/planner/Admin.h @@ -1213,6 +1213,52 @@ private: : SingleInputNode(qctx, Kind::kShowStats, input) {} }; +class ShowTSClients final : public SingleInputNode { +public: + static ShowTSClients* make(QueryContext* qctx, PlanNode* input) { + return qctx->objPool()->add(new ShowTSClients(qctx, input)); + } + +private: + ShowTSClients(QueryContext* qctx, PlanNode* input) + : SingleInputNode(qctx, Kind::kShowTSClients, input) {} +}; + +class SignInTSService final : public SingleInputNode { +public: + static SignInTSService* make(QueryContext* qctx, + PlanNode* input, + std::vector<meta::cpp2::FTClient> clients) { + return qctx->objPool()->add(new SignInTSService(qctx, input, std::move(clients))); + } + + const std::vector<meta::cpp2::FTClient> &clients() const { + return clients_; + } + + meta::cpp2::FTServiceType type() const { + return meta::cpp2::FTServiceType::ELASTICSEARCH; + } + +private: + SignInTSService(QueryContext* qctx, PlanNode* input, std::vector<meta::cpp2::FTClient> clients) + : SingleInputNode(qctx, Kind::kSignInTSService, input), + clients_(std::move(clients)) {} + + std::vector<meta::cpp2::FTClient> clients_; +}; + +class SignOutTSService final : public SingleInputNode { +public: + static SignOutTSService* make(QueryContext* qctx, + PlanNode* input) { + return qctx->objPool()->add(new SignOutTSService(qctx, input)); + } + +private: + SignOutTSService(QueryContext* qctx, PlanNode* input) + : SingleInputNode(qctx, Kind::kSignOutTSService, input) {} +}; } // namespace graph } // namespace nebula #endif // PLANNER_ADMIN_H_ diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp index c550faa9..c8813e33 100644 --- a/src/planner/PlanNode.cpp +++ b/src/planner/PlanNode.cpp @@ -234,6 +234,13 @@ const char* PlanNode::toString(PlanNode::Kind kind) { return "ShowListener"; case Kind::kShowStats: return "ShowStats"; + // text search + case Kind::kShowTSClients: + return "ShowTSClients"; + case Kind::kSignInTSService: + return "SignInTSService"; + case Kind::kSignOutTSService: + return "SignOutTSService"; // no default so the compiler will warning when lack } LOG(FATAL) << "Impossible kind plan node " << static_cast<int>(kind); diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h index 9c21aa3b..3e76f6a2 100644 --- a/src/planner/PlanNode.h +++ b/src/planner/PlanNode.h @@ -130,6 +130,10 @@ public: kAddListener, kRemoveListener, kShowListener, + // text service related + kShowTSClients, + kSignInTSService, + kSignOutTSService, }; PlanNode(QueryContext* qctx, Kind kind); diff --git a/src/planner/Query.h b/src/planner/Query.h index 86e7589d..6ac8b068 100644 --- a/src/planner/Query.h +++ b/src/planner/Query.h @@ -415,6 +415,7 @@ public: IndexReturnCols&& returnCols, bool isEdge, int32_t schemaId, + bool isEmptyResultSet = false, bool dedup = false, std::vector<storage::cpp2::OrderBy> orderBy = {}, int64_t limit = std::numeric_limits<int64_t>::max(), @@ -426,6 +427,7 @@ public: std::move(returnCols), isEdge, schemaId, + isEmptyResultSet, dedup, std::move(orderBy), limit, @@ -452,6 +454,10 @@ public: return schemaId_; } + bool isEmptyResultSet() const { + return isEmptyResultSet_; + } + void setIndexQueryContext(IndexQueryCtx contexts) { contexts_ = std::move(contexts); } @@ -476,6 +482,7 @@ private: IndexReturnCols&& returnCols, bool isEdge, int32_t schemaId, + bool isEmptyResultSet, bool dedup, std::vector<storage::cpp2::OrderBy> orderBy, int64_t limit, @@ -492,6 +499,7 @@ private: returnCols_ = std::move(returnCols); isEdge_ = isEdge; schemaId_ = schemaId; + isEmptyResultSet_ = isEmptyResultSet; } private: @@ -499,6 +507,7 @@ private: IndexReturnCols returnCols_; bool isEdge_; int32_t schemaId_; + bool isEmptyResultSet_; }; /** diff --git a/src/service/GraphFlags.cpp b/src/service/GraphFlags.cpp index f72ee4a4..b65b8142 100644 --- a/src/service/GraphFlags.cpp +++ b/src/service/GraphFlags.cpp @@ -48,3 +48,5 @@ DEFINE_string(cloud_http_url, "", "cloud http url including ip, port, url path") DEFINE_uint32(max_allowed_statements, 512, "Max allowed sequential statements"); DEFINE_bool(enable_optimizer, false, "Whether to enable optimizer"); + +DEFINE_uint32(ft_request_retry_times, 3, "Retry times if fulltext request failed"); diff --git a/src/service/PermissionCheck.cpp b/src/service/PermissionCheck.cpp index 376cae08..c998992c 100644 --- a/src/service/PermissionCheck.cpp +++ b/src/service/PermissionCheck.cpp @@ -73,7 +73,9 @@ Status PermissionCheck::permissionCheck(Session *session, case Sentence::Kind::kSetConfig: case Sentence::Kind::kGetConfig: case Sentence::Kind::kIngest: - case Sentence::Kind::kDownload: { + case Sentence::Kind::kDownload: + case Sentence::Kind::kSignOutTSService: + case Sentence::Kind::kSignInTSService: { return PermissionManager::canWriteSpace(session); } case Sentence::Kind::kCreateTag: @@ -177,14 +179,15 @@ Status PermissionCheck::permissionCheck(Session *session, return PermissionManager::canReadSpace(session, targetSpace); } case Sentence::Kind::kShowUsers: - case Sentence::Kind::kShowSnapshots: { + case Sentence::Kind::kShowSnapshots: + case Sentence::Kind::kShowTSClients: { /** * Only GOD role can be show. */ if (session->isGod()) { return Status::OK(); } else { - return Status::PermissionError("No permission to show users/snapshots"); + return Status::PermissionError("No permission to show users/snapshots/textClients"); } } case Sentence::Kind::kChangePassword: { diff --git a/src/util/ExpressionUtils.cpp b/src/util/ExpressionUtils.cpp index 8b6a20c7..6a99e207 100644 --- a/src/util/ExpressionUtils.cpp +++ b/src/util/ExpressionUtils.cpp @@ -75,5 +75,19 @@ std::unique_ptr<InputPropertyExpression> ExpressionUtils::inputPropExpr(const st return std::make_unique<InputPropertyExpression>(new std::string(prop)); } +std::unique_ptr<Expression> +ExpressionUtils::pushOrs(const std::vector<std::unique_ptr<RelationalExpression>>& rels) { + DCHECK_GT(rels.size(), 1); + auto root = std::make_unique<LogicalExpression>(Expression::Kind::kLogicalOr); + root->addOperand(rels[0]->clone().release()); + root->addOperand(rels[1]->clone().release()); + for (size_t i = 2; i < rels.size(); i++) { + auto l = std::make_unique<LogicalExpression>(Expression::Kind::kLogicalOr); + l->addOperand(root->clone().release()); + l->addOperand(rels[i]->clone().release()); + root = std::move(l); + } + return root; +} } // namespace graph } // namespace nebula diff --git a/src/util/ExpressionUtils.h b/src/util/ExpressionUtils.h index a4c20f8d..98015b49 100644 --- a/src/util/ExpressionUtils.h +++ b/src/util/ExpressionUtils.h @@ -122,6 +122,9 @@ public: const std::string& var = ""); static std::unique_ptr<InputPropertyExpression> inputPropExpr(const std::string& prop); + + static std::unique_ptr<Expression> pushOrs( + const std::vector<std::unique_ptr<RelationalExpression>>& rels); }; } // namespace graph diff --git a/src/util/test/CMakeLists.txt b/src/util/test/CMakeLists.txt index 8cadd108..ac1b024c 100644 --- a/src/util/test/CMakeLists.txt +++ b/src/util/test/CMakeLists.txt @@ -30,6 +30,7 @@ nebula_add_test( $<TARGET_OBJECTS:common_agg_function_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> $<TARGET_OBJECTS:idgenerator_obj> $<TARGET_OBJECTS:expr_visitor_obj> $<TARGET_OBJECTS:session_obj> diff --git a/src/util/test/ExpressionUtilsTest.cpp b/src/util/test/ExpressionUtilsTest.cpp index 72949309..a9f3c09f 100644 --- a/src/util/test/ExpressionUtilsTest.cpp +++ b/src/util/test/ExpressionUtilsTest.cpp @@ -362,5 +362,24 @@ TEST_F(ExpressionUtilsTest, PullOrs) { } } +TEST_F(ExpressionUtilsTest, pushOrs) { + std::vector<std::unique_ptr<RelationalExpression>> rels; + for (int16_t i = 0; i < 5; i++) { + auto r = std::make_unique<RelationalExpression>( + Expression::Kind::kRelEQ, + new LabelAttributeExpression(new LabelExpression(folly::stringPrintf("tag%d", i)), + new LabelExpression(folly::stringPrintf("col%d", i))), + new ConstantExpression(Value(folly::stringPrintf("val%d", i)))); + rels.emplace_back(std::move(r)); + } + auto t = ExpressionUtils::pushOrs(rels); + auto expected = std::string("(((((tag0.col0==val0) OR " + "(tag1.col1==val1)) OR " + "(tag2.col2==val2)) OR " + "(tag3.col3==val3)) OR " + "(tag4.col4==val4))"); + ASSERT_EQ(expected, t->toString()); +} + } // namespace graph } // namespace nebula diff --git a/src/validator/AdminValidator.cpp b/src/validator/AdminValidator.cpp index 3dc2f2a9..25ca7e5e 100644 --- a/src/validator/AdminValidator.cpp +++ b/src/validator/AdminValidator.cpp @@ -426,5 +426,42 @@ Status ShowStatusValidator::toPlan() { return Status::OK(); } +Status ShowTSClientsValidator::validateImpl() { + return Status::OK(); +} + +Status ShowTSClientsValidator::toPlan() { + auto *doNode = ShowTSClients::make(qctx_, nullptr); + root_ = doNode; + tail_ = root_; + return Status::OK(); +} + +Status SignInTSServiceValidator::validateImpl() { + return Status::OK(); +} + +Status SignInTSServiceValidator::toPlan() { + auto sentence = static_cast<SignInTextServiceSentence*>(sentence_); + std::vector<meta::cpp2::FTClient> clients; + if (sentence->clients() != nullptr) { + clients = sentence->clients()->clients(); + } + auto *node = SignInTSService::make(qctx_, nullptr, std::move(clients)); + root_ = node; + tail_ = root_; + return Status::OK(); +} + +Status SignOutTSServiceValidator::validateImpl() { + return Status::OK(); +} + +Status SignOutTSServiceValidator::toPlan() { + auto *node = SignOutTSService::make(qctx_, nullptr); + root_ = node; + tail_ = root_; + return Status::OK(); +} } // namespace graph } // namespace nebula diff --git a/src/validator/AdminValidator.h b/src/validator/AdminValidator.h index dc6bc891..85337f3b 100644 --- a/src/validator/AdminValidator.h +++ b/src/validator/AdminValidator.h @@ -12,6 +12,7 @@ #include "parser/MaintainSentences.h" #include "parser/AdminSentences.h" #include "common/clients/meta/MetaClient.h" +#include "common/plugin/fulltext/elasticsearch/ESGraphAdapter.h" namespace nebula { namespace graph { @@ -27,6 +28,9 @@ private: Status toPlan() override; + bool checkTSIndex(const std::vector<meta::cpp2::FTClient>& clients, + const std::string& index); + private: meta::cpp2::SpaceDesc spaceDesc_; bool ifNotExist_; @@ -271,6 +275,44 @@ private: Status toPlan() override; }; +class ShowTSClientsValidator final : public Validator { +public: + ShowTSClientsValidator(Sentence* sentence, QueryContext* context) + :Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class SignInTSServiceValidator final : public Validator { +public: + SignInTSServiceValidator(Sentence* sentence, QueryContext* context) + :Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; + +class SignOutTSServiceValidator final : public Validator { +public: + SignOutTSServiceValidator(Sentence* sentence, QueryContext* context) + :Validator(sentence, context) { + setNoSpaceRequired(); + } + +private: + Status validateImpl() override; + + Status toPlan() override; +}; } // namespace graph } // namespace nebula #endif // VALIDATOR_ADMINVALIDATOR_H_ diff --git a/src/validator/IndexScanValidator.cpp b/src/validator/IndexScanValidator.cpp index c814fb83..4b8b7e42 100644 --- a/src/validator/IndexScanValidator.cpp +++ b/src/validator/IndexScanValidator.cpp @@ -9,6 +9,8 @@ #include "util/ExpressionUtils.h" #include "util/SchemaUtil.h" +DECLARE_uint32(ft_request_retry_times); + namespace nebula { namespace graph { @@ -24,14 +26,15 @@ Status IndexScanValidator::toPlan() { std::move(contexts_), std::move(returnCols_), isEdge_, - schemaId_); + schemaId_, + isEmptyResultSet_); } Status IndexScanValidator::prepareFrom() { auto *sentence = static_cast<const LookupSentence *>(sentence_); spaceId_ = vctx_->whichSpace().id; - const auto* from = sentence->from(); - auto ret = qctx_->schemaMng()->getSchemaIDByName(spaceId_, *from); + from_ = *sentence->from(); + auto ret = qctx_->schemaMng()->getSchemaIDByName(spaceId_, from_); if (!ret.ok()) { return ret.status(); } @@ -56,11 +59,10 @@ Status IndexScanValidator::prepareYield() { auto schema = isEdge_ ? qctx_->schemaMng()->getEdgeSchema(spaceId_, schemaId_) : qctx_->schemaMng()->getTagSchema(spaceId_, schemaId_); - const auto* from = sentence->from(); if (schema == nullptr) { return isEdge_ - ? Status::EdgeNotFound("Edge schema not found : %s", from->c_str()) - : Status::TagNotFound("Tag schema not found : %s", from->c_str()); + ? Status::EdgeNotFound("Edge schema not found : %s", from_.c_str()) + : Status::TagNotFound("Tag schema not found : %s", from_.c_str()); } for (auto col : columns) { std::string schemaName, colName; @@ -73,13 +75,13 @@ Status IndexScanValidator::prepareYield() { col->expr()->toString().c_str()); } - if (schemaName != *from) { + if (schemaName != from_) { return Status::SemanticError("Schema name error : %s", schemaName.c_str()); } auto ret = schema->getFieldType(colName); if (ret == meta::cpp2::PropertyType::UNKNOWN) { return Status::SemanticError("Column %s not found in schema %s", - colName.c_str(), from->c_str()); + colName.c_str(), from_.c_str()); } returnCols_->emplace_back(colName); } @@ -93,16 +95,147 @@ Status IndexScanValidator::prepareFilter() { } auto *filter = sentence->whereClause()->filter(); - auto ret = checkFilter(filter, *sentence->from()); - NG_RETURN_IF_ERROR(ret); storage::cpp2::IndexQueryContext ctx; - ctx.set_filter(Expression::encode(*filter)); + if (needTextSearch(filter)) { + NG_RETURN_IF_ERROR(checkTSService()); + if (!textSearchReady_) { + return Status::Error("Text search service not ready"); + } + auto retFilter = rewriteTSFilter(filter); + if (!retFilter.ok()) { + return retFilter.status(); + } + if (isEmptyResultSet_) { + // return empty result direct. + return Status::OK(); + } + ctx.set_filter(std::move(retFilter).value()); + } else { + auto ret = checkFilter(filter); + NG_RETURN_IF_ERROR(ret); + ctx.set_filter(Expression::encode(*filter)); + } contexts_ = std::make_unique<std::vector<storage::cpp2::IndexQueryContext>>(); contexts_->emplace_back(std::move(ctx)); return Status::OK(); } -Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from) { +StatusOr<std::string> +IndexScanValidator::rewriteTSFilter(Expression* expr) { + std::vector<std::string> values; + auto tsExpr = static_cast<TextSearchExpression*>(expr); + auto vRet = textSearch(tsExpr); + if (!vRet.ok()) { + return Status::Error("Text search error."); + } + if (vRet.value().empty()) { + isEmptyResultSet_ = true; + return Status::OK(); + } + std::vector<std::unique_ptr<RelationalExpression>> rels; + for (const auto& row : vRet.value()) { + std::unique_ptr<RelationalExpression> r; + if (isEdge_) { + r = std::make_unique<RelationalExpression>( + Expression::Kind::kRelEQ, + new EdgePropertyExpression(new std::string(*tsExpr->arg()->from()), + new std::string(*tsExpr->arg()->prop())), + new ConstantExpression(Value(row))); + } else { + r = std::make_unique<RelationalExpression>( + Expression::Kind::kRelEQ, + new TagPropertyExpression(new std::string(*tsExpr->arg()->from()), + new std::string(*tsExpr->arg()->prop())), + new ConstantExpression(Value(row))); + } + rels.emplace_back(std::move(r)); + } + if (rels.size() == 1) { + return rels[0]->encode(); + } + auto newExpr = ExpressionUtils::pushOrs(rels); + return newExpr->encode(); +} + +StatusOr<std::vector<std::string>> IndexScanValidator::textSearch(TextSearchExpression* expr) { + if (*expr->arg()->from() != from_) { + return Status::SemanticError("Schema name error : %s", expr->arg()->from()->c_str()); + } + auto index = nebula::plugin::IndexTraits::indexName(space_.spaceDesc.space_name, isEdge_); + nebula::plugin::DocItem doc(index, *expr->arg()->prop(), schemaId_, *expr->arg()->val()); + nebula::plugin::LimitItem limit(expr->arg()->timeout(), expr->arg()->limit()); + std::vector<std::string> result; + // TODO (sky) : External index load balancing + auto retryCnt = FLAGS_ft_request_retry_times; + while (--retryCnt > 0) { + StatusOr<bool> ret = Status::Error(); + switch (expr->kind()) { + case Expression::Kind::kTSFuzzy: { + folly::dynamic fuzz = folly::dynamic::object(); + if (expr->arg()->fuzziness() < 0) { + fuzz = "AUTO"; + } else { + fuzz = expr->arg()->fuzziness(); + } + std::string op = (expr->arg()->op() == nullptr) ? "or" : *expr->arg()->op(); + ret = nebula::plugin::ESGraphAdapter::kAdapter->fuzzy(randomFTClient(), + doc, + limit, + fuzz, + op, + result); + break; + } + case Expression::Kind::kTSPrefix: { + ret = nebula::plugin::ESGraphAdapter::kAdapter->prefix(randomFTClient(), + doc, + limit, + result); + break; + } + case Expression::Kind::kTSRegexp: { + ret = nebula::plugin::ESGraphAdapter::kAdapter->regexp(randomFTClient(), + doc, + limit, + result); + break; + } + case Expression::Kind::kTSWildcard: { + ret = nebula::plugin::ESGraphAdapter::kAdapter->wildcard(randomFTClient(), + doc, + limit, + result); + break; + } + default: + return Status::Error("text search expression error"); + } + if (!ret.ok()) { + continue; + } else if (ret.value()) { + return result; + } else { + return Status::Error("External index error. " + "please check the status of fulltext cluster"); + } + } + return Status::Error("scan external index failed"); +} + +bool IndexScanValidator::needTextSearch(Expression* expr) { + switch (expr->kind()) { + case Expression::Kind::kTSFuzzy: + case Expression::Kind::kTSPrefix: + case Expression::Kind::kTSRegexp: + case Expression::Kind::kTSWildcard: { + return true; + } + default: + return false; + } +} + +Status IndexScanValidator::checkFilter(Expression* expr) { // TODO (sky) : Rewrite simple expressions, // for example rewrite expr from col1 > 1 + 2 to col > 3 switch (expr->kind()) { @@ -110,9 +243,9 @@ Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from case Expression::Kind::kLogicalAnd : { // TODO(dutor) Deal with n-ary operands auto lExpr = static_cast<LogicalExpression*>(expr); - auto ret = checkFilter(lExpr->operand(0), from); + auto ret = checkFilter(lExpr->operand(0)); NG_RETURN_IF_ERROR(ret); - ret = checkFilter(lExpr->operand(1), from); + ret = checkFilter(lExpr->operand(1)); NG_RETURN_IF_ERROR(ret); break; } @@ -123,7 +256,7 @@ Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from case Expression::Kind::kRelGT: case Expression::Kind::kRelNE: { auto* rExpr = static_cast<RelationalExpression*>(expr); - return checkRelExpr(rExpr, from); + return checkRelExpr(rExpr); } default: { return Status::NotSupported("Expression %s not supported yet", @@ -133,8 +266,7 @@ Status IndexScanValidator::checkFilter(Expression* expr, const std::string& from return Status::OK(); } -Status IndexScanValidator::checkRelExpr(RelationalExpression* expr, - const std::string& from) { +Status IndexScanValidator::checkRelExpr(RelationalExpression* expr) { auto* left = expr->left(); auto* right = expr->right(); // Does not support filter : schema.col1 > schema.col2 @@ -144,7 +276,7 @@ Status IndexScanValidator::checkRelExpr(RelationalExpression* expr, expr->toString().c_str()); } else if (left->kind() == Expression::Kind::kLabelAttribute || right->kind() == Expression::Kind::kLabelAttribute) { - auto ret = rewriteRelExpr(expr, from); + auto ret = rewriteRelExpr(expr); NG_RETURN_IF_ERROR(ret); } else { return Status::NotSupported("Expression %s not supported yet", @@ -153,24 +285,20 @@ Status IndexScanValidator::checkRelExpr(RelationalExpression* expr, return Status::OK(); } -Status IndexScanValidator::rewriteRelExpr(RelationalExpression* expr, - const std::string& from) { +Status IndexScanValidator::rewriteRelExpr(RelationalExpression* expr) { auto* left = expr->left(); auto* right = expr->right(); auto leftIsAE = left->kind() == Expression::Kind::kLabelAttribute; - std::string ref, prop; auto* la = leftIsAE ? static_cast<LabelAttributeExpression *>(left) : static_cast<LabelAttributeExpression *>(right); - if (*la->left()->name() != from) { + if (*la->left()->name() != from_) { return Status::SemanticError("Schema name error : %s", la->left()->name()->c_str()); } - ref = *la->left()->name(); - prop = *la->right()->name(); - + std::string prop = *la->right()->name(); // rewrite ConstantExpression auto c = leftIsAE ? checkConstExpr(right, prop) @@ -217,5 +345,47 @@ StatusOr<Value> IndexScanValidator::checkConstExpr(Expression* expr, return v; } -} // namespace graph -} // namespace nebula +Status IndexScanValidator::checkTSService() { + auto tcs = qctx_->getMetaClient()->getFTClientsFromCache(); + if (!tcs.ok()) { + return tcs.status(); + } + if (tcs.value().empty()) { + return Status::Error("No full text client found"); + } + textSearchReady_ = true; + for (const auto& c : tcs.value()) { + nebula::plugin::HttpClient hc; + hc.host = c.host; + if (c.__isset.user && c.__isset.pwd) { + hc.user = c.user; + hc.password = c.pwd; + } + esClients_.emplace_back(std::move(hc)); + } + return checkTSIndex(); +} + +Status IndexScanValidator::checkTSIndex() { + auto ftIndex = nebula::plugin::IndexTraits::indexName(space_.name, isEdge_); + auto retryCnt = FLAGS_ft_request_retry_times; + StatusOr<bool> ret = Status::Error("fulltext index not found : %s", ftIndex.c_str()); + while (--retryCnt > 0) { + ret = nebula::plugin::ESGraphAdapter::kAdapter->indexExists(randomFTClient(), ftIndex); + if (!ret.ok()) { + continue; + } else if (ret.value()) { + return Status::OK(); + } else { + return Status::Error("fulltext index not found : %s", ftIndex.c_str()); + } + } + return ret.status(); +} + +const nebula::plugin::HttpClient& IndexScanValidator::randomFTClient() const { + auto i = folly::Random::rand32(esClients_.size() - 1); + return esClients_[i]; +} +} // namespace graph +} // namespace nebula diff --git a/src/validator/IndexScanValidator.h b/src/validator/IndexScanValidator.h index 4d27eed3..839544e1 100644 --- a/src/validator/IndexScanValidator.h +++ b/src/validator/IndexScanValidator.h @@ -9,10 +9,10 @@ #include <planner/Query.h> #include "common/base/Base.h" #include "common/interface/gen-cpp2/storage_types.h" +#include "common/plugin/fulltext/elasticsearch/ESGraphAdapter.h" #include "parser/TraverseSentences.h" #include "validator/Validator.h" - namespace nebula { namespace graph { @@ -32,20 +32,36 @@ private: Status prepareFilter(); - Status checkFilter(Expression* expr, const std::string& from); + StatusOr<std::string> rewriteTSFilter(Expression* expr); + + StatusOr<std::vector<std::string>> textSearch(TextSearchExpression* expr); + + bool needTextSearch(Expression* expr); + + Status checkFilter(Expression* expr); - Status checkRelExpr(RelationalExpression* expr, const std::string& from); + Status checkRelExpr(RelationalExpression* expr); - Status rewriteRelExpr(RelationalExpression* expr, const std::string& from); + Status rewriteRelExpr(RelationalExpression* expr); StatusOr<Value> checkConstExpr(Expression* expr, const std::string& prop); + Status checkTSService(); + + Status checkTSIndex(); + + const nebula::plugin::HttpClient& randomFTClient() const; + private: - GraphSpaceID spaceId_{0}; - IndexScan::IndexQueryCtx contexts_{nullptr}; - IndexScan::IndexReturnCols returnCols_{}; - bool isEdge_{false}; - int32_t schemaId_; + GraphSpaceID spaceId_{0}; + IndexScan::IndexQueryCtx contexts_{}; + IndexScan::IndexReturnCols returnCols_{}; + bool isEdge_{false}; + int32_t schemaId_; + bool isEmptyResultSet_{false}; + bool textSearchReady_{false}; + std::string from_; + std::vector<nebula::plugin::HttpClient> esClients_; }; } // namespace graph diff --git a/src/validator/Validator.cpp b/src/validator/Validator.cpp index b51b02c8..63aae3fa 100644 --- a/src/validator/Validator.cpp +++ b/src/validator/Validator.cpp @@ -227,6 +227,12 @@ std::unique_ptr<Validator> Validator::makeValidator(Sentence* sentence, QueryCon return std::make_unique<ShowListenerValidator>(sentence, context); case Sentence::Kind::kShowStats: return std::make_unique<ShowStatusValidator>(sentence, context); + case Sentence::Kind::kShowTSClients: + return std::make_unique<ShowTSClientsValidator>(sentence, context); + case Sentence::Kind::kSignInTSService: + return std::make_unique<SignInTSServiceValidator>(sentence, context); + case Sentence::Kind::kSignOutTSService: + return std::make_unique<SignOutTSServiceValidator>(sentence, context); case Sentence::Kind::kShowGroups: case Sentence::Kind::kShowZones: case Sentence::Kind::kUnknown: diff --git a/src/validator/test/CMakeLists.txt b/src/validator/test/CMakeLists.txt index 5a9f7fb2..49b3fe95 100644 --- a/src/validator/test/CMakeLists.txt +++ b/src/validator/test/CMakeLists.txt @@ -48,6 +48,7 @@ set(VALIDATOR_TEST_LIBS $<TARGET_OBJECTS:common_process_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> ) nebula_add_test( diff --git a/src/visitor/test/CMakeLists.txt b/src/visitor/test/CMakeLists.txt index 8d9caf64..7c8e71f6 100644 --- a/src/visitor/test/CMakeLists.txt +++ b/src/visitor/test/CMakeLists.txt @@ -50,6 +50,7 @@ nebula_add_test( $<TARGET_OBJECTS:common_process_obj> $<TARGET_OBJECTS:common_time_utils_obj> $<TARGET_OBJECTS:common_graph_obj> + $<TARGET_OBJECTS:common_ft_es_graph_adapter_obj> LIBRARIES gtest ${THRIFT_LIBRARIES} -- GitLab