Skip to main content

nexus_common/db/kv/index/
sorted_sets.rs

1use crate::db::get_redis_conn;
2use crate::types::DynError;
3use redis::AsyncCommands;
4use serde::Deserialize;
5use utoipa::ToSchema;
6
7#[derive(Clone, Deserialize, Debug, ToSchema, Default)]
8#[serde(rename_all = "snake_case")]
9pub enum SortOrder {
10    Ascending,
11    #[default]
12    Descending,
13}
14
15pub enum ScoreAction {
16    Increment(f64),
17    Decrement(f64),
18}
19
20pub const SORTED_PREFIX: &str = "Sorted";
21
22/// Checks if a member exists in a Redis sorted set and retrieves its score.
23///
24/// This function checks whether a specified member exists in a Redis sorted set
25/// by retrieving its score using the `ZSCORE` command. If the member is present,
26/// its score is returned; if it is not present, `None` is returned.
27///
28/// # Arguments
29///
30/// * `prefix` - A string slice representing the prefix for the Redis key.
31/// * `key` - A string slice representing the key under which the sorted set is stored.
32/// * `member` - A string slice representing the member to check in the sorted set.
33///
34/// # Returns
35///
36/// Returns an `Option<isize>` containing the score of the member if it exists, or `None` if it does not.
37pub async fn check_member(
38    prefix: &str,
39    key: &str,
40    member: &str,
41) -> Result<Option<isize>, DynError> {
42    let index_key = format!("{prefix}:{key}");
43    let mut redis_conn = get_redis_conn().await?;
44    // Use the ZSCORE command to check if the member exists in the sorted set
45    let rank = redis_conn.zscore(index_key, member).await?;
46    Ok(rank)
47}
48
49/// Adds elements to a Redis sorted set.
50///
51/// This function adds elements to the specified Redis sorted set. If the set doesn't exist,
52/// it creates a new sorted set.
53///
54/// # Argumentsf64
55///
56/// * `prefix` - A string slice representing the prefix for the Redis keys.
57/// * `key` - A string slice representing the key under which the sorted set is stored.
58/// * `values` - A slice of tuples where each tuple contains a reference to a string slice representing the element and a f64 representing the score of the element.
59/// * `expiration` - An optional `i64` specifying the TTL (in seconds) for the set. If `None`, no TTL will be set.
60///
61/// # Errors
62///
63/// Returns an error if the operation fails.
64pub async fn put(
65    prefix: &str,
66    key: &str,
67    items: &[(f64, &str)],
68    expiration: Option<i64>,
69) -> Result<(), DynError> {
70    if items.is_empty() {
71        return Ok(());
72    }
73
74    let index_key = format!("{prefix}:{key}");
75    let mut redis_conn = get_redis_conn().await?;
76
77    let mut pipe = redis::pipe();
78
79    pipe.zadd_multiple(&index_key, items);
80
81    if let Some(ttl) = expiration {
82        // TTL convert to seconds
83        pipe.expire(&index_key, ttl);
84    }
85
86    let _: () = pipe.query_async(&mut redis_conn).await?;
87    Ok(())
88}
89
90/// Updates the score of a member in a Redis sorted set.
91///
92/// This function modifies the score of a member in the specified Redis sorted set by incrementing or decrementing it
93/// based on the provided `ScoreAction`.
94///
95/// # Arguments
96///
97/// * `prefix` - A string slice representing the prefix for the Redis keys.
98/// * `key` - A string slice representing the key under which the sorted set is stored.
99/// * `member` - A string slice representing the member whose score will be updated.
100/// * `score_mutation` - A `ScoreAction` that indicates whether to increment or decrement the score.
101pub async fn put_score(
102    prefix: &str,
103    key: &str,
104    member: &str,
105    score_mutation: ScoreAction,
106) -> Result<(), DynError> {
107    let index_key = format!("{prefix}:{key}");
108    let mut redis_conn = get_redis_conn().await?;
109    let value = match score_mutation {
110        ScoreAction::Increment(val) => val,
111        ScoreAction::Decrement(val) => -val,
112    };
113    let _: () = redis_conn.zincr(&index_key, member, value).await?;
114
115    Ok(())
116}
117
118/// Retrieves a range of elements from a Redis sorted set.
119///
120/// This function retrieves elements from a specified Redis sorted set based on a score range.
121/// The range is defined by `min_score` and `max_score` parameters, where `min_score` and `max_score`
122/// specify the inclusive lower and upper bounds of the scores.
123///
124/// # Arguments
125///
126/// * `prefix` - A string slice representing the prefix for the Redis keys.
127/// * `key` - A string slice representing the key under which the sorted set is stored.
128/// * `min_score` - The minimum score for the range (inclusive).
129/// * `max_score` - The maximum score for the range (inclusive).
130/// * `skip` - An optional number of elements to skip (useful for pagination).
131/// * `limit` - The maximum number of elements to retrieve.
132/// * `sorting` - The sorting order (ascending or descending).
133///
134/// # Returns
135///
136/// Returns a vector of tuples containing the elements and their scores.
137///
138/// # Errors
139///
140/// Returns an error if the operation fails.
141pub async fn get_range(
142    prefix: &str,
143    key: &str,
144    min_score: Option<f64>,
145    max_score: Option<f64>,
146    skip: Option<usize>,
147    limit: Option<usize>,
148    sorting: SortOrder,
149) -> Result<Option<Vec<(String, f64)>>, DynError> {
150    let mut redis_conn = get_redis_conn().await?;
151    let index_key = format!("{prefix}:{key}");
152
153    // Make sure if the key that we want to find, it is in the sorted set
154    if !redis_conn.exists(&index_key).await? {
155        return Ok(None);
156    }
157
158    let min_score = min_score.unwrap_or(f64::MIN);
159    let max_score = max_score.unwrap_or(f64::MAX);
160    let skip = skip.unwrap_or(0) as isize;
161    let limit = limit.unwrap_or(1000) as isize;
162
163    // ZRANGE with the WITHSCORES option retrieves both: the elements and their scores
164    let elements: Vec<(String, f64)> = match sorting {
165        SortOrder::Ascending => {
166            redis_conn
167                .zrangebyscore_limit_withscores(index_key, min_score, max_score, skip, limit)
168                .await?
169        }
170        SortOrder::Descending => {
171            redis_conn
172                .zrevrangebyscore_limit_withscores(index_key, max_score, min_score, skip, limit)
173                .await?
174        }
175    };
176    Ok(Some(elements))
177}
178
179/// Performs a lexicographical range search on the Redis sorted set.
180///
181/// # Arguments
182///
183/// * `prefix` - A string slice representing the prefix for the Redis keys.
184/// * `key` - A string slice representing the key under which the sorted set is stored.
185/// * `min` - The minimum lexicographical bound (inclusive).
186/// * `max` - The maximum lexicographical bound (exclusive).
187/// * `skip` - An optional number of elements to skip (useful for pagination).
188/// * `limit` - The maximum number of elements to retrieve.
189pub async fn get_lex_range(
190    prefix: &str,
191    key: &str,
192    min: &str,
193    max: &str,
194    skip: Option<usize>,
195    limit: Option<usize>,
196) -> Result<Option<Vec<String>>, DynError> {
197    let mut redis_conn = get_redis_conn().await?;
198    let index_key = format!("{prefix}:{key}");
199    let skip = skip.unwrap_or(0) as isize;
200    let limit = limit.unwrap_or(1000) as isize;
201
202    let elements: Vec<String> = redis_conn
203        .zrangebylex_limit(index_key, min, max, skip, limit)
204        .await?;
205
206    match elements.len() {
207        0 => Ok(None),
208        _ => Ok(Some(elements)),
209    }
210}
211
212/// Removes elements from the Redis sorted set.
213///
214/// # Arguments
215///
216/// * `items` - A slice of elements to remove.
217pub async fn _remove(prefix: &str, key: &str, items: &[&str]) -> Result<(), DynError> {
218    if items.is_empty() {
219        return Ok(());
220    }
221
222    let index_key = format!("{prefix}:{key}");
223    let mut redis_conn = get_redis_conn().await?;
224    let _: () = redis_conn.zrem(&index_key, items).await?;
225    Ok(())
226}
227
228/// Removes elements from a Redis sorted set.
229///
230/// This function removes the specified elements from the Redis sorted set identified by the `prefix` and `key`.
231/// If the sorted set does not exist, it will simply return without error.
232///
233/// # Arguments
234///
235/// * `prefix` - A string slice representing the prefix for the Redis keys.
236/// * `key` - A string slice representing the key under which the sorted set is stored.
237/// * `values` - A slice of string slices representing the elements to be removed from the sorted set.
238///
239/// # Errors
240///
241/// Returns an error if the operation fails.
242pub async fn del(prefix: &str, key: &str, values: &[&str]) -> Result<(), DynError> {
243    if values.is_empty() {
244        return Ok(());
245    }
246
247    let index_key = format!("{prefix}:{key}");
248    let mut redis_conn = get_redis_conn().await?;
249
250    // Remove the elements from the sorted set
251    let _: () = redis_conn.zrem(index_key, values).await?;
252    Ok(())
253}