From a1a189af93ffc2c1d97bf72530145d920778abea Mon Sep 17 00:00:00 2001 From: Nicolas del Valle Date: Sun, 20 Oct 2024 19:53:06 +0200 Subject: [PATCH] Implement PTTL command --- src/commands/mod.rs | 9 +++++++-- src/commands/pttl.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/integration.rs | 31 +++++++++++++++++++++++-------- 3 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 src/commands/pttl.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index f86e0f0..e149fbf 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -25,6 +25,7 @@ pub mod mset; pub mod msetnx; pub mod object; pub mod ping; +pub mod pttl; pub mod scan; pub mod select; pub mod set; @@ -70,6 +71,7 @@ use mset::Mset; use msetnx::Msetnx; use object::Object; use ping::Ping; +use pttl::Pttl; use scan::Scan; use select::Select; use set::Set; @@ -88,8 +90,8 @@ pub enum Command { Del(Del), Exists(Exists), Get(Get), - Getex(Getex), Getdel(Getdel), + Getex(Getex), Getrange(Getrange), Incr(Incr), IncrBy(IncrBy), @@ -101,6 +103,7 @@ pub enum Command { Mset(Mset), Msetnx(Msetnx), Object(Object), + Pttl(Pttl), Scan(Scan), Set(Set), Setnx(Setnx), @@ -132,8 +135,8 @@ impl Executable for Command { Command::Del(cmd) => cmd.exec(store), Command::Exists(cmd) => cmd.exec(store), Command::Get(cmd) => cmd.exec(store), - Command::Getex(cmd) => cmd.exec(store), Command::Getdel(cmd) => cmd.exec(store), + Command::Getex(cmd) => cmd.exec(store), Command::Getrange(cmd) => cmd.exec(store), Command::Incr(cmd) => cmd.exec(store), Command::IncrBy(cmd) => cmd.exec(store), @@ -148,6 +151,7 @@ impl Executable for Command { Command::Msetnx(cmd) => cmd.exec(store), Command::Object(cmd) => cmd.exec(store), Command::Ping(cmd) => cmd.exec(store), + Command::Pttl(cmd) => cmd.exec(store), Command::Scan(cmd) => cmd.exec(store), Command::Select(cmd) => cmd.exec(store), Command::Set(cmd) => cmd.exec(store), @@ -217,6 +221,7 @@ impl TryFrom for Command { "setrange" => Setrange::try_from(parser).map(Command::Setrange), "strlen" => Strlen::try_from(parser).map(Command::Strlen), "ttl" => Ttl::try_from(parser).map(Command::Ttl), + "pttl" => Pttl::try_from(parser).map(Command::Pttl), "type" => Type::try_from(parser).map(Command::Type), _ => Err(CommandParserError::UnknownCommand { command: command_name, diff --git a/src/commands/pttl.rs b/src/commands/pttl.rs new file mode 100644 index 0000000..0cc810e --- /dev/null +++ b/src/commands/pttl.rs @@ -0,0 +1,36 @@ +use crate::commands::executable::Executable; +use crate::commands::CommandParser; +use crate::frame::Frame; +use crate::store::Store; +use crate::Error; + +/// Like TTL this command returns the remaining time to live of a key that has an expire set, with +/// the sole difference that TTL returns the amount of remaining time in seconds while PTTL returns +/// it in milliseconds. +/// +/// Ref: +#[derive(Debug, PartialEq)] +pub struct Pttl { + pub key: String, +} + +impl Executable for Pttl { + fn exec(self, store: Store) -> Result { + let state = store.lock(); + let ttl = if state.exists(&self.key) { -1 } else { -2 }; + let ttl = state + .get_ttl(&self.key) + .map(|ttl| ttl.as_millis() as i64) + .unwrap_or(ttl); + Ok(Frame::Integer(ttl)) + } +} + +impl TryFrom<&mut CommandParser> for Pttl { + type Error = Error; + + fn try_from(parser: &mut CommandParser) -> Result { + let key = parser.next_string()?; + Ok(Self { key }) + } +} diff --git a/tests/integration.rs b/tests/integration.rs index 4f6c7d4..fa19c88 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -29,8 +29,6 @@ where let mut pipeline = redis::pipe(); f(&mut pipeline); - let our_response: Result = pipeline.clone().query_async(&mut our_connection).await; - // Since we use the same Redis instance for all tests, we flush it to start fresh. // NOTE: our implementation doesn't yet persist data between runs. let _: Value = redis::pipe() @@ -39,6 +37,7 @@ where .await .unwrap(); + let our_response: Result = pipeline.clone().query_async(&mut our_connection).await; let their_response: Result = pipeline.clone().query_async(&mut their_connection).await; assert!( @@ -123,13 +122,29 @@ async fn test_set_and_get() { #[serial] async fn test_getex() { test_compare::>(|p| { - p.cmd("SET").arg("set_getex_1").arg(1).arg("EX").arg(1); - p.cmd("GETEX").arg("set_getex_1").arg("PERSIST"); - p.cmd("TTL").arg("set_getex_1"); + p.cmd("SET").arg("getex_key_1").arg(1).arg("EX").arg(1); + p.cmd("GETEX").arg("getex_key_1").arg("PERSIST"); + p.cmd("TTL").arg("getex_key_1"); + + p.cmd("SET").arg("getex_key_2").arg(1).arg("EX").arg(1); + p.cmd("TTL").arg("getex_key_2"); + p.cmd("GETEX").arg("getex_key_2").arg("EX").arg(10); + p.cmd("TTL").arg("getex_key_2"); + }) + .await; +} + +#[tokio::test] +#[serial] +async fn test_pttl() { + test_compare::>(|p| { + p.cmd("SET").arg("pttl_key_1").arg(1).arg("EX").arg(1); + p.cmd("PTTL").arg("pttl_key_1"); + + p.cmd("SET").arg("pttl_key_2").arg(1); + p.cmd("PTTL").arg("pttl_key_2"); - p.cmd("SET").arg("set_getex_2").arg(1).arg("EX").arg(1); - p.cmd("GETEX").arg("set_getex_1").arg("EX").arg(10); - p.cmd("TTL").arg("set_getex_1"); + p.cmd("PTTL").arg("pttl_key_3"); }) .await; }