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}