3838#include " src/core/overloaded.h"
3939
4040ABSL_FLAG (bool , search_reject_legacy_field, true , " FT.AGGREGATE: Reject legacy field names." );
41+ ABSL_FLAG (bool , cluster_search, false ,
42+ " Enable search commands for cross-shard search. turned off by default for safety." );
4143
4244ABSL_FLAG (size_t , MAXSEARCHRESULTS, 1000000 , " Maximum number of results from ft.search command" );
4345
@@ -1074,13 +1076,13 @@ void SearchFamily::FtCreate(CmdArgList args, const CommandContext& cmd_cntx) {
10741076 return builder->SendError (" Index already exists" );
10751077 }
10761078
1077- if (!is_cross_shard && IsClusterEnabled ()) {
1079+ if (absl::GetFlag (FLAGS_cluster_search) && !is_cross_shard && IsClusterEnabled ()) {
10781080 std::string args_str = absl::StrJoin (args.subspan (1 ), " " );
10791081 std::string cmd = absl::StrCat (" FT.CREATE " , idx_name, " CSS " , args_str);
10801082
10811083 // TODO add processing of the reply to make sure index was created successfully on all shards,
10821084 // and prevent simultaneous creation of the same index.
1083- cluster::Coordinator::Current ().DispatchAll (cmd);
1085+ cluster::Coordinator::Current ().DispatchAll (cmd, []( const facade::RespVec&) {} );
10841086 }
10851087
10861088 auto idx_ptr = make_shared<DocIndex>(std::move (parsed_index).value ());
@@ -1292,6 +1294,66 @@ void SearchFamily::FtList(CmdArgList args, const CommandContext& cmd_cntx) {
12921294 rb->SendBulkStrArr (names);
12931295}
12941296
1297+ static vector<SearchResult> FtSearchCSS (std::string_view idx, std::string_view query,
1298+ std::string_view args_str) {
1299+ vector<SearchResult> results;
1300+ std::string cmd = absl::StrCat (" FT.SEARCH " , idx, " " , query, " CSS " , args_str);
1301+
1302+ // TODO for now we suppose that callback is called synchronously. If not, we need to add
1303+ // synchronization here for results vector modification.
1304+ cluster::Coordinator::Current ().DispatchAll (cmd, [&](const facade::RespVec& res) {
1305+ VLOG (3 ) << " FT.SEARCH CSS reply: " << res;
1306+
1307+ if (res.empty ()) {
1308+ LOG (ERROR) << " FT.SEARCH CSS reply is empty" ;
1309+ return ;
1310+ }
1311+ if (res[0 ].type == facade::RespExpr::Type::ERROR) {
1312+ LOG (WARNING) << " FT.SEARCH CSS reply error: " << res[0 ].GetView ();
1313+ return ;
1314+ }
1315+
1316+ const auto size = res[0 ].GetInt ();
1317+ if (!size.has_value ()) {
1318+ LOG (ERROR) << " FT.SEARCH CSS reply unexpected type: " << static_cast <int >(res[0 ].type );
1319+ return ;
1320+ }
1321+
1322+ results.emplace_back ();
1323+ results.back ().total_hits = *size;
1324+ for (size_t i = 2 ; i < res.size (); i += 2 ) {
1325+ auto & search_doc = results.back ().docs .emplace_back ();
1326+ search_doc.key = res[i - 1 ].GetString ();
1327+ if (res[i].type != facade::RespExpr::Type::ARRAY) {
1328+ LOG (ERROR) << " FT.SEARCH CSS reply unexpected type for document data: "
1329+ << static_cast <int >(res[i].type );
1330+ return ;
1331+ }
1332+ const auto & arr_res = res[i].GetVec ();
1333+ if (arr_res.size () % 2 != 0 ) {
1334+ LOG (ERROR) << " FT.SEARCH CSS reply unexpected number of elements for document data: "
1335+ << arr_res.size ();
1336+ return ;
1337+ }
1338+
1339+ for (size_t j = 0 ; j < arr_res.size (); j += 2 ) {
1340+ if (arr_res[j].type != facade::RespExpr::Type::STRING) {
1341+ LOG (ERROR) << " FT.SEARCH CSS reply unexpected type for document data: "
1342+ << static_cast <int >(arr_res[j].type );
1343+ return ;
1344+ }
1345+ if (arr_res[j + 1 ].type != facade::RespExpr::Type::STRING) {
1346+ LOG (ERROR) << " FT.SEARCH CSS reply unexpected type for document data: "
1347+ << static_cast <int >(arr_res[j].type );
1348+ return ;
1349+ }
1350+ search_doc.values .emplace (arr_res[j].GetString (), arr_res[j + 1 ].GetString ());
1351+ }
1352+ }
1353+ });
1354+ return results;
1355+ }
1356+
12951357void SearchFamily::FtSearch (CmdArgList args, const CommandContext& cmd_cntx) {
12961358 CmdArgParser parser{args};
12971359 string_view index_name = parser.Next ();
@@ -1311,11 +1373,11 @@ void SearchFamily::FtSearch(CmdArgList args, const CommandContext& cmd_cntx) {
13111373 absl::StrCat (" Query string is too long, max length is " , max_query_bytes, " bytes" ));
13121374 }
13131375
1314- if (!is_cross_shard && IsClusterEnabled ()) {
1376+ vector<SearchResult> css_docs;
1377+ if (absl::GetFlag (FLAGS_cluster_search) && !is_cross_shard && IsClusterEnabled ()) {
13151378 std::string args_str = absl::StrJoin (args.subspan (2 ), " " );
1316- std::string cmd = absl::StrCat (" FT.SEARCH " , index_name, " " , query_str, " CSS " , args_str);
13171379
1318- cluster::Coordinator::Current (). DispatchAll (cmd );
1380+ css_docs = FtSearchCSS (index_name, query_str, args_str );
13191381 }
13201382
13211383 search::SearchAlgorithm search_algo;
@@ -1342,6 +1404,10 @@ void SearchFamily::FtSearch(CmdArgList args, const CommandContext& cmd_cntx) {
13421404 return builder->SendError (*res.error );
13431405 }
13441406
1407+ // TODO add merging of CSS results with local results (SORT, LIMIT, etc)
1408+ docs.insert (docs.end (), std::make_move_iterator (css_docs.begin ()),
1409+ std::make_move_iterator (css_docs.end ()));
1410+
13451411 SearchReply (*params, search_algo.GetKnnScoreSortOption (), absl::MakeSpan (docs), builder);
13461412}
13471413
0 commit comments