@@ -611,6 +611,90 @@ TEST_F(RedisHashFieldExpirationEncodingTest, CompactionGhostDoesNotDecrementMeta
611611 EXPECT_EQ (metadata.upper , before.upper );
612612}
613613
614+ TEST_F (RedisHashFieldExpirationEncodingTest, SizeRepairsExpiredPhysicalAndGhostMetadata) {
615+ const Slice key = " hfe-size-repair" ;
616+ uint64_t ret = 0 ;
617+ auto s = hash_->MSet (*ctx_, key, {{" persistent" , " 1" }, {" live" , " 2" }, {" expired" , " 3" }, {" ghost" , " 4" }}, false , &ret);
618+ ASSERT_TRUE (s.ok ()) << s.ToString ();
619+ ASSERT_EQ (ret, 4 );
620+
621+ std::vector<int64_t > results;
622+ uint64_t now = util::GetTimeStampMS ();
623+ uint64_t live_expire = now + 60'000 ;
624+ uint64_t expired_at = now - 1 ;
625+ s = hash_->ExpireFields (*ctx_, key, {" live" }, live_expire, HashFieldExpireCondition::kNone , &results);
626+ ASSERT_TRUE (s.ok ()) << s.ToString ();
627+ s = hash_->ExpireFields (*ctx_, key, {" ghost" }, live_expire + 60'000 , HashFieldExpireCondition::kNone , &results);
628+ ASSERT_TRUE (s.ok ()) << s.ToString ();
629+
630+ HashMetadata metadata = hashMetadata (key.ToString ());
631+ s = putRawHashValue (key.ToString (), " expired" , expired_at, " 3" );
632+ ASSERT_TRUE (s.ok ()) << s.ToString ();
633+ s = deleteRawHashValue (key.ToString (), " ghost" );
634+ ASSERT_TRUE (s.ok ()) << s.ToString ();
635+
636+ metadata.size = 4 ;
637+ metadata.persist = 1 ;
638+ metadata.lower = expired_at;
639+ metadata.upper = live_expire + 60'000 ;
640+ s = putHashMetadata (key.ToString (), metadata);
641+ ASSERT_TRUE (s.ok ()) << s.ToString ();
642+
643+ s = hash_->Size (*ctx_, key, &ret);
644+ ASSERT_TRUE (s.ok ()) << s.ToString ();
645+ EXPECT_EQ (ret, 2 );
646+
647+ metadata = hashMetadata (key.ToString ());
648+ EXPECT_EQ (metadata.size , 2 );
649+ EXPECT_EQ (metadata.persist , 1 );
650+ EXPECT_EQ (metadata.lower , live_expire);
651+ EXPECT_EQ (metadata.upper , live_expire);
652+
653+ std::string value;
654+ s = hash_->Get (*ctx_, key, " expired" , &value);
655+ EXPECT_TRUE (s.IsNotFound ());
656+ s = hash_->Get (*ctx_, key, " ghost" , &value);
657+ EXPECT_TRUE (s.IsNotFound ());
658+ s = hash_->Get (*ctx_, key, " persistent" , &value);
659+ ASSERT_TRUE (s.ok ()) << s.ToString ();
660+ EXPECT_EQ (value, " 1" );
661+ s = hash_->Get (*ctx_, key, " live" , &value);
662+ ASSERT_TRUE (s.ok ()) << s.ToString ();
663+ EXPECT_EQ (value, " 2" );
664+ }
665+
666+ TEST_F (RedisHashFieldExpirationEncodingTest, SizeDeletesHashWhenAllTtlCandidatesExpired) {
667+ const Slice key = " hfe-size-delete-all-expired" ;
668+ uint64_t ret = 0 ;
669+ auto s = hash_->MSet (*ctx_, key, {{" a" , " 1" }, {" b" , " 2" }}, false , &ret);
670+ ASSERT_TRUE (s.ok ()) << s.ToString ();
671+ ASSERT_EQ (ret, 2 );
672+
673+ uint64_t now = util::GetTimeStampMS ();
674+ uint64_t lower = now - 2'000 ;
675+ uint64_t upper = now - 1'000 ;
676+ s = putRawHashValue (key.ToString (), " a" , lower, " 1" );
677+ ASSERT_TRUE (s.ok ()) << s.ToString ();
678+ s = putRawHashValue (key.ToString (), " b" , upper, " 2" );
679+ ASSERT_TRUE (s.ok ()) << s.ToString ();
680+
681+ HashMetadata metadata = hashMetadata (key.ToString ());
682+ metadata.size = 2 ;
683+ metadata.persist = 0 ;
684+ metadata.lower = lower;
685+ metadata.upper = upper;
686+ s = putHashMetadata (key.ToString (), metadata);
687+ ASSERT_TRUE (s.ok ()) << s.ToString ();
688+
689+ s = hash_->Size (*ctx_, key, &ret);
690+ ASSERT_TRUE (s.ok ()) << s.ToString ();
691+ EXPECT_EQ (ret, 0 );
692+
693+ metadata = HashMetadata (false );
694+ s = getHashMetadata (key.ToString (), &metadata);
695+ EXPECT_TRUE (s.IsNotFound ());
696+ }
697+
614698TEST_F (RedisHashFieldExpirationEncodingTest, DeleteHandlesPersistentLiveExpiredMissingAndDuplicateFields) {
615699 const Slice key = " hfe-delete-state-matrix" ;
616700 uint64_t ret = 0 ;
0 commit comments