@@ -111,6 +111,17 @@ class RedisCuckooFilterTest : public TestBase {
111111 value);
112112 }
113113
114+ void writeMetadata (const std::string &key, const CuckooChainMetadata &metadata) {
115+ std::string metadata_bytes;
116+ metadata.Encode (&metadata_bytes);
117+ auto batch = storage_->GetWriteBatchBase ();
118+ auto s =
119+ batch->Put (storage_->GetCFHandle (ColumnFamilyID::Metadata), db_->AppendNamespacePrefix (key), metadata_bytes);
120+ ASSERT_TRUE (s.ok ()) << s.ToString ();
121+ s = storage_->Write (*ctx_, storage_->DefaultWriteOptions (), batch->GetWriteBatch ());
122+ ASSERT_TRUE (s.ok ()) << s.ToString ();
123+ }
124+
114125 std::unique_ptr<redis::CuckooChain> cuckoo_;
115126 std::unique_ptr<redis::Database> db_;
116127 std::string key_;
@@ -197,12 +208,10 @@ TEST_F(RedisCuckooFilterTest, ReserveValidParams) {
197208}
198209
199210TEST_F (RedisCuckooFilterTest, ReserveDuplicate) {
200- // First reserve should succeed
201- auto s = cuckoo_->Reserve (*ctx_, key_, 1000 , 4 , 500 , 2 , kCuckooFilterDefaultPageSize );
202- ASSERT_TRUE (s.ok ());
211+ reserveAndVerify (key_, 1000 , 4 , 500 , 2 );
203212
204213 // Second reserve with same key should fail
205- s = cuckoo_->Reserve (*ctx_, key_, 2000 , 4 , 500 , 2 , kCuckooFilterDefaultPageSize );
214+ auto s = cuckoo_->Reserve (*ctx_, key_, 2000 , 4 , 500 , 2 , kCuckooFilterDefaultPageSize );
206215 ASSERT_FALSE (s.ok ());
207216 ASSERT_TRUE (s.IsInvalidArgument ());
208217 ASSERT_NE (s.ToString ().find (" already exists" ), std::string::npos);
@@ -315,21 +324,18 @@ TEST_F(RedisCuckooFilterTest, ReserveVerifyMetadata) {
315324 uint16_t expansion = 2 ;
316325
317326 // Create the filter
318- auto s =
319- cuckoo_->Reserve (*ctx_, key_, capacity, bucket_size, max_iterations, expansion, kCuckooFilterDefaultPageSize );
320- ASSERT_TRUE (s.ok ()) << " First reserve should succeed" ;
327+ reserveAndVerify (key_, capacity, bucket_size, max_iterations, expansion);
321328
322329 // Verify metadata was stored by trying to reserve again with same key
323330 // This should fail with "already exists" error
324- s = cuckoo_->Reserve (*ctx_, key_, capacity * 2 , bucket_size, max_iterations, expansion, kCuckooFilterDefaultPageSize );
331+ auto s =
332+ cuckoo_->Reserve (*ctx_, key_, capacity * 2 , bucket_size, max_iterations, expansion, kCuckooFilterDefaultPageSize );
325333 ASSERT_FALSE (s.ok ()) << " Second reserve with same key should fail" ;
326334 ASSERT_TRUE (s.IsInvalidArgument ()) << " Should return InvalidArgument error" ;
327335 ASSERT_NE (s.ToString ().find (" already exists" ), std::string::npos) << " Error message should mention 'already exists'" ;
328336
329337 // Verify we can still create filters with different keys
330- s = cuckoo_->Reserve (*ctx_, " different_key" , capacity, bucket_size, max_iterations, expansion,
331- kCuckooFilterDefaultPageSize );
332- ASSERT_TRUE (s.ok ()) << " Should be able to create filter with different key" ;
338+ reserveAndVerify (" different_key" , capacity, bucket_size, max_iterations, expansion);
333339
334340 // Verify the original key still exists (can't create it again)
335341 s = cuckoo_->Reserve (*ctx_, key_, capacity, bucket_size, max_iterations, expansion, kCuckooFilterDefaultPageSize );
@@ -395,14 +401,10 @@ TEST_F(RedisCuckooFilterTest, AddWritesPagedLayout) {
395401
396402TEST_F (RedisCuckooFilterTest, AddToNonExistentFilter) {
397403 std::string key = " nonexistent_key" ;
398- bool added = false ;
399- auto s = cuckoo_->Add (*ctx_, key, " item1" , &added);
400- ASSERT_TRUE (s.ok ()) << s.ToString ();
401- ASSERT_TRUE (added) << " CF.ADD should create the filter when the key does not exist" ;
402- verifyMetadata (key, redis::kCFDefaultCapacity , redis::kCFDefaultBucketSize , redis::kCFDefaultMaxIterations ,
403- redis::kCFDefaultExpansion , 1 , 1 , 0 );
404+ addAndVerify (key, " item1" , redis::kCFDefaultCapacity , redis::kCFDefaultBucketSize , redis::kCFDefaultMaxIterations ,
405+ redis::kCFDefaultExpansion , 1 );
404406
405- s = cuckoo_->Reserve (*ctx_, key, 1000 , 4 , 500 , 2 , kCuckooFilterDefaultPageSize );
407+ auto s = cuckoo_->Reserve (*ctx_, key, 1000 , 4 , 500 , 2 , kCuckooFilterDefaultPageSize );
406408 ASSERT_FALSE (s.ok ());
407409 ASSERT_TRUE (s.IsInvalidArgument ());
408410 ASSERT_NE (s.ToString ().find (" already exists" ), std::string::npos);
@@ -427,6 +429,48 @@ TEST_F(RedisCuckooFilterTest, AddDuplicateItems) {
427429 }
428430}
429431
432+ TEST_F (RedisCuckooFilterTest, AddPrioritizesNewestFilter) {
433+ CuckooChainMetadata metadata;
434+ metadata.size = 0 ;
435+ metadata.base_capacity = 1000 ;
436+ metadata.bucket_size = 4 ;
437+ metadata.max_iterations = 500 ;
438+ metadata.expansion = 2 ;
439+ metadata.n_filters = 2 ;
440+ metadata.num_deleted_items = 0 ;
441+ metadata.page_size = kCuckooFilterDefaultPageSize ;
442+ writeMetadata (key_, metadata);
443+
444+ const std::string item = " item" ;
445+ addAndVerify (key_, item, metadata.base_capacity , metadata.bucket_size , metadata.max_iterations , metadata.expansion , 1 ,
446+ metadata.n_filters );
447+
448+ auto stored_metadata = getMetadata (key_);
449+ ASSERT_EQ (stored_metadata.n_filters , 2 );
450+ ASSERT_EQ (stored_metadata.size , 1 );
451+
452+ auto hash = redis::CuckooFilter::Hash (item);
453+ uint32_t old_num_buckets = 0 ;
454+ auto s = redis::CuckooFilter::OptimalNumBuckets (metadata.base_capacity , metadata.bucket_size , &old_num_buckets);
455+ ASSERT_TRUE (s.ok ()) << s.ToString ();
456+ auto buckets_per_page = stored_metadata.page_size / stored_metadata.bucket_size ;
457+ auto old_page_idx = static_cast <uint32_t >(hash % old_num_buckets) / buckets_per_page;
458+
459+ std::string page;
460+ s = readPage (makePageKey (key_, stored_metadata, 0 , old_page_idx), &page);
461+ EXPECT_TRUE (s.IsNotFound ()) << s.ToString ();
462+
463+ uint32_t num_buckets = 0 ;
464+ s = redis::CuckooFilter::OptimalNumBuckets (metadata.base_capacity * metadata.expansion , metadata.bucket_size ,
465+ &num_buckets);
466+ ASSERT_TRUE (s.ok ()) << s.ToString ();
467+ auto bucket1_idx = static_cast <uint32_t >(hash % num_buckets);
468+ auto expected_page_idx = bucket1_idx / buckets_per_page;
469+
470+ s = readPage (makePageKey (key_, stored_metadata, 1 , expected_page_idx), &page);
471+ ASSERT_TRUE (s.ok ()) << s.ToString ();
472+ }
473+
430474TEST_F (RedisCuckooFilterTest, AddManyItems) {
431475 reserveAndVerify (key_, 1000 , 4 , 500 , 2 );
432476 for (int i = 0 ; i < 100 ; ++i) {
@@ -436,15 +480,14 @@ TEST_F(RedisCuckooFilterTest, AddManyItems) {
436480
437481TEST_F (RedisCuckooFilterTest, AddSmallFilterCapacity) {
438482 uint64_t small_capacity = 10 ;
439- auto s = cuckoo_->Reserve (*ctx_, key_, small_capacity, 2 , 500 , 0 , kCuckooFilterDefaultPageSize );
440- ASSERT_TRUE (s.ok ());
483+ reserveAndVerify (key_, small_capacity, 2 , 500 , 0 );
441484
442485 uint64_t added_count = 0 ;
443486 bool full = false ;
444487 for (int i = 0 ; i < 100 ; ++i) {
445488 std::string item = " item_" + std::to_string (i);
446489 bool added = false ;
447- s = cuckoo_->Add (*ctx_, key_, item, &added);
490+ auto s = cuckoo_->Add (*ctx_, key_, item, &added);
448491
449492 if (!s.ok ()) {
450493 ASSERT_TRUE (s.IsAborted ()) << " Should be Aborted status when full" ;
0 commit comments