chore: introduce GlobMatcher (#4521)

Right now it's just a wrapper around stringmatchlen, so no functional changes are expected.

Signed-off-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
Roman Gershman 2025-01-29 10:58:17 +02:00 committed by GitHub
parent f2309f4e7b
commit bb9819464f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 70 additions and 46 deletions

View file

@ -5,7 +5,7 @@ set(SEARCH_LIB query_parser)
add_library(dfly_core allocation_tracker.cc bloom.cc compact_object.cc dense_set.cc
dragonfly_core.cc extent_tree.cc
interpreter.cc mi_memory_resource.cc qlist.cc sds_utils.cc
interpreter.cc glob_matcher.cc mi_memory_resource.cc qlist.cc sds_utils.cc
segment_allocator.cc score_map.cc small_string.cc sorted_map.cc task_queue.cc
tx_queue.cc string_set.cc string_map.cc detail/bitpacking.cc)

View file

@ -3,13 +3,10 @@
//
#include "base/gtest.h"
#include "core/glob_matcher.h"
#include "core/intent_lock.h"
#include "core/tx_queue.h"
extern "C" {
#include "redis/util.h"
}
namespace dfly {
using namespace std;
@ -75,7 +72,8 @@ class StringMatchTest : public ::testing::Test {
protected:
// wrapper around stringmatchlen with stringview arguments
int MatchLen(string_view pattern, string_view str, bool nocase) {
return stringmatchlen(pattern.data(), pattern.size(), str.data(), str.size(), nocase);
GlobMatcher matcher(pattern, !nocase);
return matcher.Matches(str);
}
};

22
src/core/glob_matcher.cc Normal file
View file

@ -0,0 +1,22 @@
// Copyright 2025, DragonflyDB authors. All rights reserved.
// See LICENSE for licensing terms.
//
#include "core/glob_matcher.h"
extern "C" {
#include "redis/util.h"
}
namespace dfly {
GlobMatcher::GlobMatcher(std::string_view pattern, bool case_sensitive)
: pattern_(pattern), case_sensitive_(case_sensitive) {
}
bool GlobMatcher::Matches(std::string_view str) const {
return stringmatchlen(pattern_.data(), pattern_.size(), str.data(), str.size(),
int(!case_sensitive_)) != 0;
}
} // namespace dfly

21
src/core/glob_matcher.h Normal file
View file

@ -0,0 +1,21 @@
// Copyright 2025, DragonflyDB authors. All rights reserved.
// See LICENSE for licensing terms.
//
#pragma once
#include <string_view>
namespace dfly {
class GlobMatcher {
public:
explicit GlobMatcher(std::string_view pattern, bool case_sensitive);
bool Matches(std::string_view str) const;
private:
std::string_view pattern_;
bool case_sensitive_;
};
} // namespace dfly

View file

@ -5,18 +5,20 @@
#include "server/acl/validator.h"
#include "base/logging.h"
#include "core/glob_matcher.h"
#include "facade/dragonfly_connection.h"
#include "server/acl/acl_commands_def.h"
#include "server/command_registry.h"
#include "server/server_state.h"
#include "server/transaction.h"
// we need this because of stringmatchlen
extern "C" {
#include "redis/util.h"
}
namespace dfly::acl {
inline bool Matches(std::string_view pattern, std::string_view target) {
GlobMatcher matcher(pattern, true);
return matcher.Matches(target);
};
[[nodiscard]] bool IsUserAllowedToInvokeCommand(const ConnectionContext& cntx, const CommandId& id,
ArgSlice tail_args) {
if (cntx.skip_acl_validation) {
@ -58,16 +60,12 @@ static bool ValidateCommand(const std::vector<uint64_t>& acl_commands, const Com
return {false, AclLog::Reason::COMMAND};
}
auto match = [](const auto& pattern, const auto& target) {
return stringmatchlen(pattern.data(), pattern.size(), target.data(), target.size(), 0);
};
const bool is_read_command = id.IsReadOnly();
const bool is_write_command = id.IsWriteOnly();
auto iterate_globs = [&](auto target) {
for (auto& [elem, op] : keys.key_globs) {
if (match(elem, target)) {
if (Matches(elem, target)) {
if (is_read_command && (op == KeyOp::READ || op == KeyOp::READ_WRITE)) {
return true;
}
@ -98,16 +96,12 @@ static bool ValidateCommand(const std::vector<uint64_t>& acl_commands, const Com
return {false, AclLog::Reason::COMMAND};
}
auto match = [](std::string_view pattern, std::string_view target) {
return stringmatchlen(pattern.data(), pattern.size(), target.data(), target.size(), 0);
};
auto iterate_globs = [&](std::string_view target) {
for (auto& [glob, has_asterisk] : pub_sub.globs) {
if (literal_match && (glob == target)) {
return true;
}
if (!literal_match && match(glob, target)) {
if (!literal_match && Matches(glob, target)) {
return true;
}
}

View file

@ -4,15 +4,10 @@
// See LICENSE for licensing terms.
//
#include <shared_mutex>
extern "C" {
#include "redis/util.h"
}
#include <absl/container/fixed_array.h>
#include "base/logging.h"
#include "core/glob_matcher.h"
#include "server/engine_shard_set.h"
#include "server/server_state.h"
@ -21,10 +16,6 @@ using namespace std;
namespace {
bool Matches(string_view pattern, string_view channel) {
return stringmatchlen(pattern.data(), pattern.size(), channel.data(), channel.size(), 0) == 1;
}
// Build functor for sending messages to connection
auto BuildSender(string_view channel, facade::ArgRange messages) {
absl::FixedArray<string_view, 1> views(messages.Size());
@ -171,7 +162,8 @@ vector<ChannelStore::Subscriber> ChannelStore::FetchSubscribers(string_view chan
Fill(*it->second, string{}, &res);
for (const auto& [pat, subs] : *patterns_) {
if (Matches(pat, channel))
GlobMatcher matcher{pat, true};
if (matcher.Matches(channel))
Fill(*subs, pat, &res);
}
@ -192,8 +184,9 @@ void ChannelStore::Fill(const SubscribeMap& src, const string& pattern, vector<S
std::vector<string> ChannelStore::ListChannels(const string_view pattern) const {
vector<string> res;
GlobMatcher matcher{pattern, true};
for (const auto& [channel, _] : *channels_) {
if (pattern.empty() || Matches(pattern, channel))
if (pattern.empty() || matcher.Matches(channel))
res.push_back(channel);
}
return res;

View file

@ -12,7 +12,6 @@
extern "C" {
#include "redis/rdb.h"
#include "redis/util.h"
}
#include "base/flags.h"
@ -298,7 +297,7 @@ OpResult<ScanOpts> ScanOpts::TryFrom(CmdArgList args) {
} else if (opt == "MATCH") {
string_view pattern = ArgS(args, i + 1);
if (pattern != "*")
scan_opts.pattern = pattern;
scan_opts.matcher.emplace(pattern, true);
} else if (opt == "TYPE") {
auto obj_type = ObjTypeFromString(ArgS(args, i + 1));
if (!obj_type) {
@ -317,9 +316,7 @@ OpResult<ScanOpts> ScanOpts::TryFrom(CmdArgList args) {
}
bool ScanOpts::Matches(std::string_view val_name) const {
if (!pattern)
return true;
return stringmatchlen(pattern->data(), pattern->size(), val_name.data(), val_name.size(), 0) == 1;
return !matcher || matcher->Matches(val_name);
}
GenericError::operator std::error_code() const {

View file

@ -15,6 +15,7 @@
#include <vector>
#include "core/compact_object.h"
#include "core/glob_matcher.h"
#include "facade/facade_types.h"
#include "facade/op_status.h"
#include "helio/io/proc_reader.h"
@ -303,7 +304,7 @@ class Context : protected Cancellation {
};
struct ScanOpts {
std::optional<std::string_view> pattern;
std::optional<GlobMatcher> matcher;
size_t limit = 10;
std::optional<CompactObjType> type_filter;
unsigned bucket_id = UINT_MAX;

View file

@ -7,12 +7,9 @@
#include <absl/strings/str_replace.h>
#include "base/logging.h"
#include "core/glob_matcher.h"
#include "server/common.h"
extern "C" {
#include "redis/util.h"
}
namespace dfly {
namespace {
using namespace std;
@ -67,11 +64,13 @@ void ConfigRegistry::Reset() {
vector<string> ConfigRegistry::List(string_view glob) const {
string normalized_glob = NormalizeConfigName(glob);
GlobMatcher matcher(normalized_glob, false /* case insensitive*/);
vector<string> res;
util::fb2::LockGuard lk(mu_);
for (const auto& [name, _] : registry_) {
if (stringmatchlen(normalized_glob.data(), normalized_glob.size(), name.data(), name.size(), 1))
if (matcher.Matches(name))
res.push_back(name);
}
return res;

View file

@ -875,7 +875,7 @@ static void BM_MatchPattern(benchmark::State& state) {
absl::InsecureBitGen eng;
string random_val = GetRandomHex(eng, state.range(0));
ScanOpts scan_opts;
scan_opts.pattern = "*foobar*";
scan_opts.matcher.emplace("*foobar*", true);
while (state.KeepRunning()) {
DoNotOptimize(scan_opts.Matches(random_val));
}

View file

@ -12,7 +12,6 @@
extern "C" {
#include "redis/crc64.h"
#include "redis/util.h"
}
#include "base/flags.h"
@ -1180,7 +1179,7 @@ void GenericFamily::Keys(CmdArgList args, const CommandContext& cmd_cntx) {
ScanOpts scan_opts;
if (pattern != "*") {
scan_opts.pattern = pattern;
scan_opts.matcher.emplace(pattern, true);
}
scan_opts.limit = 512;