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 #include <map>
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "db/db_impl/db_impl.h"
13 #include "db/dbformat.h"
14 #include "db/table_properties_collector.h"
15 #include "env/composite_env_wrapper.h"
16 #include "file/sequence_file_reader.h"
17 #include "file/writable_file_writer.h"
18 #include "options/cf_options.h"
19 #include "rocksdb/table.h"
20 #include "table/block_based/block_based_table_factory.h"
21 #include "table/meta_blocks.h"
22 #include "table/plain/plain_table_factory.h"
23 #include "table/table_builder.h"
24 #include "test_util/testharness.h"
25 #include "test_util/testutil.h"
26 #include "util/coding.h"
27 
28 namespace ROCKSDB_NAMESPACE {
29 
30 class TablePropertiesTest : public testing::Test,
31                             public testing::WithParamInterface<bool> {
32  public:
SetUp()33   void SetUp() override { backward_mode_ = GetParam(); }
34 
35   bool backward_mode_;
36 };
37 
38 // Utilities test functions
39 namespace {
40 static const uint32_t kTestColumnFamilyId = 66;
41 static const std::string kTestColumnFamilyName = "test_column_fam";
42 
MakeBuilder(const Options & options,const ImmutableCFOptions & ioptions,const MutableCFOptions & moptions,const InternalKeyComparator & internal_comparator,const std::vector<std::unique_ptr<IntTblPropCollectorFactory>> * int_tbl_prop_collector_factories,std::unique_ptr<WritableFileWriter> * writable,std::unique_ptr<TableBuilder> * builder)43 void MakeBuilder(const Options& options, const ImmutableCFOptions& ioptions,
44                  const MutableCFOptions& moptions,
45                  const InternalKeyComparator& internal_comparator,
46                  const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
47                      int_tbl_prop_collector_factories,
48                  std::unique_ptr<WritableFileWriter>* writable,
49                  std::unique_ptr<TableBuilder>* builder) {
50   std::unique_ptr<WritableFile> wf(new test::StringSink);
51   writable->reset(
52       new WritableFileWriter(NewLegacyWritableFileWrapper(std::move(wf)),
53                              "" /* don't care */, EnvOptions()));
54   int unknown_level = -1;
55   builder->reset(NewTableBuilder(
56       ioptions, moptions, internal_comparator, int_tbl_prop_collector_factories,
57       kTestColumnFamilyId, kTestColumnFamilyName, writable->get(),
58       options.compression, options.sample_for_compression,
59       options.compression_opts, unknown_level));
60 }
61 }  // namespace
62 
63 // Collects keys that starts with "A" in a table.
64 class RegularKeysStartWithA: public TablePropertiesCollector {
65  public:
Name() const66   const char* Name() const override { return "RegularKeysStartWithA"; }
67 
Finish(UserCollectedProperties * properties)68   Status Finish(UserCollectedProperties* properties) override {
69      std::string encoded;
70      std::string encoded_num_puts;
71      std::string encoded_num_deletes;
72      std::string encoded_num_single_deletes;
73      std::string encoded_num_size_changes;
74      PutVarint32(&encoded, count_);
75      PutVarint32(&encoded_num_puts, num_puts_);
76      PutVarint32(&encoded_num_deletes, num_deletes_);
77      PutVarint32(&encoded_num_single_deletes, num_single_deletes_);
78      PutVarint32(&encoded_num_size_changes, num_size_changes_);
79      *properties = UserCollectedProperties{
80          {"TablePropertiesTest", message_},
81          {"Count", encoded},
82          {"NumPuts", encoded_num_puts},
83          {"NumDeletes", encoded_num_deletes},
84          {"NumSingleDeletes", encoded_num_single_deletes},
85          {"NumSizeChanges", encoded_num_size_changes},
86      };
87      return Status::OK();
88   }
89 
AddUserKey(const Slice & user_key,const Slice &,EntryType type,SequenceNumber,uint64_t file_size)90   Status AddUserKey(const Slice& user_key, const Slice& /*value*/,
91                     EntryType type, SequenceNumber /*seq*/,
92                     uint64_t file_size) override {
93     // simply asssume all user keys are not empty.
94     if (user_key.data()[0] == 'A') {
95       ++count_;
96     }
97     if (type == kEntryPut) {
98       num_puts_++;
99     } else if (type == kEntryDelete) {
100       num_deletes_++;
101     } else if (type == kEntrySingleDelete) {
102       num_single_deletes_++;
103     }
104     if (file_size < file_size_) {
105       message_ = "File size should not decrease.";
106     } else if (file_size != file_size_) {
107       num_size_changes_++;
108     }
109 
110     return Status::OK();
111   }
112 
GetReadableProperties() const113   UserCollectedProperties GetReadableProperties() const override {
114     return UserCollectedProperties{};
115   }
116 
117  private:
118   std::string message_ = "Rocksdb";
119   uint32_t count_ = 0;
120   uint32_t num_puts_ = 0;
121   uint32_t num_deletes_ = 0;
122   uint32_t num_single_deletes_ = 0;
123   uint32_t num_size_changes_ = 0;
124   uint64_t file_size_ = 0;
125 };
126 
127 // Collects keys that starts with "A" in a table. Backward compatible mode
128 // It is also used to test internal key table property collector
129 class RegularKeysStartWithABackwardCompatible
130     : public TablePropertiesCollector {
131  public:
Name() const132   const char* Name() const override { return "RegularKeysStartWithA"; }
133 
Finish(UserCollectedProperties * properties)134   Status Finish(UserCollectedProperties* properties) override {
135     std::string encoded;
136     PutVarint32(&encoded, count_);
137     *properties = UserCollectedProperties{{"TablePropertiesTest", "Rocksdb"},
138                                           {"Count", encoded}};
139     return Status::OK();
140   }
141 
Add(const Slice & user_key,const Slice &)142   Status Add(const Slice& user_key, const Slice& /*value*/) override {
143     // simply asssume all user keys are not empty.
144     if (user_key.data()[0] == 'A') {
145       ++count_;
146     }
147     return Status::OK();
148   }
149 
GetReadableProperties() const150   UserCollectedProperties GetReadableProperties() const override {
151     return UserCollectedProperties{};
152   }
153 
154  private:
155   uint32_t count_ = 0;
156 };
157 
158 class RegularKeysStartWithAInternal : public IntTblPropCollector {
159  public:
Name() const160   const char* Name() const override { return "RegularKeysStartWithA"; }
161 
Finish(UserCollectedProperties * properties)162   Status Finish(UserCollectedProperties* properties) override {
163     std::string encoded;
164     PutVarint32(&encoded, count_);
165     *properties = UserCollectedProperties{{"TablePropertiesTest", "Rocksdb"},
166                                           {"Count", encoded}};
167     return Status::OK();
168   }
169 
InternalAdd(const Slice & user_key,const Slice &,uint64_t)170   Status InternalAdd(const Slice& user_key, const Slice& /*value*/,
171                      uint64_t /*file_size*/) override {
172     // simply asssume all user keys are not empty.
173     if (user_key.data()[0] == 'A') {
174       ++count_;
175     }
176     return Status::OK();
177   }
178 
BlockAdd(uint64_t,uint64_t,uint64_t)179   void BlockAdd(uint64_t /* blockRawBytes */,
180                 uint64_t /* blockCompressedBytesFast */,
181                 uint64_t /* blockCompressedBytesSlow */) override {
182     // Nothing to do.
183     return;
184   }
185 
GetReadableProperties() const186   UserCollectedProperties GetReadableProperties() const override {
187     return UserCollectedProperties{};
188   }
189 
190  private:
191   uint32_t count_ = 0;
192 };
193 
194 class RegularKeysStartWithAFactory : public IntTblPropCollectorFactory,
195                                      public TablePropertiesCollectorFactory {
196  public:
RegularKeysStartWithAFactory(bool backward_mode)197   explicit RegularKeysStartWithAFactory(bool backward_mode)
198       : backward_mode_(backward_mode) {}
CreateTablePropertiesCollector(TablePropertiesCollectorFactory::Context context)199   TablePropertiesCollector* CreateTablePropertiesCollector(
200       TablePropertiesCollectorFactory::Context context) override {
201     EXPECT_EQ(kTestColumnFamilyId, context.column_family_id);
202     if (!backward_mode_) {
203       return new RegularKeysStartWithA();
204     } else {
205       return new RegularKeysStartWithABackwardCompatible();
206     }
207   }
CreateIntTblPropCollector(uint32_t)208   IntTblPropCollector* CreateIntTblPropCollector(
209       uint32_t /*column_family_id*/) override {
210     return new RegularKeysStartWithAInternal();
211   }
Name() const212   const char* Name() const override { return "RegularKeysStartWithA"; }
213 
214   bool backward_mode_;
215 };
216 
217 class FlushBlockEveryThreePolicy : public FlushBlockPolicy {
218  public:
Update(const Slice &,const Slice &)219   bool Update(const Slice& /*key*/, const Slice& /*value*/) override {
220     return (++count_ % 3U == 0);
221   }
222 
223  private:
224   uint64_t count_ = 0;
225 };
226 
227 class FlushBlockEveryThreePolicyFactory : public FlushBlockPolicyFactory {
228  public:
FlushBlockEveryThreePolicyFactory()229   explicit FlushBlockEveryThreePolicyFactory() {}
230 
Name() const231   const char* Name() const override {
232     return "FlushBlockEveryThreePolicyFactory";
233   }
234 
NewFlushBlockPolicy(const BlockBasedTableOptions &,const BlockBuilder &) const235   FlushBlockPolicy* NewFlushBlockPolicy(
236       const BlockBasedTableOptions& /*table_options*/,
237       const BlockBuilder& /*data_block_builder*/) const override {
238     return new FlushBlockEveryThreePolicy;
239   }
240 };
241 
242 extern const uint64_t kBlockBasedTableMagicNumber;
243 extern const uint64_t kPlainTableMagicNumber;
244 namespace {
TestCustomizedTablePropertiesCollector(bool backward_mode,uint64_t magic_number,bool test_int_tbl_prop_collector,const Options & options,const InternalKeyComparator & internal_comparator)245 void TestCustomizedTablePropertiesCollector(
246     bool backward_mode, uint64_t magic_number, bool test_int_tbl_prop_collector,
247     const Options& options, const InternalKeyComparator& internal_comparator) {
248   // make sure the entries will be inserted with order.
249   std::map<std::pair<std::string, ValueType>, std::string> kvs = {
250       {{"About   ", kTypeValue}, "val5"},  // starts with 'A'
251       {{"Abstract", kTypeValue}, "val2"},  // starts with 'A'
252       {{"Around  ", kTypeValue}, "val7"},  // starts with 'A'
253       {{"Beyond  ", kTypeValue}, "val3"},
254       {{"Builder ", kTypeValue}, "val1"},
255       {{"Love    ", kTypeDeletion}, ""},
256       {{"Cancel  ", kTypeValue}, "val4"},
257       {{"Find    ", kTypeValue}, "val6"},
258       {{"Rocks   ", kTypeDeletion}, ""},
259       {{"Foo     ", kTypeSingleDeletion}, ""},
260   };
261 
262   // -- Step 1: build table
263   std::unique_ptr<TableBuilder> builder;
264   std::unique_ptr<WritableFileWriter> writer;
265   const ImmutableCFOptions ioptions(options);
266   const MutableCFOptions moptions(options);
267   std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
268       int_tbl_prop_collector_factories;
269   if (test_int_tbl_prop_collector) {
270     int_tbl_prop_collector_factories.emplace_back(
271         new RegularKeysStartWithAFactory(backward_mode));
272   } else {
273     GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories);
274   }
275   MakeBuilder(options, ioptions, moptions, internal_comparator,
276               &int_tbl_prop_collector_factories, &writer, &builder);
277 
278   SequenceNumber seqNum = 0U;
279   for (const auto& kv : kvs) {
280     InternalKey ikey(kv.first.first, seqNum++, kv.first.second);
281     builder->Add(ikey.Encode(), kv.second);
282   }
283   ASSERT_OK(builder->Finish());
284   writer->Flush();
285 
286   // -- Step 2: Read properties
287   LegacyWritableFileWrapper* file =
288       static_cast<LegacyWritableFileWrapper*>(writer->writable_file());
289   test::StringSink* fwf = static_cast<test::StringSink*>(file->target());
290   std::unique_ptr<RandomAccessFileReader> fake_file_reader(
291       test::GetRandomAccessFileReader(
292           new test::StringSource(fwf->contents())));
293   TableProperties* props;
294   Status s = ReadTableProperties(fake_file_reader.get(), fwf->contents().size(),
295                                  magic_number, ioptions, &props,
296                                  true /* compression_type_missing */);
297   std::unique_ptr<TableProperties> props_guard(props);
298   ASSERT_OK(s);
299 
300   auto user_collected = props->user_collected_properties;
301 
302   ASSERT_NE(user_collected.find("TablePropertiesTest"), user_collected.end());
303   ASSERT_EQ("Rocksdb", user_collected.at("TablePropertiesTest"));
304 
305   uint32_t starts_with_A = 0;
306   ASSERT_NE(user_collected.find("Count"), user_collected.end());
307   Slice key(user_collected.at("Count"));
308   ASSERT_TRUE(GetVarint32(&key, &starts_with_A));
309   ASSERT_EQ(3u, starts_with_A);
310 
311   if (!backward_mode && !test_int_tbl_prop_collector) {
312     uint32_t num_puts;
313     ASSERT_NE(user_collected.find("NumPuts"), user_collected.end());
314     Slice key_puts(user_collected.at("NumPuts"));
315     ASSERT_TRUE(GetVarint32(&key_puts, &num_puts));
316     ASSERT_EQ(7u, num_puts);
317 
318     uint32_t num_deletes;
319     ASSERT_NE(user_collected.find("NumDeletes"), user_collected.end());
320     Slice key_deletes(user_collected.at("NumDeletes"));
321     ASSERT_TRUE(GetVarint32(&key_deletes, &num_deletes));
322     ASSERT_EQ(2u, num_deletes);
323 
324     uint32_t num_single_deletes;
325     ASSERT_NE(user_collected.find("NumSingleDeletes"), user_collected.end());
326     Slice key_single_deletes(user_collected.at("NumSingleDeletes"));
327     ASSERT_TRUE(GetVarint32(&key_single_deletes, &num_single_deletes));
328     ASSERT_EQ(1u, num_single_deletes);
329 
330     uint32_t num_size_changes;
331     ASSERT_NE(user_collected.find("NumSizeChanges"), user_collected.end());
332     Slice key_size_changes(user_collected.at("NumSizeChanges"));
333     ASSERT_TRUE(GetVarint32(&key_size_changes, &num_size_changes));
334     ASSERT_GE(num_size_changes, 2u);
335   }
336 }
337 }  // namespace
338 
TEST_P(TablePropertiesTest,CustomizedTablePropertiesCollector)339 TEST_P(TablePropertiesTest, CustomizedTablePropertiesCollector) {
340   // Test properties collectors with internal keys or regular keys
341   // for block based table
342   for (bool encode_as_internal : { true, false }) {
343     Options options;
344     BlockBasedTableOptions table_options;
345     table_options.flush_block_policy_factory =
346         std::make_shared<FlushBlockEveryThreePolicyFactory>();
347     options.table_factory.reset(NewBlockBasedTableFactory(table_options));
348 
349     test::PlainInternalKeyComparator ikc(options.comparator);
350     std::shared_ptr<TablePropertiesCollectorFactory> collector_factory(
351         new RegularKeysStartWithAFactory(backward_mode_));
352     options.table_properties_collector_factories.resize(1);
353     options.table_properties_collector_factories[0] = collector_factory;
354 
355     TestCustomizedTablePropertiesCollector(backward_mode_,
356                                            kBlockBasedTableMagicNumber,
357                                            encode_as_internal, options, ikc);
358 
359 #ifndef ROCKSDB_LITE  // PlainTable is not supported in Lite
360     // test plain table
361     PlainTableOptions plain_table_options;
362     plain_table_options.user_key_len = 8;
363     plain_table_options.bloom_bits_per_key = 8;
364     plain_table_options.hash_table_ratio = 0;
365 
366     options.table_factory =
367         std::make_shared<PlainTableFactory>(plain_table_options);
368     TestCustomizedTablePropertiesCollector(backward_mode_,
369                                            kPlainTableMagicNumber,
370                                            encode_as_internal, options, ikc);
371 #endif  // !ROCKSDB_LITE
372   }
373 }
374 
375 namespace {
TestInternalKeyPropertiesCollector(bool backward_mode,uint64_t magic_number,bool sanitized,std::shared_ptr<TableFactory> table_factory)376 void TestInternalKeyPropertiesCollector(
377     bool backward_mode, uint64_t magic_number, bool sanitized,
378     std::shared_ptr<TableFactory> table_factory) {
379   InternalKey keys[] = {
380       InternalKey("A       ", 0, ValueType::kTypeValue),
381       InternalKey("B       ", 1, ValueType::kTypeValue),
382       InternalKey("C       ", 2, ValueType::kTypeValue),
383       InternalKey("W       ", 3, ValueType::kTypeDeletion),
384       InternalKey("X       ", 4, ValueType::kTypeDeletion),
385       InternalKey("Y       ", 5, ValueType::kTypeDeletion),
386       InternalKey("Z       ", 6, ValueType::kTypeDeletion),
387       InternalKey("a       ", 7, ValueType::kTypeSingleDeletion),
388       InternalKey("b       ", 8, ValueType::kTypeMerge),
389       InternalKey("c       ", 9, ValueType::kTypeMerge),
390   };
391 
392   std::unique_ptr<TableBuilder> builder;
393   std::unique_ptr<WritableFileWriter> writable;
394   Options options;
395   test::PlainInternalKeyComparator pikc(options.comparator);
396 
397   std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
398       int_tbl_prop_collector_factories;
399   options.table_factory = table_factory;
400   if (sanitized) {
401     options.table_properties_collector_factories.emplace_back(
402         new RegularKeysStartWithAFactory(backward_mode));
403     // with sanitization, even regular properties collector will be able to
404     // handle internal keys.
405     auto comparator = options.comparator;
406     // HACK: Set options.info_log to avoid writing log in
407     // SanitizeOptions().
408     options.info_log = std::make_shared<test::NullLogger>();
409     options = SanitizeOptions("db",            // just a place holder
410                               options);
411     ImmutableCFOptions ioptions(options);
412     GetIntTblPropCollectorFactory(ioptions, &int_tbl_prop_collector_factories);
413     options.comparator = comparator;
414   }
415   const ImmutableCFOptions ioptions(options);
416   MutableCFOptions moptions(options);
417 
418   for (int iter = 0; iter < 2; ++iter) {
419     MakeBuilder(options, ioptions, moptions, pikc,
420                 &int_tbl_prop_collector_factories, &writable, &builder);
421     for (const auto& k : keys) {
422       builder->Add(k.Encode(), "val");
423     }
424 
425     ASSERT_OK(builder->Finish());
426     writable->Flush();
427 
428     LegacyWritableFileWrapper* file =
429         static_cast<LegacyWritableFileWrapper*>(writable->writable_file());
430     test::StringSink* fwf = static_cast<test::StringSink*>(file->target());
431     std::unique_ptr<RandomAccessFileReader> reader(
432         test::GetRandomAccessFileReader(
433             new test::StringSource(fwf->contents())));
434     TableProperties* props;
435     Status s =
436         ReadTableProperties(reader.get(), fwf->contents().size(), magic_number,
437                             ioptions, &props, true /* compression_type_missing */);
438     ASSERT_OK(s);
439 
440     std::unique_ptr<TableProperties> props_guard(props);
441     auto user_collected = props->user_collected_properties;
442     uint64_t deleted = GetDeletedKeys(user_collected);
443     ASSERT_EQ(5u, deleted);  // deletes + single-deletes
444 
445     bool property_present;
446     uint64_t merges = GetMergeOperands(user_collected, &property_present);
447     ASSERT_TRUE(property_present);
448     ASSERT_EQ(2u, merges);
449 
450     if (sanitized) {
451       uint32_t starts_with_A = 0;
452       ASSERT_NE(user_collected.find("Count"), user_collected.end());
453       Slice key(user_collected.at("Count"));
454       ASSERT_TRUE(GetVarint32(&key, &starts_with_A));
455       ASSERT_EQ(1u, starts_with_A);
456 
457       if (!backward_mode) {
458         uint32_t num_puts;
459         ASSERT_NE(user_collected.find("NumPuts"), user_collected.end());
460         Slice key_puts(user_collected.at("NumPuts"));
461         ASSERT_TRUE(GetVarint32(&key_puts, &num_puts));
462         ASSERT_EQ(3u, num_puts);
463 
464         uint32_t num_deletes;
465         ASSERT_NE(user_collected.find("NumDeletes"), user_collected.end());
466         Slice key_deletes(user_collected.at("NumDeletes"));
467         ASSERT_TRUE(GetVarint32(&key_deletes, &num_deletes));
468         ASSERT_EQ(4u, num_deletes);
469 
470         uint32_t num_single_deletes;
471         ASSERT_NE(user_collected.find("NumSingleDeletes"),
472                   user_collected.end());
473         Slice key_single_deletes(user_collected.at("NumSingleDeletes"));
474         ASSERT_TRUE(GetVarint32(&key_single_deletes, &num_single_deletes));
475         ASSERT_EQ(1u, num_single_deletes);
476       }
477     }
478   }
479 }
480 }  // namespace
481 
TEST_P(TablePropertiesTest,InternalKeyPropertiesCollector)482 TEST_P(TablePropertiesTest, InternalKeyPropertiesCollector) {
483   TestInternalKeyPropertiesCollector(
484       backward_mode_, kBlockBasedTableMagicNumber, true /* sanitize */,
485       std::make_shared<BlockBasedTableFactory>());
486   if (backward_mode_) {
487     TestInternalKeyPropertiesCollector(
488         backward_mode_, kBlockBasedTableMagicNumber, false /* not sanitize */,
489         std::make_shared<BlockBasedTableFactory>());
490   }
491 
492 #ifndef ROCKSDB_LITE  // PlainTable is not supported in Lite
493   PlainTableOptions plain_table_options;
494   plain_table_options.user_key_len = 8;
495   plain_table_options.bloom_bits_per_key = 8;
496   plain_table_options.hash_table_ratio = 0;
497 
498   TestInternalKeyPropertiesCollector(
499       backward_mode_, kPlainTableMagicNumber, false /* not sanitize */,
500       std::make_shared<PlainTableFactory>(plain_table_options));
501 #endif  // !ROCKSDB_LITE
502 }
503 
504 INSTANTIATE_TEST_CASE_P(InternalKeyPropertiesCollector, TablePropertiesTest,
505                         ::testing::Bool());
506 
507 INSTANTIATE_TEST_CASE_P(CustomizedTablePropertiesCollector, TablePropertiesTest,
508                         ::testing::Bool());
509 
510 }  // namespace ROCKSDB_NAMESPACE
511 
main(int argc,char ** argv)512 int main(int argc, char** argv) {
513   ::testing::InitGoogleTest(&argc, argv);
514   return RUN_ALL_TESTS();
515 }
516