Remove/Reduce use of Regex in ObjectRegistry/Library (#9264)
Summary: Added new ObjectLibrary::Entry classes to replace/reduce the use of Regex. For simple factories that only do name matching, there are "StringEntry" and "AltStringEntry" classes. For classes that use some semblance of regular expressions, there is a PatternEntry class that can match a name and prefixes. There is also a class for Customizable::IndividualId format matches. Added tests for the new derivative classes and got all unit tests to pass. Resolves https://github.com/facebook/rocksdb/issues/9225. Pull Request resolved: https://github.com/facebook/rocksdb/pull/9264 Reviewed By: pdillinger Differential Revision: D33062001 Pulled By: mrambacher fbshipit-source-id: c2d2143bd2d38bdf522705c8280c35381b135c03
This commit is contained in:
parent
0a563ae278
commit
1c39b7952b
10
env/env_encryption.cc
vendored
10
env/env_encryption.cc
vendored
@ -1274,10 +1274,10 @@ static void RegisterEncryptionBuiltins() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
auto lib = ObjectRegistry::Default()->AddLibrary("encryption");
|
||||
std::string ctr =
|
||||
std::string(CTREncryptionProvider::kClassName()) + "?(://test)";
|
||||
// Match "CTR" or "CTR://test"
|
||||
lib->Register<EncryptionProvider>(
|
||||
std::string(CTREncryptionProvider::kClassName()) + "(://test)?",
|
||||
ObjectLibrary::PatternEntry(CTREncryptionProvider::kClassName(), true)
|
||||
.AddSuffix("://test"),
|
||||
[](const std::string& uri, std::unique_ptr<EncryptionProvider>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
if (EndsWith(uri, "://test")) {
|
||||
@ -1300,8 +1300,10 @@ static void RegisterEncryptionBuiltins() {
|
||||
return guard->get();
|
||||
});
|
||||
|
||||
// Match "ROT13" or "ROT13:[0-9]+"
|
||||
lib->Register<BlockCipher>(
|
||||
std::string(ROT13BlockCipher::kClassName()) + "(:.*)?",
|
||||
ObjectLibrary::PatternEntry(ROT13BlockCipher::kClassName(), true)
|
||||
.AddNumber(":"),
|
||||
[](const std::string& uri, std::unique_ptr<BlockCipher>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
size_t colon = uri.find(':');
|
||||
|
@ -16,7 +16,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/utilities/regex.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class Customizable;
|
||||
@ -43,58 +42,158 @@ using ConfigureFunc = std::function<Status(T*)>;
|
||||
|
||||
class ObjectLibrary {
|
||||
public:
|
||||
// Class for matching target strings to a pattern.
|
||||
// Entries consist of a name that starts the pattern and attributes
|
||||
// The following attributes can be added to the entry:
|
||||
// -Suffix: Comparable to name(suffix)
|
||||
// -Separator: Comparable to name(separator).+
|
||||
// -Number: Comparable to name(separator).[0-9]+
|
||||
// -AltName: Comparable to (name|alt)
|
||||
// -Optional: Comparable to name(separator)?
|
||||
// Multiple separators can be combined and cause multiple matches.
|
||||
// For example, Pattern("A").AnotherName("B"),AddSeparator("@").AddNumber("#")
|
||||
// is roughly equivalent to "(A|B)@.+#.+"
|
||||
//
|
||||
// Note that though this class does provide some regex-style matching,
|
||||
// it is not a full regex parser and has some key differences:
|
||||
// - Separators are matched left-most. For example, an entry
|
||||
// Name("Hello").AddSeparator(" ").AddSuffix("!") would match
|
||||
// "Hello world!", but not "Hello world!!"
|
||||
// - No backtracking is necessary, enabling reliably efficient matching
|
||||
class PatternEntry {
|
||||
private:
|
||||
enum Quantifier {
|
||||
kMatchPattern, // [suffix].+
|
||||
kMatchExact, // [suffix]
|
||||
kMatchNumeric, // [suffix][0-9]+
|
||||
};
|
||||
|
||||
public:
|
||||
// Short-cut for creating an entry that matches to a
|
||||
// Customizable::IndividualId
|
||||
static PatternEntry AsIndividualId(const std::string& name) {
|
||||
PatternEntry entry(name, true);
|
||||
entry.AddSeparator("@");
|
||||
entry.AddSeparator("#");
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Creates a new pattern entry for "name". If optional is true,
|
||||
// Matches will also return true if name==target
|
||||
explicit PatternEntry(const std::string& name, bool optional = true)
|
||||
: name_(name), optional_(optional), slength_(0) {
|
||||
nlength_ = name_.size();
|
||||
}
|
||||
|
||||
// Adds a suffix (exact match of separator with no trailing characters) to
|
||||
// the separator
|
||||
PatternEntry& AddSuffix(const std::string& suffix) {
|
||||
separators_.emplace_back(suffix, kMatchExact);
|
||||
slength_ += suffix.size();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Adds a separator (exact match of separator with trailing characters) to
|
||||
// the entry
|
||||
PatternEntry& AddSeparator(const std::string& separator) {
|
||||
separators_.emplace_back(separator, kMatchPattern);
|
||||
slength_ += separator.size() + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Adds a separator (exact match of separator with trailing numbers) to the
|
||||
// entry
|
||||
PatternEntry& AddNumber(const std::string& separator) {
|
||||
separators_.emplace_back(separator, kMatchNumeric);
|
||||
slength_ += separator.size() + 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Sets another name that this entry will match, similar to (name|alt)
|
||||
PatternEntry& AnotherName(const std::string& alt) {
|
||||
names_.emplace_back(alt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Sets whether the separators are required -- similar to name(separator)?
|
||||
// If optional is true, then name(separator)? would match
|
||||
// If optional is false, then the separators must also match
|
||||
PatternEntry& SetOptional(bool optional) {
|
||||
optional_ = optional;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Checks to see if the target matches this entry
|
||||
bool Matches(const std::string& target) const;
|
||||
const char* Name() const { return name_.c_str(); }
|
||||
|
||||
private:
|
||||
size_t MatchSeparatorAt(size_t start, Quantifier mode,
|
||||
const std::string& target, size_t tlen,
|
||||
const std::string& pattern) const;
|
||||
|
||||
bool MatchesTarget(const std::string& name, size_t nlen,
|
||||
const std::string& target, size_t ylen) const;
|
||||
std::string name_; // The base name for this entry
|
||||
size_t nlength_; // The length of name_
|
||||
std::vector<std::string> names_; // Alternative names for this entry
|
||||
bool optional_; // Whether matching of separators is required
|
||||
size_t slength_; // The minimum required length to match the separators
|
||||
std::vector<std::pair<std::string, Quantifier>>
|
||||
separators_; // What to match
|
||||
}; // End class Entry
|
||||
|
||||
private:
|
||||
// Base class for an Entry in the Registry.
|
||||
class Entry {
|
||||
public:
|
||||
virtual ~Entry() {}
|
||||
Entry(const std::string& name) : name_(std::move(name)) {}
|
||||
|
||||
// Checks to see if the target matches this entry
|
||||
virtual bool matches(const std::string& target) const {
|
||||
return name_ == target;
|
||||
}
|
||||
const std::string& Name() const { return name_; }
|
||||
|
||||
private:
|
||||
const std::string name_; // The name of the Entry
|
||||
}; // End class Entry
|
||||
virtual bool Matches(const std::string& target) const = 0;
|
||||
virtual const char* Name() const = 0;
|
||||
};
|
||||
|
||||
// An Entry containing a FactoryFunc for creating new Objects
|
||||
//
|
||||
// !!!!!! WARNING !!!!!!: The implementation currently uses std::regex, which
|
||||
// has terrible performance in some cases, including possible crash due to
|
||||
// stack overflow. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61582
|
||||
// for example. Avoid complicated regexes as much as possible.
|
||||
template <typename T>
|
||||
class FactoryEntry : public Entry {
|
||||
public:
|
||||
FactoryEntry(const std::string& name, FactoryFunc<T> f)
|
||||
: Entry(name), factory_(std::move(f)) {
|
||||
// FIXME: the API needs to expose this failure mode. For now, bad regexes
|
||||
// will match nothing.
|
||||
Regex::Parse(name, ®ex_).PermitUncheckedError();
|
||||
}
|
||||
~FactoryEntry() override {}
|
||||
bool matches(const std::string& target) const override {
|
||||
return regex_.Matches(target);
|
||||
FactoryEntry(const PatternEntry& e, FactoryFunc<T> f)
|
||||
: entry_(e), factory_(std::move(f)) {}
|
||||
bool Matches(const std::string& target) const override {
|
||||
return entry_.Matches(target);
|
||||
}
|
||||
const char* Name() const override { return entry_.Name(); }
|
||||
|
||||
// Creates a new T object.
|
||||
T* NewFactoryObject(const std::string& target, std::unique_ptr<T>* guard,
|
||||
std::string* msg) const {
|
||||
return factory_(target, guard, msg);
|
||||
}
|
||||
const FactoryFunc<T>& GetFactory() const { return factory_; }
|
||||
|
||||
private:
|
||||
Regex regex_; // The pattern for this entry
|
||||
PatternEntry entry_; // The pattern for this entry
|
||||
FactoryFunc<T> factory_;
|
||||
}; // End class FactoryEntry
|
||||
public:
|
||||
explicit ObjectLibrary(const std::string& id) { id_ = id; }
|
||||
|
||||
const std::string& GetID() const { return id_; }
|
||||
// Finds the entry matching the input name and type
|
||||
const Entry* FindEntry(const std::string& type,
|
||||
const std::string& name) const;
|
||||
|
||||
template <typename T>
|
||||
FactoryFunc<T> FindFactory(const std::string& pattern) const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
auto factories = factories_.find(T::Type());
|
||||
if (factories != factories_.end()) {
|
||||
for (const auto& e : factories->second) {
|
||||
if (e->Matches(pattern)) {
|
||||
const auto* fe =
|
||||
static_cast<const ObjectLibrary::FactoryEntry<T>*>(e.get());
|
||||
return fe->GetFactory();
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Returns the total number of factories registered for this library.
|
||||
// This method returns the sum of all factories registered for all types.
|
||||
@ -108,9 +207,18 @@ class ObjectLibrary {
|
||||
template <typename T>
|
||||
const FactoryFunc<T>& Register(const std::string& pattern,
|
||||
const FactoryFunc<T>& factory) {
|
||||
std::unique_ptr<Entry> entry(new FactoryEntry<T>(pattern, factory));
|
||||
AddEntry(T::Type(), entry);
|
||||
return factory;
|
||||
PatternEntry entry(pattern);
|
||||
return Register(entry, factory);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const FactoryFunc<T>& Register(const PatternEntry& pattern,
|
||||
const FactoryFunc<T>& func) {
|
||||
std::unique_ptr<Entry> entry(new FactoryEntry<T>(pattern, func));
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
auto& factories = factories_[T::Type()];
|
||||
factories.emplace_back(std::move(entry));
|
||||
return func;
|
||||
}
|
||||
|
||||
// Invokes the registrar function with the supplied arg for this library.
|
||||
@ -122,13 +230,11 @@ class ObjectLibrary {
|
||||
static std::shared_ptr<ObjectLibrary>& Default();
|
||||
|
||||
private:
|
||||
// Adds the input entry to the list for the given type
|
||||
void AddEntry(const std::string& type, std::unique_ptr<Entry>& entry);
|
||||
|
||||
// Protects the entry map
|
||||
mutable std::mutex mu_;
|
||||
// ** FactoryFunctions for this loader, organized by type
|
||||
std::unordered_map<std::string, std::vector<std::unique_ptr<Entry>>> entries_;
|
||||
std::unordered_map<std::string, std::vector<std::unique_ptr<Entry>>>
|
||||
factories_;
|
||||
|
||||
// The name for this library
|
||||
std::string id_;
|
||||
@ -178,11 +284,9 @@ class ObjectRegistry {
|
||||
T* NewObject(const std::string& target, std::unique_ptr<T>* guard,
|
||||
std::string* errmsg) {
|
||||
guard->reset();
|
||||
const auto* basic = FindEntry(T::Type(), target);
|
||||
if (basic != nullptr) {
|
||||
const auto* factory =
|
||||
static_cast<const ObjectLibrary::FactoryEntry<T>*>(basic);
|
||||
return factory->NewFactoryObject(target, guard, errmsg);
|
||||
auto factory = FindFactory<T>(target);
|
||||
if (factory != nullptr) {
|
||||
return factory(target, guard, errmsg);
|
||||
} else {
|
||||
*errmsg = std::string("Could not load ") + T::Type();
|
||||
return nullptr;
|
||||
@ -386,8 +490,27 @@ class ObjectRegistry {
|
||||
Status SetManagedObject(const std::string& type, const std::string& id,
|
||||
const std::shared_ptr<Customizable>& c);
|
||||
|
||||
const ObjectLibrary::Entry* FindEntry(const std::string& type,
|
||||
const std::string& name) const;
|
||||
// Searches (from back to front) the libraries looking for the
|
||||
// factory that matches this pattern.
|
||||
// Returns the factory if it is found, and nullptr otherwise
|
||||
template <typename T>
|
||||
const FactoryFunc<T> FindFactory(const std::string& name) const {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(library_mutex_);
|
||||
for (auto iter = libraries_.crbegin(); iter != libraries_.crend();
|
||||
++iter) {
|
||||
const auto factory = iter->get()->FindFactory<T>(name);
|
||||
if (factory != nullptr) {
|
||||
return factory;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent_ != nullptr) {
|
||||
return parent_->FindFactory<T>(name);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// The set of libraries to search for factories for this registry.
|
||||
// The libraries are searched in reverse order (back to front) when
|
||||
|
@ -41,6 +41,10 @@
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/compaction_filters/remove_emptyvalue_compactionfilter.h"
|
||||
#include "utilities/memory_allocators.h"
|
||||
#include "utilities/merge_operators/bytesxor.h"
|
||||
#include "utilities/merge_operators/sortlist.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
|
||||
#ifndef GFLAGS
|
||||
bool FLAGS_enable_print = false;
|
||||
@ -177,7 +181,7 @@ static int A_count = 0;
|
||||
static int RegisterCustomTestObjects(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
library.Register<TestCustomizable>(
|
||||
"A.*",
|
||||
ObjectLibrary::PatternEntry("A", true).AddSeparator("_"),
|
||||
[](const std::string& name, std::unique_ptr<TestCustomizable>* guard,
|
||||
std::string* /* msg */) {
|
||||
guard->reset(new ACustomizable(name));
|
||||
@ -322,7 +326,7 @@ class CustomizableTest : public testing::Test {
|
||||
// - a property with a name
|
||||
TEST_F(CustomizableTest, CreateByNameTest) {
|
||||
ObjectLibrary::Default()->Register<TestCustomizable>(
|
||||
"TEST.*",
|
||||
ObjectLibrary::PatternEntry("TEST", false).AddSeparator("_"),
|
||||
[](const std::string& name, std::unique_ptr<TestCustomizable>* guard,
|
||||
std::string* /* msg */) {
|
||||
guard->reset(new TestCustomizable(name));
|
||||
@ -931,12 +935,12 @@ TEST_F(CustomizableTest, NoNameTest) {
|
||||
auto copts = copy.GetOptions<SimpleOptions>();
|
||||
sopts->cu.reset(new ACustomizable(""));
|
||||
orig.cv.push_back(std::make_shared<ACustomizable>(""));
|
||||
orig.cv.push_back(std::make_shared<ACustomizable>("A1"));
|
||||
orig.cv.push_back(std::make_shared<ACustomizable>("A_1"));
|
||||
std::string opt_str, mismatch;
|
||||
ASSERT_OK(orig.GetOptionString(config_options_, &opt_str));
|
||||
ASSERT_OK(copy.ConfigureFromString(config_options_, opt_str));
|
||||
ASSERT_EQ(copy.cv.size(), 1U);
|
||||
ASSERT_EQ(copy.cv[0]->GetId(), "A1");
|
||||
ASSERT_EQ(copy.cv[0]->GetId(), "A_1");
|
||||
ASSERT_EQ(copts->cu, nullptr);
|
||||
}
|
||||
|
||||
@ -1016,19 +1020,27 @@ TEST_F(CustomizableTest, FactoryFunctionTest) {
|
||||
|
||||
TEST_F(CustomizableTest, URLFactoryTest) {
|
||||
std::unique_ptr<TestCustomizable> unique;
|
||||
config_options_.registry->AddLibrary("URL")->Register<TestCustomizable>(
|
||||
ObjectLibrary::PatternEntry("Z", false).AddSeparator(""),
|
||||
[](const std::string& name, std::unique_ptr<TestCustomizable>* guard,
|
||||
std::string* /* msg */) {
|
||||
guard->reset(new TestCustomizable(name));
|
||||
return guard->get();
|
||||
});
|
||||
|
||||
ConfigOptions ignore = config_options_;
|
||||
ignore.ignore_unsupported_options = false;
|
||||
ignore.ignore_unsupported_options = false;
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "A=1;x=y", &unique));
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1;x=y", &unique));
|
||||
ASSERT_NE(unique, nullptr);
|
||||
ASSERT_EQ(unique->GetId(), "A=1;x=y");
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "A;x=y", &unique));
|
||||
ASSERT_EQ(unique->GetId(), "Z=1;x=y");
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z;x=y", &unique));
|
||||
ASSERT_NE(unique, nullptr);
|
||||
ASSERT_EQ(unique->GetId(), "A;x=y");
|
||||
ASSERT_EQ(unique->GetId(), "Z;x=y");
|
||||
unique.reset();
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "A=1?x=y", &unique));
|
||||
ASSERT_OK(TestCustomizable::CreateFromString(ignore, "Z=1?x=y", &unique));
|
||||
ASSERT_NE(unique, nullptr);
|
||||
ASSERT_EQ(unique->GetId(), "A=1?x=y");
|
||||
ASSERT_EQ(unique->GetId(), "Z=1?x=y");
|
||||
}
|
||||
|
||||
TEST_F(CustomizableTest, MutableOptionsTest) {
|
||||
@ -1126,6 +1138,7 @@ TEST_F(CustomizableTest, CustomManagedObjects) {
|
||||
std::shared_ptr<TestCustomizable> object1, object2;
|
||||
ASSERT_OK(LoadManagedObject<TestCustomizable>(
|
||||
config_options_, "id=A_1;int=1;bool=true", &object1));
|
||||
ASSERT_NE(object1, nullptr);
|
||||
ASSERT_OK(
|
||||
LoadManagedObject<TestCustomizable>(config_options_, "A_1", &object2));
|
||||
ASSERT_EQ(object1, object2);
|
||||
@ -1165,9 +1178,11 @@ TEST_F(CustomizableTest, CreateManagedObjects) {
|
||||
|
||||
config_options_.registry->AddLibrary("Managed")
|
||||
->Register<ManagedCustomizable>(
|
||||
"Managed(@.*)?", [](const std::string& /*name*/,
|
||||
std::unique_ptr<ManagedCustomizable>* guard,
|
||||
std::string* /* msg */) {
|
||||
ObjectLibrary::PatternEntry::AsIndividualId(
|
||||
ManagedCustomizable::kClassName()),
|
||||
[](const std::string& /*name*/,
|
||||
std::unique_ptr<ManagedCustomizable>* guard,
|
||||
std::string* /* msg */) {
|
||||
guard->reset(new ManagedCustomizable());
|
||||
return guard->get();
|
||||
});
|
||||
@ -1317,7 +1332,8 @@ class MockMemoryAllocator : public BaseMemoryAllocator {
|
||||
class MockEncryptionProvider : public EncryptionProvider {
|
||||
public:
|
||||
explicit MockEncryptionProvider(const std::string& id) : id_(id) {}
|
||||
const char* Name() const override { return "Mock"; }
|
||||
static const char* kClassName() { return "Mock"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
size_t GetPrefixLength() const override { return 0; }
|
||||
Status CreateNewPrefix(const std::string& /*fname*/, char* /*prefix*/,
|
||||
size_t /*prefixLength*/) const override {
|
||||
@ -1459,7 +1475,8 @@ static int RegisterLocalObjects(ObjectLibrary& library,
|
||||
});
|
||||
|
||||
library.Register<EncryptionProvider>(
|
||||
"Mock(://test)?",
|
||||
ObjectLibrary::PatternEntry(MockEncryptionProvider::kClassName(), true)
|
||||
.AddSuffix("://test"),
|
||||
[](const std::string& uri, std::unique_ptr<EncryptionProvider>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
guard->reset(new MockEncryptionProvider(uri));
|
||||
@ -1811,9 +1828,74 @@ TEST_F(LoadCustomizableTest, LoadMergeOperatorTest) {
|
||||
|
||||
ASSERT_NOK(
|
||||
MergeOperator::CreateFromString(config_options_, "Changling", &result));
|
||||
//**TODO: MJR: Use the constants when these names are in public classes
|
||||
ASSERT_OK(MergeOperator::CreateFromString(config_options_, "put", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "PutOperator");
|
||||
ASSERT_OK(
|
||||
MergeOperator::CreateFromString(config_options_, "PutOperator", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "PutOperator");
|
||||
ASSERT_OK(
|
||||
MergeOperator::CreateFromString(config_options_, "put_v1", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "PutOperator");
|
||||
|
||||
ASSERT_OK(
|
||||
MergeOperator::CreateFromString(config_options_, "uint64add", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "UInt64AddOperator");
|
||||
ASSERT_OK(MergeOperator::CreateFromString(config_options_,
|
||||
"UInt64AddOperator", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "UInt64AddOperator");
|
||||
|
||||
ASSERT_OK(MergeOperator::CreateFromString(config_options_, "max", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "MaxOperator");
|
||||
ASSERT_OK(
|
||||
MergeOperator::CreateFromString(config_options_, "MaxOperator", &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), "MaxOperator");
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, StringAppendOperator::kNickName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), StringAppendOperator::kClassName());
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, StringAppendOperator::kClassName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), StringAppendOperator::kClassName());
|
||||
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, StringAppendTESTOperator::kNickName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), StringAppendTESTOperator::kClassName());
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, StringAppendTESTOperator::kClassName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), StringAppendTESTOperator::kClassName());
|
||||
|
||||
ASSERT_OK(MergeOperator::CreateFromString(config_options_,
|
||||
SortList::kNickName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), SortList::kClassName());
|
||||
ASSERT_OK(MergeOperator::CreateFromString(config_options_,
|
||||
SortList::kClassName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), SortList::kClassName());
|
||||
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, BytesXOROperator::kNickName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), BytesXOROperator::kClassName());
|
||||
ASSERT_OK(MergeOperator::CreateFromString(
|
||||
config_options_, BytesXOROperator::kClassName(), &result));
|
||||
ASSERT_NE(result, nullptr);
|
||||
ASSERT_STREQ(result->Name(), BytesXOROperator::kClassName());
|
||||
#endif // ROCKSDB_LITE
|
||||
ASSERT_NOK(
|
||||
MergeOperator::CreateFromString(config_options_, "Changling", &result));
|
||||
if (RegisterTests("Test")) {
|
||||
ASSERT_OK(
|
||||
MergeOperator::CreateFromString(config_options_, "Changling", &result));
|
||||
|
@ -2079,8 +2079,9 @@ TEST_F(OptionsTest, OptionTablePropertiesTest) {
|
||||
// properties as the original
|
||||
cfg_opts.registry->AddLibrary("collector")
|
||||
->Register<TablePropertiesCollectorFactory>(
|
||||
std::string(TestTablePropertiesCollectorFactory::kClassName()) +
|
||||
":.*",
|
||||
ObjectLibrary::PatternEntry(
|
||||
TestTablePropertiesCollectorFactory::kClassName(), false)
|
||||
.AddSeparator(":"),
|
||||
[](const std::string& name,
|
||||
std::unique_ptr<TablePropertiesCollectorFactory>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
|
@ -161,15 +161,14 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library,
|
||||
// The MemTableRepFactory built-in classes will be either a class
|
||||
// (VectorRepFactory) or a nickname (vector), followed optionally by ":#",
|
||||
// where # is the "size" of the factory.
|
||||
auto AsRegex = [](const std::string& name, const std::string& alt) {
|
||||
std::string regex;
|
||||
regex.append("(").append(name);
|
||||
regex.append("|").append(alt).append(")(:[0-9]*)?");
|
||||
return regex;
|
||||
auto AsPattern = [](const std::string& name, const std::string& alt) {
|
||||
auto pattern = ObjectLibrary::PatternEntry(name, true);
|
||||
pattern.AnotherName(alt);
|
||||
pattern.AddNumber(":");
|
||||
return pattern;
|
||||
};
|
||||
|
||||
library.Register<MemTableRepFactory>(
|
||||
AsRegex(VectorRepFactory::kClassName(), VectorRepFactory::kNickName()),
|
||||
AsPattern(VectorRepFactory::kClassName(), VectorRepFactory::kNickName()),
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
auto colon = uri.find(":");
|
||||
@ -182,7 +181,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MemTableRepFactory>(
|
||||
AsRegex(SkipListFactory::kClassName(), SkipListFactory::kNickName()),
|
||||
AsPattern(SkipListFactory::kClassName(), SkipListFactory::kNickName()),
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
auto colon = uri.find(":");
|
||||
@ -195,7 +194,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MemTableRepFactory>(
|
||||
AsRegex("HashLinkListRepFactory", "hash_linkedlist"),
|
||||
AsPattern("HashLinkListRepFactory", "hash_linkedlist"),
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
// Expecting format: hash_linkedlist:<hash_bucket_count>
|
||||
@ -209,7 +208,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MemTableRepFactory>(
|
||||
AsRegex("HashSkipListRepFactory", "prefix_hash"),
|
||||
AsPattern("HashSkipListRepFactory", "prefix_hash"),
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
// Expecting format: prefix_hash:<hash_bucket_count>
|
||||
@ -230,9 +229,11 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library,
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
return 5;
|
||||
size_t num_types;
|
||||
return static_cast<int>(library.GetFactoryCount(&num_types));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status GetMemTableRepFactoryFromString(
|
||||
const std::string& opts_str, std::unique_ptr<MemTableRepFactory>* result) {
|
||||
ConfigOptions config_options;
|
||||
|
@ -676,6 +676,25 @@ class SpecialMemTableRep : public MemTableRep {
|
||||
};
|
||||
class SpecialSkipListFactory : public MemTableRepFactory {
|
||||
public:
|
||||
#ifndef ROCKSDB_LITE
|
||||
static bool Register(ObjectLibrary& library, const std::string& /*arg*/) {
|
||||
library.Register<MemTableRepFactory>(
|
||||
ObjectLibrary::PatternEntry(SpecialSkipListFactory::kClassName(), true)
|
||||
.AddNumber(":"),
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
auto colon = uri.find(":");
|
||||
if (colon != std::string::npos) {
|
||||
auto count = ParseInt(uri.substr(colon + 1));
|
||||
guard->reset(new SpecialSkipListFactory(count));
|
||||
} else {
|
||||
guard->reset(new SpecialSkipListFactory(2));
|
||||
}
|
||||
return guard->get();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
// After number of inserts exceeds `num_entries_flush` in a mem table, trigger
|
||||
// flush.
|
||||
explicit SpecialSkipListFactory(int num_entries_flush)
|
||||
@ -717,7 +736,7 @@ MemTableRepFactory* NewSpecialSkipListFactory(int num_entries_per_flush) {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// This method loads existing test classes into the ObjectRegistry
|
||||
int RegisterTestObjects(ObjectLibrary& library, const std::string& /*arg*/) {
|
||||
int RegisterTestObjects(ObjectLibrary& library, const std::string& arg) {
|
||||
size_t num_types;
|
||||
library.Register<const Comparator>(
|
||||
test::SimpleSuffixReverseComparator::kClassName(),
|
||||
@ -727,19 +746,7 @@ int RegisterTestObjects(ObjectLibrary& library, const std::string& /*arg*/) {
|
||||
static test::SimpleSuffixReverseComparator ssrc;
|
||||
return &ssrc;
|
||||
});
|
||||
library.Register<MemTableRepFactory>(
|
||||
std::string(SpecialSkipListFactory::kClassName()) + "(:[0-9]*)?",
|
||||
[](const std::string& uri, std::unique_ptr<MemTableRepFactory>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
auto colon = uri.find(":");
|
||||
if (colon != std::string::npos) {
|
||||
auto count = ParseInt(uri.substr(colon + 1));
|
||||
guard->reset(new SpecialSkipListFactory(count));
|
||||
} else {
|
||||
guard->reset(new SpecialSkipListFactory(2));
|
||||
}
|
||||
return guard->get();
|
||||
});
|
||||
SpecialSkipListFactory::Register(library, arg);
|
||||
library.Register<MergeOperator>(
|
||||
"Changling",
|
||||
[](const std::string& uri, std::unique_ptr<MergeOperator>* guard,
|
||||
|
@ -143,6 +143,9 @@ const SliceTransform* NewNoopTransform() { return new NoopTransform; }
|
||||
#ifndef ROCKSDB_LITE
|
||||
static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
// For the builtin transforms, the format is typically
|
||||
// [Name] or [Name].[0-9]+
|
||||
// [NickName]:[0-9]+
|
||||
library.Register<const SliceTransform>(
|
||||
NoopTransform::kClassName(),
|
||||
[](const std::string& /*uri*/,
|
||||
@ -152,7 +155,8 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<const SliceTransform>(
|
||||
std::string(FixedPrefixTransform::kNickName()) + ":[0-9]+",
|
||||
ObjectLibrary::PatternEntry(FixedPrefixTransform::kNickName(), false)
|
||||
.AddNumber(":"),
|
||||
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
auto colon = uri.find(":");
|
||||
@ -161,24 +165,22 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<const SliceTransform>(
|
||||
FixedPrefixTransform::kClassName(),
|
||||
[](const std::string& /*uri*/,
|
||||
std::unique_ptr<const SliceTransform>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
guard->reset(NewFixedPrefixTransform(0));
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<const SliceTransform>(
|
||||
std::string(FixedPrefixTransform::kClassName()) + "\\.[0-9]+",
|
||||
ObjectLibrary::PatternEntry(FixedPrefixTransform::kClassName(), true)
|
||||
.AddNumber("."),
|
||||
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
auto len = ParseSizeT(
|
||||
uri.substr(strlen(FixedPrefixTransform::kClassName()) + 1));
|
||||
guard->reset(NewFixedPrefixTransform(len));
|
||||
if (uri == FixedPrefixTransform::kClassName()) {
|
||||
guard->reset(NewFixedPrefixTransform(0));
|
||||
} else {
|
||||
auto len = ParseSizeT(
|
||||
uri.substr(strlen(FixedPrefixTransform::kClassName()) + 1));
|
||||
guard->reset(NewFixedPrefixTransform(len));
|
||||
}
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<const SliceTransform>(
|
||||
std::string(CappedPrefixTransform::kNickName()) + ":[0-9]+",
|
||||
ObjectLibrary::PatternEntry(CappedPrefixTransform::kNickName(), false)
|
||||
.AddNumber(":"),
|
||||
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
auto colon = uri.find(":");
|
||||
@ -187,19 +189,21 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library,
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<const SliceTransform>(
|
||||
std::string(CappedPrefixTransform::kClassName()) + "(\\.[0-9]+)?",
|
||||
ObjectLibrary::PatternEntry(CappedPrefixTransform::kClassName(), true)
|
||||
.AddNumber("."),
|
||||
[](const std::string& uri, std::unique_ptr<const SliceTransform>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
if (uri == CappedPrefixTransform::kClassName()) {
|
||||
guard->reset(NewCappedPrefixTransform(0));
|
||||
} else { // Length + "."
|
||||
} else {
|
||||
auto len = ParseSizeT(
|
||||
uri.substr(strlen(CappedPrefixTransform::kClassName()) + 1));
|
||||
guard->reset(NewCappedPrefixTransform(len));
|
||||
}
|
||||
return guard->get();
|
||||
});
|
||||
return 5;
|
||||
size_t num_types;
|
||||
return static_cast<int>(library.GetFactoryCount(&num_types));
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
@ -55,38 +55,33 @@ static bool LoadMergeOperator(const std::string& id,
|
||||
static int RegisterBuiltinMergeOperators(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
size_t num_types;
|
||||
auto AsRegex = [](const std::string& name, const std::string& alt) {
|
||||
std::string regex;
|
||||
regex.append("(").append(name);
|
||||
regex.append("|").append(alt).append(")");
|
||||
return regex;
|
||||
};
|
||||
|
||||
library.Register<MergeOperator>(
|
||||
AsRegex(StringAppendOperator::kClassName(),
|
||||
StringAppendOperator::kNickName()),
|
||||
ObjectLibrary::PatternEntry(StringAppendOperator::kClassName())
|
||||
.AnotherName(StringAppendOperator::kNickName()),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<MergeOperator>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
guard->reset(new StringAppendOperator(","));
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MergeOperator>(
|
||||
AsRegex(StringAppendTESTOperator::kClassName(),
|
||||
StringAppendTESTOperator::kNickName()),
|
||||
ObjectLibrary::PatternEntry(StringAppendTESTOperator::kClassName())
|
||||
.AnotherName(StringAppendTESTOperator::kNickName()),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<MergeOperator>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
guard->reset(new StringAppendTESTOperator(","));
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MergeOperator>(
|
||||
AsRegex(SortList::kClassName(), SortList::kNickName()),
|
||||
ObjectLibrary::PatternEntry(SortList::kClassName())
|
||||
.AnotherName(SortList::kNickName()),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<MergeOperator>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
guard->reset(new SortList());
|
||||
return guard->get();
|
||||
});
|
||||
library.Register<MergeOperator>(
|
||||
AsRegex(BytesXOROperator::kClassName(), BytesXOROperator::kNickName()),
|
||||
ObjectLibrary::PatternEntry(BytesXOROperator::kClassName())
|
||||
.AnotherName(BytesXOROperator::kNickName()),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<MergeOperator>* guard,
|
||||
std::string* /*errmsg*/) {
|
||||
guard->reset(new BytesXOROperator());
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include "logging/logging.h"
|
||||
#include "rocksdb/customizable.h"
|
||||
#include "rocksdb/env.h"
|
||||
@ -12,35 +14,105 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Looks through the "type" factories for one that matches "name".
|
||||
// If found, returns the pointer to the Entry matching this name.
|
||||
// Otherwise, nullptr is returned
|
||||
const ObjectLibrary::Entry *ObjectLibrary::FindEntry(
|
||||
const std::string &type, const std::string &name) const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
auto entries = entries_.find(type);
|
||||
if (entries != entries_.end()) {
|
||||
for (const auto &entry : entries->second) {
|
||||
if (entry->matches(name)) {
|
||||
return entry.get();
|
||||
size_t ObjectLibrary::PatternEntry::MatchSeparatorAt(
|
||||
size_t start, Quantifier mode, const std::string &target, size_t tlen,
|
||||
const std::string &separator) const {
|
||||
size_t slen = separator.size();
|
||||
// See if there is enough space. If so, find the separator
|
||||
if (tlen < start + slen) {
|
||||
return std::string::npos; // not enough space left
|
||||
} else if (mode == kMatchExact) {
|
||||
// Exact mode means the next thing we are looking for is the separator
|
||||
if (target.compare(start, slen, separator) != 0) {
|
||||
return std::string::npos;
|
||||
} else {
|
||||
return start + slen; // Found the separator, return where we found it
|
||||
}
|
||||
} else {
|
||||
auto pos = start + 1;
|
||||
if (!separator.empty()) {
|
||||
pos = target.find(separator, pos);
|
||||
}
|
||||
if (pos == std::string::npos) {
|
||||
return pos;
|
||||
} else if (mode == kMatchNumeric) {
|
||||
// If it is numeric, everything up to the match must be a number
|
||||
while (start < pos) {
|
||||
if (!isdigit(target[start++])) {
|
||||
return std::string::npos;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pos + slen;
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectLibrary::PatternEntry::MatchesTarget(const std::string &name,
|
||||
size_t nlen,
|
||||
const std::string &target,
|
||||
size_t tlen) const {
|
||||
if (separators_.empty()) {
|
||||
assert(optional_); // If there are no separators, it must be only a name
|
||||
return nlen == tlen && name == target;
|
||||
} else if (nlen == tlen) { // The lengths are the same
|
||||
return optional_ && name == target;
|
||||
} else if (tlen < nlen + slength_) {
|
||||
// The target is not long enough
|
||||
return false;
|
||||
} else if (target.compare(0, nlen, name) != 0) {
|
||||
return false; // Target does not start with name
|
||||
} else {
|
||||
// Loop through all of the separators one at a time matching them.
|
||||
// Note that we first match the separator and then its quantifiers.
|
||||
// Since we expect the separator first, we start with an exact match
|
||||
// Subsequent matches will use the quantifier of the previous separator
|
||||
size_t start = nlen;
|
||||
auto mode = kMatchExact;
|
||||
for (size_t idx = 0; idx < separators_.size(); ++idx) {
|
||||
const auto &separator = separators_[idx];
|
||||
start = MatchSeparatorAt(start, mode, target, tlen, separator.first);
|
||||
if (start == std::string::npos) {
|
||||
return false;
|
||||
} else {
|
||||
mode = separator.second;
|
||||
}
|
||||
}
|
||||
// We have matched all of the separators. Now check that what is left
|
||||
// unmatched in the target is acceptable.
|
||||
if (mode == kMatchExact) {
|
||||
return (start == tlen);
|
||||
} else if (start >= tlen) {
|
||||
return false;
|
||||
} else if (mode == kMatchNumeric) {
|
||||
while (start < tlen) {
|
||||
if (!isdigit(target[start++])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ObjectLibrary::AddEntry(const std::string &type,
|
||||
std::unique_ptr<Entry> &entry) {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
auto &entries = entries_[type];
|
||||
entries.emplace_back(std::move(entry));
|
||||
bool ObjectLibrary::PatternEntry::Matches(const std::string &target) const {
|
||||
auto tlen = target.size();
|
||||
if (MatchesTarget(name_, nlength_, target, tlen)) {
|
||||
return true;
|
||||
} else if (!names_.empty()) {
|
||||
for (const auto &alt : names_) {
|
||||
if (MatchesTarget(alt, alt.size(), target, tlen)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t ObjectLibrary::GetFactoryCount(size_t *types) const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
*types = entries_.size();
|
||||
*types = factories_.size();
|
||||
size_t factories = 0;
|
||||
for (const auto &e : entries_) {
|
||||
for (const auto &e : factories_) {
|
||||
factories += e.second.size();
|
||||
}
|
||||
return factories;
|
||||
@ -48,13 +120,12 @@ size_t ObjectLibrary::GetFactoryCount(size_t *types) const {
|
||||
|
||||
void ObjectLibrary::Dump(Logger *logger) const {
|
||||
std::unique_lock<std::mutex> lock(mu_);
|
||||
for (const auto &iter : entries_) {
|
||||
for (const auto &iter : factories_) {
|
||||
ROCKS_LOG_HEADER(logger, " Registered factories for type[%s] ",
|
||||
iter.first.c_str());
|
||||
bool printed_one = false;
|
||||
for (const auto &e : iter.second) {
|
||||
ROCKS_LOG_HEADER(logger, "%c %s", (printed_one) ? ',' : ':',
|
||||
e->Name().c_str());
|
||||
ROCKS_LOG_HEADER(logger, "%c %s", (printed_one) ? ',' : ':', e->Name());
|
||||
printed_one = true;
|
||||
}
|
||||
}
|
||||
@ -84,26 +155,6 @@ std::shared_ptr<ObjectRegistry> ObjectRegistry::NewInstance(
|
||||
return std::make_shared<ObjectRegistry>(parent);
|
||||
}
|
||||
|
||||
// Searches (from back to front) the libraries looking for the
|
||||
// an entry that matches this pattern.
|
||||
// Returns the entry if it is found, and nullptr otherwise
|
||||
const ObjectLibrary::Entry *ObjectRegistry::FindEntry(
|
||||
const std::string &type, const std::string &name) const {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(library_mutex_);
|
||||
for (auto iter = libraries_.crbegin(); iter != libraries_.crend(); ++iter) {
|
||||
const auto *entry = iter->get()->FindEntry(type, name);
|
||||
if (entry != nullptr) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parent_ != nullptr) {
|
||||
return parent_->FindEntry(type, name);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
Status ObjectRegistry::SetManagedObject(
|
||||
const std::string &type, const std::string &id,
|
||||
const std::shared_ptr<Customizable> &object) {
|
||||
|
@ -12,32 +12,33 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class EnvRegistryTest : public testing::Test {
|
||||
class ObjRegistryTest : public testing::Test {
|
||||
public:
|
||||
static int num_a, num_b;
|
||||
};
|
||||
|
||||
int EnvRegistryTest::num_a = 0;
|
||||
int EnvRegistryTest::num_b = 0;
|
||||
int ObjRegistryTest::num_a = 0;
|
||||
int ObjRegistryTest::num_b = 0;
|
||||
static FactoryFunc<Env> test_reg_a = ObjectLibrary::Default()->Register<Env>(
|
||||
"a://.*",
|
||||
ObjectLibrary::PatternEntry("a", false).AddSeparator("://"),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<Env>* /*env_guard*/,
|
||||
std::string* /* errmsg */) {
|
||||
++EnvRegistryTest::num_a;
|
||||
++ObjRegistryTest::num_a;
|
||||
return Env::Default();
|
||||
});
|
||||
|
||||
static FactoryFunc<Env> test_reg_b = ObjectLibrary::Default()->Register<Env>(
|
||||
"b://.*", [](const std::string& /*uri*/, std::unique_ptr<Env>* env_guard,
|
||||
std::string* /* errmsg */) {
|
||||
++EnvRegistryTest::num_b;
|
||||
ObjectLibrary::PatternEntry("b", false).AddSeparator("://"),
|
||||
[](const std::string& /*uri*/, std::unique_ptr<Env>* env_guard,
|
||||
std::string* /* errmsg */) {
|
||||
++ObjRegistryTest::num_b;
|
||||
// Env::Default() is a singleton so we can't grant ownership directly to
|
||||
// the caller - we must wrap it first.
|
||||
env_guard->reset(new EnvWrapper(Env::Default()));
|
||||
return env_guard->get();
|
||||
});
|
||||
|
||||
TEST_F(EnvRegistryTest, Basics) {
|
||||
TEST_F(ObjRegistryTest, Basics) {
|
||||
std::string msg;
|
||||
std::unique_ptr<Env> env_guard;
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
@ -60,7 +61,7 @@ TEST_F(EnvRegistryTest, Basics) {
|
||||
ASSERT_EQ(1, num_b);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, LocalRegistry) {
|
||||
TEST_F(ObjRegistryTest, LocalRegistry) {
|
||||
std::string msg;
|
||||
std::unique_ptr<Env> guard;
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
@ -87,7 +88,7 @@ TEST_F(EnvRegistryTest, LocalRegistry) {
|
||||
ASSERT_NE(registry->NewObject<Env>("test-global", &guard, &msg), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, CheckShared) {
|
||||
TEST_F(ObjRegistryTest, CheckShared) {
|
||||
std::shared_ptr<Env> shared;
|
||||
std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
|
||||
std::shared_ptr<ObjectLibrary> library =
|
||||
@ -112,7 +113,7 @@ TEST_F(EnvRegistryTest, CheckShared) {
|
||||
ASSERT_EQ(shared, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, CheckStatic) {
|
||||
TEST_F(ObjRegistryTest, CheckStatic) {
|
||||
Env* env = nullptr;
|
||||
std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
|
||||
std::shared_ptr<ObjectLibrary> library =
|
||||
@ -137,7 +138,7 @@ TEST_F(EnvRegistryTest, CheckStatic) {
|
||||
ASSERT_NE(env, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, CheckUnique) {
|
||||
TEST_F(ObjRegistryTest, CheckUnique) {
|
||||
std::unique_ptr<Env> unique;
|
||||
std::shared_ptr<ObjectRegistry> registry = ObjectRegistry::NewInstance();
|
||||
std::shared_ptr<ObjectLibrary> library =
|
||||
@ -162,7 +163,7 @@ TEST_F(EnvRegistryTest, CheckUnique) {
|
||||
ASSERT_EQ(unique, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestRegistryParents) {
|
||||
TEST_F(ObjRegistryTest, TestRegistryParents) {
|
||||
auto grand = ObjectRegistry::Default();
|
||||
auto parent = ObjectRegistry::NewInstance(); // parent with a grandparent
|
||||
auto uncle = ObjectRegistry::NewInstance(grand);
|
||||
@ -221,7 +222,7 @@ class MyCustomizable : public Customizable {
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
TEST_F(EnvRegistryTest, TestManagedObjects) {
|
||||
TEST_F(ObjRegistryTest, TestManagedObjects) {
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
auto m_a1 = std::make_shared<MyCustomizable>("", "A");
|
||||
auto m_a2 = std::make_shared<MyCustomizable>("", "A");
|
||||
@ -238,7 +239,7 @@ TEST_F(EnvRegistryTest, TestManagedObjects) {
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("A"), m_a2);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestTwoManagedObjects) {
|
||||
TEST_F(ObjRegistryTest, TestTwoManagedObjects) {
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
auto m_a = std::make_shared<MyCustomizable>("", "A");
|
||||
auto m_b = std::make_shared<MyCustomizable>("", "B");
|
||||
@ -284,7 +285,7 @@ TEST_F(EnvRegistryTest, TestTwoManagedObjects) {
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("B"), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestAlternateNames) {
|
||||
TEST_F(ObjRegistryTest, TestAlternateNames) {
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
auto m_a = std::make_shared<MyCustomizable>("", "A");
|
||||
auto m_b = std::make_shared<MyCustomizable>("", "B");
|
||||
@ -337,7 +338,7 @@ TEST_F(EnvRegistryTest, TestAlternateNames) {
|
||||
ASSERT_EQ(objects.size(), 0U);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestTwoManagedClasses) {
|
||||
TEST_F(ObjRegistryTest, TestTwoManagedClasses) {
|
||||
class MyCustomizable2 : public MyCustomizable {
|
||||
public:
|
||||
static const char* Type() { return "MyCustomizable2"; }
|
||||
@ -377,7 +378,7 @@ TEST_F(EnvRegistryTest, TestTwoManagedClasses) {
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable2>("A"), nullptr);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestManagedObjectsWithParent) {
|
||||
TEST_F(ObjRegistryTest, TestManagedObjectsWithParent) {
|
||||
auto base = ObjectRegistry::NewInstance();
|
||||
auto registry = ObjectRegistry::NewInstance(base);
|
||||
|
||||
@ -397,10 +398,10 @@ TEST_F(EnvRegistryTest, TestManagedObjectsWithParent) {
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("A"), m_b);
|
||||
}
|
||||
|
||||
TEST_F(EnvRegistryTest, TestGetOrCreateManagedObject) {
|
||||
TEST_F(ObjRegistryTest, TestGetOrCreateManagedObject) {
|
||||
auto registry = ObjectRegistry::NewInstance();
|
||||
registry->AddLibrary("test")->Register<MyCustomizable>(
|
||||
"MC(@.*)?",
|
||||
ObjectLibrary::PatternEntry::AsIndividualId("MC"),
|
||||
[](const std::string& uri, std::unique_ptr<MyCustomizable>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
guard->reset(new MyCustomizable("MC", uri));
|
||||
@ -411,14 +412,14 @@ TEST_F(EnvRegistryTest, TestGetOrCreateManagedObject) {
|
||||
|
||||
std::unordered_map<std::string, std::string> opt_map;
|
||||
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@A"), nullptr);
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@B"), nullptr);
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A", &m_a));
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B", &m_b));
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@A"), m_a);
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A", &obj));
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@A#1"), nullptr);
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@B#1"), nullptr);
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &m_a));
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &m_b));
|
||||
ASSERT_EQ(registry->GetManagedObject<MyCustomizable>("MC@A#1"), m_a);
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &obj));
|
||||
ASSERT_EQ(obj, m_a);
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B", &obj));
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &obj));
|
||||
ASSERT_EQ(obj, m_b);
|
||||
ASSERT_OK(registry->ListManagedObjects(&objs));
|
||||
ASSERT_EQ(objs.size(), 2U);
|
||||
@ -426,11 +427,216 @@ TEST_F(EnvRegistryTest, TestGetOrCreateManagedObject) {
|
||||
objs.clear();
|
||||
m_a.reset();
|
||||
obj.reset();
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A", &m_a));
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@A#1", &m_a));
|
||||
ASSERT_EQ(1, m_a.use_count());
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B", &obj));
|
||||
ASSERT_OK(registry->GetOrCreateManagedObject("MC@B#1", &obj));
|
||||
ASSERT_EQ(2, obj.use_count());
|
||||
}
|
||||
|
||||
class PatternEntryTest : public testing::Test {};
|
||||
|
||||
TEST_F(PatternEntryTest, TestSimpleEntry) {
|
||||
ObjectLibrary::PatternEntry entry("ABC", true);
|
||||
|
||||
ASSERT_TRUE(entry.Matches("ABC"));
|
||||
ASSERT_FALSE(entry.Matches("AABC"));
|
||||
ASSERT_FALSE(entry.Matches("ABCA"));
|
||||
ASSERT_FALSE(entry.Matches("AABCA"));
|
||||
ASSERT_FALSE(entry.Matches("AB"));
|
||||
ASSERT_FALSE(entry.Matches("BC"));
|
||||
ASSERT_FALSE(entry.Matches("ABD"));
|
||||
ASSERT_FALSE(entry.Matches("BCA"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestPatternEntry) {
|
||||
// Matches A:+
|
||||
ObjectLibrary::PatternEntry entry("A", false);
|
||||
entry.AddSeparator(":");
|
||||
ASSERT_FALSE(entry.Matches("A"));
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("AB"));
|
||||
ASSERT_FALSE(entry.Matches("B"));
|
||||
ASSERT_FALSE(entry.Matches("A:"));
|
||||
ASSERT_FALSE(entry.Matches("AA:"));
|
||||
ASSERT_FALSE(entry.Matches("AA:B"));
|
||||
ASSERT_FALSE(entry.Matches("AA:BB"));
|
||||
ASSERT_TRUE(entry.Matches("A:B"));
|
||||
ASSERT_TRUE(entry.Matches("A:BB"));
|
||||
|
||||
entry.SetOptional(true); // Now matches "A" or "A:+"
|
||||
ASSERT_TRUE(entry.Matches("A"));
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("AB"));
|
||||
ASSERT_FALSE(entry.Matches("B"));
|
||||
ASSERT_FALSE(entry.Matches("A:"));
|
||||
ASSERT_FALSE(entry.Matches("AA:"));
|
||||
ASSERT_FALSE(entry.Matches("AA:B"));
|
||||
ASSERT_FALSE(entry.Matches("AA:BB"));
|
||||
ASSERT_TRUE(entry.Matches("A:B"));
|
||||
ASSERT_TRUE(entry.Matches("A:BB"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestSuffixEntry) {
|
||||
ObjectLibrary::PatternEntry entry("AA", true);
|
||||
entry.AddSuffix("BB");
|
||||
|
||||
ASSERT_TRUE(entry.Matches("AA"));
|
||||
ASSERT_TRUE(entry.Matches("AABB"));
|
||||
|
||||
ASSERT_FALSE(entry.Matches("A"));
|
||||
ASSERT_FALSE(entry.Matches("AB"));
|
||||
ASSERT_FALSE(entry.Matches("B"));
|
||||
ASSERT_FALSE(entry.Matches("BB"));
|
||||
ASSERT_FALSE(entry.Matches("ABA"));
|
||||
ASSERT_FALSE(entry.Matches("BBAA"));
|
||||
ASSERT_FALSE(entry.Matches("AABBA"));
|
||||
ASSERT_FALSE(entry.Matches("AABBB"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestNumericEntry) {
|
||||
ObjectLibrary::PatternEntry entry("A", false);
|
||||
entry.AddNumber(":");
|
||||
ASSERT_FALSE(entry.Matches("A"));
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("A:"));
|
||||
ASSERT_FALSE(entry.Matches("AA:"));
|
||||
ASSERT_TRUE(entry.Matches("A:1"));
|
||||
ASSERT_TRUE(entry.Matches("A:11"));
|
||||
ASSERT_FALSE(entry.Matches("AA:1"));
|
||||
ASSERT_FALSE(entry.Matches("AA:11"));
|
||||
ASSERT_FALSE(entry.Matches("A:B"));
|
||||
ASSERT_FALSE(entry.Matches("A:1B"));
|
||||
ASSERT_FALSE(entry.Matches("A:B1"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestIndividualIdEntry) {
|
||||
auto entry = ObjectLibrary::PatternEntry::AsIndividualId("AA");
|
||||
ASSERT_TRUE(entry.Matches("AA"));
|
||||
ASSERT_TRUE(entry.Matches("AA@123#456"));
|
||||
ASSERT_TRUE(entry.Matches("AA@deadbeef#id"));
|
||||
|
||||
ASSERT_FALSE(entry.Matches("A"));
|
||||
ASSERT_FALSE(entry.Matches("AAA"));
|
||||
ASSERT_FALSE(entry.Matches("AA@123"));
|
||||
ASSERT_FALSE(entry.Matches("AA@123#"));
|
||||
ASSERT_FALSE(entry.Matches("AA@#123"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestTwoNameEntry) {
|
||||
ObjectLibrary::PatternEntry entry("A");
|
||||
entry.AnotherName("B");
|
||||
ASSERT_TRUE(entry.Matches("A"));
|
||||
ASSERT_TRUE(entry.Matches("B"));
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("BB"));
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("BA"));
|
||||
ASSERT_FALSE(entry.Matches("AB"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestTwoPatternEntry) {
|
||||
ObjectLibrary::PatternEntry entry("AA", false);
|
||||
entry.AddSeparator(":");
|
||||
entry.AddSeparator(":");
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("AA:"));
|
||||
ASSERT_FALSE(entry.Matches("AA::"));
|
||||
ASSERT_FALSE(entry.Matches("AA::12"));
|
||||
ASSERT_TRUE(entry.Matches("AA:1:2"));
|
||||
ASSERT_TRUE(entry.Matches("AA:1:2:"));
|
||||
|
||||
ObjectLibrary::PatternEntry entry2("AA", false);
|
||||
entry2.AddSeparator("::");
|
||||
entry2.AddSeparator("##");
|
||||
ASSERT_FALSE(entry2.Matches("AA"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::#"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::##"));
|
||||
ASSERT_FALSE(entry2.Matches("AA##1::2"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::123##"));
|
||||
ASSERT_TRUE(entry2.Matches("AA::1##2"));
|
||||
ASSERT_TRUE(entry2.Matches("AA::12##34:"));
|
||||
ASSERT_TRUE(entry2.Matches("AA::12::34##56"));
|
||||
ASSERT_TRUE(entry2.Matches("AA::12##34::56"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestTwoNumbersEntry) {
|
||||
ObjectLibrary::PatternEntry entry("AA", false);
|
||||
entry.AddNumber(":");
|
||||
entry.AddNumber(":");
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("AA:"));
|
||||
ASSERT_FALSE(entry.Matches("AA::"));
|
||||
ASSERT_FALSE(entry.Matches("AA::12"));
|
||||
ASSERT_FALSE(entry.Matches("AA:1:2:"));
|
||||
ASSERT_TRUE(entry.Matches("AA:1:2"));
|
||||
ASSERT_TRUE(entry.Matches("AA:12:23456"));
|
||||
|
||||
ObjectLibrary::PatternEntry entry2("AA", false);
|
||||
entry2.AddNumber(":");
|
||||
entry2.AddNumber("#");
|
||||
ASSERT_FALSE(entry2.Matches("AA"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:#"));
|
||||
ASSERT_FALSE(entry2.Matches("AA#:"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:123#"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:123#B"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:B#123"));
|
||||
ASSERT_TRUE(entry2.Matches("AA:1#2"));
|
||||
ASSERT_FALSE(entry2.Matches("AA:123#23:"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::12#234"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestPatternAndSuffix) {
|
||||
ObjectLibrary::PatternEntry entry("AA", false);
|
||||
entry.AddSeparator("::");
|
||||
entry.AddSuffix("##");
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("AA::"));
|
||||
ASSERT_FALSE(entry.Matches("AA::##"));
|
||||
ASSERT_FALSE(entry.Matches("AB::1##"));
|
||||
ASSERT_FALSE(entry.Matches("AB::1##2"));
|
||||
ASSERT_FALSE(entry.Matches("AA##1::"));
|
||||
ASSERT_TRUE(entry.Matches("AA::1##"));
|
||||
ASSERT_FALSE(entry.Matches("AA::1###"));
|
||||
|
||||
ObjectLibrary::PatternEntry entry2("AA", false);
|
||||
entry2.AddSuffix("::");
|
||||
entry2.AddSeparator("##");
|
||||
ASSERT_FALSE(entry2.Matches("AA"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::"));
|
||||
ASSERT_FALSE(entry2.Matches("AA::##"));
|
||||
ASSERT_FALSE(entry2.Matches("AB::1##"));
|
||||
ASSERT_FALSE(entry2.Matches("AB::1##2"));
|
||||
ASSERT_TRUE(entry2.Matches("AA::##12"));
|
||||
}
|
||||
|
||||
TEST_F(PatternEntryTest, TestTwoNamesAndPattern) {
|
||||
ObjectLibrary::PatternEntry entry("AA", true);
|
||||
entry.AddSeparator("::");
|
||||
entry.AnotherName("BBB");
|
||||
ASSERT_TRUE(entry.Matches("AA"));
|
||||
ASSERT_TRUE(entry.Matches("AA::1"));
|
||||
ASSERT_TRUE(entry.Matches("BBB"));
|
||||
ASSERT_TRUE(entry.Matches("BBB::2"));
|
||||
|
||||
ASSERT_FALSE(entry.Matches("AA::"));
|
||||
ASSERT_FALSE(entry.Matches("AAA::"));
|
||||
ASSERT_FALSE(entry.Matches("BBB::"));
|
||||
|
||||
entry.SetOptional(false);
|
||||
ASSERT_FALSE(entry.Matches("AA"));
|
||||
ASSERT_FALSE(entry.Matches("BBB"));
|
||||
|
||||
ASSERT_FALSE(entry.Matches("AA::"));
|
||||
ASSERT_FALSE(entry.Matches("AAA::"));
|
||||
ASSERT_FALSE(entry.Matches("BBB::"));
|
||||
|
||||
ASSERT_TRUE(entry.Matches("AA::1"));
|
||||
ASSERT_TRUE(entry.Matches("BBB::2"));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
@ -442,7 +648,7 @@ int main(int argc, char** argv) {
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr, "SKIPPED as EnvRegistry is not supported in ROCKSDB_LITE\n");
|
||||
fprintf(stderr, "SKIPPED as ObjRegistry is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user