Skip to main content

redis_objects/
set.rs

1//! Storing collections of unique objects in redis
2
3use std::collections::HashSet;
4use std::marker::PhantomData;
5use std::sync::atomic::AtomicI64;
6use std::sync::Arc;
7use std::time::Duration;
8
9use redis::{cmd, AsyncTypedCommands};
10use serde::de::DeserializeOwned;
11use serde::Serialize;
12use tracing::instrument;
13
14use crate::{RedisObjects, ErrorTypes, retry_call};
15
16const DROP_CARD_SCRIPT: &str = r#"
17local set_name = KEYS[1]
18local key = ARGV[1]
19
20redis.call('srem', set_name, key)
21return redis.call('scard', set_name)
22"#;
23
24const LIMITED_ADD: &str = r#"
25local set_name = KEYS[1]
26local key = ARGV[1]
27local limit = tonumber(ARGV[2])
28
29if redis.call('scard', set_name) < limit then
30    redis.call('sadd', set_name, key)
31    return true
32end
33return false
34"#;
35
36/// A collection of unique values in a redis object
37pub struct Set<T> {
38    name: String,
39    store: Arc<RedisObjects>,
40    drop_card_script: redis::Script,
41    limited_add: redis::Script,
42    ttl: Option<i64>,
43    last_expire_time: AtomicI64,
44    _data: PhantomData<T>
45}
46
47impl<T> std::fmt::Debug for Set<T> {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("Set").field("name", &self.name).field("store", &self.store).finish()
50    }
51}
52
53impl<T: Serialize + DeserializeOwned> Set<T> {
54    pub (crate) fn new(name: String, store: Arc<RedisObjects>, ttl: Option<Duration>) -> Self {
55        Self {
56            name,
57            store,
58            drop_card_script: redis::Script::new(DROP_CARD_SCRIPT),
59            limited_add: redis::Script::new(LIMITED_ADD),
60            ttl: ttl.map(|v| v.as_secs() as i64),
61            last_expire_time: AtomicI64::new(i64::MIN),
62            _data: PhantomData,
63        }
64    }
65
66    /// set expiry on the remote object if it has not been recently set
67    async fn _conditional_expire(&self) -> Result<(), ErrorTypes> {
68        if let Some(ttl) = self.ttl {
69            let ctime = chrono::Utc::now().timestamp();
70            let last_expire_time: i64 = self.last_expire_time.load(std::sync::atomic::Ordering::Acquire);
71            if ctime > last_expire_time + (ttl / 2) {
72                let _: bool = retry_call!(self.store.pool, expire, &self.name, ttl)?;
73                self.last_expire_time.store(ctime, std::sync::atomic::Ordering::Release);
74            }
75        }
76        Ok(())
77    }
78
79    /// Insert an item into the set.
80    /// Return whether the item is new to the set.
81    #[instrument(skip(value))]
82    pub async fn add(&self, value: &T) -> Result<bool, ErrorTypes> {
83        let data = serde_json::to_string(&value)?;
84        let result = retry_call!(self.store.pool, sadd, &self.name, &data)?;
85        self._conditional_expire().await?;
86        Ok(result > 0)
87    }
88
89    /// Insert a batch of items to the set.
90    /// Return how many items were new to the set.
91    #[instrument(skip(values))]
92    pub async fn add_batch(&self, values: &[T]) -> Result<usize, ErrorTypes> {
93        let mut data = vec![];
94        for item in values {
95            data.push(serde_json::to_string(&item)?);
96        }
97        let result = retry_call!(self.store.pool, sadd, &self.name, &data)?;
98        self._conditional_expire().await?;
99        Ok(result)
100    }
101
102    /// Add a single value to the set, but only if that wouldn't make the set grow past a given size.
103    #[instrument(skip(value))]
104    pub async fn limited_add(&self, value: &T, size_limit: usize) -> Result<bool, ErrorTypes> {
105        let data = serde_json::to_vec(&value)?;
106        let result = retry_call!(method, self.store.pool,
107            self.limited_add.key(&self.name).arg(&data).arg(size_limit), invoke_async)?;
108        self._conditional_expire().await?;
109        Ok(result)
110    }
111
112    /// Check if an item is in the set
113    #[instrument(skip(value))]
114    pub async fn exist(&self, value: &T) -> Result<bool, ErrorTypes> {
115        let data = serde_json::to_vec(&value)?;
116        retry_call!(self.store.pool, sismember, &self.name, &data)
117    }
118
119    /// Read the number of items in the set
120    #[instrument]
121    pub async fn length(&self) -> Result<usize, ErrorTypes> {
122        retry_call!(self.store.pool, scard, &self.name)
123    }
124
125    /// Read the entire content of the set
126    #[instrument]
127    pub async fn members(&self) -> Result<Vec<T>, ErrorTypes> {
128        let data: HashSet<String> = retry_call!(self.store.pool, smembers, &self.name)?;
129        Ok(data.into_iter()
130            .map(|v| serde_json::from_str::<T>(&v))
131            .collect::<Result<Vec<T>, _>>()?)
132    }
133
134    /// Try to remove a given value from the set and return if any change has been made.
135    #[instrument(skip(value))]
136    pub async fn remove(&self, value: &T) -> Result<bool, ErrorTypes> {
137        let data = serde_json::to_vec(&value)?;
138        let rem: usize = retry_call!(self.store.pool, srem, &self.name, &data)?;
139        Ok(rem > 0)
140    }
141
142    /// Try to remove multiple items from the set.
143    /// Return how many items were removed.
144    #[instrument(skip(values))]
145    pub async fn remove_batch(&self, values: &[T]) -> Result<usize, ErrorTypes> {
146        let mut data = vec![];
147        for item in values {
148            data.push(serde_json::to_vec(&item)?);
149        }
150        retry_call!(self.store.pool, srem, &self.name, &data)
151    }
152
153    /// Remove a given value from the set and return the new size of the set.
154    #[instrument(skip(value))]
155    pub async fn drop(&self, value: &T) -> Result<usize, ErrorTypes> {
156        let data = serde_json::to_vec(&value)?;
157        let size: Option<usize> = retry_call!(method, self.store.pool,
158            self.drop_card_script.key(&self.name).arg(&data), invoke_async)?;
159        Ok(size.unwrap_or_default())
160    }
161
162    /// Remove and return a random item from the set.
163    #[instrument]
164    pub async fn random(&self) -> Result<Option<T>, ErrorTypes>{
165        let ret_val: Option<String> = retry_call!(self.store.pool, srandmember, &self.name)?;
166        match ret_val {
167            Some(data) => Ok(Some(serde_json::from_str(&data)?)),
168            None => Ok(None),
169        }
170    }
171
172    // pub async fn random(&self, num=None) -> Result<Option<T>, ErrorTypes>{
173    //     ret_val = retry_call(self.c.srandmember, self.name, num)
174    //     if isinstance(ret_val, list):
175    //         return [json.loads(s) for s in ret_val]
176    //     else:
177    //         return json.loads(ret_val)
178    // }
179
180    /// Remove and return a single value from the set.
181    #[instrument]
182    pub async fn pop(&self) -> Result<Option<T>, ErrorTypes> {
183        let data: Option<String> = retry_call!(self.store.pool, spop, &self.name)?;
184        match data {
185            Some(data) => Ok(Some(serde_json::from_str(&data)?)),
186            None => Ok(None),
187        }
188    }
189
190    /// Remove and return all values from the set.
191    #[instrument]
192    pub async fn pop_all(&self) -> Result<Vec<T>, ErrorTypes> {
193        let length = self.length().await?;
194        let mut command = cmd("SPOP");
195        let command = command.arg(&self.name).arg(length);
196        let data: Vec<String> = retry_call!(method, self.store.pool, command, query_async)?;
197        Ok(data.into_iter()
198            .map(|v| serde_json::from_str::<T>(&v))
199            .collect::<Result<Vec<T>, _>>()?)
200    }
201
202    /// Remove and drop all values from the set.
203    #[instrument]
204    pub async fn delete(&self) -> Result<usize, ErrorTypes> {
205        retry_call!(self.store.pool, del, &self.name)
206    }
207}
208