use yo_index::Cursor as KeyCursor;
use crate::keyspace::Keyspace;
use crate::value::{self, Kind};
const TRIES: usize = 64;
impl Keyspace {
pub fn scan(
&mut self,
from: KeyCursor,
budget: usize,
ty: Option<Kind>,
mut out: impl FnMut(&[u8]),
) -> KeyCursor {
let now = self.clock.now_ms();
let mut dead = Vec::new();
let next = self.map.scan(from, budget, |key, rec| {
if value::is_expired(rec, now) {
dead.push(key.to_vec());
return;
}
if ty.is_some_and(|want| value::kind(rec) != want) {
return;
}
out(key);
});
self.reap_all(dead);
next
}
pub fn keys(&mut self, out: impl FnMut(&[u8])) {
self.scan(KeyCursor::START, usize::MAX, None, out);
}
fn reap_all(&mut self, dead: Vec<Vec<u8>>) {
for key in dead {
self.drop_key(&key);
self.expired += 1;
}
}
pub fn random_key(&mut self) -> Option<Vec<u8>> {
if self.map.is_empty() {
return None;
}
for _ in 0..TRIES {
let from = KeyCursor::from_raw(self.rng.next_u64());
if let Some(key) = self.sample(from, 0) {
return Some(key);
}
}
self.sample(KeyCursor::START, usize::MAX)
}
fn sample(&mut self, from: KeyCursor, budget: usize) -> Option<Vec<u8>> {
let now = self.clock.now_ms();
let rng = &mut self.rng;
let mut seen = 0usize;
let mut pick = None;
let mut dead = Vec::new();
self.map.scan(from, budget, |key, rec| {
if value::is_expired(rec, now) {
dead.push(key.to_vec());
return;
}
seen += 1;
if rng.below(seen) == 0 {
pick = Some(key.to_vec());
}
});
self.reap_all(dead);
pick
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use super::*;
use crate::Clock;
fn db() -> Keyspace {
Keyspace::with_clock(Clock::fixed(1_000_000))
}
fn put(d: &mut Keyspace, key: &[u8]) {
d.set_plain(key, b"v").expect("room for a record");
}
fn keys_of(db: &mut Keyspace) -> HashSet<Vec<u8>> {
let mut out = HashSet::new();
db.keys(|k| {
out.insert(k.to_vec());
});
out
}
#[test]
fn an_empty_database_has_nothing_to_walk() {
let mut db = db();
assert!(keys_of(&mut db).is_empty());
assert_eq!(db.random_key(), None);
assert!(db.scan(KeyCursor::START, 10, None, |_| {}).is_end());
}
#[test]
fn a_scan_comes_back_with_every_key_once() {
let mut db = db();
for i in 0..2_000u32 {
put(&mut db, format!("k{i}").as_bytes());
}
let mut seen: Vec<Vec<u8>> = Vec::new();
let mut at = KeyCursor::START;
loop {
at = db.scan(at, 10, None, |k| seen.push(k.to_vec()));
if at.is_end() {
break;
}
}
let unique: HashSet<Vec<u8>> = seen.iter().cloned().collect();
assert_eq!(unique.len(), 2_000);
assert_eq!(seen.len(), 2_000, "a quiet scan returned a key twice");
assert_eq!(unique, keys_of(&mut db));
}
#[test]
fn a_scan_can_ask_for_one_type() {
let mut db = db();
put(&mut db, b"s");
db.sadd(b"members", [b"a".as_slice()].into_iter())
.expect("a fresh key");
db.hset(b"h", [(b"f".as_slice(), b"v".as_slice())].into_iter())
.expect("a fresh key");
for (want, name) in [
(Kind::String, "s"),
(Kind::Set, "members"),
(Kind::Hash, "h"),
] {
let mut seen = Vec::new();
let mut at = KeyCursor::START;
loop {
at = db.scan(at, 100, Some(want), |k| seen.push(k.to_vec()));
if at.is_end() {
break;
}
}
assert_eq!(seen, vec![name.as_bytes().to_vec()], "type {want:?}");
}
}
#[test]
fn a_key_past_its_deadline_is_collected_by_the_walk() {
let mut db = db();
put(&mut db, b"alive");
put(&mut db, b"dead");
assert!(db.set_expiry(b"dead", Some(1_000_500)));
db.clock_mut().advance(1_000);
let before = db.expired_keys();
assert_eq!(keys_of(&mut db), HashSet::from([b"alive".to_vec()]));
assert_eq!(db.len(), 1);
assert_eq!(db.expired_keys(), before + 1);
assert_eq!(keys_of(&mut db), HashSet::from([b"alive".to_vec()]));
assert_eq!(db.expired_keys(), before + 1);
}
#[test]
fn a_random_key_is_a_key_that_is_there() {
let mut db = db();
for i in 0..500u32 {
put(&mut db, format!("k{i}").as_bytes());
}
let all = keys_of(&mut db);
let mut picked = HashSet::new();
for _ in 0..200 {
let k = db.random_key().expect("the database is not empty");
assert!(
all.contains(&k),
"randomkey answered a key that is not there"
);
picked.insert(k);
}
assert!(
picked.len() > 10,
"only {} distinct keys in 200 draws",
picked.len()
);
}
#[test]
fn the_last_key_left_is_the_one_randomkey_finds() {
let mut db = db();
for i in 0..5_000u32 {
put(&mut db, format!("k{i}").as_bytes());
}
for i in 0..5_000u32 {
if i != 4_242 {
db.del(format!("k{i}").as_bytes());
}
}
assert_eq!(db.random_key().as_deref(), Some(&b"k4242"[..]));
}
#[test]
fn a_scan_survives_the_keyspace_growing_underneath_it() {
let mut db = db();
for i in 0..2_000u32 {
put(&mut db, format!("k{i}").as_bytes());
}
let mut seen: HashSet<Vec<u8>> = HashSet::new();
let mut at = KeyCursor::START;
let mut added = 2_000u32;
loop {
at = db.scan(at, 8, None, |k| {
seen.insert(k.to_vec());
});
if at.is_end() {
break;
}
for _ in 0..64 {
put(&mut db, format!("k{added}").as_bytes());
added += 1;
}
}
for i in 0..2_000u32 {
let k = format!("k{i}").into_bytes();
assert!(
seen.contains(&k),
"k{i} was there throughout and never came back"
);
}
}
}