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