]> git.proxmox.com Git - ceph.git/blob - ceph/src/rocksdb/utilities/ttl/ttl_test.cc
bump version to 18.2.4-pve3
[ceph.git] / ceph / src / rocksdb / utilities / ttl / ttl_test.cc
1 // Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
2 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file. See the AUTHORS file for names of contributors.
5
6 #ifndef ROCKSDB_LITE
7
8 #include <map>
9 #include <memory>
10
11 #include "rocksdb/compaction_filter.h"
12 #include "rocksdb/convenience.h"
13 #include "rocksdb/merge_operator.h"
14 #include "rocksdb/utilities/db_ttl.h"
15 #include "rocksdb/utilities/object_registry.h"
16 #include "test_util/testharness.h"
17 #include "util/string_util.h"
18 #include "utilities/merge_operators/bytesxor.h"
19 #include "utilities/ttl/db_ttl_impl.h"
20 #ifndef OS_WIN
21 #include <unistd.h>
22 #endif
23
24 namespace ROCKSDB_NAMESPACE {
25
26 namespace {
27
28 using KVMap = std::map<std::string, std::string>;
29
30 enum BatchOperation { OP_PUT = 0, OP_DELETE = 1 };
31 } // namespace
32
33 class SpecialTimeEnv : public EnvWrapper {
34 public:
35 explicit SpecialTimeEnv(Env* base) : EnvWrapper(base) {
36 EXPECT_OK(base->GetCurrentTime(&current_time_));
37 }
38 const char* Name() const override { return "SpecialTimeEnv"; }
39 void Sleep(int64_t sleep_time) { current_time_ += sleep_time; }
40 Status GetCurrentTime(int64_t* current_time) override {
41 *current_time = current_time_;
42 return Status::OK();
43 }
44
45 private:
46 int64_t current_time_ = 0;
47 };
48
49 class TtlTest : public testing::Test {
50 public:
51 TtlTest() {
52 env_.reset(new SpecialTimeEnv(Env::Default()));
53 dbname_ = test::PerThreadDBPath("db_ttl");
54 options_.create_if_missing = true;
55 options_.env = env_.get();
56 // ensure that compaction is kicked in to always strip timestamp from kvs
57 options_.max_compaction_bytes = 1;
58 // compaction should take place always from level0 for determinism
59 db_ttl_ = nullptr;
60 EXPECT_OK(DestroyDB(dbname_, Options()));
61 }
62
63 ~TtlTest() override {
64 CloseTtl();
65 EXPECT_OK(DestroyDB(dbname_, Options()));
66 }
67
68 // Open database with TTL support when TTL not provided with db_ttl_ pointer
69 void OpenTtl() {
70 ASSERT_TRUE(db_ttl_ ==
71 nullptr); // db should be closed before opening again
72 ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_));
73 }
74
75 // Open database with TTL support when TTL provided with db_ttl_ pointer
76 void OpenTtl(int32_t ttl) {
77 ASSERT_TRUE(db_ttl_ == nullptr);
78 ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl));
79 }
80
81 // Open with TestFilter compaction filter
82 void OpenTtlWithTestCompaction(int32_t ttl) {
83 options_.compaction_filter_factory =
84 std::shared_ptr<CompactionFilterFactory>(
85 new TestFilterFactory(kSampleSize_, kNewValue_));
86 OpenTtl(ttl);
87 }
88
89 // Open database with TTL support in read_only mode
90 void OpenReadOnlyTtl(int32_t ttl) {
91 ASSERT_TRUE(db_ttl_ == nullptr);
92 ASSERT_OK(DBWithTTL::Open(options_, dbname_, &db_ttl_, ttl, true));
93 }
94
95 // Call db_ttl_->Close() before delete db_ttl_
96 void CloseTtl() { CloseTtlHelper(true); }
97
98 // No db_ttl_->Close() before delete db_ttl_
99 void CloseTtlNoDBClose() { CloseTtlHelper(false); }
100
101 void CloseTtlHelper(bool close_db) {
102 if (db_ttl_ != nullptr) {
103 if (close_db) {
104 EXPECT_OK(db_ttl_->Close());
105 }
106 delete db_ttl_;
107 db_ttl_ = nullptr;
108 }
109 }
110
111 // Populates and returns a kv-map
112 void MakeKVMap(int64_t num_entries) {
113 kvmap_.clear();
114 int digits = 1;
115 for (int64_t dummy = num_entries; dummy /= 10; ++digits) {
116 }
117 int digits_in_i = 1;
118 for (int64_t i = 0; i < num_entries; i++) {
119 std::string key = "key";
120 std::string value = "value";
121 if (i % 10 == 0) {
122 digits_in_i++;
123 }
124 for (int j = digits_in_i; j < digits; j++) {
125 key.append("0");
126 value.append("0");
127 }
128 AppendNumberTo(&key, i);
129 AppendNumberTo(&value, i);
130 kvmap_[key] = value;
131 }
132 ASSERT_EQ(static_cast<int64_t>(kvmap_.size()),
133 num_entries); // check all insertions done
134 }
135
136 // Makes a write-batch with key-vals from kvmap_ and 'Write''s it
137 void MakePutWriteBatch(const BatchOperation* batch_ops, int64_t num_ops) {
138 ASSERT_LE(num_ops, static_cast<int64_t>(kvmap_.size()));
139 static WriteOptions wopts;
140 static FlushOptions flush_opts;
141 WriteBatch batch;
142 kv_it_ = kvmap_.begin();
143 for (int64_t i = 0; i < num_ops && kv_it_ != kvmap_.end(); i++, ++kv_it_) {
144 switch (batch_ops[i]) {
145 case OP_PUT:
146 ASSERT_OK(batch.Put(kv_it_->first, kv_it_->second));
147 break;
148 case OP_DELETE:
149 ASSERT_OK(batch.Delete(kv_it_->first));
150 break;
151 default:
152 FAIL();
153 }
154 }
155 ASSERT_OK(db_ttl_->Write(wopts, &batch));
156 ASSERT_OK(db_ttl_->Flush(flush_opts));
157 }
158
159 // Puts num_entries starting from start_pos_map from kvmap_ into the database
160 void PutValues(int64_t start_pos_map, int64_t num_entries, bool flush = true,
161 ColumnFamilyHandle* cf = nullptr) {
162 ASSERT_TRUE(db_ttl_);
163 ASSERT_LE(start_pos_map + num_entries, static_cast<int64_t>(kvmap_.size()));
164 static WriteOptions wopts;
165 static FlushOptions flush_opts;
166 kv_it_ = kvmap_.begin();
167 advance(kv_it_, start_pos_map);
168 for (int64_t i = 0; kv_it_ != kvmap_.end() && i < num_entries;
169 i++, ++kv_it_) {
170 ASSERT_OK(cf == nullptr
171 ? db_ttl_->Put(wopts, kv_it_->first, kv_it_->second)
172 : db_ttl_->Put(wopts, cf, kv_it_->first, kv_it_->second));
173 }
174 // Put a mock kv at the end because CompactionFilter doesn't delete last key
175 ASSERT_OK(cf == nullptr ? db_ttl_->Put(wopts, "keymock", "valuemock")
176 : db_ttl_->Put(wopts, cf, "keymock", "valuemock"));
177 if (flush) {
178 if (cf == nullptr) {
179 ASSERT_OK(db_ttl_->Flush(flush_opts));
180 } else {
181 ASSERT_OK(db_ttl_->Flush(flush_opts, cf));
182 }
183 }
184 }
185
186 // Runs a manual compaction
187 Status ManualCompact(ColumnFamilyHandle* cf = nullptr) {
188 assert(db_ttl_);
189 if (cf == nullptr) {
190 return db_ttl_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
191 } else {
192 return db_ttl_->CompactRange(CompactRangeOptions(), cf, nullptr, nullptr);
193 }
194 }
195
196 // Runs a DeleteRange
197 void MakeDeleteRange(std::string start, std::string end,
198 ColumnFamilyHandle* cf = nullptr) {
199 ASSERT_TRUE(db_ttl_);
200 static WriteOptions wops;
201 WriteBatch wb;
202 ASSERT_OK(cf == nullptr
203 ? wb.DeleteRange(db_ttl_->DefaultColumnFamily(), start, end)
204 : wb.DeleteRange(cf, start, end));
205 ASSERT_OK(db_ttl_->Write(wops, &wb));
206 }
207
208 // checks the whole kvmap_ to return correct values using KeyMayExist
209 void SimpleKeyMayExistCheck() {
210 static ReadOptions ropts;
211 bool value_found;
212 std::string val;
213 for (auto& kv : kvmap_) {
214 bool ret = db_ttl_->KeyMayExist(ropts, kv.first, &val, &value_found);
215 if (ret == false || value_found == false) {
216 fprintf(stderr,
217 "KeyMayExist could not find key=%s in the database but"
218 " should have\n",
219 kv.first.c_str());
220 FAIL();
221 } else if (val.compare(kv.second) != 0) {
222 fprintf(stderr,
223 " value for key=%s present in database is %s but"
224 " should be %s\n",
225 kv.first.c_str(), val.c_str(), kv.second.c_str());
226 FAIL();
227 }
228 }
229 }
230
231 // checks the whole kvmap_ to return correct values using MultiGet
232 void SimpleMultiGetTest() {
233 static ReadOptions ropts;
234 std::vector<Slice> keys;
235 std::vector<std::string> values;
236
237 for (auto& kv : kvmap_) {
238 keys.emplace_back(kv.first);
239 }
240
241 auto statuses = db_ttl_->MultiGet(ropts, keys, &values);
242 size_t i = 0;
243 for (auto& kv : kvmap_) {
244 ASSERT_OK(statuses[i]);
245 ASSERT_EQ(values[i], kv.second);
246 ++i;
247 }
248 }
249
250 void CompactCheck(int64_t st_pos, int64_t span, bool check = true,
251 bool test_compaction_change = false,
252 ColumnFamilyHandle* cf = nullptr) {
253 static ReadOptions ropts;
254 kv_it_ = kvmap_.begin();
255 advance(kv_it_, st_pos);
256 std::string v;
257 for (int64_t i = 0; kv_it_ != kvmap_.end() && i < span; i++, ++kv_it_) {
258 Status s = (cf == nullptr) ? db_ttl_->Get(ropts, kv_it_->first, &v)
259 : db_ttl_->Get(ropts, cf, kv_it_->first, &v);
260 if (s.ok() != check) {
261 fprintf(stderr, "key=%s ", kv_it_->first.c_str());
262 if (!s.ok()) {
263 fprintf(stderr, "is absent from db but was expected to be present\n");
264 } else {
265 fprintf(stderr, "is present in db but was expected to be absent\n");
266 }
267 FAIL();
268 } else if (s.ok()) {
269 if (test_compaction_change && v.compare(kNewValue_) != 0) {
270 fprintf(stderr,
271 " value for key=%s present in database is %s but "
272 " should be %s\n",
273 kv_it_->first.c_str(), v.c_str(), kNewValue_.c_str());
274 FAIL();
275 } else if (!test_compaction_change && v.compare(kv_it_->second) != 0) {
276 fprintf(stderr,
277 " value for key=%s present in database is %s but "
278 " should be %s\n",
279 kv_it_->first.c_str(), v.c_str(), kv_it_->second.c_str());
280 FAIL();
281 }
282 }
283 }
284 }
285 // Sleeps for slp_tim then runs a manual compaction
286 // Checks span starting from st_pos from kvmap_ in the db and
287 // Gets should return true if check is true and false otherwise
288 // Also checks that value that we got is the same as inserted; and =kNewValue
289 // if test_compaction_change is true
290 void SleepCompactCheck(int slp_tim, int64_t st_pos, int64_t span,
291 bool check = true, bool test_compaction_change = false,
292 ColumnFamilyHandle* cf = nullptr) {
293 ASSERT_TRUE(db_ttl_);
294
295 env_->Sleep(slp_tim);
296 ASSERT_OK(ManualCompact(cf));
297 CompactCheck(st_pos, span, check, test_compaction_change, cf);
298 }
299
300 // Similar as SleepCompactCheck but uses TtlIterator to read from db
301 void SleepCompactCheckIter(int slp, int st_pos, int64_t span,
302 bool check = true) {
303 ASSERT_TRUE(db_ttl_);
304 env_->Sleep(slp);
305 ASSERT_OK(ManualCompact());
306 static ReadOptions ropts;
307 Iterator* dbiter = db_ttl_->NewIterator(ropts);
308 kv_it_ = kvmap_.begin();
309 advance(kv_it_, st_pos);
310
311 dbiter->Seek(kv_it_->first);
312 if (!check) {
313 if (dbiter->Valid()) {
314 ASSERT_NE(dbiter->value().compare(kv_it_->second), 0);
315 }
316 } else { // dbiter should have found out kvmap_[st_pos]
317 for (int64_t i = st_pos; kv_it_ != kvmap_.end() && i < st_pos + span;
318 i++, ++kv_it_) {
319 ASSERT_TRUE(dbiter->Valid());
320 ASSERT_EQ(dbiter->value().compare(kv_it_->second), 0);
321 dbiter->Next();
322 }
323 }
324 ASSERT_OK(dbiter->status());
325 delete dbiter;
326 }
327
328 // Set ttl on open db
329 void SetTtl(int32_t ttl, ColumnFamilyHandle* cf = nullptr) {
330 ASSERT_TRUE(db_ttl_);
331 cf == nullptr ? db_ttl_->SetTtl(ttl) : db_ttl_->SetTtl(cf, ttl);
332 }
333
334 class TestFilter : public CompactionFilter {
335 public:
336 TestFilter(const int64_t kSampleSize, const std::string& kNewValue)
337 : kSampleSize_(kSampleSize), kNewValue_(kNewValue) {}
338
339 // Works on keys of the form "key<number>"
340 // Drops key if number at the end of key is in [0, kSampleSize_/3),
341 // Keeps key if it is in [kSampleSize_/3, 2*kSampleSize_/3),
342 // Change value if it is in [2*kSampleSize_/3, kSampleSize_)
343 // Eg. kSampleSize_=6. Drop:key0-1...Keep:key2-3...Change:key4-5...
344 bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
345 std::string* new_value, bool* value_changed) const override {
346 assert(new_value != nullptr);
347
348 std::string search_str = "0123456789";
349 std::string key_string = key.ToString();
350 size_t pos = key_string.find_first_of(search_str);
351 int num_key_end;
352 if (pos != std::string::npos) {
353 auto key_substr = key_string.substr(pos, key.size() - pos);
354 #ifndef CYGWIN
355 num_key_end = std::stoi(key_substr);
356 #else
357 num_key_end = std::strtol(key_substr.c_str(), 0, 10);
358 #endif
359
360 } else {
361 return false; // Keep keys not matching the format "key<NUMBER>"
362 }
363
364 int64_t partition = kSampleSize_ / 3;
365 if (num_key_end < partition) {
366 return true;
367 } else if (num_key_end < partition * 2) {
368 return false;
369 } else {
370 *new_value = kNewValue_;
371 *value_changed = true;
372 return false;
373 }
374 }
375
376 const char* Name() const override { return "TestFilter"; }
377
378 private:
379 const int64_t kSampleSize_;
380 const std::string kNewValue_;
381 };
382
383 class TestFilterFactory : public CompactionFilterFactory {
384 public:
385 TestFilterFactory(const int64_t kSampleSize, const std::string& kNewValue)
386 : kSampleSize_(kSampleSize), kNewValue_(kNewValue) {}
387
388 std::unique_ptr<CompactionFilter> CreateCompactionFilter(
389 const CompactionFilter::Context& /*context*/) override {
390 return std::unique_ptr<CompactionFilter>(
391 new TestFilter(kSampleSize_, kNewValue_));
392 }
393
394 const char* Name() const override { return "TestFilterFactory"; }
395
396 private:
397 const int64_t kSampleSize_;
398 const std::string kNewValue_;
399 };
400
401 // Choose carefully so that Put, Gets & Compaction complete in 1 second buffer
402 static const int64_t kSampleSize_ = 100;
403 std::string dbname_;
404 DBWithTTL* db_ttl_;
405 std::unique_ptr<SpecialTimeEnv> env_;
406
407 private:
408 Options options_;
409 KVMap kvmap_;
410 KVMap::iterator kv_it_;
411 const std::string kNewValue_ = "new_value";
412 std::unique_ptr<CompactionFilter> test_comp_filter_;
413 }; // class TtlTest
414
415 // If TTL is non positive or not provided, the behaviour is TTL = infinity
416 // This test opens the db 3 times with such default behavior and inserts a
417 // bunch of kvs each time. All kvs should accumulate in the db till the end
418 // Partitions the sample-size provided into 3 sets over boundary1 and boundary2
419 TEST_F(TtlTest, NoEffect) {
420 MakeKVMap(kSampleSize_);
421 int64_t boundary1 = kSampleSize_ / 3;
422 int64_t boundary2 = 2 * boundary1;
423
424 OpenTtl();
425 PutValues(0, boundary1); // T=0: Set1 never deleted
426 SleepCompactCheck(1, 0, boundary1); // T=1: Set1 still there
427 CloseTtl();
428
429 OpenTtl(0);
430 PutValues(boundary1, boundary2 - boundary1); // T=1: Set2 never deleted
431 SleepCompactCheck(1, 0, boundary2); // T=2: Sets1 & 2 still there
432 CloseTtl();
433
434 OpenTtl(-1);
435 PutValues(boundary2, kSampleSize_ - boundary2); // T=3: Set3 never deleted
436 SleepCompactCheck(1, 0, kSampleSize_, true); // T=4: Sets 1,2,3 still there
437 CloseTtl();
438 }
439
440 // Rerun the NoEffect test with a different version of CloseTtl
441 // function, where db is directly deleted without close.
442 TEST_F(TtlTest, DestructWithoutClose) {
443 MakeKVMap(kSampleSize_);
444 int64_t boundary1 = kSampleSize_ / 3;
445 int64_t boundary2 = 2 * boundary1;
446
447 OpenTtl();
448 PutValues(0, boundary1); // T=0: Set1 never deleted
449 SleepCompactCheck(1, 0, boundary1); // T=1: Set1 still there
450 CloseTtlNoDBClose();
451
452 OpenTtl(0);
453 PutValues(boundary1, boundary2 - boundary1); // T=1: Set2 never deleted
454 SleepCompactCheck(1, 0, boundary2); // T=2: Sets1 & 2 still there
455 CloseTtlNoDBClose();
456
457 OpenTtl(-1);
458 PutValues(boundary2, kSampleSize_ - boundary2); // T=3: Set3 never deleted
459 SleepCompactCheck(1, 0, kSampleSize_, true); // T=4: Sets 1,2,3 still there
460 CloseTtlNoDBClose();
461 }
462
463 // Puts a set of values and checks its presence using Get during ttl
464 TEST_F(TtlTest, PresentDuringTTL) {
465 MakeKVMap(kSampleSize_);
466
467 OpenTtl(2); // T=0:Open the db with ttl = 2
468 PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
469 SleepCompactCheck(1, 0, kSampleSize_,
470 true); // T=1:Set1 should still be there
471 CloseTtl();
472 }
473
474 // Puts a set of values and checks its absence using Get after ttl
475 TEST_F(TtlTest, AbsentAfterTTL) {
476 MakeKVMap(kSampleSize_);
477
478 OpenTtl(1); // T=0:Open the db with ttl = 2
479 PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
480 SleepCompactCheck(2, 0, kSampleSize_, false); // T=2:Set1 should not be there
481 CloseTtl();
482 }
483
484 // Resets the timestamp of a set of kvs by updating them and checks that they
485 // are not deleted according to the old timestamp
486 TEST_F(TtlTest, ResetTimestamp) {
487 MakeKVMap(kSampleSize_);
488
489 OpenTtl(3);
490 PutValues(0, kSampleSize_); // T=0: Insert Set1. Delete at t=3
491 env_->Sleep(2); // T=2
492 PutValues(0, kSampleSize_); // T=2: Insert Set1. Delete at t=5
493 SleepCompactCheck(2, 0, kSampleSize_); // T=4: Set1 should still be there
494 CloseTtl();
495 }
496
497 // Similar to PresentDuringTTL but uses Iterator
498 TEST_F(TtlTest, IterPresentDuringTTL) {
499 MakeKVMap(kSampleSize_);
500
501 OpenTtl(2);
502 PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
503 SleepCompactCheckIter(1, 0, kSampleSize_); // T=1: Set should be there
504 CloseTtl();
505 }
506
507 // Similar to AbsentAfterTTL but uses Iterator
508 TEST_F(TtlTest, IterAbsentAfterTTL) {
509 MakeKVMap(kSampleSize_);
510
511 OpenTtl(1);
512 PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
513 SleepCompactCheckIter(2, 0, kSampleSize_, false); // T=2: Should not be there
514 CloseTtl();
515 }
516
517 // Checks presence while opening the same db more than once with the same ttl
518 // Note: The second open will open the same db
519 TEST_F(TtlTest, MultiOpenSamePresent) {
520 MakeKVMap(kSampleSize_);
521
522 OpenTtl(2);
523 PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=2
524 CloseTtl();
525
526 OpenTtl(2); // T=0. Delete at t=2
527 SleepCompactCheck(1, 0, kSampleSize_); // T=1: Set should be there
528 CloseTtl();
529 }
530
531 // Checks absence while opening the same db more than once with the same ttl
532 // Note: The second open will open the same db
533 TEST_F(TtlTest, MultiOpenSameAbsent) {
534 MakeKVMap(kSampleSize_);
535
536 OpenTtl(1);
537 PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
538 CloseTtl();
539
540 OpenTtl(1); // T=0.Delete at t=1
541 SleepCompactCheck(2, 0, kSampleSize_, false); // T=2: Set should not be there
542 CloseTtl();
543 }
544
545 // Checks presence while opening the same db more than once with bigger ttl
546 TEST_F(TtlTest, MultiOpenDifferent) {
547 MakeKVMap(kSampleSize_);
548
549 OpenTtl(1);
550 PutValues(0, kSampleSize_); // T=0: Insert. Delete at t=1
551 CloseTtl();
552
553 OpenTtl(3); // T=0: Set deleted at t=3
554 SleepCompactCheck(2, 0, kSampleSize_); // T=2: Set should be there
555 CloseTtl();
556 }
557
558 // Checks presence during ttl in read_only mode
559 TEST_F(TtlTest, ReadOnlyPresentForever) {
560 MakeKVMap(kSampleSize_);
561
562 OpenTtl(1); // T=0:Open the db normally
563 PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
564 CloseTtl();
565
566 OpenReadOnlyTtl(1);
567 ASSERT_TRUE(db_ttl_);
568
569 env_->Sleep(2);
570 Status s = ManualCompact(); // T=2:Set1 should still be there
571 ASSERT_TRUE(s.IsNotSupported());
572 CompactCheck(0, kSampleSize_);
573 CloseTtl();
574 }
575
576 // Checks whether WriteBatch works well with TTL
577 // Puts all kvs in kvmap_ in a batch and writes first, then deletes first half
578 TEST_F(TtlTest, WriteBatchTest) {
579 MakeKVMap(kSampleSize_);
580 BatchOperation batch_ops[kSampleSize_];
581 for (int i = 0; i < kSampleSize_; i++) {
582 batch_ops[i] = OP_PUT;
583 }
584
585 OpenTtl(2);
586 MakePutWriteBatch(batch_ops, kSampleSize_);
587 for (int i = 0; i < kSampleSize_ / 2; i++) {
588 batch_ops[i] = OP_DELETE;
589 }
590 MakePutWriteBatch(batch_ops, kSampleSize_ / 2);
591 SleepCompactCheck(0, 0, kSampleSize_ / 2, false);
592 SleepCompactCheck(0, kSampleSize_ / 2, kSampleSize_ - kSampleSize_ / 2);
593 CloseTtl();
594 }
595
596 // Checks user's compaction filter for correctness with TTL logic
597 TEST_F(TtlTest, CompactionFilter) {
598 MakeKVMap(kSampleSize_);
599
600 OpenTtlWithTestCompaction(1);
601 PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=1
602 // T=2: TTL logic takes precedence over TestFilter:-Set1 should not be there
603 SleepCompactCheck(2, 0, kSampleSize_, false);
604 CloseTtl();
605
606 OpenTtlWithTestCompaction(3);
607 PutValues(0, kSampleSize_); // T=0:Insert Set1.
608 int64_t partition = kSampleSize_ / 3;
609 SleepCompactCheck(1, 0, partition, false); // Part dropped
610 SleepCompactCheck(0, partition, partition); // Part kept
611 SleepCompactCheck(0, 2 * partition, partition, true, true); // Part changed
612 CloseTtl();
613 }
614
615 // Insert some key-values which KeyMayExist should be able to get and check that
616 // values returned are fine
617 TEST_F(TtlTest, KeyMayExist) {
618 MakeKVMap(kSampleSize_);
619
620 OpenTtl();
621 PutValues(0, kSampleSize_, false);
622
623 SimpleKeyMayExistCheck();
624
625 CloseTtl();
626 }
627
628 TEST_F(TtlTest, MultiGetTest) {
629 MakeKVMap(kSampleSize_);
630
631 OpenTtl();
632 PutValues(0, kSampleSize_, false);
633
634 SimpleMultiGetTest();
635
636 CloseTtl();
637 }
638
639 TEST_F(TtlTest, ColumnFamiliesTest) {
640 DB* db;
641 Options options;
642 options.create_if_missing = true;
643 options.env = env_.get();
644
645 DB::Open(options, dbname_, &db);
646 ColumnFamilyHandle* handle;
647 ASSERT_OK(db->CreateColumnFamily(ColumnFamilyOptions(options),
648 "ttl_column_family", &handle));
649
650 delete handle;
651 delete db;
652
653 std::vector<ColumnFamilyDescriptor> column_families;
654 column_families.push_back(ColumnFamilyDescriptor(
655 kDefaultColumnFamilyName, ColumnFamilyOptions(options)));
656 column_families.push_back(ColumnFamilyDescriptor(
657 "ttl_column_family", ColumnFamilyOptions(options)));
658
659 std::vector<ColumnFamilyHandle*> handles;
660
661 ASSERT_OK(DBWithTTL::Open(DBOptions(options), dbname_, column_families,
662 &handles, &db_ttl_, {3, 5}, false));
663 ASSERT_EQ(handles.size(), 2U);
664 ColumnFamilyHandle* new_handle;
665 ASSERT_OK(db_ttl_->CreateColumnFamilyWithTtl(options, "ttl_column_family_2",
666 &new_handle, 2));
667 handles.push_back(new_handle);
668
669 MakeKVMap(kSampleSize_);
670 PutValues(0, kSampleSize_, false, handles[0]);
671 PutValues(0, kSampleSize_, false, handles[1]);
672 PutValues(0, kSampleSize_, false, handles[2]);
673
674 // everything should be there after 1 second
675 SleepCompactCheck(1, 0, kSampleSize_, true, false, handles[0]);
676 SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[1]);
677 SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[2]);
678
679 // only column family 1 should be alive after 4 seconds
680 SleepCompactCheck(3, 0, kSampleSize_, false, false, handles[0]);
681 SleepCompactCheck(0, 0, kSampleSize_, true, false, handles[1]);
682 SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[2]);
683
684 // nothing should be there after 6 seconds
685 SleepCompactCheck(2, 0, kSampleSize_, false, false, handles[0]);
686 SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[1]);
687 SleepCompactCheck(0, 0, kSampleSize_, false, false, handles[2]);
688
689 for (auto h : handles) {
690 delete h;
691 }
692 delete db_ttl_;
693 db_ttl_ = nullptr;
694 }
695
696 // Puts a set of values and checks its absence using Get after ttl
697 TEST_F(TtlTest, ChangeTtlOnOpenDb) {
698 MakeKVMap(kSampleSize_);
699
700 OpenTtl(1); // T=0:Open the db with ttl = 2
701 SetTtl(3);
702 PutValues(0, kSampleSize_); // T=0:Insert Set1. Delete at t=2
703 SleepCompactCheck(2, 0, kSampleSize_, true); // T=2:Set1 should be there
704 CloseTtl();
705 }
706
707 // Test DeleteRange for DBWithTtl
708 TEST_F(TtlTest, DeleteRangeTest) {
709 OpenTtl();
710 ASSERT_OK(db_ttl_->Put(WriteOptions(), "a", "val"));
711 MakeDeleteRange("a", "b");
712 ASSERT_OK(db_ttl_->Put(WriteOptions(), "c", "val"));
713 MakeDeleteRange("b", "d");
714 ASSERT_OK(db_ttl_->Put(WriteOptions(), "e", "val"));
715 MakeDeleteRange("d", "e");
716 // first iteration verifies query correctness in memtable, second verifies
717 // query correctness for a single SST file
718 for (int i = 0; i < 2; i++) {
719 if (i > 0) {
720 ASSERT_OK(db_ttl_->Flush(FlushOptions()));
721 }
722 std::string value;
723 ASSERT_TRUE(db_ttl_->Get(ReadOptions(), "a", &value).IsNotFound());
724 ASSERT_TRUE(db_ttl_->Get(ReadOptions(), "c", &value).IsNotFound());
725 ASSERT_OK(db_ttl_->Get(ReadOptions(), "e", &value));
726 }
727 CloseTtl();
728 }
729
730 class DummyFilter : public CompactionFilter {
731 public:
732 bool Filter(int /*level*/, const Slice& /*key*/, const Slice& /*value*/,
733 std::string* /*new_value*/,
734 bool* /*value_changed*/) const override {
735 return false;
736 }
737
738 const char* Name() const override { return kClassName(); }
739 static const char* kClassName() { return "DummyFilter"; }
740 };
741
742 class DummyFilterFactory : public CompactionFilterFactory {
743 public:
744 const char* Name() const override { return kClassName(); }
745 static const char* kClassName() { return "DummyFilterFactory"; }
746
747 std::unique_ptr<CompactionFilter> CreateCompactionFilter(
748 const CompactionFilter::Context&) override {
749 std::unique_ptr<CompactionFilter> f(new DummyFilter());
750 return f;
751 }
752 };
753
754 static int RegisterTestObjects(ObjectLibrary& library,
755 const std::string& /*arg*/) {
756 library.AddFactory<CompactionFilter>(
757 "DummyFilter", [](const std::string& /*uri*/,
758 std::unique_ptr<CompactionFilter>* /*guard*/,
759 std::string* /* errmsg */) {
760 static DummyFilter dummy;
761 return &dummy;
762 });
763 library.AddFactory<CompactionFilterFactory>(
764 "DummyFilterFactory", [](const std::string& /*uri*/,
765 std::unique_ptr<CompactionFilterFactory>* guard,
766 std::string* /* errmsg */) {
767 guard->reset(new DummyFilterFactory());
768 return guard->get();
769 });
770 return 2;
771 }
772
773 class TtlOptionsTest : public testing::Test {
774 public:
775 TtlOptionsTest() {
776 config_options_.registry->AddLibrary("RegisterTtlObjects",
777 RegisterTtlObjects, "");
778 config_options_.registry->AddLibrary("RegisterTtlTestObjects",
779 RegisterTestObjects, "");
780 }
781 ConfigOptions config_options_;
782 };
783
784 TEST_F(TtlOptionsTest, LoadTtlCompactionFilter) {
785 const CompactionFilter* filter = nullptr;
786
787 ASSERT_OK(CompactionFilter::CreateFromString(
788 config_options_, TtlCompactionFilter::kClassName(), &filter));
789 ASSERT_NE(filter, nullptr);
790 ASSERT_STREQ(filter->Name(), TtlCompactionFilter::kClassName());
791 auto ttl = filter->GetOptions<int32_t>("TTL");
792 ASSERT_NE(ttl, nullptr);
793 ASSERT_EQ(*ttl, 0);
794 ASSERT_OK(filter->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
795 delete filter;
796 filter = nullptr;
797
798 ASSERT_OK(CompactionFilter::CreateFromString(
799 config_options_, "id=TtlCompactionFilter; ttl=123", &filter));
800 ASSERT_NE(filter, nullptr);
801 ttl = filter->GetOptions<int32_t>("TTL");
802 ASSERT_NE(ttl, nullptr);
803 ASSERT_EQ(*ttl, 123);
804 ASSERT_OK(filter->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
805 delete filter;
806 filter = nullptr;
807
808 ASSERT_OK(CompactionFilter::CreateFromString(
809 config_options_,
810 "id=TtlCompactionFilter; ttl=456; user_filter=DummyFilter;", &filter));
811 ASSERT_NE(filter, nullptr);
812 auto inner = filter->CheckedCast<DummyFilter>();
813 ASSERT_NE(inner, nullptr);
814 ASSERT_OK(filter->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
815 std::string mismatch;
816 std::string opts_str = filter->ToString(config_options_);
817 const CompactionFilter* copy = nullptr;
818 ASSERT_OK(
819 CompactionFilter::CreateFromString(config_options_, opts_str, &copy));
820 ASSERT_TRUE(filter->AreEquivalent(config_options_, copy, &mismatch));
821 delete filter;
822 delete copy;
823 }
824
825 TEST_F(TtlOptionsTest, LoadTtlCompactionFilterFactory) {
826 std::shared_ptr<CompactionFilterFactory> cff;
827
828 ASSERT_OK(CompactionFilterFactory::CreateFromString(
829 config_options_, TtlCompactionFilterFactory::kClassName(), &cff));
830 ASSERT_NE(cff.get(), nullptr);
831 ASSERT_STREQ(cff->Name(), TtlCompactionFilterFactory::kClassName());
832 auto ttl = cff->GetOptions<int32_t>("TTL");
833 ASSERT_NE(ttl, nullptr);
834 ASSERT_EQ(*ttl, 0);
835 ASSERT_OK(cff->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
836
837 ASSERT_OK(CompactionFilterFactory::CreateFromString(
838 config_options_, "id=TtlCompactionFilterFactory; ttl=123", &cff));
839 ASSERT_NE(cff.get(), nullptr);
840 ASSERT_STREQ(cff->Name(), TtlCompactionFilterFactory::kClassName());
841 ttl = cff->GetOptions<int32_t>("TTL");
842 ASSERT_NE(ttl, nullptr);
843 ASSERT_EQ(*ttl, 123);
844 ASSERT_OK(cff->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
845
846 ASSERT_OK(CompactionFilterFactory::CreateFromString(
847 config_options_,
848 "id=TtlCompactionFilterFactory; ttl=456; "
849 "user_filter_factory=DummyFilterFactory;",
850 &cff));
851 ASSERT_NE(cff.get(), nullptr);
852 auto filter = cff->CreateCompactionFilter(CompactionFilter::Context());
853 ASSERT_NE(filter.get(), nullptr);
854 auto ttlf = filter->CheckedCast<TtlCompactionFilter>();
855 ASSERT_EQ(filter.get(), ttlf);
856 auto user = filter->CheckedCast<DummyFilter>();
857 ASSERT_NE(user, nullptr);
858 ASSERT_OK(cff->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
859
860 std::string opts_str = cff->ToString(config_options_);
861 std::string mismatch;
862 std::shared_ptr<CompactionFilterFactory> copy;
863 ASSERT_OK(CompactionFilterFactory::CreateFromString(config_options_, opts_str,
864 &copy));
865 ASSERT_TRUE(cff->AreEquivalent(config_options_, copy.get(), &mismatch));
866 }
867
868 TEST_F(TtlOptionsTest, LoadTtlMergeOperator) {
869 std::shared_ptr<MergeOperator> mo;
870
871 config_options_.invoke_prepare_options = false;
872 ASSERT_OK(MergeOperator::CreateFromString(
873 config_options_, TtlMergeOperator::kClassName(), &mo));
874 ASSERT_NE(mo.get(), nullptr);
875 ASSERT_STREQ(mo->Name(), TtlMergeOperator::kClassName());
876 ASSERT_NOK(mo->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
877
878 config_options_.invoke_prepare_options = true;
879 ASSERT_OK(MergeOperator::CreateFromString(
880 config_options_, "id=TtlMergeOperator; user_operator=bytesxor", &mo));
881 ASSERT_NE(mo.get(), nullptr);
882 ASSERT_STREQ(mo->Name(), TtlMergeOperator::kClassName());
883 ASSERT_OK(mo->ValidateOptions(DBOptions(), ColumnFamilyOptions()));
884 auto ttl_mo = mo->CheckedCast<TtlMergeOperator>();
885 ASSERT_EQ(mo.get(), ttl_mo);
886 auto user = ttl_mo->CheckedCast<BytesXOROperator>();
887 ASSERT_NE(user, nullptr);
888
889 std::string mismatch;
890 std::string opts_str = mo->ToString(config_options_);
891 std::shared_ptr<MergeOperator> copy;
892 ASSERT_OK(MergeOperator::CreateFromString(config_options_, opts_str, &copy));
893 ASSERT_TRUE(mo->AreEquivalent(config_options_, copy.get(), &mismatch));
894 }
895 } // namespace ROCKSDB_NAMESPACE
896
897 // A black-box test for the ttl wrapper around rocksdb
898 int main(int argc, char** argv) {
899 ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
900 ::testing::InitGoogleTest(&argc, argv);
901 return RUN_ALL_TESTS();
902 }
903
904 #else
905 #include <stdio.h>
906
907 int main(int /*argc*/, char** /*argv*/) {
908 fprintf(stderr, "SKIPPED as DBWithTTL is not supported in ROCKSDB_LITE\n");
909 return 0;
910 }
911
912 #endif // !ROCKSDB_LITE