Implemented DBSIZE

This commit is contained in:
Maxime Van Hees 2025-08-19 16:05:25 +02:00
parent 463000c8f7
commit b9a9f3e6d6
3 changed files with 76 additions and 0 deletions

View File

@ -17,6 +17,7 @@ pub enum Cmd {
MGet(Vec<String>), MGet(Vec<String>),
MSet(Vec<(String, String)>), MSet(Vec<(String, String)>),
Keys, Keys,
DbSize,
ConfigGet(String), ConfigGet(String),
Info(Option<String>), Info(Option<String>),
Del(String), Del(String),
@ -191,6 +192,12 @@ impl Cmd {
Cmd::Keys Cmd::Keys
} }
} }
"dbsize" => {
if cmd.len() != 1 {
return Err(DBError(format!("wrong number of arguments for DBSIZE command")));
}
Cmd::DbSize
}
"info" => { "info" => {
let section = if cmd.len() == 2 { let section = if cmd.len() == 2 {
Some(cmd[1].clone()) Some(cmd[1].clone())
@ -634,6 +641,7 @@ impl Cmd {
Cmd::DelMulti(keys) => del_multi_cmd(server, &keys).await, Cmd::DelMulti(keys) => del_multi_cmd(server, &keys).await,
Cmd::ConfigGet(name) => config_get_cmd(&name, server), Cmd::ConfigGet(name) => config_get_cmd(&name, server),
Cmd::Keys => keys_cmd(server).await, Cmd::Keys => keys_cmd(server).await,
Cmd::DbSize => dbsize_cmd(server).await,
Cmd::Info(section) => info_cmd(server, &section).await, Cmd::Info(section) => info_cmd(server, &section).await,
Cmd::Type(k) => type_cmd(server, &k).await, Cmd::Type(k) => type_cmd(server, &k).await,
Cmd::Incr(key) => incr_cmd(server, &key).await, Cmd::Incr(key) => incr_cmd(server, &key).await,
@ -1060,6 +1068,13 @@ async fn keys_cmd(server: &Server) -> Result<Protocol, DBError> {
)) ))
} }
async fn dbsize_cmd(server: &Server) -> Result<Protocol, DBError> {
match server.current_storage()?.dbsize() {
Ok(n) => Ok(Protocol::SimpleString(n.to_string())),
Err(e) => Ok(Protocol::err(&e.0)),
}
}
#[derive(Serialize)] #[derive(Serialize)]
struct ServerInfo { struct ServerInfo {
redis_version: String, redis_version: String,

View File

@ -216,3 +216,30 @@ impl Storage {
Ok(keys) Ok(keys)
} }
} }
impl Storage {
pub fn dbsize(&self) -> Result<i64, DBError> {
let read_txn = self.db.begin_read()?;
let types_table = read_txn.open_table(TYPES_TABLE)?;
let expiration_table = read_txn.open_table(EXPIRATION_TABLE)?;
let mut count: i64 = 0;
let mut iter = types_table.iter()?;
while let Some(entry) = iter.next() {
let entry = entry?;
let key = entry.0.value();
let ty = entry.1.value();
if ty == "string" {
if let Some(expires_at) = expiration_table.get(key)? {
if now_in_millis() > expires_at.value() as u128 {
// Skip logically expired string keys
continue;
}
}
}
count += 1;
}
Ok(count)
}
}

View File

@ -816,3 +816,37 @@ async fn test_05b_brpop_suite() {
assert_contains(&brpop_res, "q:blockr", "BRPOP returned key"); assert_contains(&brpop_res, "q:blockr", "BRPOP returned key");
assert_contains(&brpop_res, "X", "BRPOP returned element"); assert_contains(&brpop_res, "X", "BRPOP returned element");
} }
#[tokio::test]
async fn test_13_dbsize() {
let (server, port) = start_test_server("dbsize").await;
spawn_listener(server, port).await;
sleep(Duration::from_millis(150)).await;
let mut s = connect(port).await;
// Initially empty
let n0 = send_cmd(&mut s, &["DBSIZE"]).await;
assert_contains(&n0, "0", "DBSIZE initial should be 0");
// Add a string, a hash, and a list -> dbsize = 3
let _ = send_cmd(&mut s, &["SET", "s", "v"]).await;
let _ = send_cmd(&mut s, &["HSET", "h", "f", "v"]).await;
let _ = send_cmd(&mut s, &["LPUSH", "l", "a", "b"]).await;
let n3 = send_cmd(&mut s, &["DBSIZE"]).await;
assert_contains(&n3, "3", "DBSIZE after adding s,h,l should be 3");
// Expire the string and wait, dbsize should drop to 2
let _ = send_cmd(&mut s, &["PEXPIRE", "s", "400"]).await;
sleep(Duration::from_millis(500)).await;
let n2 = send_cmd(&mut s, &["DBSIZE"]).await;
assert_contains(&n2, "2", "DBSIZE after string expiry should be 2");
// Delete remaining keys and confirm 0
let _ = send_cmd(&mut s, &["DEL", "h"]).await;
let _ = send_cmd(&mut s, &["DEL", "l"]).await;
let n_final = send_cmd(&mut s, &["DBSIZE"]).await;
assert_contains(&n_final, "0", "DBSIZE after deleting all keys should be 0");
}