use crate::{
client::{prepare_command, PreparedCommand},
resp::{
cmd, CollectionResponse, CommandArgs, KeyValueArgsCollection, PrimitiveResponse, SingleArg,
SingleArgCollection, ToArgs,
},
};
use serde::{
de::{self, SeqAccess, Visitor},
Deserialize, Deserializer,
};
use std::fmt;
/// A group of Redis commands related to [`Strings`](https://redis.io/docs/data-types/strings/)
/// # See Also
/// [Redis Generic Commands](https://redis.io/commands/?group=string)
pub trait StringCommands<'a> {
/// If key already exists and is a string,
/// this command appends the value at the end of the string.
/// If key does not exist it is created and set as an empty string,
/// so APPEND will be similar to SET in this special case.
///
/// # Return
/// the length of the string after the append operation.
///
/// # See Also
/// [](https://redis.io/commands/append/)
#[must_use]
fn append(self, key: K, value: V) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("APPEND").arg(key).arg(value))
}
/// Decrements the number stored at key by one.
///
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if the key contains a value of the wrong type or contains
/// a string that can not be represented as integer.
/// This operation is limited to 64 bit signed integers.
///
/// # Return
/// the value of key after the decrement
///
/// # See Also
/// [](https://redis.io/commands/decr/)
#[must_use]
fn decr(self, key: K) -> PreparedCommand<'a, Self, i64>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("DECR").arg(key))
}
/// Decrements the number stored at key by one.
///
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if the key contains a value of the wrong type or contains
/// a string that can not be represented as integer.
/// This operation is limited to 64 bit signed integers.
///
/// # Return
/// the value of key after the decrement
///
/// # See Also
/// [](https://redis.io/commands/decrby/)
#[must_use]
fn decrby(self, key: K, decrement: i64) -> PreparedCommand<'a, Self, i64>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("DECRBY").arg(key).arg(decrement))
}
/// Get the value of key.
///
/// Get the value of key. If the key does not exist the special
/// value nil is returned. An error is returned if the value
/// stored at key is not a string, because GET only handles
/// string values.
///
/// # Return
/// the value of key, or `nil` when key does not exist.
///
/// # Example
/// ```
/// use rustis::{
/// client::{Client, ClientPreparedCommand},
/// commands::{FlushingMode, ServerCommands, StringCommands},
/// resp::{cmd},
/// Result
/// };
///
/// #[cfg_attr(feature = "tokio-runtime", tokio::main)]
/// #[cfg_attr(feature = "async-std-runtime", async_std::main)]
/// async fn main() -> Result<()> {
/// let client = Client::connect("127.0.0.1:6379").await?;
/// client.flushdb(FlushingMode::Sync).await?;
///
/// // return value can be an Option...
/// let value: Option = client.get("key").await?;
/// assert_eq!(None, value);
///
/// // ... or it can be directly a String.
/// // In this cas a `nil` value will result in an empty String
/// let value: String = client.get("key").await?;
/// assert_eq!("", value);
///
/// client.set("key", "value").await?;
/// let value: String = client.get("key").await?;
/// assert_eq!("value", value);
///
/// Ok(())
/// }
/// ```
///
/// # See Also
/// [](https://redis.io/commands/get/)
#[must_use]
fn get(self, key: K) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("GET").arg(key))
}
/// Get the value of key and delete the key.
///
/// This command is similar to GET, except for the fact that it also deletes the key on success
/// (if and only if the key's value type is a string).
///
/// # Return
/// the value of key, `nil` when key does not exist, or an error if the key's value type isn't a string.
///
/// # See Also
/// [](https://redis.io/commands/getdel/)
#[must_use]
fn getdel(self, key: K) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("GETDEL").arg(key))
}
/// Get the value of key and optionally set its expiration. GETEX is similar to GET, but is a write command with additional options.
///
/// Decrements the number stored at key by decrement.
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if the key contains a value of the wrong type
/// or contains a string that can not be represented as integer.
/// This operation is limited to 64 bit signed integers.
///
/// # Return
/// the value of key, or `nil` when key does not exist.
///
/// # Example
/// ```
/// use rustis::{
/// client::{Client, ClientPreparedCommand},
/// commands::{FlushingMode, GetExOptions, GenericCommands, ServerCommands, StringCommands},
/// resp::cmd,
/// Result,
/// };
///
/// #[cfg_attr(feature = "tokio-runtime", tokio::main)]
/// #[cfg_attr(feature = "async-std-runtime", async_std::main)]
/// async fn main() -> Result<()> {
/// let client = Client::connect("127.0.0.1:6379").await?;
/// client.flushdb(FlushingMode::Sync).await?;
///
/// client.set("key", "value").await?;
/// let value: String = client.getex("key", GetExOptions::Ex(60)).await?;
/// assert_eq!("value", value);
///
/// let ttl = client.ttl("key").await?;
/// assert!(59 <= ttl && ttl <= 60);
///
/// Ok(())
/// }
/// ```
///
/// # See Also
/// [](https://redis.io/commands/getex/)
#[must_use]
fn getex(self, key: K, options: GetExOptions) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("GETEX").arg(key).arg(options))
}
/// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).
///
/// Negative offsets can be used in order to provide an offset starting from the end of the string.
/// So -1 means the last character, -2 the penultimate and so forth.
///
/// The function handles out of range requests by limiting the resulting range to the actual length of the string.
///
/// # Example
/// ```
/// # use rustis::{
/// # client::Client,
/// # commands::{FlushingMode, ServerCommands, StringCommands},
/// # Result,
/// # };
///
/// # #[cfg_attr(feature = "tokio-runtime", tokio::main)]
/// # #[cfg_attr(feature = "async-std-runtime", async_std::main)]
/// # async fn main() -> Result<()> {
/// # let client = Client::connect("127.0.0.1:6379").await?;
/// # client.flushdb(FlushingMode::Sync).await?;
/// client.set("mykey", "This is a string").await?;
///
/// let value: String = client.getrange("mykey", 0, 3).await?;
/// assert_eq!("This", value);
/// let value: String = client.getrange("mykey", -3, -1).await?;
/// assert_eq!("ing", value);
/// let value: String = client.getrange("mykey", 0, -1).await?;
/// assert_eq!("This is a string", value);
/// let value: String = client.getrange("mykey", 10, 100).await?;
/// assert_eq!("string", value);
/// # Ok(())
/// # }
/// ```
///
/// # See Also
/// [](https://redis.io/commands/getrange/)
#[must_use]
fn getrange(self, key: K, start: isize, end: isize) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("GETRANGE").arg(key).arg(start).arg(end))
}
/// Atomically sets key to value and returns the old value stored at key.
/// Returns an error when key exists but does not hold a string value.
/// Any previous time to live associated with the key is discarded on successful SET operation.
///
/// # Return
/// the old value stored at key, or nil when key did not exist.
///
/// # See Also
/// [](https://redis.io/commands/getset/)
#[must_use]
fn getset(self, key: K, value: V) -> PreparedCommand<'a, Self, R>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
R: PrimitiveResponse,
{
prepare_command(self, cmd("GETSET").arg(key).arg(value))
}
/// Increments the number stored at key by one.
///
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if the key contains a value of the wrong type
/// or contains a string that can not be represented as integer.
/// This operation is limited to 64 bit signed integers.
///
/// Note: this is a string operation because Redis does not have a dedicated integer type.
/// The string stored at the key is interpreted as a base-10 64 bit signed integer to execute the operation.
///
/// Redis stores integers in their integer representation, so for string values that actually hold an integer,
/// there is no overhead for storing the string representation of the integer.
///
/// # Return
/// the value of key after the increment
///
/// # See Also
/// [](https://redis.io/commands/incr/)
#[must_use]
fn incr(self, key: K) -> PreparedCommand<'a, Self, i64>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("INCR").arg(key))
}
/// Increments the number stored at key by increment.
///
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if the key contains a value of the wrong type
/// or contains a string that can not be represented as integer.
/// This operation is limited to 64 bit signed integers.
///
/// See [incr](StringCommands::incr) for extra information on increment/decrement operations.
///
/// # Return
/// the value of key after the increment
///
/// # See Also
/// [](https://redis.io/commands/incrby/)
#[must_use]
fn incrby(self, key: K, increment: i64) -> PreparedCommand<'a, Self, i64>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("INCRBY").arg(key).arg(increment))
}
///Increment the string representing a floating point number stored at key by the specified increment.
/// By using a negative increment value, the result is that the value stored at the key is decremented (by the obvious properties of addition).
/// If the key does not exist, it is set to 0 before performing the operation.
/// An error is returned if one of the following conditions occur:
///
/// - The key contains a value of the wrong type (not a string).
///
/// - The current key content or the specified increment are not parsable as a double precision floating point number.
///
/// If the command is successful the new incremented value is stored as the new value of the key (replacing the old one),
/// and returned to the caller as a string.
///
/// Both the value already contained in the string key and the increment argument can be optionally provided in exponential notation,
/// however the value computed after the increment is stored consistently in the same format, that is,
/// an integer number followed (if needed) by a dot, and a variable number of digits representing the decimal part of the number.
/// Trailing zeroes are always removed.
///
/// The precision of the output is fixed at 17 digits after the decimal point
/// regardless of the actual internal precision of the computation.
///
/// # Return
/// the value of key after the increment
///
/// # See Also
/// [](https://redis.io/commands/incrbyfloat/)
#[must_use]
fn incrbyfloat(self, key: K, increment: f64) -> PreparedCommand<'a, Self, f64>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("INCRBYFLOAT").arg(key).arg(increment))
}
/// The LCS command implements the longest common subsequence algorithm
///
/// # Return
/// The string representing the longest common substring.
///
/// # See Also
/// [](https://redis.io/commands/lcs/)
#[must_use]
fn lcs(self, key1: K, key2: K) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("LCS").arg(key1).arg(key2))
}
/// The LCS command implements the longest common subsequence algorithm
///
/// # Return
/// The length of the longest common substring.
///
/// # See Also
/// [](https://redis.io/commands/lcs/)
#[must_use]
fn lcs_len(self, key1: K, key2: K) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("LCS").arg(key1).arg(key2).arg("LEN"))
}
/// The LCS command implements the longest common subsequence algorithm
///
/// # Return
/// An array with the LCS length and all the ranges in both the strings,
/// start and end offset for each string, where there are matches.
/// When `with_match_len` is given each match will also have the length of the match
///
/// # See Also
/// [](https://redis.io/commands/lcs/)
#[must_use]
fn lcs_idx(
self,
key1: K,
key2: K,
min_match_len: Option,
with_match_len: bool,
) -> PreparedCommand<'a, Self, LcsResult>
where
Self: Sized,
K: SingleArg,
{
prepare_command(
self,
cmd("LCS")
.arg(key1)
.arg(key2)
.arg("IDX")
.arg(min_match_len.map(|len| ("MINMATCHLEN", len)))
.arg_if(with_match_len, "WITHMATCHLEN"),
)
}
/// Returns the values of all specified keys.
///
/// For every key that does not hold a string value or does not exist,
/// the special value nil is returned. Because of this, the operation never fails.
///
/// # Return
/// Array reply: list of values at the specified keys.
///
/// # See Also
/// [](https://redis.io/commands/mget/)
#[must_use]
fn mget(self, keys: KK) -> PreparedCommand<'a, Self, VV>
where
Self: Sized,
K: SingleArg,
KK: SingleArgCollection,
V: PrimitiveResponse + serde::de::DeserializeOwned,
VV: CollectionResponse,
{
prepare_command(self, cmd("MGET").arg(keys))
}
/// Sets the given keys to their respective values.
///
/// # Return
/// always OK since MSET can't fail.
///
/// # See Also
/// [](https://redis.io/commands/mset/)
#[must_use]
fn mset(self, items: C) -> PreparedCommand<'a, Self, ()>
where
Self: Sized,
C: KeyValueArgsCollection,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("MSET").arg(items))
}
/// Sets the given keys to their respective values.
/// MSETNX will not perform any operation at all even if just a single key already exists.
///
/// Because of this semantic MSETNX can be used in order to set different keys representing
/// different fields of a unique logic object in a way that ensures that either
/// all the fields or none at all are set.
///
/// MSETNX is atomic, so all given keys are set at once. It is not possible for
/// clients to see that some of the keys were updated while others are unchanged.
///
/// # Return
/// specifically:
/// - 1 if the all the keys were set.
/// - 0 if no key was set (at least one key already existed).
///
/// # See Also
/// [](https://redis.io/commands/msetnx/)
#[must_use]
fn msetnx(self, items: C) -> PreparedCommand<'a, Self, bool>
where
Self: Sized,
C: KeyValueArgsCollection,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("MSETNX").arg(items))
}
/// Works exactly like [setex](StringCommands::setex) with the sole
/// difference that the expire time is specified in milliseconds instead of seconds.
///
/// If key already holds a value, it is overwritten, regardless of its type.
/// Any previous time to live associated with the key is discarded on successful SET operation.
///
/// # See Also
/// [](https://redis.io/commands/psetex/)
#[must_use]
fn psetex(self, key: K, milliseconds: u64, value: V) -> PreparedCommand<'a, Self, ()>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("PSETEX").arg(key).arg(milliseconds).arg(value))
}
///Set key to hold the string value.
///
/// If key already holds a value, it is overwritten, regardless of its type.
/// Any previous time to live associated with the key is discarded on successful SET operation.
///
/// # See Also
/// [](https://redis.io/commands/set/)
#[must_use]
fn set(self, key: K, value: V) -> PreparedCommand<'a, Self, ()>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
Self: Sized,
{
prepare_command(self, cmd("SET").arg(key).arg(value))
}
///Set key to hold the string value.
///
/// # Return
/// * `true` if SET was executed correctly.
/// * `false` if the SET operation was not performed because the user
/// specified the NX or XX option but the condition was not met.
///
/// # See Also
/// [](https://redis.io/commands/set/)
#[must_use]
fn set_with_options(
self,
key: K,
value: V,
condition: SetCondition,
expiration: SetExpiration,
keep_ttl: bool,
) -> PreparedCommand<'a, Self, bool>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(
self,
cmd("SET")
.arg(key)
.arg(value)
.arg(condition)
.arg(expiration)
.arg_if(keep_ttl, "KEEPTTL"),
)
}
/// Set key to hold the string value wit GET option enforced
///
/// # See Also
/// [](https://redis.io/commands/set/)
#[must_use]
fn set_get_with_options(
self,
key: K,
value: V1,
condition: SetCondition,
expiration: SetExpiration,
keep_ttl: bool,
) -> PreparedCommand<'a, Self, V2>
where
Self: Sized,
K: SingleArg,
V1: SingleArg,
V2: PrimitiveResponse,
{
prepare_command(
self,
cmd("SET")
.arg(key)
.arg(value)
.arg(condition)
.arg("GET")
.arg(expiration)
.arg_if(keep_ttl, "KEEPTTL"),
)
}
/// Set key to hold the string value and set key to timeout after a given number of seconds.
///
/// # See Also
/// [](https://redis.io/commands/setex/)
#[must_use]
fn setex(self, key: K, seconds: u64, value: V) -> PreparedCommand<'a, Self, ()>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("SETEX").arg(key).arg(seconds).arg(value))
}
/// Set key to hold string value if key does not exist.
///
/// In that case, it is equal to SET.
/// When key already holds a value, no operation is performed.
/// SETNX is short for "SET if Not eXists".
///
/// # Return
/// specifically:
/// * `true` - if the key was set
/// * `false` - if the key was not set
///
/// # See Also
/// [](https://redis.io/commands/setnx/)
#[must_use]
fn setnx(self, key: K, value: V) -> PreparedCommand<'a, Self, bool>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("SETNX").arg(key).arg(value))
}
/// Overwrites part of the string stored at key,
/// starting at the specified offset,
/// for the entire length of value.
///
/// # Return
/// the length of the string after it was modified by the command.
///
/// # See Also
/// [](https://redis.io/commands/setrange/)
#[must_use]
fn setrange(self, key: K, offset: usize, value: V) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
V: SingleArg,
{
prepare_command(self, cmd("SETRANGE").arg(key).arg(offset).arg(value))
}
/// Returns the length of the string value stored at key.
///
/// An error is returned when key holds a non-string value.
///
/// # Return
/// the length of the string at key, or 0 when key does not exist.
///
/// # See Also
/// [](https://redis.io/commands/strlen/)
#[must_use]
fn strlen(self, key: K) -> PreparedCommand<'a, Self, usize>
where
Self: Sized,
K: SingleArg,
{
prepare_command(self, cmd("STRLEN").arg(key))
}
/// Returns the substring of the string value stored at key, determined by the offsets start and end (both are inclusive).
///
/// Negative offsets can be used in order to provide an offset starting from the end of the string.
/// So -1 means the last character, -2 the penultimate and so forth.
///
/// The function handles out of range requests by limiting the resulting range to the actual length of the string.
///
/// # Example
/// ```
/// # use rustis::{
/// # client::Client,
/// # commands::{FlushingMode, ServerCommands, StringCommands},
/// # Result,
/// # };
///
/// # #[cfg_attr(feature = "tokio-runtime", tokio::main)]
/// # #[cfg_attr(feature = "async-std-runtime", async_std::main)]
/// # async fn main() -> Result<()> {
/// # let client = Client::connect("127.0.0.1:6379").await?;
/// # client.flushdb(FlushingMode::Sync).await?;
/// client.set("mykey", "This is a string").await?;
///
/// let value: String = client.substr("mykey", 0, 3).await?;
/// assert_eq!("This", value);
/// let value: String = client.substr("mykey", -3, -1).await?;
/// assert_eq!("ing", value);
/// let value: String = client.substr("mykey", 0, -1).await?;
/// assert_eq!("This is a string", value);
/// let value: String = client.substr("mykey", 10, 100).await?;
/// assert_eq!("string", value);
/// # Ok(())
/// # }
/// ```
///
/// # See Also
/// [](https://redis.io/commands/substr/)
#[must_use]
fn substr(self, key: K, start: isize, end: isize) -> PreparedCommand<'a, Self, V>
where
Self: Sized,
K: SingleArg,
V: PrimitiveResponse,
{
prepare_command(self, cmd("SUBSTR").arg(key).arg(start).arg(end))
}
}
/// Options for the [`getex`](StringCommands::getex) command
pub enum GetExOptions {
/// Set the specified expire time, in seconds.
Ex(u64),
/// Set the specified expire time, in milliseconds.
Px(u64),
/// Set the specified Unix time at which the key will expire, in seconds.
Exat(u64),
/// Set the specified Unix time at which the key will expire, in milliseconds.
Pxat(u64),
/// Remove the time to live associated with the key.
Persist,
}
impl ToArgs for GetExOptions {
fn write_args(&self, args: &mut CommandArgs) {
match self {
GetExOptions::Ex(duration) => args.arg(("EX", *duration)),
GetExOptions::Px(duration) => args.arg(("PX", *duration)),
GetExOptions::Exat(timestamp) => args.arg(("EXAT", *timestamp)),
GetExOptions::Pxat(timestamp) => args.arg(("PXAT", *timestamp)),
GetExOptions::Persist => args.arg("PERSIST"),
};
}
}
/// Part of the result for the [`lcs`](StringCommands::lcs) command
#[derive(Debug, PartialEq, Eq)]
pub struct LcsMatch(pub (usize, usize), pub (usize, usize), pub Option);
impl<'de> Deserialize<'de> for LcsMatch {
fn deserialize(deserializer: D) -> std::result::Result
where
D: Deserializer<'de>,
{
struct LcsMatchVisitor;
impl<'de> Visitor<'de> for LcsMatchVisitor {
type Value = LcsMatch;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("LcsMatch")
}
fn visit_seq(self, mut seq: A) -> std::result::Result
where
A: SeqAccess<'de>,
{
let Some(first): Option<(usize, usize)> = seq.next_element()? else {
return Err(de::Error::invalid_length(0, &"fewer elements in tuple"));
};
let Some(second): Option<(usize, usize)> = seq.next_element()? else {
return Err(de::Error::invalid_length(1, &"fewer elements in tuple"));
};
let match_len: Option = seq.next_element()?;
Ok(LcsMatch(first, second, match_len))
}
}
deserializer.deserialize_seq(LcsMatchVisitor)
}
}
/// Result for the [`lcs`](StringCommands::lcs) command
#[derive(Debug, Deserialize)]
pub struct LcsResult {
pub matches: Vec,
pub len: usize,
}
/// Expiration option for the [`set_with_options`](StringCommands::set_with_options) command
#[derive(Default)]
pub enum SetExpiration {
/// No expiration
#[default]
None,
/// Set the specified expire time, in seconds.
Ex(u64),
/// Set the specified expire time, in milliseconds.
Px(u64),
/// Set the specified Unix time at which the key will expire, in seconds.
Exat(u64),
/// Set the specified Unix time at which the key will expire, in milliseconds.
Pxat(u64),
}
impl ToArgs for SetExpiration {
fn write_args(&self, args: &mut CommandArgs) {
match self {
SetExpiration::None => {}
SetExpiration::Ex(duration) => {
args.arg(("EX", *duration));
}
SetExpiration::Px(duration) => {
args.arg(("PX", *duration));
}
SetExpiration::Exat(timestamp) => {
args.arg(("EXAT", *timestamp));
}
SetExpiration::Pxat(timestamp) => {
args.arg(("PXAT", *timestamp));
}
};
}
}
/// Condition option for the [`set_with_options`](StringCommands::set_with_options) command
#[derive(Default)]
pub enum SetCondition {
/// No condition
#[default]
None,
/// Only set the key if it does not already exist.
NX,
/// Only set the key if it already exist.
XX,
}
impl ToArgs for SetCondition {
fn write_args(&self, args: &mut CommandArgs) {
match self {
SetCondition::None => {}
SetCondition::NX => {
args.arg("NX");
}
SetCondition::XX => {
args.arg("XX");
}
}
}
}