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 "rocksdb/utilities/options_util.h"
9 
10 #include <cctype>
11 #include <cinttypes>
12 #include <unordered_map>
13 
14 #include "env/mock_env.h"
15 #include "file/filename.h"
16 #include "options/options_parser.h"
17 #include "rocksdb/convenience.h"
18 #include "rocksdb/db.h"
19 #include "rocksdb/table.h"
20 #include "test_util/testharness.h"
21 #include "test_util/testutil.h"
22 #include "util/random.h"
23 
24 #ifndef GFLAGS
25 bool FLAGS_enable_print = false;
26 #else
27 #include "util/gflags_compat.h"
28 using GFLAGS_NAMESPACE::ParseCommandLineFlags;
29 DEFINE_bool(enable_print, false, "Print options generated to console.");
30 #endif  // GFLAGS
31 
32 namespace ROCKSDB_NAMESPACE {
33 class OptionsUtilTest : public testing::Test {
34  public:
OptionsUtilTest()35   OptionsUtilTest() : rnd_(0xFB) {
36     env_.reset(NewMemEnv(Env::Default()));
37     dbname_ = test::PerThreadDBPath("options_util_test");
38   }
39 
40  protected:
41   std::unique_ptr<Env> env_;
42   std::string dbname_;
43   Random rnd_;
44 };
45 
TEST_F(OptionsUtilTest,SaveAndLoad)46 TEST_F(OptionsUtilTest, SaveAndLoad) {
47   const size_t kCFCount = 5;
48 
49   DBOptions db_opt;
50   std::vector<std::string> cf_names;
51   std::vector<ColumnFamilyOptions> cf_opts;
52   test::RandomInitDBOptions(&db_opt, &rnd_);
53   for (size_t i = 0; i < kCFCount; ++i) {
54     cf_names.push_back(i == 0 ? kDefaultColumnFamilyName
55                               : test::RandomName(&rnd_, 10));
56     cf_opts.emplace_back();
57     test::RandomInitCFOptions(&cf_opts.back(), db_opt, &rnd_);
58   }
59 
60   const std::string kFileName = "OPTIONS-123456";
61   ASSERT_OK(PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName,
62                                   env_->GetFileSystem().get()));
63 
64   DBOptions loaded_db_opt;
65   std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
66   ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
67                                 &loaded_cf_descs));
68   ConfigOptions exact;
69   exact.sanity_level = ConfigOptions::kSanityLevelExactMatch;
70   ASSERT_OK(
71       RocksDBOptionsParser::VerifyDBOptions(exact, db_opt, loaded_db_opt));
72   test::RandomInitDBOptions(&db_opt, &rnd_);
73   ASSERT_NOK(
74       RocksDBOptionsParser::VerifyDBOptions(exact, db_opt, loaded_db_opt));
75 
76   for (size_t i = 0; i < kCFCount; ++i) {
77     ASSERT_EQ(cf_names[i], loaded_cf_descs[i].name);
78     ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(
79         exact, cf_opts[i], loaded_cf_descs[i].options));
80     ASSERT_OK(RocksDBOptionsParser::VerifyTableFactory(
81         exact, cf_opts[i].table_factory.get(),
82         loaded_cf_descs[i].options.table_factory.get()));
83     test::RandomInitCFOptions(&cf_opts[i], db_opt, &rnd_);
84     ASSERT_NOK(RocksDBOptionsParser::VerifyCFOptions(
85         exact, cf_opts[i], loaded_cf_descs[i].options));
86   }
87 
88   ASSERT_OK(DestroyDB(dbname_, Options(db_opt, cf_opts[0])));
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   ASSERT_OK(PersistRocksDBOptions(db_opt, cf_names, cf_opts, kFileName,
126                                   env_->GetFileSystem().get()));
127   DBOptions loaded_db_opt;
128   std::vector<ColumnFamilyDescriptor> loaded_cf_descs;
129 
130   ConfigOptions config_options;
131   config_options.ignore_unknown_options = false;
132   config_options.input_strings_escaped = true;
133   config_options.env = env_.get();
134   ASSERT_OK(LoadOptionsFromFile(config_options, kFileName, &loaded_db_opt,
135                                 &loaded_cf_descs, &cache));
136   for (size_t i = 0; i < loaded_cf_descs.size(); i++) {
137     auto* loaded_bbt_opt =
138         loaded_cf_descs[i]
139             .options.table_factory->GetOptions<BlockBasedTableOptions>();
140     // Expect the same cache will be loaded
141     if (loaded_bbt_opt != nullptr) {
142       ASSERT_EQ(loaded_bbt_opt->block_cache.get(), cache.get());
143     }
144   }
145 
146   // Test the old interface
147   ASSERT_OK(LoadOptionsFromFile(kFileName, env_.get(), &loaded_db_opt,
148                                 &loaded_cf_descs, false, &cache));
149   for (size_t i = 0; i < loaded_cf_descs.size(); i++) {
150     auto* loaded_bbt_opt =
151         loaded_cf_descs[i]
152             .options.table_factory->GetOptions<BlockBasedTableOptions>();
153     // Expect the same cache will be loaded
154     if (loaded_bbt_opt != nullptr) {
155       ASSERT_EQ(loaded_bbt_opt->block_cache.get(), cache.get());
156     }
157   }
158   ASSERT_OK(DestroyDB(dbname_, Options(loaded_db_opt, cf_opts[0])));
159 }
160 
161 namespace {
162 class DummyTableFactory : public TableFactory {
163  public:
DummyTableFactory()164   DummyTableFactory() {}
~DummyTableFactory()165   ~DummyTableFactory() override {}
166 
Name() const167   const char* Name() const override { return "DummyTableFactory"; }
168 
169   using TableFactory::NewTableReader;
NewTableReader(const ReadOptions &,const TableReaderOptions &,std::unique_ptr<RandomAccessFileReader> &&,uint64_t,std::unique_ptr<TableReader> *,bool) const170   Status NewTableReader(
171       const ReadOptions& /*ro*/,
172       const TableReaderOptions& /*table_reader_options*/,
173       std::unique_ptr<RandomAccessFileReader>&& /*file*/,
174       uint64_t /*file_size*/, std::unique_ptr<TableReader>* /*table_reader*/,
175       bool /*prefetch_index_and_filter_in_cache*/) const override {
176     return Status::NotSupported();
177   }
178 
NewTableBuilder(const TableBuilderOptions &,WritableFileWriter *) const179   TableBuilder* NewTableBuilder(
180       const TableBuilderOptions& /*table_builder_options*/,
181       WritableFileWriter* /*file*/) const override {
182     return nullptr;
183   }
184 
ValidateOptions(const DBOptions &,const ColumnFamilyOptions &) const185   Status ValidateOptions(
186       const DBOptions& /*db_opts*/,
187       const ColumnFamilyOptions& /*cf_opts*/) const override {
188     return Status::NotSupported();
189   }
190 
GetPrintableOptions() const191   std::string GetPrintableOptions() const override { return ""; }
192 };
193 
194 class DummyMergeOperator : public MergeOperator {
195  public:
DummyMergeOperator()196   DummyMergeOperator() {}
~DummyMergeOperator()197   ~DummyMergeOperator() override {}
198 
FullMergeV2(const MergeOperationInput &,MergeOperationOutput *) const199   bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
200                    MergeOperationOutput* /*merge_out*/) const override {
201     return false;
202   }
203 
PartialMergeMulti(const Slice &,const std::deque<Slice> &,std::string *,Logger *) const204   bool PartialMergeMulti(const Slice& /*key*/,
205                          const std::deque<Slice>& /*operand_list*/,
206                          std::string* /*new_value*/,
207                          Logger* /*logger*/) const override {
208     return false;
209   }
210 
Name() const211   const char* Name() const override { return "DummyMergeOperator"; }
212 };
213 
214 class DummySliceTransform : public SliceTransform {
215  public:
DummySliceTransform()216   DummySliceTransform() {}
~DummySliceTransform()217   ~DummySliceTransform() override {}
218 
219   // Return the name of this transformation.
Name() const220   const char* Name() const override { return "DummySliceTransform"; }
221 
222   // transform a src in domain to a dst in the range
Transform(const Slice & src) const223   Slice Transform(const Slice& src) const override { return src; }
224 
225   // determine whether this is a valid src upon the function applies
InDomain(const Slice &) const226   bool InDomain(const Slice& /*src*/) const override { return false; }
227 
228   // determine whether dst=Transform(src) for some src
InRange(const Slice &) const229   bool InRange(const Slice& /*dst*/) const override { return false; }
230 };
231 
232 }  // namespace
233 
TEST_F(OptionsUtilTest,SanityCheck)234 TEST_F(OptionsUtilTest, SanityCheck) {
235   DBOptions db_opt;
236   std::vector<ColumnFamilyDescriptor> cf_descs;
237   const size_t kCFCount = 5;
238   for (size_t i = 0; i < kCFCount; ++i) {
239     cf_descs.emplace_back();
240     cf_descs.back().name =
241         (i == 0) ? kDefaultColumnFamilyName : test::RandomName(&rnd_, 10);
242 
243     cf_descs.back().options.table_factory.reset(NewBlockBasedTableFactory());
244     // Assign non-null values to prefix_extractors except the first cf.
245     cf_descs.back().options.prefix_extractor.reset(
246         i != 0 ? test::RandomSliceTransform(&rnd_) : nullptr);
247     cf_descs.back().options.merge_operator.reset(
248         test::RandomMergeOperator(&rnd_));
249   }
250 
251   db_opt.create_missing_column_families = true;
252   db_opt.create_if_missing = true;
253 
254   ASSERT_OK(DestroyDB(dbname_, Options(db_opt, cf_descs[0].options)));
255   DB* db;
256   std::vector<ColumnFamilyHandle*> handles;
257   // open and persist the options
258   ASSERT_OK(DB::Open(db_opt, dbname_, cf_descs, &handles, &db));
259 
260   // close the db
261   for (auto* handle : handles) {
262     delete handle;
263   }
264   delete db;
265 
266   ConfigOptions config_options;
267   config_options.ignore_unknown_options = false;
268   config_options.input_strings_escaped = true;
269   config_options.sanity_level = ConfigOptions::kSanityLevelLooselyCompatible;
270   // perform sanity check
271   ASSERT_OK(
272       CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
273 
274   ASSERT_GE(kCFCount, 5);
275   // merge operator
276   {
277     std::shared_ptr<MergeOperator> merge_op =
278         cf_descs[0].options.merge_operator;
279 
280     ASSERT_NE(merge_op.get(), nullptr);
281     cf_descs[0].options.merge_operator.reset();
282     ASSERT_NOK(
283         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
284 
285     cf_descs[0].options.merge_operator.reset(new DummyMergeOperator());
286     ASSERT_NOK(
287         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
288 
289     cf_descs[0].options.merge_operator = merge_op;
290     ASSERT_OK(
291         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
292   }
293 
294   // prefix extractor
295   {
296     std::shared_ptr<const SliceTransform> prefix_extractor =
297         cf_descs[1].options.prefix_extractor;
298 
299     // It's okay to set prefix_extractor to nullptr.
300     ASSERT_NE(prefix_extractor, nullptr);
301     cf_descs[1].options.prefix_extractor.reset();
302     ASSERT_OK(
303         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
304 
305     cf_descs[1].options.prefix_extractor.reset(new DummySliceTransform());
306     ASSERT_OK(
307         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
308 
309     cf_descs[1].options.prefix_extractor = prefix_extractor;
310     ASSERT_OK(
311         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
312   }
313 
314   // prefix extractor nullptr case
315   {
316     std::shared_ptr<const SliceTransform> prefix_extractor =
317         cf_descs[0].options.prefix_extractor;
318 
319     // It's okay to set prefix_extractor to nullptr.
320     ASSERT_EQ(prefix_extractor, nullptr);
321     cf_descs[0].options.prefix_extractor.reset();
322     ASSERT_OK(
323         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
324 
325     // It's okay to change prefix_extractor from nullptr to non-nullptr
326     cf_descs[0].options.prefix_extractor.reset(new DummySliceTransform());
327     ASSERT_OK(
328         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
329 
330     cf_descs[0].options.prefix_extractor = prefix_extractor;
331     ASSERT_OK(
332         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
333   }
334 
335   // comparator
336   {
337     test::SimpleSuffixReverseComparator comparator;
338 
339     auto* prev_comparator = cf_descs[2].options.comparator;
340     cf_descs[2].options.comparator = &comparator;
341     ASSERT_NOK(
342         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
343 
344     cf_descs[2].options.comparator = prev_comparator;
345     ASSERT_OK(
346         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
347   }
348 
349   // table factory
350   {
351     std::shared_ptr<TableFactory> table_factory =
352         cf_descs[3].options.table_factory;
353 
354     ASSERT_NE(table_factory, nullptr);
355     cf_descs[3].options.table_factory.reset(new DummyTableFactory());
356     ASSERT_NOK(
357         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
358 
359     cf_descs[3].options.table_factory = table_factory;
360     ASSERT_OK(
361         CheckOptionsCompatibility(config_options, dbname_, db_opt, cf_descs));
362   }
363   ASSERT_OK(DestroyDB(dbname_, Options(db_opt, cf_descs[0].options)));
364 }
365 
TEST_F(OptionsUtilTest,LatestOptionsNotFound)366 TEST_F(OptionsUtilTest, LatestOptionsNotFound) {
367   std::unique_ptr<Env> env(NewMemEnv(Env::Default()));
368   Status s;
369   Options options;
370   ConfigOptions config_opts;
371   std::vector<ColumnFamilyDescriptor> cf_descs;
372 
373   options.env = env.get();
374   options.create_if_missing = true;
375   config_opts.env = options.env;
376   config_opts.ignore_unknown_options = false;
377 
378   std::vector<std::string> children;
379 
380   std::string options_file_name;
381   ASSERT_OK(DestroyDB(dbname_, options));
382   // First, test where the db directory does not exist
383   ASSERT_NOK(options.env->GetChildren(dbname_, &children));
384 
385   s = GetLatestOptionsFileName(dbname_, options.env, &options_file_name);
386   ASSERT_TRUE(s.IsNotFound());
387   ASSERT_TRUE(s.IsPathNotFound());
388 
389   s = LoadLatestOptions(dbname_, options.env, &options, &cf_descs);
390   ASSERT_TRUE(s.IsNotFound());
391   ASSERT_TRUE(s.IsPathNotFound());
392 
393   s = LoadLatestOptions(config_opts, dbname_, &options, &cf_descs);
394   ASSERT_TRUE(s.IsPathNotFound());
395 
396   s = GetLatestOptionsFileName(dbname_, options.env, &options_file_name);
397   ASSERT_TRUE(s.IsNotFound());
398   ASSERT_TRUE(s.IsPathNotFound());
399 
400   // Second, test where the db directory exists but is empty
401   ASSERT_OK(options.env->CreateDir(dbname_));
402 
403   s = GetLatestOptionsFileName(dbname_, options.env, &options_file_name);
404   ASSERT_TRUE(s.IsNotFound());
405   ASSERT_TRUE(s.IsPathNotFound());
406 
407   s = LoadLatestOptions(dbname_, options.env, &options, &cf_descs);
408   ASSERT_TRUE(s.IsNotFound());
409   ASSERT_TRUE(s.IsPathNotFound());
410 
411   // Finally, test where a file exists but is not an "Options" file
412   std::unique_ptr<WritableFile> file;
413   ASSERT_OK(
414       options.env->NewWritableFile(dbname_ + "/temp.txt", &file, EnvOptions()));
415   ASSERT_OK(file->Close());
416   s = GetLatestOptionsFileName(dbname_, options.env, &options_file_name);
417   ASSERT_TRUE(s.IsNotFound());
418   ASSERT_TRUE(s.IsPathNotFound());
419 
420   s = LoadLatestOptions(config_opts, dbname_, &options, &cf_descs);
421   ASSERT_TRUE(s.IsNotFound());
422   ASSERT_TRUE(s.IsPathNotFound());
423   ASSERT_OK(options.env->DeleteFile(dbname_ + "/temp.txt"));
424   ASSERT_OK(options.env->DeleteDir(dbname_));
425 }
426 
TEST_F(OptionsUtilTest,LoadLatestOptions)427 TEST_F(OptionsUtilTest, LoadLatestOptions) {
428   Options options;
429   options.OptimizeForSmallDb();
430   ColumnFamilyDescriptor cf_desc;
431   ConfigOptions config_opts;
432   DBOptions db_opts;
433   std::vector<ColumnFamilyDescriptor> cf_descs;
434   std::vector<ColumnFamilyHandle*> handles;
435   DB* db;
436   options.create_if_missing = true;
437 
438   ASSERT_OK(DestroyDB(dbname_, options));
439 
440   cf_descs.emplace_back();
441   cf_descs.back().name = kDefaultColumnFamilyName;
442   cf_descs.back().options.table_factory.reset(NewBlockBasedTableFactory());
443   cf_descs.emplace_back();
444   cf_descs.back().name = "Plain";
445   cf_descs.back().options.table_factory.reset(NewPlainTableFactory());
446   db_opts.create_missing_column_families = true;
447   db_opts.create_if_missing = true;
448 
449   // open and persist the options
450   ASSERT_OK(DB::Open(db_opts, dbname_, cf_descs, &handles, &db));
451 
452   std::string options_file_name;
453   std::string new_options_file;
454 
455   ASSERT_OK(GetLatestOptionsFileName(dbname_, options.env, &options_file_name));
456   ASSERT_OK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
457   ASSERT_EQ(cf_descs.size(), 2U);
458   ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_opts,
459                                                   db->GetDBOptions(), db_opts));
460   ASSERT_OK(handles[0]->GetDescriptor(&cf_desc));
461   ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_opts, cf_desc.options,
462                                                   cf_descs[0].options));
463   ASSERT_OK(handles[1]->GetDescriptor(&cf_desc));
464   ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_opts, cf_desc.options,
465                                                   cf_descs[1].options));
466 
467   // Now change some of the DBOptions
468   ASSERT_OK(db->SetDBOptions(
469       {{"delayed_write_rate", "1234"}, {"bytes_per_sync", "32768"}}));
470   ASSERT_OK(GetLatestOptionsFileName(dbname_, options.env, &new_options_file));
471   ASSERT_NE(options_file_name, new_options_file);
472   ASSERT_OK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
473   ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_opts,
474                                                   db->GetDBOptions(), db_opts));
475   options_file_name = new_options_file;
476 
477   // Now change some of the ColumnFamilyOptions
478   ASSERT_OK(db->SetOptions(handles[1], {{"write_buffer_size", "32768"}}));
479   ASSERT_OK(GetLatestOptionsFileName(dbname_, options.env, &new_options_file));
480   ASSERT_NE(options_file_name, new_options_file);
481   ASSERT_OK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
482   ASSERT_OK(RocksDBOptionsParser::VerifyDBOptions(config_opts,
483                                                   db->GetDBOptions(), db_opts));
484   ASSERT_OK(handles[0]->GetDescriptor(&cf_desc));
485   ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_opts, cf_desc.options,
486                                                   cf_descs[0].options));
487   ASSERT_OK(handles[1]->GetDescriptor(&cf_desc));
488   ASSERT_OK(RocksDBOptionsParser::VerifyCFOptions(config_opts, cf_desc.options,
489                                                   cf_descs[1].options));
490 
491   // close the db
492   for (auto* handle : handles) {
493     delete handle;
494   }
495   delete db;
496   ASSERT_OK(DestroyDB(dbname_, options, cf_descs));
497 }
498 
WriteOptionsFile(Env * env,const std::string & path,const std::string & options_file,int major,int minor,const std::string & db_opts,const std::string & cf_opts,const std::string & bbt_opts="")499 static void WriteOptionsFile(Env* env, const std::string& path,
500                              const std::string& options_file, int major,
501                              int minor, const std::string& db_opts,
502                              const std::string& cf_opts,
503                              const std::string& bbt_opts = "") {
504   std::string options_file_header =
505       "\n"
506       "[Version]\n"
507       "  rocksdb_version=" +
508       ToString(major) + "." + ToString(minor) +
509       ".0\n"
510       "  options_file_version=1\n";
511 
512   std::unique_ptr<WritableFile> wf;
513   ASSERT_OK(env->NewWritableFile(path + "/" + options_file, &wf, EnvOptions()));
514   ASSERT_OK(
515       wf->Append(options_file_header + "[ DBOptions ]\n" + db_opts + "\n"));
516   ASSERT_OK(wf->Append(
517       "[CFOptions   \"default\"]  # column family must be specified\n" +
518       cf_opts + "\n"));
519   ASSERT_OK(wf->Append("[TableOptions/BlockBasedTable   \"default\"]\n" +
520                        bbt_opts + "\n"));
521   ASSERT_OK(wf->Close());
522 
523   std::string latest_options_file;
524   ASSERT_OK(GetLatestOptionsFileName(path, env, &latest_options_file));
525   ASSERT_EQ(latest_options_file, options_file);
526 }
527 
TEST_F(OptionsUtilTest,BadLatestOptions)528 TEST_F(OptionsUtilTest, BadLatestOptions) {
529   Status s;
530   ConfigOptions config_opts;
531   DBOptions db_opts;
532   std::vector<ColumnFamilyDescriptor> cf_descs;
533   Options options;
534   options.env = env_.get();
535   config_opts.env = env_.get();
536   config_opts.ignore_unknown_options = false;
537   config_opts.delimiter = "\n";
538 
539   ConfigOptions ignore_opts = config_opts;
540   ignore_opts.ignore_unknown_options = true;
541 
542   std::string options_file_name;
543 
544   // Test where the db directory exists but is empty
545   ASSERT_OK(options.env->CreateDir(dbname_));
546   ASSERT_NOK(
547       GetLatestOptionsFileName(dbname_, options.env, &options_file_name));
548   ASSERT_NOK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
549 
550   // Write an options file for a previous major release with an unknown DB
551   // Option
552   WriteOptionsFile(options.env, dbname_, "OPTIONS-0001", ROCKSDB_MAJOR - 1,
553                    ROCKSDB_MINOR, "unknown_db_opt=true", "");
554   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
555   ASSERT_NOK(s);
556   ASSERT_TRUE(s.IsInvalidArgument());
557   // Even though ignore_unknown_options=true, we still return an error...
558   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
559   ASSERT_NOK(s);
560   ASSERT_TRUE(s.IsInvalidArgument());
561   // Write an options file for a previous minor release with an unknown CF
562   // Option
563   WriteOptionsFile(options.env, dbname_, "OPTIONS-0002", ROCKSDB_MAJOR,
564                    ROCKSDB_MINOR - 1, "", "unknown_cf_opt=true");
565   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
566   ASSERT_NOK(s);
567   ASSERT_TRUE(s.IsInvalidArgument());
568   // Even though ignore_unknown_options=true, we still return an error...
569   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
570   ASSERT_NOK(s);
571   ASSERT_TRUE(s.IsInvalidArgument());
572   // Write an options file for a previous minor release with an unknown BBT
573   // Option
574   WriteOptionsFile(options.env, dbname_, "OPTIONS-0003", ROCKSDB_MAJOR,
575                    ROCKSDB_MINOR - 1, "", "", "unknown_bbt_opt=true");
576   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
577   ASSERT_NOK(s);
578   ASSERT_TRUE(s.IsInvalidArgument());
579   // Even though ignore_unknown_options=true, we still return an error...
580   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
581   ASSERT_NOK(s);
582   ASSERT_TRUE(s.IsInvalidArgument());
583 
584   // Write an options file for the current release with an unknown DB Option
585   WriteOptionsFile(options.env, dbname_, "OPTIONS-0004", ROCKSDB_MAJOR,
586                    ROCKSDB_MINOR, "unknown_db_opt=true", "");
587   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
588   ASSERT_NOK(s);
589   ASSERT_TRUE(s.IsInvalidArgument());
590   // Even though ignore_unknown_options=true, we still return an error...
591   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
592   ASSERT_NOK(s);
593   ASSERT_TRUE(s.IsInvalidArgument());
594 
595   // Write an options file for the current release with an unknown CF Option
596   WriteOptionsFile(options.env, dbname_, "OPTIONS-0005", ROCKSDB_MAJOR,
597                    ROCKSDB_MINOR, "", "unknown_cf_opt=true");
598   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
599   ASSERT_NOK(s);
600   ASSERT_TRUE(s.IsInvalidArgument());
601   // Even though ignore_unknown_options=true, we still return an error...
602   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
603   ASSERT_NOK(s);
604   ASSERT_TRUE(s.IsInvalidArgument());
605 
606   // Write an options file for the current release with an invalid DB Option
607   WriteOptionsFile(options.env, dbname_, "OPTIONS-0006", ROCKSDB_MAJOR,
608                    ROCKSDB_MINOR, "create_if_missing=hello", "");
609   s = LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs);
610   ASSERT_NOK(s);
611   ASSERT_TRUE(s.IsInvalidArgument());
612   // Even though ignore_unknown_options=true, we still return an error...
613   s = LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs);
614   ASSERT_NOK(s);
615   ASSERT_TRUE(s.IsInvalidArgument());
616 
617   // Write an options file for the next release with an invalid DB Option
618   WriteOptionsFile(options.env, dbname_, "OPTIONS-0007", ROCKSDB_MAJOR,
619                    ROCKSDB_MINOR + 1, "create_if_missing=hello", "");
620   ASSERT_NOK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
621   ASSERT_OK(LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs));
622 
623   // Write an options file for the next release with an unknown DB Option
624   WriteOptionsFile(options.env, dbname_, "OPTIONS-0008", ROCKSDB_MAJOR,
625                    ROCKSDB_MINOR + 1, "unknown_db_opt=true", "");
626   ASSERT_NOK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
627   // Ignore the errors for future releases when ignore_unknown_options=true
628   ASSERT_OK(LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs));
629 
630   // Write an options file for the next major release with an unknown CF Option
631   WriteOptionsFile(options.env, dbname_, "OPTIONS-0009", ROCKSDB_MAJOR + 1,
632                    ROCKSDB_MINOR, "", "unknown_cf_opt=true");
633   ASSERT_NOK(LoadLatestOptions(config_opts, dbname_, &db_opts, &cf_descs));
634   // Ignore the errors for future releases when ignore_unknown_options=true
635   ASSERT_OK(LoadLatestOptions(ignore_opts, dbname_, &db_opts, &cf_descs));
636 }
637 }  // namespace ROCKSDB_NAMESPACE
638 
main(int argc,char ** argv)639 int main(int argc, char** argv) {
640   ::testing::InitGoogleTest(&argc, argv);
641 #ifdef GFLAGS
642   ParseCommandLineFlags(&argc, &argv, true);
643 #endif  // GFLAGS
644   return RUN_ALL_TESTS();
645 }
646 
647 #else
648 #include <cstdio>
649 
main(int,char **)650 int main(int /*argc*/, char** /*argv*/) {
651   printf("Skipped in RocksDBLite as utilities are not supported.\n");
652   return 0;
653 }
654 #endif  // !ROCKSDB_LITE
655