Skip to content

Commit

Permalink
Merge pull request #60 from ndelvalle/impl-pttl
Browse files Browse the repository at this point in the history
Implement PTTL command
  • Loading branch information
ndelvalle authored Oct 21, 2024
2 parents 7db8830 + b1c8b04 commit 3dfd85c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
9 changes: 7 additions & 2 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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),
Expand All @@ -101,6 +103,7 @@ pub enum Command {
Mset(Mset),
Msetnx(Msetnx),
Object(Object),
Pttl(Pttl),
Scan(Scan),
Set(Set),
Setnx(Setnx),
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down Expand Up @@ -217,6 +221,7 @@ impl TryFrom<Frame> 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,
Expand Down
36 changes: 36 additions & 0 deletions src/commands/pttl.rs
Original file line number Diff line number Diff line change
@@ -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: <https://redis.io/docs/latest/commands/pttl/>
#[derive(Debug, PartialEq)]
pub struct Pttl {
pub key: String,
}

impl Executable for Pttl {
fn exec(self, store: Store) -> Result<Frame, Error> {
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<Self, Self::Error> {
let key = parser.next_string()?;
Ok(Self { key })
}
}
34 changes: 24 additions & 10 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ where
let mut pipeline = redis::pipe();
f(&mut pipeline);

let our_response: Result<Res, _> = 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()
Expand All @@ -39,6 +37,7 @@ where
.await
.unwrap();

let our_response: Result<Res, _> = pipeline.clone().query_async(&mut our_connection).await;
let their_response: Result<Res, _> = pipeline.clone().query_async(&mut their_connection).await;

assert!(
Expand Down Expand Up @@ -123,14 +122,29 @@ async fn test_set_and_get() {
#[serial]
async fn test_getex() {
test_compare::<Vec<Value>>(|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("set_getex_2").arg(1).arg("EX").arg(1);
p.cmd("GETEX").arg("set_getex_2").arg("EX").arg(10);
// `TTL set_getex_2` gives different results here.
// It isn't clear if it is a race condition or a bug in our implementation.
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::<Vec<Value>>(|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("PTTL").arg("pttl_key_3");
})
.await;
}
Expand Down

0 comments on commit 3dfd85c

Please sign in to comment.