From 1c39b7952bfff1beff1d473444cd75c3313b73bd Mon Sep 17 00:00:00 2001 From: mrambacher Date: Wed, 29 Dec 2021 07:55:17 -0800 Subject: [PATCH] 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 --- env/env_encryption.cc | 10 +- include/rocksdb/utilities/object_registry.h | 211 +++++++++++---- options/customizable_test.cc | 112 ++++++-- options/options_test.cc | 5 +- table/plain/plain_table_factory.cc | 23 +- test_util/testutil.cc | 35 ++- util/slice.cc | 38 +-- utilities/merge_operators.cc | 21 +- utilities/object_registry.cc | 135 +++++++--- utilities/object_registry_test.cc | 268 +++++++++++++++++--- 10 files changed, 665 insertions(+), 193 deletions(-) diff --git a/env/env_encryption.cc b/env/env_encryption.cc index 97c7439c43..4a2b68bc75 100644 --- a/env/env_encryption.cc +++ b/env/env_encryption.cc @@ -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( - std::string(CTREncryptionProvider::kClassName()) + "(://test)?", + ObjectLibrary::PatternEntry(CTREncryptionProvider::kClassName(), true) + .AddSuffix("://test"), [](const std::string& uri, std::unique_ptr* 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( - std::string(ROT13BlockCipher::kClassName()) + "(:.*)?", + ObjectLibrary::PatternEntry(ROT13BlockCipher::kClassName(), true) + .AddNumber(":"), [](const std::string& uri, std::unique_ptr* guard, std::string* /* errmsg */) { size_t colon = uri.find(':'); diff --git a/include/rocksdb/utilities/object_registry.h b/include/rocksdb/utilities/object_registry.h index b21868709f..bc7d7ce3f2 100644 --- a/include/rocksdb/utilities/object_registry.h +++ b/include/rocksdb/utilities/object_registry.h @@ -16,7 +16,6 @@ #include #include "rocksdb/status.h" -#include "rocksdb/utilities/regex.h" namespace ROCKSDB_NAMESPACE { class Customizable; @@ -43,58 +42,158 @@ using ConfigureFunc = std::function; 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 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> + 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 class FactoryEntry : public Entry { public: - FactoryEntry(const std::string& name, FactoryFunc 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 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* guard, std::string* msg) const { return factory_(target, guard, msg); } + const FactoryFunc& GetFactory() const { return factory_; } private: - Regex regex_; // The pattern for this entry + PatternEntry entry_; // The pattern for this entry FactoryFunc 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 + FactoryFunc FindFactory(const std::string& pattern) const { + std::unique_lock 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*>(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 const FactoryFunc& Register(const std::string& pattern, const FactoryFunc& factory) { - std::unique_ptr entry(new FactoryEntry(pattern, factory)); - AddEntry(T::Type(), entry); - return factory; + PatternEntry entry(pattern); + return Register(entry, factory); + } + + template + const FactoryFunc& Register(const PatternEntry& pattern, + const FactoryFunc& func) { + std::unique_ptr entry(new FactoryEntry(pattern, func)); + std::unique_lock 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& Default(); private: - // Adds the input entry to the list for the given type - void AddEntry(const std::string& type, std::unique_ptr& entry); - // Protects the entry map mutable std::mutex mu_; // ** FactoryFunctions for this loader, organized by type - std::unordered_map>> entries_; + std::unordered_map>> + factories_; // The name for this library std::string id_; @@ -178,11 +284,9 @@ class ObjectRegistry { T* NewObject(const std::string& target, std::unique_ptr* guard, std::string* errmsg) { guard->reset(); - const auto* basic = FindEntry(T::Type(), target); - if (basic != nullptr) { - const auto* factory = - static_cast*>(basic); - return factory->NewFactoryObject(target, guard, errmsg); + auto factory = FindFactory(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& 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 + const FactoryFunc FindFactory(const std::string& name) const { + { + std::unique_lock lock(library_mutex_); + for (auto iter = libraries_.crbegin(); iter != libraries_.crend(); + ++iter) { + const auto factory = iter->get()->FindFactory(name); + if (factory != nullptr) { + return factory; + } + } + } + if (parent_ != nullptr) { + return parent_->FindFactory(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 diff --git a/options/customizable_test.cc b/options/customizable_test.cc index f4872ad9d5..ea6e73fb26 100644 --- a/options/customizable_test.cc +++ b/options/customizable_test.cc @@ -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( - "A.*", + ObjectLibrary::PatternEntry("A", true).AddSeparator("_"), [](const std::string& name, std::unique_ptr* 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( - "TEST.*", + ObjectLibrary::PatternEntry("TEST", false).AddSeparator("_"), [](const std::string& name, std::unique_ptr* guard, std::string* /* msg */) { guard->reset(new TestCustomizable(name)); @@ -931,12 +935,12 @@ TEST_F(CustomizableTest, NoNameTest) { auto copts = copy.GetOptions(); sopts->cu.reset(new ACustomizable("")); orig.cv.push_back(std::make_shared("")); - orig.cv.push_back(std::make_shared("A1")); + orig.cv.push_back(std::make_shared("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 unique; + config_options_.registry->AddLibrary("URL")->Register( + ObjectLibrary::PatternEntry("Z", false).AddSeparator(""), + [](const std::string& name, std::unique_ptr* 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 object1, object2; ASSERT_OK(LoadManagedObject( config_options_, "id=A_1;int=1;bool=true", &object1)); + ASSERT_NE(object1, nullptr); ASSERT_OK( LoadManagedObject(config_options_, "A_1", &object2)); ASSERT_EQ(object1, object2); @@ -1165,9 +1178,11 @@ TEST_F(CustomizableTest, CreateManagedObjects) { config_options_.registry->AddLibrary("Managed") ->Register( - "Managed(@.*)?", [](const std::string& /*name*/, - std::unique_ptr* guard, - std::string* /* msg */) { + ObjectLibrary::PatternEntry::AsIndividualId( + ManagedCustomizable::kClassName()), + [](const std::string& /*name*/, + std::unique_ptr* 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( - "Mock(://test)?", + ObjectLibrary::PatternEntry(MockEncryptionProvider::kClassName(), true) + .AddSuffix("://test"), [](const std::string& uri, std::unique_ptr* 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)); diff --git a/options/options_test.cc b/options/options_test.cc index 0392320a8e..36e538dc54 100644 --- a/options/options_test.cc +++ b/options/options_test.cc @@ -2079,8 +2079,9 @@ TEST_F(OptionsTest, OptionTablePropertiesTest) { // properties as the original cfg_opts.registry->AddLibrary("collector") ->Register( - std::string(TestTablePropertiesCollectorFactory::kClassName()) + - ":.*", + ObjectLibrary::PatternEntry( + TestTablePropertiesCollectorFactory::kClassName(), false) + .AddSeparator(":"), [](const std::string& name, std::unique_ptr* guard, std::string* /* errmsg */) { diff --git a/table/plain/plain_table_factory.cc b/table/plain/plain_table_factory.cc index 737b6b58f0..a6cf42f1e3 100644 --- a/table/plain/plain_table_factory.cc +++ b/table/plain/plain_table_factory.cc @@ -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( - AsRegex(VectorRepFactory::kClassName(), VectorRepFactory::kNickName()), + AsPattern(VectorRepFactory::kClassName(), VectorRepFactory::kNickName()), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { auto colon = uri.find(":"); @@ -182,7 +181,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library, return guard->get(); }); library.Register( - AsRegex(SkipListFactory::kClassName(), SkipListFactory::kNickName()), + AsPattern(SkipListFactory::kClassName(), SkipListFactory::kNickName()), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { auto colon = uri.find(":"); @@ -195,7 +194,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library, return guard->get(); }); library.Register( - AsRegex("HashLinkListRepFactory", "hash_linkedlist"), + AsPattern("HashLinkListRepFactory", "hash_linkedlist"), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { // Expecting format: hash_linkedlist: @@ -209,7 +208,7 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library, return guard->get(); }); library.Register( - AsRegex("HashSkipListRepFactory", "prefix_hash"), + AsPattern("HashSkipListRepFactory", "prefix_hash"), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { // Expecting format: prefix_hash: @@ -230,9 +229,11 @@ static int RegisterBuiltinMemTableRepFactory(ObjectLibrary& library, return nullptr; }); - return 5; + size_t num_types; + return static_cast(library.GetFactoryCount(&num_types)); } #endif // ROCKSDB_LITE + Status GetMemTableRepFactoryFromString( const std::string& opts_str, std::unique_ptr* result) { ConfigOptions config_options; diff --git a/test_util/testutil.cc b/test_util/testutil.cc index 0fe789e71a..07d0ef2520 100644 --- a/test_util/testutil.cc +++ b/test_util/testutil.cc @@ -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( + ObjectLibrary::PatternEntry(SpecialSkipListFactory::kClassName(), true) + .AddNumber(":"), + [](const std::string& uri, std::unique_ptr* 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( test::SimpleSuffixReverseComparator::kClassName(), @@ -727,19 +746,7 @@ int RegisterTestObjects(ObjectLibrary& library, const std::string& /*arg*/) { static test::SimpleSuffixReverseComparator ssrc; return &ssrc; }); - library.Register( - std::string(SpecialSkipListFactory::kClassName()) + "(:[0-9]*)?", - [](const std::string& uri, std::unique_ptr* 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( "Changling", [](const std::string& uri, std::unique_ptr* guard, diff --git a/util/slice.cc b/util/slice.cc index f96926cfc4..268eb1b4ed 100644 --- a/util/slice.cc +++ b/util/slice.cc @@ -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( NoopTransform::kClassName(), [](const std::string& /*uri*/, @@ -152,7 +155,8 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library, return guard->get(); }); library.Register( - std::string(FixedPrefixTransform::kNickName()) + ":[0-9]+", + ObjectLibrary::PatternEntry(FixedPrefixTransform::kNickName(), false) + .AddNumber(":"), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { auto colon = uri.find(":"); @@ -161,24 +165,22 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library, return guard->get(); }); library.Register( - FixedPrefixTransform::kClassName(), - [](const std::string& /*uri*/, - std::unique_ptr* guard, - std::string* /*errmsg*/) { - guard->reset(NewFixedPrefixTransform(0)); - return guard->get(); - }); - library.Register( - std::string(FixedPrefixTransform::kClassName()) + "\\.[0-9]+", + ObjectLibrary::PatternEntry(FixedPrefixTransform::kClassName(), true) + .AddNumber("."), [](const std::string& uri, std::unique_ptr* 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( - std::string(CappedPrefixTransform::kNickName()) + ":[0-9]+", + ObjectLibrary::PatternEntry(CappedPrefixTransform::kNickName(), false) + .AddNumber(":"), [](const std::string& uri, std::unique_ptr* guard, std::string* /*errmsg*/) { auto colon = uri.find(":"); @@ -187,19 +189,21 @@ static int RegisterBuiltinSliceTransform(ObjectLibrary& library, return guard->get(); }); library.Register( - std::string(CappedPrefixTransform::kClassName()) + "(\\.[0-9]+)?", + ObjectLibrary::PatternEntry(CappedPrefixTransform::kClassName(), true) + .AddNumber("."), [](const std::string& uri, std::unique_ptr* 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(library.GetFactoryCount(&num_types)); } #endif // ROCKSDB_LITE diff --git a/utilities/merge_operators.cc b/utilities/merge_operators.cc index 7fe0abfaf6..180e577db7 100644 --- a/utilities/merge_operators.cc +++ b/utilities/merge_operators.cc @@ -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( - AsRegex(StringAppendOperator::kClassName(), - StringAppendOperator::kNickName()), + ObjectLibrary::PatternEntry(StringAppendOperator::kClassName()) + .AnotherName(StringAppendOperator::kNickName()), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /*errmsg*/) { guard->reset(new StringAppendOperator(",")); return guard->get(); }); library.Register( - AsRegex(StringAppendTESTOperator::kClassName(), - StringAppendTESTOperator::kNickName()), + ObjectLibrary::PatternEntry(StringAppendTESTOperator::kClassName()) + .AnotherName(StringAppendTESTOperator::kNickName()), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /*errmsg*/) { guard->reset(new StringAppendTESTOperator(",")); return guard->get(); }); library.Register( - AsRegex(SortList::kClassName(), SortList::kNickName()), + ObjectLibrary::PatternEntry(SortList::kClassName()) + .AnotherName(SortList::kNickName()), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /*errmsg*/) { guard->reset(new SortList()); return guard->get(); }); library.Register( - AsRegex(BytesXOROperator::kClassName(), BytesXOROperator::kNickName()), + ObjectLibrary::PatternEntry(BytesXOROperator::kClassName()) + .AnotherName(BytesXOROperator::kNickName()), [](const std::string& /*uri*/, std::unique_ptr* guard, std::string* /*errmsg*/) { guard->reset(new BytesXOROperator()); diff --git a/utilities/object_registry.cc b/utilities/object_registry.cc index b0a60f53ec..e41d3172d2 100644 --- a/utilities/object_registry.cc +++ b/utilities/object_registry.cc @@ -5,6 +5,8 @@ #include "rocksdb/utilities/object_registry.h" +#include + #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 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) { - std::unique_lock 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 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 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::NewInstance( return std::make_shared(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 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 &object) { diff --git a/utilities/object_registry_test.cc b/utilities/object_registry_test.cc index d9a5b1526a..a2f61c8f67 100644 --- a/utilities/object_registry_test.cc +++ b/utilities/object_registry_test.cc @@ -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 test_reg_a = ObjectLibrary::Default()->Register( - "a://.*", + ObjectLibrary::PatternEntry("a", false).AddSeparator("://"), [](const std::string& /*uri*/, std::unique_ptr* /*env_guard*/, std::string* /* errmsg */) { - ++EnvRegistryTest::num_a; + ++ObjRegistryTest::num_a; return Env::Default(); }); static FactoryFunc test_reg_b = ObjectLibrary::Default()->Register( - "b://.*", [](const std::string& /*uri*/, std::unique_ptr* env_guard, - std::string* /* errmsg */) { - ++EnvRegistryTest::num_b; + ObjectLibrary::PatternEntry("b", false).AddSeparator("://"), + [](const std::string& /*uri*/, std::unique_ptr* 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_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 guard; auto registry = ObjectRegistry::NewInstance(); @@ -87,7 +88,7 @@ TEST_F(EnvRegistryTest, LocalRegistry) { ASSERT_NE(registry->NewObject("test-global", &guard, &msg), nullptr); } -TEST_F(EnvRegistryTest, CheckShared) { +TEST_F(ObjRegistryTest, CheckShared) { std::shared_ptr shared; std::shared_ptr registry = ObjectRegistry::NewInstance(); std::shared_ptr 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 registry = ObjectRegistry::NewInstance(); std::shared_ptr library = @@ -137,7 +138,7 @@ TEST_F(EnvRegistryTest, CheckStatic) { ASSERT_NE(env, nullptr); } -TEST_F(EnvRegistryTest, CheckUnique) { +TEST_F(ObjRegistryTest, CheckUnique) { std::unique_ptr unique; std::shared_ptr registry = ObjectRegistry::NewInstance(); std::shared_ptr 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("", "A"); auto m_a2 = std::make_shared("", "A"); @@ -238,7 +239,7 @@ TEST_F(EnvRegistryTest, TestManagedObjects) { ASSERT_EQ(registry->GetManagedObject("A"), m_a2); } -TEST_F(EnvRegistryTest, TestTwoManagedObjects) { +TEST_F(ObjRegistryTest, TestTwoManagedObjects) { auto registry = ObjectRegistry::NewInstance(); auto m_a = std::make_shared("", "A"); auto m_b = std::make_shared("", "B"); @@ -284,7 +285,7 @@ TEST_F(EnvRegistryTest, TestTwoManagedObjects) { ASSERT_EQ(registry->GetManagedObject("B"), nullptr); } -TEST_F(EnvRegistryTest, TestAlternateNames) { +TEST_F(ObjRegistryTest, TestAlternateNames) { auto registry = ObjectRegistry::NewInstance(); auto m_a = std::make_shared("", "A"); auto m_b = std::make_shared("", "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("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("A"), m_b); } -TEST_F(EnvRegistryTest, TestGetOrCreateManagedObject) { +TEST_F(ObjRegistryTest, TestGetOrCreateManagedObject) { auto registry = ObjectRegistry::NewInstance(); registry->AddLibrary("test")->Register( - "MC(@.*)?", + ObjectLibrary::PatternEntry::AsIndividualId("MC"), [](const std::string& uri, std::unique_ptr* guard, std::string* /* errmsg */) { guard->reset(new MyCustomizable("MC", uri)); @@ -411,14 +412,14 @@ TEST_F(EnvRegistryTest, TestGetOrCreateManagedObject) { std::unordered_map opt_map; - ASSERT_EQ(registry->GetManagedObject("MC@A"), nullptr); - ASSERT_EQ(registry->GetManagedObject("MC@B"), nullptr); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@A", &m_a)); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@B", &m_b)); - ASSERT_EQ(registry->GetManagedObject("MC@A"), m_a); - ASSERT_OK(registry->GetOrCreateManagedObject("MC@A", &obj)); + ASSERT_EQ(registry->GetManagedObject("MC@A#1"), nullptr); + ASSERT_EQ(registry->GetManagedObject("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("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 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; }