use crate::backend::native::v2::kv_store::store::KvStore;
use crate::backend::native::v2::kv_store::types::KvEntry;
use std::time::SystemTime;
pub fn is_expired(entry: &KvEntry) -> bool {
if let Some(ttl) = entry.metadata.ttl_seconds {
if ttl == 0 {
return true;
}
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let expiration_time = entry.metadata.created_at.saturating_add(ttl);
now > expiration_time
} else {
false
}
}
pub fn seconds_until_expiration(entry: &KvEntry) -> Option<u64> {
if let Some(ttl) = entry.metadata.ttl_seconds {
if ttl == 0 {
return Some(0);
}
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let expiration_time = entry.metadata.created_at.saturating_add(ttl);
if now >= expiration_time {
Some(0)
} else {
Some(expiration_time - now)
}
} else {
None
}
}
pub fn cleanup_expired_entries(store: &mut KvStore) -> usize {
use parking_lot::RwLockWriteGuard;
use std::collections::HashMap;
let mut entries: RwLockWriteGuard<'_, HashMap<Vec<u8>, Vec<KvEntry>>> = store.entries.write();
let mut total_removed = 0;
let keys_to_remove: Vec<Vec<u8>> = entries
.iter()
.filter(|(_, versions)| {
versions.iter().all(|v| is_expired(v))
})
.map(|(key, _)| key.clone())
.collect();
for key in keys_to_remove {
let count = entries.remove(&key).map_or(0, |v| v.len());
total_removed += count;
}
for versions in entries.values_mut() {
let original_len = versions.len();
versions.retain(|v| !is_expired(v));
total_removed += original_len - versions.len();
}
total_removed
}
#[cfg(test)]
mod tests {
use super::*;
use crate::backend::native::v2::kv_store::types::{KvEntry, KvMetadata, KvValue};
use std::time::{Duration, SystemTime};
fn create_test_entry(ttl_seconds: Option<u64>) -> KvEntry {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
KvEntry {
key: b"test_key".to_vec(),
value: KvValue::Integer(42),
metadata: KvMetadata {
created_at: now,
updated_at: now,
ttl_seconds,
version: 100,
},
}
}
fn create_old_entry(ttl_seconds: u64) -> KvEntry {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let old_created_at = now.saturating_sub(ttl_seconds + 1);
KvEntry {
key: b"old_key".to_vec(),
value: KvValue::Integer(42),
metadata: KvMetadata {
created_at: old_created_at,
updated_at: old_created_at,
ttl_seconds: Some(ttl_seconds),
version: 100,
},
}
}
#[test]
fn test_is_expired_no_ttl() {
let entry = create_test_entry(None);
assert!(!is_expired(&entry));
}
#[test]
fn test_is_expired_not_yet() {
let entry = create_test_entry(Some(60)); assert!(!is_expired(&entry));
}
#[test]
fn test_is_expired_after_ttl() {
let entry = create_old_entry(1); assert!(is_expired(&entry));
}
#[test]
fn test_seconds_until_expiration_no_ttl() {
let entry = create_test_entry(None);
assert_eq!(seconds_until_expiration(&entry), None);
}
#[test]
fn test_seconds_until_expiration_future() {
let entry = create_test_entry(Some(60)); let seconds = seconds_until_expiration(&entry).expect("should have TTL");
assert!(seconds <= 60);
assert!(seconds > 58); }
#[test]
fn test_seconds_until_expiration_past() {
let entry = create_old_entry(1); let seconds = seconds_until_expiration(&entry).expect("should have TTL");
assert_eq!(seconds, 0);
}
#[test]
fn test_cleanup_expired_entries() {
let mut store = KvStore::new();
store
.set(b"key1".to_vec(), KvValue::Integer(1), None)
.unwrap();
store
.set(b"key2".to_vec(), KvValue::Integer(2), Some(3600))
.unwrap();
{
let old_created_at = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs().saturating_sub(10))
.unwrap_or(0);
let expired_entry = KvEntry {
key: b"key3".to_vec(),
value: KvValue::Integer(3),
metadata: KvMetadata {
created_at: old_created_at,
updated_at: old_created_at,
ttl_seconds: Some(1), version: 100,
},
};
let mut entries = store.entries.write();
entries.insert(b"key3".to_vec(), vec![expired_entry]);
}
assert_eq!(store.len(), 3);
let removed = cleanup_expired_entries(&mut store);
assert_eq!(removed, 1);
assert_eq!(store.len(), 2);
assert!(store.exists(b"key1"));
assert!(store.exists(b"key2"));
assert!(!store.exists(b"key3"));
}
#[test]
fn test_cleanup_empty_store() {
let mut store = KvStore::new();
let removed = cleanup_expired_entries(&mut store);
assert_eq!(removed, 0);
assert_eq!(store.len(), 0);
}
#[test]
fn test_ttl_overflow_handling() {
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0);
let entry = KvEntry {
key: b"test_key".to_vec(),
value: KvValue::Integer(42),
metadata: KvMetadata {
created_at: now,
updated_at: now,
ttl_seconds: Some(u64::MAX - 100), version: 100,
},
};
assert!(!is_expired(&entry));
let seconds = seconds_until_expiration(&entry);
assert!(seconds.is_some());
}
}