diff --git a/src/context/test/CMakeLists.txt b/src/context/test/CMakeLists.txt
index 046c9a7e13091121fd5dbf91c48b5c8f56fedde7..d0d172679070ef9e43592987cbd8cded1cc37a0a 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 7ecffcb90db7b32504f6af93cd9f1e693878ec14..e4593708035a641ff6992801055ce6eb1edf8e8a 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 a4f7f7f563efb0b311154d22d8c528b42a634f68..f98f916b5d2f650c0ddc4f7cb7dcc51a83a86539 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 2afdec417afe34fba02979bc1d8e4bc6de9839dd..897d7d59628abb30bd85946557f26026114ef424 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 0000000000000000000000000000000000000000..a0e273989d1f9becc151a39251034effafe8c531
--- /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 0000000000000000000000000000000000000000..f7e1fdbfe3dc97c326ecf98445ffa35e254b715a
--- /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 0000000000000000000000000000000000000000..07ad13dbd70a190599f41fc5a1df76eb408c4d32
--- /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 0000000000000000000000000000000000000000..2cfca634a8c43da5a24529fbcf76912b5154ec9c
--- /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 0000000000000000000000000000000000000000..18cccaf2295f28f9f929dece0f5b5f594bac195c
--- /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 0000000000000000000000000000000000000000..cde08ea3fb3a6d359bc67942d446c226d293e8b0
--- /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 8156b5fe9dc921540fecbc69f28e31b2f74185e5..698175763d24c0d3207219161c3c514edfa2728e 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 2092040420a2b3611ab01f2f1188963590b7216d..39fb0227fd303b6f29f28fe162e705a237cfbd8e 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 78fffec7e53cc9577f9063f545ef3fb08ae456e4..bce01b4541172d87c59133896e77acf20d67da9c 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 7581475192335eb244366c889d92ae5bd99615fb..909c29cfc3d265dfffe117b1b91fb6a2c7204cd5 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 9c135d06c71a0d4116ca9a3c6fb67d72b2575ea8..64c6e743ac06ca284050312f7f8db22b805c345f 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 4cce5470564d65eedb5c0003b0c5bd0d11e18189..bd036555031e825715922d0e5f6d6e730e4a7783 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 00ac019f98f401c87d6a7b7b86b1501e6d968e1b..6bd545f641f8ac0605d9f311d44481b60e7312c9 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 a6241a8ce1d7df5f7fb9550324d02e95b94f62ac..8a7392c7ba8b5dd24514e6e8927867ef0e3fa09d 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 20a9e140e44ccd1e3d126e43c25767a936f1ef30..cc57ce6892e133144914e2ce81215479007b753a 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 c9c1d70110c1a2ac111e2a99f8adcf37ee033d6f..8d18a436848c34d06e7978f124ace2880b6d4fe0 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 db33e1d2b8641c429fe95c1a1fbb2a024ffe102b..5c3776cfd8884268d3edb8579a949caa7c50339e 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 7831bb8d245290a365804b1aa93d25b13a5797fa..1cde51b3822f38fc8bf47d26f630f3515b9836fa 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 3ed9dd32acb3b27859d475dc87189ce30b61dbf4..6ec6396a48da9c8eac03c9b06e225521c5a52e28 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 c550faa901bc551b820c80a6ebb1484da5cb3c2a..c8813e334270e1c597856a70dc8421d9c6771eb6 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 9c21aa3b5a61efd06f1c915437e3efdee3bd0f70..3e76f6a28102aecd11a0a5bce9312d98e261c444 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 86e7589d9d3fc0a9eedcbe74aa02ea73ffe55005..6ac8b06822fa744493429a1144e638e95f6a084e 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 f72ee4a4b7a7dde9a709a627530beb65f2ab0394..b65b81423103889b954b7aebd6eee6b7760e9ad4 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 376cae082d07ec944104ec9c7f04ac2f2111948e..c998992c1f59f0b1733e959fec7cf9e936e75600 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 8b6a20c77b7349f64dc5cdaff9d2b0505fa34acd..6a99e2071ccc35bca96a5322356e7a5317989c1b 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 a4c20f8d1cd312cda5253086cb66c8b9f1be2024..98015b490e6ec4984cdaf8cd0d9edfafcb393548 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 8cadd108f572cc3e1d2a6a5f53df5227ae75dac5..ac1b024ca2dcd6f5d02cf9c6686506a11351de96 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 7294930965451ce0f740304d86892e60e0f34f24..a9f3c09f717f40fd42f0c6b58f5b7d4814491fb8 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 3dc2f2a971ce94b68d190c4005b75e2425a9d463..25ca7e5e6129b4dbdad524664aa43a268fddaf22 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 dc6bc891d19b985499400044b19f989916119a5f..85337f3b70573b8c43a39f24889f9fede9d28604 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 c814fb832c3f90f793cc15f78af1b266f96df52f..4b8b7e42bc53cf0d253f379a13e38afed553bee8 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 4d27eed343260c62420601957b1a93297d699fa9..839544e1a8076f783b8959f62711de8dd3bc7a2e 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 b51b02c831dbc7fd0b7b7ea5a04ee096ce02a0c3..63aae3fae8a02aa8f74886830d625436b648a9b7 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 5a9f7fb23f3fd1fdc1ea0bd3e1087d569e18bec3..49b3fe95869ffe6357a116ca478c8e89e8ea966f 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 8d9caf64414d43705c6af69921f01a378185897d..7c8e71f6ef17026285968fadc7a8e0b6b222e203 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}