|
25 | 25 | #include "facade/reply_builder.h"
|
26 | 26 | #include "server/acl/acl_commands_def.h"
|
27 | 27 | #include "server/command_registry.h"
|
| 28 | +#include "server/config_registry.h" |
28 | 29 | #include "server/conn_context.h"
|
29 | 30 | #include "server/container_utils.h"
|
30 | 31 | #include "server/engine_shard_set.h"
|
|
35 | 36 |
|
36 | 37 | ABSL_FLAG(bool, search_reject_legacy_field, true, "FT.AGGREGATE: Reject legacy field names.");
|
37 | 38 |
|
| 39 | +ABSL_FLAG(size_t, MAXSEARCHRESULTS, 1000000, "Maximum number of results from ft.search command"); |
38 | 40 | namespace dfly {
|
39 | 41 |
|
40 | 42 | using namespace std;
|
41 | 43 | using namespace facade;
|
42 | 44 |
|
43 | 45 | namespace {
|
| 46 | +// we use it to find which flags are belong to search |
| 47 | +const std::string kCurrentFile = std::filesystem::path(__FILE__).filename().string(); |
44 | 48 |
|
45 | 49 | using nonstd::make_unexpected;
|
46 | 50 |
|
@@ -385,11 +389,16 @@ search::QueryParams ParseQueryParams(CmdArgParser* parser) {
|
385 | 389 | ParseResult<SearchParams> ParseSearchParams(CmdArgParser* parser) {
|
386 | 390 | SearchParams params;
|
387 | 391 |
|
| 392 | + const size_t max_results = absl::GetFlag(FLAGS_MAXSEARCHRESULTS); |
| 393 | + |
388 | 394 | while (parser->HasNext()) {
|
389 | 395 | // [LIMIT offset total]
|
390 | 396 | if (parser->Check("LIMIT")) {
|
391 | 397 | params.limit_offset = parser->Next<size_t>();
|
392 | 398 | params.limit_total = parser->Next<size_t>();
|
| 399 | + if (params.limit_total > max_results) { |
| 400 | + return CreateSyntaxError(absl::StrFormat("LIMIT exceeds maximum of %d", max_results)); |
| 401 | + } |
393 | 402 | } else if (parser->Check("LOAD")) {
|
394 | 403 | if (params.return_fields) {
|
395 | 404 | return CreateSyntaxError("LOAD cannot be applied after RETURN"sv);
|
@@ -418,6 +427,8 @@ ParseResult<SearchParams> ParseSearchParams(CmdArgParser* parser) {
|
418 | 427 | }
|
419 | 428 | }
|
420 | 429 |
|
| 430 | + params.limit_total = std::min(params.limit_total, max_results); |
| 431 | + |
421 | 432 | return params;
|
422 | 433 | }
|
423 | 434 |
|
@@ -1649,6 +1660,97 @@ void SearchFamily::FtSynDump(CmdArgList args, const CommandContext& cmd_cntx) {
|
1649 | 1660 | }
|
1650 | 1661 | }
|
1651 | 1662 |
|
| 1663 | +void FtConfigHelp(CmdArgParser* parser, RedisReplyBuilder* rb) { |
| 1664 | + string_view param = parser->Next(); |
| 1665 | + |
| 1666 | + vector<string> names = config_registry.List(param); |
| 1667 | + vector<absl::CommandLineFlag*> res; |
| 1668 | + |
| 1669 | + for (const auto& name : names) { |
| 1670 | + auto* flag = config_registry.GetFlag(name); |
| 1671 | + DCHECK(flag); |
| 1672 | + if (flag && flag->Filename().find(kCurrentFile) != std::string::npos) { |
| 1673 | + res.push_back(flag); |
| 1674 | + } |
| 1675 | + } |
| 1676 | + |
| 1677 | + rb->StartArray(res.size()); |
| 1678 | + for (const auto& flag : res) { |
| 1679 | + rb->StartArray(5); |
| 1680 | + rb->SendBulkString(flag->Name()); |
| 1681 | + rb->SendBulkString("Description"sv); |
| 1682 | + rb->SendBulkString(flag->Help()); |
| 1683 | + rb->SendBulkString("Value"sv); |
| 1684 | + rb->SendBulkString(flag->CurrentValue()); |
| 1685 | + } |
| 1686 | +} |
| 1687 | + |
| 1688 | +void FtConfigGet(CmdArgParser* parser, RedisReplyBuilder* rb) { |
| 1689 | + string_view param = parser->Next(); |
| 1690 | + vector<string> names = config_registry.List(param); |
| 1691 | + |
| 1692 | + vector<string> res; |
| 1693 | + |
| 1694 | + for (const auto& name : names) { |
| 1695 | + auto* flag = config_registry.GetFlag(name); |
| 1696 | + DCHECK(flag); |
| 1697 | + if (flag && flag->Filename().find(kCurrentFile) != std::string::npos) { |
| 1698 | + res.push_back(name); |
| 1699 | + res.push_back(flag->CurrentValue()); |
| 1700 | + } |
| 1701 | + } |
| 1702 | + return rb->SendBulkStrArr(res, RedisReplyBuilder::MAP); |
| 1703 | +} |
| 1704 | + |
| 1705 | +void FtConfigSet(CmdArgParser* parser, RedisReplyBuilder* rb) { |
| 1706 | + auto [param, value] = parser->Next<string_view, string_view>(); |
| 1707 | + |
| 1708 | + if (!parser->Finalize()) { |
| 1709 | + rb->SendError(parser->TakeError().MakeReply()); |
| 1710 | + return; |
| 1711 | + } |
| 1712 | + |
| 1713 | + vector<string> names = config_registry.List(param); |
| 1714 | + if (names.size() != 1 || |
| 1715 | + config_registry.GetFlag(names[0])->Filename().find(kCurrentFile) == std::string::npos) { |
| 1716 | + return rb->SendError("Invalid option name"); |
| 1717 | + } |
| 1718 | + |
| 1719 | + ConfigRegistry::SetResult result = config_registry.Set(param, value); |
| 1720 | + |
| 1721 | + const char kErrPrefix[] = "FT.CONFIG SET failed (possibly related to argument '"; |
| 1722 | + switch (result) { |
| 1723 | + case ConfigRegistry::SetResult::OK: |
| 1724 | + return rb->SendOk(); |
| 1725 | + case ConfigRegistry::SetResult::UNKNOWN: |
| 1726 | + return rb->SendError( |
| 1727 | + absl::StrCat("Unknown option or number of arguments for CONFIG SET - '", param, "'"), |
| 1728 | + kConfigErrType); |
| 1729 | + |
| 1730 | + case ConfigRegistry::SetResult::READONLY: |
| 1731 | + return rb->SendError(absl::StrCat(kErrPrefix, param, "') - can't set immutable config"), |
| 1732 | + kConfigErrType); |
| 1733 | + |
| 1734 | + case ConfigRegistry::SetResult::INVALID: |
| 1735 | + return rb->SendError(absl::StrCat(kErrPrefix, param, "') - argument can not be set"), |
| 1736 | + kConfigErrType); |
| 1737 | + } |
| 1738 | + ABSL_UNREACHABLE(); |
| 1739 | +} |
| 1740 | + |
| 1741 | +void SearchFamily::FtConfig(CmdArgList args, const CommandContext& cmd_cntx) { |
| 1742 | + CmdArgParser parser{args}; |
| 1743 | + auto* rb = static_cast<RedisReplyBuilder*>(cmd_cntx.rb); |
| 1744 | + |
| 1745 | + auto func = parser.MapNext("GET", &FtConfigGet, "SET", &FtConfigSet, "HELP", &FtConfigHelp); |
| 1746 | + |
| 1747 | + if (auto err = parser.TakeError(); err) { |
| 1748 | + rb->SendError("Unknown subcommand"); |
| 1749 | + return; |
| 1750 | + } |
| 1751 | + func(&parser, rb); |
| 1752 | +} |
| 1753 | + |
1652 | 1754 | void SearchFamily::FtSynUpdate(CmdArgList args, const CommandContext& cmd_cntx) {
|
1653 | 1755 | facade::CmdArgParser parser{args};
|
1654 | 1756 | auto [index_name, group_id] = parser.Next<string_view, string>();
|
@@ -1711,21 +1813,23 @@ void SearchFamily::Register(CommandRegistry* registry) {
|
1711 | 1813 | CO::NO_KEY_TRANSACTIONAL | CO::NO_KEY_TX_SPAN_ALL | CO::NO_AUTOJOURNAL | CO::IDEMPOTENT;
|
1712 | 1814 |
|
1713 | 1815 | registry->StartFamily();
|
1714 |
| - *registry << CI{"FT.CREATE", CO::WRITE | CO::GLOBAL_TRANS, -2, 0, 0, acl::FT_SEARCH}.HFUNC( |
1715 |
| - FtCreate) |
1716 |
| - << CI{"FT.ALTER", CO::WRITE | CO::GLOBAL_TRANS, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtAlter) |
1717 |
| - << CI{"FT.DROPINDEX", CO::WRITE | CO::GLOBAL_TRANS, -2, 0, 0, acl::FT_SEARCH}.HFUNC( |
1718 |
| - FtDropIndex) |
1719 |
| - << CI{"FT.INFO", kReadOnlyMask, 2, 0, 0, acl::FT_SEARCH}.HFUNC(FtInfo) |
1720 |
| - // Underscore same as in RediSearch because it's "temporary" (long time already) |
1721 |
| - << CI{"FT._LIST", kReadOnlyMask, 1, 0, 0, acl::FT_SEARCH}.HFUNC(FtList) |
1722 |
| - << CI{"FT.SEARCH", kReadOnlyMask, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtSearch) |
1723 |
| - << CI{"FT.AGGREGATE", kReadOnlyMask, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtAggregate) |
1724 |
| - << CI{"FT.PROFILE", kReadOnlyMask, -4, 0, 0, acl::FT_SEARCH}.HFUNC(FtProfile) |
1725 |
| - << CI{"FT.TAGVALS", kReadOnlyMask, 3, 0, 0, acl::FT_SEARCH}.HFUNC(FtTagVals) |
1726 |
| - << CI{"FT.SYNDUMP", kReadOnlyMask, 2, 0, 0, acl::FT_SEARCH}.HFUNC(FtSynDump) |
1727 |
| - << CI{"FT.SYNUPDATE", CO::WRITE | CO::GLOBAL_TRANS, -4, 0, 0, acl::FT_SEARCH}.HFUNC( |
1728 |
| - FtSynUpdate); |
| 1816 | + *registry |
| 1817 | + << CI{"FT.CREATE", CO::WRITE | CO::GLOBAL_TRANS, -2, 0, 0, acl::FT_SEARCH}.HFUNC(FtCreate) |
| 1818 | + << CI{"FT.ALTER", CO::WRITE | CO::GLOBAL_TRANS, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtAlter) |
| 1819 | + << CI{"FT.DROPINDEX", CO::WRITE | CO::GLOBAL_TRANS, -2, 0, 0, acl::FT_SEARCH}.HFUNC( |
| 1820 | + FtDropIndex) |
| 1821 | + << CI{"FT.INFO", kReadOnlyMask, 2, 0, 0, acl::FT_SEARCH}.HFUNC(FtInfo) |
| 1822 | + << CI{"FT.CONFIG", CO::ADMIN | CO::LOADING | CO::DANGEROUS, -3, 0, 0, acl::FT_SEARCH}.HFUNC( |
| 1823 | + FtConfig) |
| 1824 | + // Underscore same as in RediSearch because it's "temporary" (long time already) |
| 1825 | + << CI{"FT._LIST", kReadOnlyMask, 1, 0, 0, acl::FT_SEARCH}.HFUNC(FtList) |
| 1826 | + << CI{"FT.SEARCH", kReadOnlyMask, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtSearch) |
| 1827 | + << CI{"FT.AGGREGATE", kReadOnlyMask, -3, 0, 0, acl::FT_SEARCH}.HFUNC(FtAggregate) |
| 1828 | + << CI{"FT.PROFILE", kReadOnlyMask, -4, 0, 0, acl::FT_SEARCH}.HFUNC(FtProfile) |
| 1829 | + << CI{"FT.TAGVALS", kReadOnlyMask, 3, 0, 0, acl::FT_SEARCH}.HFUNC(FtTagVals) |
| 1830 | + << CI{"FT.SYNDUMP", kReadOnlyMask, 2, 0, 0, acl::FT_SEARCH}.HFUNC(FtSynDump) |
| 1831 | + << CI{"FT.SYNUPDATE", CO::WRITE | CO::GLOBAL_TRANS, -4, 0, 0, acl::FT_SEARCH}.HFUNC( |
| 1832 | + FtSynUpdate); |
1729 | 1833 | }
|
1730 | 1834 |
|
1731 | 1835 | } // namespace dfly
|
0 commit comments