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