24
24
#include " server/conn_context.h"
25
25
#include " server/container_utils.h"
26
26
#include " server/engine_shard_set.h"
27
+ #include " server/main_service.h"
27
28
#include " server/search/aggregator.h"
28
29
#include " server/search/doc_index.h"
30
+ #include " server/server_family.h"
29
31
#include " server/transaction.h"
30
32
#include " src/core/overloaded.h"
31
33
@@ -599,8 +601,107 @@ void SearchReply(const SearchParams& params, std::optional<search::AggregationIn
599
601
}
600
602
}
601
603
604
+ constexpr size_t kOptionsCount = 2 ;
605
+ constexpr std::string_view kSearchLimit = " MAXSEARCHRESULTS" sv;
606
+ constexpr std::string_view kAggregateLimit = " MAXAGGREGATERESULTS" sv;
607
+
608
+ template <typename V>
609
+ using ConfigOptionsMap = std::array<std::pair<const std::string_view, V>, kOptionsCount >;
610
+ using Config = ConfigOptionsMap<uint64_t >;
611
+
612
+ // Do not forget to update kConfigOptions after adding new option
613
+ constexpr ConfigOptionsMap<const std::string_view> kConfigOptionsHelp = {{
614
+ {kSearchLimit , " Maximum number of results from ft.search command" sv},
615
+ {kAggregateLimit , " Maximum number of results from ft.aggregate command" sv},
616
+ }};
617
+
618
+ // Do not forget to update kConfigOptionsHelp after adding new option
619
+ thread_local Config kConfigOptions = {{
620
+ {kSearchLimit , 10000 },
621
+ {kAggregateLimit , 10000 },
622
+ }};
623
+
624
+ static_assert (
625
+ kConfigOptions .size() == kConfigOptionsHelp .size() && kConfigOptions .size() == kOptionsCount ,
626
+ " kConfigOptions and kConfigOptionsHelp must have the same size and equal to kOptionsCount." );
627
+
628
+ template <typename V>
629
+ std::optional<V> FindOptionsMapValue (const ConfigOptionsMap<V>& options, std::string_view name) {
630
+ auto it = std::find_if (options.begin (), options.end (), [name](const auto & opt) {
631
+ return absl::EqualsIgnoreCase (opt.first , name);
632
+ });
633
+ return it != options.end () ? it->second : std::optional<V>{};
634
+ }
635
+
636
+ void FtConfigHelp (CmdArgParser* parser, RedisReplyBuilder* rb) {
637
+ string_view option = parser->Next ();
638
+
639
+ auto send_value = [&](string_view option_name) {
640
+ auto value = FindOptionsMapValue (kConfigOptions , option_name);
641
+ DCHECK (value.has_value ());
642
+ rb->SendLong (value.value ());
643
+ };
644
+
645
+ if (option == " *" sv) {
646
+ rb->StartArray (kOptionsCount );
647
+ for (const auto & option_help : kConfigOptionsHelp ) {
648
+ rb->StartArray (5 );
649
+ rb->SendBulkString (option_help.first );
650
+ rb->SendBulkString (" Description" sv);
651
+ rb->SendBulkString (option_help.second );
652
+ rb->SendBulkString (" Value" sv);
653
+ send_value (option_help.first );
654
+ }
655
+ return ;
656
+ }
657
+
658
+ auto option_description = FindOptionsMapValue (kConfigOptionsHelp , option);
659
+ if (option_description) {
660
+ rb->StartArray (1 );
661
+ rb->StartArray (5 );
662
+ rb->SendBulkString (absl::AsciiStrToUpper (option));
663
+ rb->SendBulkString (" Description" sv);
664
+ rb->SendBulkString (option_description.value ());
665
+ rb->SendBulkString (" Value" sv);
666
+ send_value (option);
667
+ return ;
668
+ }
669
+
670
+ LOG (WARNING) << " Unknown configuration option: " << option;
671
+ rb->SendEmptyArray ();
672
+ }
673
+
674
+ void FtConfigGet (CmdArgParser* parser, RedisReplyBuilder* rb) {
675
+ string_view option = parser->Next ();
676
+
677
+ if (option == " *" sv) {
678
+ rb->StartArray (kOptionsCount );
679
+ for (const auto & option_help : kConfigOptions ) {
680
+ rb->StartArray (2 );
681
+ rb->SendBulkString (option_help.first );
682
+ rb->SendLong (option_help.second );
683
+ }
684
+ return ;
685
+ }
686
+
687
+ auto option_value = FindOptionsMapValue (kConfigOptions , option);
688
+ if (option_value) {
689
+ rb->StartArray (1 );
690
+ rb->StartArray (2 );
691
+ rb->SendBulkString (absl::AsciiStrToUpper (option));
692
+ rb->SendLong (option_value.value ());
693
+ return ;
694
+ }
695
+
696
+ LOG (WARNING) << " Unknown configuration option: " << option;
697
+ rb->SendEmptyArray ();
698
+ }
699
+
602
700
} // namespace
603
701
702
+ SearchFamily::SearchFamily (ServerFamily* server_family) : server_family_(server_family) {
703
+ }
704
+
604
705
void SearchFamily::FtCreate (CmdArgList args, const CommandContext& cmd_cntx) {
605
706
auto * builder = cmd_cntx.rb ;
606
707
if (cmd_cntx.conn_cntx ->conn_state .db_index != 0 ) {
@@ -815,6 +916,10 @@ void SearchFamily::FtSearch(CmdArgList args, const CommandContext& cmd_cntx) {
815
916
if (!search_algo.Init (query_str, ¶ms->query_params , sort_opt))
816
917
return builder->SendError (" Query syntax error" );
817
918
919
+ auto search_limit = FindOptionsMapValue (kConfigOptions , kSearchLimit );
920
+ DCHECK (search_limit.has_value ());
921
+ params->limit_total = std::min (params->limit_total , search_limit.value ());
922
+
818
923
// Because our coordinator thread may not have a shard, we can't check ahead if the index exists.
819
924
atomic<bool > index_not_found{false };
820
925
vector<SearchResult> docs (shard_set->size ());
@@ -966,6 +1071,54 @@ void SearchFamily::FtProfile(CmdArgList args, const CommandContext& cmd_cntx) {
966
1071
}
967
1072
}
968
1073
1074
+ void SearchFamily::FtConfigSet (CmdArgList args, const CommandContext& cmd_cntx) {
1075
+ CmdArgParser parser{args};
1076
+ auto * rb = static_cast <RedisReplyBuilder*>(cmd_cntx.rb );
1077
+
1078
+ string_view option = parser.Next ();
1079
+ uint64_t value = parser.Next <uint64_t >();
1080
+
1081
+ if (auto err = parser.Error (); err)
1082
+ return rb->SendError (err->MakeReply ());
1083
+
1084
+ auto it = std::find_if (kConfigOptions .begin (), kConfigOptions .end (),
1085
+ [option_name = option](const auto & opt) {
1086
+ return absl::EqualsIgnoreCase (opt.first , option_name);
1087
+ });
1088
+
1089
+ if (it != kConfigOptions .end ()) {
1090
+ DVLOG (2 ) << " Setting " << option << " to " << value;
1091
+
1092
+ auto cb = [&](util::ProactorBase*) { it->second = value; };
1093
+ server_family_->service ().proactor_pool ().AwaitFiberOnAll (std::move (cb));
1094
+
1095
+ // Schedule empty callback inorder to journal command via transaction framework.
1096
+ cmd_cntx.tx ->ScheduleSingleHop ([](auto * t, auto * shard) { return OpStatus::OK; });
1097
+
1098
+ rb->SendOk ();
1099
+ } else {
1100
+ rb->SendError (" Invalid option" sv);
1101
+ }
1102
+ }
1103
+
1104
+ void SearchFamily::FtConfig (CmdArgList args, const CommandContext& cmd_cntx) {
1105
+ CmdArgParser parser{args};
1106
+ auto * rb = static_cast <RedisReplyBuilder*>(cmd_cntx.rb );
1107
+
1108
+ if (parser.Check (" SET" )) {
1109
+ FtConfigSet (parser.Tail (), cmd_cntx);
1110
+ return ;
1111
+ }
1112
+
1113
+ auto func = parser.TryMapNext (" GET" , &FtConfigGet, " HELP" , &FtConfigHelp);
1114
+
1115
+ if (func) {
1116
+ return func.value ()(&parser, rb);
1117
+ } else {
1118
+ return rb->SendError (" Unknown subcommand" );
1119
+ }
1120
+ }
1121
+
969
1122
void SearchFamily::FtTagVals (CmdArgList args, const CommandContext& cmd_cntx) {
970
1123
string_view index_name = ArgS (args, 0 );
971
1124
string_view field_name = ArgS (args, 1 );
@@ -1052,11 +1205,19 @@ void SearchFamily::FtAggregate(CmdArgList args, const CommandContext& cmd_cntx)
1052
1205
auto * rb = static_cast <RedisReplyBuilder*>(cmd_cntx.rb );
1053
1206
auto sortable_value_sender = SortableValueSender (rb);
1054
1207
1055
- const size_t result_size = agg_results.values .size ();
1208
+ auto aggregate_limit = FindOptionsMapValue (kConfigOptions , kAggregateLimit );
1209
+ DCHECK (aggregate_limit.has_value ());
1210
+ size_t result_size = std::min (agg_results.values .size (), aggregate_limit.value ());
1211
+
1056
1212
rb->StartArray (result_size + 1 );
1057
1213
rb->SendLong (result_size);
1058
1214
1059
1215
for (const auto & value : agg_results.values ) {
1216
+ if (result_size == 0 ) {
1217
+ break ;
1218
+ }
1219
+ result_size--;
1220
+
1060
1221
size_t fields_count = 0 ;
1061
1222
for (const auto & field : agg_results.fields_to_print ) {
1062
1223
if (value.find (field) != value.end ()) {
@@ -1075,7 +1236,13 @@ void SearchFamily::FtAggregate(CmdArgList args, const CommandContext& cmd_cntx)
1075
1236
}
1076
1237
}
1077
1238
1078
- #define HFUNC (x ) SetHandler(&SearchFamily::x)
1239
+ using EngineFunc = void (SearchFamily::*)(CmdArgList args, const CommandContext& cmd_cntx);
1240
+
1241
+ inline CommandId::Handler3 HandlerFunc (SearchFamily* se, EngineFunc f) {
1242
+ return [=](CmdArgList args, const CommandContext& cmd_cntx) { return (se->*f)(args, cmd_cntx); };
1243
+ }
1244
+
1245
+ #define HFUNC (x ) SetHandler(HandlerFunc(this , &SearchFamily::x))
1079
1246
1080
1247
// Redis search is a module. Therefore we introduce dragonfly extension search
1081
1248
// to set as the default for the search family of commands. More sensible defaults,
@@ -1089,18 +1256,19 @@ void SearchFamily::Register(CommandRegistry* registry) {
1089
1256
CO::NO_KEY_TRANSACTIONAL | CO::NO_KEY_TX_SPAN_ALL | CO::NO_AUTOJOURNAL;
1090
1257
1091
1258
registry->StartFamily ();
1092
- *registry << CI{" FT.CREATE" , CO::WRITE | CO::GLOBAL_TRANS, -2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (
1093
- FtCreate)
1094
- << CI{" FT.ALTER" , CO::WRITE | CO::GLOBAL_TRANS, -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtAlter)
1095
- << CI{" FT.DROPINDEX" , CO::WRITE | CO::GLOBAL_TRANS, -2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (
1096
- FtDropIndex)
1097
- << CI{" FT.INFO" , kReadOnlyMask , 2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtInfo)
1098
- // Underscore same as in RediSearch because it's "temporary" (long time already)
1099
- << CI{" FT._LIST" , kReadOnlyMask , 1 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtList)
1100
- << CI{" FT.SEARCH" , kReadOnlyMask , -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtSearch)
1101
- << CI{" FT.AGGREGATE" , kReadOnlyMask , -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtAggregate)
1102
- << CI{" FT.PROFILE" , kReadOnlyMask , -4 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtProfile)
1103
- << CI{" FT.TAGVALS" , kReadOnlyMask , 3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtTagVals);
1259
+ *registry
1260
+ << CI{" FT.CREATE" , CO::WRITE | CO::GLOBAL_TRANS, -2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtCreate)
1261
+ << CI{" FT.ALTER" , CO::WRITE | CO::GLOBAL_TRANS, -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtAlter)
1262
+ << CI{" FT.DROPINDEX" , CO::WRITE | CO::GLOBAL_TRANS, -2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (
1263
+ FtDropIndex)
1264
+ << CI{" FT.CONFIG" , CO::WRITE | CO::GLOBAL_TRANS, -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtConfig)
1265
+ << CI{" FT.INFO" , kReadOnlyMask , 2 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtInfo)
1266
+ // Underscore same as in RediSearch because it's "temporary" (long time already)
1267
+ << CI{" FT._LIST" , kReadOnlyMask , 1 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtList)
1268
+ << CI{" FT.SEARCH" , kReadOnlyMask , -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtSearch)
1269
+ << CI{" FT.AGGREGATE" , kReadOnlyMask , -3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtAggregate)
1270
+ << CI{" FT.PROFILE" , kReadOnlyMask , -4 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtProfile)
1271
+ << CI{" FT.TAGVALS" , kReadOnlyMask , 3 , 0 , 0 , acl::FT_SEARCH}.HFUNC (FtTagVals);
1104
1272
}
1105
1273
1106
1274
} // namespace dfly
0 commit comments