1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5
6 #ifndef ROCKSDB_LITE
7
8 #include <cinttypes>
9
10 #include <cctype>
11 #include <unordered_map>
12
13 #include "options/options_parser.h"
14 #include "rocksdb/db.h"
15 #include "rocksdb/table.h"
16 #include "rocksdb/utilities/options_util.h"
17 #include "test_util/testharness.h"
18 #include "test_util/testutil.h"
19 #include "util/random.h"
20
21 #ifndef GFLAGS
22 bool FLAGS_enable_print = false;
23 #else
24 #include "util/gflags_compat.h"
25 using GFLAGS_NAMESPACE::ParseCommandLineFlags;
26 DEFINE_bool(enable_print, false, "Print options generated to console.");
27 #endif // GFLAGS
28
29 namespace rocksdb {
30 class OptionsUtilTest : public testing::Test {
31 public:
OptionsUtilTest()32 OptionsUtilTest() : rnd_(0xFB) {
33 env_.reset(new test::StringEnv(Env::Default()));
34 fs_.reset(new LegacyFileSystemWrapper(env_.get()));
35 dbname_ = test::PerThreadDBPath("options_util_test");
36 }
37
38 protected:
39 std::unique_ptr<test::StringEnv> env_;
40 std::unique_ptr<LegacyFileSystemWrapper> fs_;
41 std::string dbname_;
42 Random rnd_;
43 };
44
IsBlockBasedTableFactory(TableFactory * tf)45 bool IsBlockBasedTableFactory(TableFactory* tf) {
46 return tf->Name() == BlockBasedTableFactory().Name();
47 }
48
TEST_F(OptionsUtilTest,SaveAndLoad)49 TEST_F(OptionsUtilTest, SaveAndLoad) {
50 const size_t kCFCount = 5;
51
52 DBOptions db_opt;
53 std::vector<std::string> cf_names;
54 std::vector<ColumnFamilyOptions> cf_opts;
55 test::RandomInitDBOptions(&db_opt, &rnd_);
56 for (size_t i = 0; i < kCFCount; ++i) {
57 cf_names.push_back(i == 0 ? kDefaultColumnFamilyName
58 : test::RandomName(&rnd_, 10));
59 cf_opts.emplace_back();
60 test::RandomInitCFOptions(&cf_opts.back(), db_opt, &rnd_);
61 }
62
63 const std::string kFileName = "OPTIONS-123456";
64 PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName, fs_.get());
65
66 DBOptions loaded_db_opt;
67 std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
68 ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
69 &loaded_cf_descs));
70
71 ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt));
72 test::RandomInitDBOptions(&db_opt, &rnd_);
73 ASSERT_NOK(RocksDBOptionsParser::VerifyDBOptions(db_opt, loaded_db_opt));
74
75 for (size_t i = 0; i < kCFCount; ++i) {
76 ASSERT_EQ(cf_names[i], loaded_cf_descs[i].name);
77 ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
78 cf_opts[i], loaded_cf_descs[i].options));
79 if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) {
80 ASSERT_OK(RocksDBOptionsParser::VerifyTableFactory(
81 cf_opts[i].table_factory.get(),
82 loaded_cf_descs[i].options.table_factory.get()));
83 }
84 test::RandomInitCFOptions(&cf_opts[i], db_opt, &rnd_);
85 ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions(
86 cf_opts[i], loaded_cf_descs[i].options));
87 }
88
89 for (size_t i = 0; i < kCFCount; ++i) {
90 if (cf_opts[i].compaction_filter) {
91 delete cf_opts[i].compaction_filter;
92 }
93 }
94 }
95
TEST_F(OptionsUtilTest,SaveAndLoadWithCacheCheck)96 TEST_F(OptionsUtilTest, SaveAndLoadWithCacheCheck) {
97 // creating db
98 DBOptions db_opt;
99 db_opt.create_if_missing = true;
100 // initialize BlockBasedTableOptions
101 std::shared_ptr<Cache> cache = NewLRUCache(1 * 1024);
102 BlockBasedTableOptions bbt_opts;
103 bbt_opts.block_size = 32 * 1024;
104 // saving cf options
105 std::vector<ColumnFamilyOptions> cf_opts;
106 ColumnFamilyOptions default_column_family_opt = ColumnFamilyOptions();
107 default_column_family_opt.table_factory.reset(
108 NewBlockBasedTableFactory(bbt_opts));
109 cf_opts.push_back(default_column_family_opt);
110
111 ColumnFamilyOptions cf_opt_sample = ColumnFamilyOptions();
112 cf_opt_sample.table_factory.reset(NewBlockBasedTableFactory(bbt_opts));
113 cf_opts.push_back(cf_opt_sample);
114
115 ColumnFamilyOptions cf_opt_plain_table_opt = ColumnFamilyOptions();
116 cf_opt_plain_table_opt.table_factory.reset(NewPlainTableFactory());
117 cf_opts.push_back(cf_opt_plain_table_opt);
118
119 std::vector<std::string> cf_names;
120 cf_names.push_back(kDefaultColumnFamilyName);
121 cf_names.push_back("cf_sample");
122 cf_names.push_back("cf_plain_table_sample");
123 // Saving DB in file
124 const std::string kFileName = "OPTIONS-LOAD_CACHE_123456";
125 PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName, fs_.get());
126 DBOptions loaded_db_opt;
127 std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
128 ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
129 &loaded_cf_descs, false, &cache));
130 for (size_t i = 0; i < loaded_cf_descs.size(); i++) {
131 if (IsBlockBasedTableFactory(cf_opts[i].table_factory.get())) {
132 auto* loaded_bbt_opt = reinterpret_cast<BlockBasedTableOptions*>(
133 loaded_cf_descs[i].options.table_factory->GetOptions());
134 // Expect the same cache will be loaded
135 if (loaded_bbt_opt != nullptr) {
136 ASSERT_EQ(loaded_bbt_opt->block_cache.get(), cache.get());
137 }
138 }
139 }
140 }
141
142 namespace {
143 class DummyTableFactory : public TableFactory {
144 public:
DummyTableFactory()145 DummyTableFactory() {}
~DummyTableFactory()146 ~DummyTableFactory() override {}
147
Name() const148 const char* Name() const override { return "DummyTableFactory"; }
149
NewTableReader(const TableReaderOptions &,std::unique_ptr<RandomAccessFileReader> &&,uint64_t,std::unique_ptr<TableReader> *,bool) const150 Status NewTableReader(
151 const TableReaderOptions& /*table_reader_options*/,
152 std::unique_ptr<RandomAccessFileReader>&& /*file*/,
153 uint64_t /*file_size*/, std::unique_ptr<TableReader>* /*table_reader*/,
154 bool /*prefetch_index_and_filter_in_cache*/) const override {
155 return Status::NotSupported();
156 }
157
NewTableBuilder(const TableBuilderOptions &,uint32_t,WritableFileWriter *) const158 TableBuilder* NewTableBuilder(
159 const TableBuilderOptions& /*table_builder_options*/,
160 uint32_t /*column_family_id*/,
161 WritableFileWriter* /*file*/) const override {
162 return nullptr;
163 }
164
SanitizeOptions(const DBOptions &,const ColumnFamilyOptions &) const165 Status SanitizeOptions(
166 const DBOptions& /*db_opts*/,
167 const ColumnFamilyOptions& /*cf_opts*/) const override {
168 return Status::NotSupported();
169 }
170
GetPrintableTableOptions() const171 std::string GetPrintableTableOptions() const override { return ""; }
172
GetOptionString(std::string *,const std::string &) const173 Status GetOptionString(std::string* /*opt_string*/,
174 const std::string& /*delimiter*/) const override {
175 return Status::OK();
176 }
177 };
178
179 class DummyMergeOperator : public MergeOperator {
180 public:
DummyMergeOperator()181 DummyMergeOperator() {}
~DummyMergeOperator()182 ~DummyMergeOperator() override {}
183
FullMergeV2(const MergeOperationInput &,MergeOperationOutput *) const184 bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
185 MergeOperationOutput* /*merge_out*/) const override {
186 return false;
187 }
188
PartialMergeMulti(const Slice &,const std::deque<Slice> &,std::string *,Logger *) const189 bool PartialMergeMulti(const Slice& /*key*/,
190 const std::deque<Slice>& /*operand_list*/,
191 std::string* /*new_value*/,
192 Logger* /*logger*/) const override {
193 return false;
194 }
195
Name() const196 const char* Name() const override { return "DummyMergeOperator"; }
197 };
198
199 class DummySliceTransform : public SliceTransform {
200 public:
DummySliceTransform()201 DummySliceTransform() {}
~DummySliceTransform()202 ~DummySliceTransform() override {}
203
204 // Return the name of this transformation.
Name() const205 const char* Name() const override { return "DummySliceTransform"; }
206
207 // transform a src in domain to a dst in the range
Transform(const Slice & src) const208 Slice Transform(const Slice& src) const override { return src; }
209
210 // determine whether this is a valid src upon the function applies
InDomain(const Slice &) const211 bool InDomain(const Slice& /*src*/) const override { return false; }
212
213 // determine whether dst=Transform(src) for some src
InRange(const Slice &) const214 bool InRange(const Slice& /*dst*/) const override { return false; }
215 };
216
217 } // namespace
218
TEST_F(OptionsUtilTest,SanityCheck)219 TEST_F(OptionsUtilTest, SanityCheck) {
220 DBOptions db_opt;
221 std::vector<ColumnFamilyDescriptor> cf_descs;
222 const size_t kCFCount = 5;
223 for (size_t i = 0; i < kCFCount; ++i) {
224 cf_descs.emplace_back();
225 cf_descs.back().name =
226 (i == 0) ? kDefaultColumnFamilyName : test::RandomName(&rnd_, 10);
227
228 cf_descs.back().options.table_factory.reset(NewBlockBasedTableFactory());
229 // Assign non-null values to prefix_extractors except the first cf.
230 cf_descs.back().options.prefix_extractor.reset(
231 i != 0 ? test::RandomSliceTransform(&rnd_) : nullptr);
232 cf_descs.back().options.merge_operator.reset(
233 test::RandomMergeOperator(&rnd_));
234 }
235
236 db_opt.create_missing_column_families = true;
237 db_opt.create_if_missing = true;
238
239 DestroyDB(dbname_, Options(db_opt, cf_descs[0].options));
240 DB* db;
241 std::vector<ColumnFamilyHandle*> handles;
242 // open and persist the options
243 ASSERT_OK(DB::Open(db_opt, dbname_, cf_descs, &handles, &db));
244
245 // close the db
246 for (auto* handle : handles) {
247 delete handle;
248 }
249 delete db;
250
251 // perform sanity check
252 ASSERT_OK(
253 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
254
255 ASSERT_GE(kCFCount, 5);
256 // merge operator
257 {
258 std::shared_ptr<MergeOperator> merge_op =
259 cf_descs[0].options.merge_operator;
260
261 ASSERT_NE(merge_op.get(), nullptr);
262 cf_descs[0].options.merge_operator.reset();
263 ASSERT_NOK(
264 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
265
266 cf_descs[0].options.merge_operator.reset(new DummyMergeOperator());
267 ASSERT_NOK(
268 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
269
270 cf_descs[0].options.merge_operator = merge_op;
271 ASSERT_OK(
272 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
273 }
274
275 // prefix extractor
276 {
277 std::shared_ptr<const SliceTransform> prefix_extractor =
278 cf_descs[1].options.prefix_extractor;
279
280 // It's okay to set prefix_extractor to nullptr.
281 ASSERT_NE(prefix_extractor, nullptr);
282 cf_descs[1].options.prefix_extractor.reset();
283 ASSERT_OK(
284 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
285
286 cf_descs[1].options.prefix_extractor.reset(new DummySliceTransform());
287 ASSERT_OK(
288 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
289
290 cf_descs[1].options.prefix_extractor = prefix_extractor;
291 ASSERT_OK(
292 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
293 }
294
295 // prefix extractor nullptr case
296 {
297 std::shared_ptr<const SliceTransform> prefix_extractor =
298 cf_descs[0].options.prefix_extractor;
299
300 // It's okay to set prefix_extractor to nullptr.
301 ASSERT_EQ(prefix_extractor, nullptr);
302 cf_descs[0].options.prefix_extractor.reset();
303 ASSERT_OK(
304 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
305
306 // It's okay to change prefix_extractor from nullptr to non-nullptr
307 cf_descs[0].options.prefix_extractor.reset(new DummySliceTransform());
308 ASSERT_OK(
309 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
310
311 cf_descs[0].options.prefix_extractor = prefix_extractor;
312 ASSERT_OK(
313 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
314 }
315
316 // comparator
317 {
318 test::SimpleSuffixReverseComparator comparator;
319
320 auto* prev_comparator = cf_descs[2].options.comparator;
321 cf_descs[2].options.comparator = &comparator;
322 ASSERT_NOK(
323 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
324
325 cf_descs[2].options.comparator = prev_comparator;
326 ASSERT_OK(
327 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
328 }
329
330 // table factory
331 {
332 std::shared_ptr<TableFactory> table_factory =
333 cf_descs[3].options.table_factory;
334
335 ASSERT_NE(table_factory, nullptr);
336 cf_descs[3].options.table_factory.reset(new DummyTableFactory());
337 ASSERT_NOK(
338 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
339
340 cf_descs[3].options.table_factory = table_factory;
341 ASSERT_OK(
342 CheckOptionsCompatibility(dbname_, Env::Default(), db_opt, cf_descs));
343 }
344 }
345
346 } // namespace rocksdb
347
main(int argc,char ** argv)348 int main(int argc, char** argv) {
349 ::testing::InitGoogleTest(&argc, argv);
350 #ifdef GFLAGS
351 ParseCommandLineFlags(&argc, &argv, true);
352 #endif // GFLAGS
353 return RUN_ALL_TESTS();
354 }
355
356 #else
357 #include <cstdio>
358
main(int,char **)359 int main(int /*argc*/, char** /*argv*/) {
360 printf("Skipped in RocksDBLite as utilities are not supported.\n");
361 return 0;
362 }
363 #endif // !ROCKSDB_LITE
364