Make Comparator into a Customizable Object (#8336)

Summary:
Makes the Comparator class into a Customizable object.  Added/Updated the CreateFromString method to create Comparators.  Added test for using the ObjectRegistry to create one.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/8336

Reviewed By: jay-zhuang

Differential Revision: D28999612

Pulled By: mrambacher

fbshipit-source-id: bff2cb2814eeb9fef6a00fddc61d6e34b6fbcf2e
This commit is contained in:
mrambacher 2021-06-11 06:21:55 -07:00 committed by Facebook GitHub Bot
parent 3897ce3125
commit 6ad0810393
8 changed files with 152 additions and 44 deletions

View File

@ -10,6 +10,7 @@
#include <string> #include <string>
#include "rocksdb/customizable.h"
#include "rocksdb/rocksdb_namespace.h" #include "rocksdb/rocksdb_namespace.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
@ -20,7 +21,7 @@ class Slice;
// used as keys in an sstable or a database. A Comparator implementation // used as keys in an sstable or a database. A Comparator implementation
// must be thread-safe since rocksdb may invoke its methods concurrently // must be thread-safe since rocksdb may invoke its methods concurrently
// from multiple threads. // from multiple threads.
class Comparator { class Comparator : public Customizable {
public: public:
Comparator() : timestamp_size_(0) {} Comparator() : timestamp_size_(0) {}
@ -37,7 +38,11 @@ class Comparator {
virtual ~Comparator() {} virtual ~Comparator() {}
static Status CreateFromString(const ConfigOptions& opts,
const std::string& id,
const Comparator** comp);
static const char* Type() { return "Comparator"; } static const char* Type() { return "Comparator"; }
// Three-way comparison. Returns value: // Three-way comparison. Returns value:
// < 0 iff "a" < "b", // < 0 iff "a" < "b",
// == 0 iff "a" == "b", // == 0 iff "a" == "b",

View File

@ -8,6 +8,7 @@
#pragma once #pragma once
#include <atomic>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
@ -269,7 +270,7 @@ class Configurable {
protected: protected:
// True once the object is prepared. Once the object is prepared, only // True once the object is prepared. Once the object is prepared, only
// mutable options can be configured. // mutable options can be configured.
bool prepared_; std::atomic<bool> prepared_;
// Returns the raw pointer for the associated named option. // Returns the raw pointer for the associated named option.
// The name is typically the name of an option registered via the // The name is typically the name of an option registered via the

View File

@ -35,7 +35,6 @@ enum class OptionType {
kCompactionPri, kCompactionPri,
kSliceTransform, kSliceTransform,
kCompressionType, kCompressionType,
kComparator,
kCompactionFilter, kCompactionFilter,
kCompactionFilterFactory, kCompactionFilterFactory,
kCompactionStopStyle, kCompactionStopStyle,

View File

@ -535,22 +535,30 @@ static std::unordered_map<std::string, OptionTypeInfo>
OptionVerificationType::kNormal, OptionTypeFlags::kNone, OptionVerificationType::kNormal, OptionTypeFlags::kNone,
{0, OptionType::kCompressionType})}, {0, OptionType::kCompressionType})},
{"comparator", {"comparator",
{offset_of(&ImmutableCFOptions::user_comparator), OptionTypeInfo::AsCustomRawPtr<const Comparator>(
OptionType::kComparator, OptionVerificationType::kByName, offset_of(&ImmutableCFOptions::user_comparator),
OptionTypeFlags::kCompareLoose, OptionVerificationType::kByName, OptionTypeFlags::kCompareLoose,
// Parses the string and sets the corresponding comparator // Serializes a Comparator
[](const ConfigOptions& opts, const std::string& /*name*/, [](const ConfigOptions& /*opts*/, const std::string&,
const std::string& value, void* addr) { const void* addr, std::string* value) {
auto old_comparator = static_cast<const Comparator**>(addr); // it's a const pointer of const Comparator*
const Comparator* new_comparator = *old_comparator; const auto* ptr = static_cast<const Comparator* const*>(addr);
Status status =
opts.registry->NewStaticObject(value, &new_comparator); // Since the user-specified comparator will be wrapped by
if (status.ok()) { // InternalKeyComparator, we should persist the user-specified
*old_comparator = new_comparator; // one instead of InternalKeyComparator.
return status; if (*ptr == nullptr) {
*value = kNullptrString;
} else {
const Comparator* root_comp = (*ptr)->GetRootComparator();
if (root_comp == nullptr) {
root_comp = (*ptr);
}
*value = root_comp->Name();
} }
return Status::OK(); return Status::OK();
}}}, },
/* Use the default match function*/ nullptr)},
{"memtable_insert_with_hint_prefix_extractor", {"memtable_insert_with_hint_prefix_extractor",
{offset_of( {offset_of(
&ImmutableCFOptions::memtable_insert_with_hint_prefix_extractor), &ImmutableCFOptions::memtable_insert_with_hint_prefix_extractor),

View File

@ -707,6 +707,15 @@ static int RegisterTestObjects(ObjectLibrary& library,
guard->reset(new mock::MockTableFactory()); guard->reset(new mock::MockTableFactory());
return guard->get(); return guard->get();
}); });
library.Register<const Comparator>(
test::SimpleSuffixReverseComparator::kClassName(),
[](const std::string& /*uri*/,
std::unique_ptr<const Comparator>* /*guard*/,
std::string* /* errmsg */) {
static test::SimpleSuffixReverseComparator ssrc;
return &ssrc;
});
return static_cast<int>(library.GetFactoryCount(&num_types)); return static_cast<int>(library.GetFactoryCount(&num_types));
} }
@ -716,6 +725,7 @@ static int RegisterLocalObjects(ObjectLibrary& library,
// Load any locally defined objects here // Load any locally defined objects here
return static_cast<int>(library.GetFactoryCount(&num_types)); return static_cast<int>(library.GetFactoryCount(&num_types));
} }
#endif // !ROCKSDB_LITE
class LoadCustomizableTest : public testing::Test { class LoadCustomizableTest : public testing::Test {
public: public:
@ -755,7 +765,31 @@ TEST_F(LoadCustomizableTest, LoadTableFactoryTest) {
ASSERT_STREQ(factory->Name(), "MockTable"); ASSERT_STREQ(factory->Name(), "MockTable");
} }
} }
#endif // !ROCKSDB_LITE
TEST_F(LoadCustomizableTest, LoadComparatorTest) {
const Comparator* bytewise = BytewiseComparator();
const Comparator* reverse = ReverseBytewiseComparator();
const Comparator* result = nullptr;
ASSERT_NOK(Comparator::CreateFromString(
config_options_, test::SimpleSuffixReverseComparator::kClassName(),
&result));
ASSERT_OK(
Comparator::CreateFromString(config_options_, bytewise->Name(), &result));
ASSERT_EQ(result, bytewise);
ASSERT_OK(
Comparator::CreateFromString(config_options_, reverse->Name(), &result));
ASSERT_EQ(result, reverse);
if (RegisterTests("Test")) {
ASSERT_OK(Comparator::CreateFromString(
config_options_, test::SimpleSuffixReverseComparator::kClassName(),
&result));
ASSERT_NE(result, nullptr);
ASSERT_STREQ(result->Name(),
test::SimpleSuffixReverseComparator::kClassName());
}
}
} // namespace ROCKSDB_NAMESPACE } // namespace ROCKSDB_NAMESPACE
int main(int argc, char** argv) { int main(int argc, char** argv) {

View File

@ -562,23 +562,6 @@ bool SerializeSingleOptionHelper(const void* opt_address,
: kNullptrString; : kNullptrString;
break; break;
} }
case OptionType::kComparator: {
// it's a const pointer of const Comparator*
const auto* ptr = static_cast<const Comparator* const*>(opt_address);
// Since the user-specified comparator will be wrapped by
// InternalKeyComparator, we should persist the user-specified one
// instead of InternalKeyComparator.
if (*ptr == nullptr) {
*value = kNullptrString;
} else {
const Comparator* root_comp = (*ptr)->GetRootComparator();
if (root_comp == nullptr) {
root_comp = (*ptr);
}
*value = root_comp->Name();
}
break;
}
case OptionType::kCompactionFilter: { case OptionType::kCompactionFilter: {
// it's a const pointer of const CompactionFilter* // it's a const pointer of const CompactionFilter*
const auto* ptr = const auto* ptr =

View File

@ -98,10 +98,8 @@ class PlainInternalKeyComparator : public InternalKeyComparator {
class SimpleSuffixReverseComparator : public Comparator { class SimpleSuffixReverseComparator : public Comparator {
public: public:
SimpleSuffixReverseComparator() {} SimpleSuffixReverseComparator() {}
static const char* kClassName() { return "SimpleSuffixReverseComparator"; }
virtual const char* Name() const override { virtual const char* Name() const override { return kClassName(); }
return "SimpleSuffixReverseComparator";
}
virtual int Compare(const Slice& a, const Slice& b) const override { virtual int Compare(const Slice& a, const Slice& b) const override {
Slice prefix_a = Slice(a.data(), 8); Slice prefix_a = Slice(a.data(), 8);

View File

@ -8,11 +8,17 @@
// found in the LICENSE file. See the AUTHORS file for names of contributors. // found in the LICENSE file. See the AUTHORS file for names of contributors.
#include "rocksdb/comparator.h" #include "rocksdb/comparator.h"
#include <stdint.h> #include <stdint.h>
#include <algorithm> #include <algorithm>
#include <memory> #include <memory>
#include <mutex>
#include "options/configurable_helper.h"
#include "port/port.h" #include "port/port.h"
#include "rocksdb/slice.h" #include "rocksdb/slice.h"
#include "rocksdb/utilities/object_registry.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
@ -20,8 +26,8 @@ namespace {
class BytewiseComparatorImpl : public Comparator { class BytewiseComparatorImpl : public Comparator {
public: public:
BytewiseComparatorImpl() { } BytewiseComparatorImpl() { }
static const char* kClassName() { return "leveldb.BytewiseComparator"; }
const char* Name() const override { return "leveldb.BytewiseComparator"; } const char* Name() const override { return kClassName(); }
int Compare(const Slice& a, const Slice& b) const override { int Compare(const Slice& a, const Slice& b) const override {
return a.compare(b); return a.compare(b);
@ -139,9 +145,10 @@ class ReverseBytewiseComparatorImpl : public BytewiseComparatorImpl {
public: public:
ReverseBytewiseComparatorImpl() { } ReverseBytewiseComparatorImpl() { }
const char* Name() const override { static const char* kClassName() {
return "rocksdb.ReverseBytewiseComparator"; return "rocksdb.ReverseBytewiseComparator";
} }
const char* Name() const override { return kClassName(); }
int Compare(const Slice& a, const Slice& b) const override { int Compare(const Slice& a, const Slice& b) const override {
return -a.compare(b); return -a.compare(b);
@ -220,4 +227,77 @@ const Comparator* ReverseBytewiseComparator() {
return &rbytewise; return &rbytewise;
} }
#ifndef ROCKSDB_LITE
static int RegisterBuiltinComparators(ObjectLibrary& library,
const std::string& /*arg*/) {
library.Register<const Comparator>(
BytewiseComparatorImpl::kClassName(),
[](const std::string& /*uri*/,
std::unique_ptr<const Comparator>* /*guard */,
std::string* /* errmsg */) { return BytewiseComparator(); });
library.Register<const Comparator>(
ReverseBytewiseComparatorImpl::kClassName(),
[](const std::string& /*uri*/,
std::unique_ptr<const Comparator>* /*guard */,
std::string* /* errmsg */) { return ReverseBytewiseComparator(); });
return 2;
}
#endif // ROCKSDB_LITE
Status Comparator::CreateFromString(const ConfigOptions& config_options,
const std::string& value,
const Comparator** result) {
#ifndef ROCKSDB_LITE
static std::once_flag once;
std::call_once(once, [&]() {
RegisterBuiltinComparators(*(ObjectLibrary::Default().get()), "");
});
#endif // ROCKSDB_LITE
std::string id;
std::unordered_map<std::string, std::string> opt_map;
Status status =
ConfigurableHelper::GetOptionsMap(value, *result, &id, &opt_map);
if (!status.ok()) { // GetOptionsMap failed
return status;
}
std::string curr_opts;
#ifndef ROCKSDB_LITE
if (*result != nullptr && (*result)->GetId() == id) {
// Try to get the existing options, ignoring any errors
ConfigOptions embedded = config_options;
embedded.delimiter = ";";
(*result)->GetOptionString(embedded, &curr_opts).PermitUncheckedError();
}
#endif
if (id == BytewiseComparatorImpl::kClassName()) {
*result = BytewiseComparator();
} else if (id == ReverseBytewiseComparatorImpl::kClassName()) {
*result = ReverseBytewiseComparator();
} else if (value.empty()) {
// No Id and no options. Clear the object
*result = nullptr;
return Status::OK();
} else if (id.empty()) { // We have no Id but have options. Not good
return Status::NotSupported("Cannot reset object ", id);
} else {
#ifndef ROCKSDB_LITE
status = config_options.registry->NewStaticObject(id, result);
#else
status = Status::NotSupported("Cannot load object in LITE mode ", id);
#endif // ROCKSDB_LITE
if (!status.ok()) {
if (config_options.ignore_unsupported_options &&
status.IsNotSupported()) {
return Status::OK();
} else {
return status;
}
} else if (!curr_opts.empty() || !opt_map.empty()) {
Comparator* comparator = const_cast<Comparator*>(*result);
status = ConfigurableHelper::ConfigureNewObject(
config_options, comparator, id, curr_opts, opt_map);
}
}
return status;
}
} // namespace ROCKSDB_NAMESPACE } // namespace ROCKSDB_NAMESPACE