diff --git a/src/commands.rs b/src/commands.rs index d23ce058b..ab5a19103 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -3,7 +3,7 @@ use crate::cmd::{cmd, Cmd, Iter}; use crate::connection::{Connection, ConnectionLike, Msg}; use crate::pipeline::Pipeline; -use crate::types::{FromRedisValue, NumericBehavior, RedisResult, ToRedisArgs, RedisWrite}; +use crate::types::{FromRedisValue, NumericBehavior, RedisResult, ToRedisArgs, RedisWrite, Expiry}; #[cfg(feature = "cluster")] use crate::cluster_pipeline::ClusterPipeline; @@ -397,6 +397,24 @@ implement_commands! { cmd("PTTL").arg(key) } + /// Get the value of a key and set expiration + fn get_ex(key: K, expire_at: Expiry) { + let (option, time_arg) = match expire_at { + Expiry::EX(sec) => ("EX", Some(sec)), + Expiry::PX(ms) => ("PX", Some(ms)), + Expiry::EXAT(timestamp_sec) => ("EXAT", Some(timestamp_sec)), + Expiry::PXAT(timestamp_ms) => ("PXAT", Some(timestamp_ms)), + Expiry::PERSIST => ("PERSIST", None), + }; + + cmd("GETEX").arg(key).arg(option).arg(time_arg) + } + + /// Get the value of a key and delete it + fn get_del(key: K) { + cmd("GETDEL").arg(key) + } + /// Rename a key. fn rename(key: K, new_key: K) { cmd("RENAME").arg(key).arg(new_key) diff --git a/src/lib.rs b/src/lib.rs index fd89205d5..51f85ec81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -372,6 +372,8 @@ pub use crate::pipeline::Pipeline; #[cfg_attr(docsrs, doc(cfg(feature = "script")))] pub use crate::script::{Script, ScriptInvocation}; +// preserve grouping and order +#[rustfmt::skip] pub use crate::types::{ // utility functions from_redis_value, @@ -385,6 +387,7 @@ pub use crate::types::{ // utility types InfoDict, NumericBehavior, + Expiry, // error and result types RedisError, diff --git a/src/types.rs b/src/types.rs index 1c86e9dc4..9a14bdb13 100644 --- a/src/types.rs +++ b/src/types.rs @@ -25,6 +25,20 @@ macro_rules! invalid_type_error_inner { }; } +/// Helper enum that is used to define expiry time +pub enum Expiry { + /// EX seconds -- Set the specified expire time, in seconds. + EX(usize), + /// PX milliseconds -- Set the specified expire time, in milliseconds. + PX(usize), + /// EXAT timestamp-seconds -- Set the specified Unix time at which the key will expire, in seconds. + EXAT(usize), + /// PXAT timestamp-milliseconds -- Set the specified Unix time at which the key will expire, in milliseconds. + PXAT(usize), + /// PERSIST -- Remove the time to live associated with the key. + PERSIST, +} + /// Helper enum that is used in some situations to describe /// the behavior of arguments in a numeric context. #[derive(PartialEq, Eq, Clone, Debug, Copy)] diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 119ef2dee..cb0b354d5 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -1,7 +1,8 @@ #![allow(clippy::let_unit_value)] use redis::{ - Commands, ConnectionInfo, ConnectionLike, ControlFlow, ErrorKind, PubSubCommands, RedisResult, + Commands, ConnectionInfo, ConnectionLike, ControlFlow, ErrorKind, Expiry, PubSubCommands, + RedisResult, }; use std::collections::{BTreeMap, BTreeSet}; @@ -64,6 +65,55 @@ fn test_incr() { assert_eq!(redis::cmd("INCR").arg("foo").query(&mut con), Ok(43usize)); } +#[test] +fn test_getdel() { + let ctx = TestContext::new(); + let mut con = ctx.connection(); + + redis::cmd("SET").arg("foo").arg(42).execute(&mut con); + + assert_eq!(con.get_del("foo"), Ok(42usize)); + + assert_eq!( + redis::cmd("GET").arg("foo").query(&mut con), + Ok(None::) + ); +} + +#[test] +fn test_getex() { + let ctx = TestContext::new(); + let mut con = ctx.connection(); + + redis::cmd("SET").arg("foo").arg(42usize).execute(&mut con); + + // Return of get_ex must match set value + let ret_value = con.get_ex::<_, usize>("foo", Expiry::EX(1)).unwrap(); + assert_eq!(ret_value, 42usize); + + // Get before expiry time must also return value + sleep(Duration::from_millis(100)); + let delayed_get = con.get::<_, usize>("foo").unwrap(); + assert_eq!(delayed_get, 42usize); + + // Get after expiry time mustn't return value + sleep(Duration::from_secs(1)); + let after_expire_get = con.get::<_, Option>("foo").unwrap(); + assert_eq!(after_expire_get, None); + + // Persist option test prep + redis::cmd("SET").arg("foo").arg(420usize).execute(&mut con); + + // Return of get_ex with persist option must match set value + let ret_value = con.get_ex::<_, usize>("foo", Expiry::PERSIST).unwrap(); + assert_eq!(ret_value, 420usize); + + // Get after persist get_ex must return value + sleep(Duration::from_millis(200)); + let delayed_get = con.get::<_, usize>("foo").unwrap(); + assert_eq!(delayed_get, 420usize); +} + #[test] fn test_info() { let ctx = TestContext::new();