cache_any/caches/
mysql.rs

1use std::sync::Arc;
2use crate::{Cache, Cacheable};
3
4/// [`MySqlCache`] is a cache using mysql to store data.
5/// 
6/// It uses [`sqlx::MySqlPool`] to connect to mysql.
7/// Feature `mysql` must be enabled.
8/// 
9/// ## Prepare
10/// 
11/// Create a table named `cache` with the following schema:
12/// 
13/// ```sql
14/// CREATE TABLE IF NOT EXISTS cache (
15///     name varchar(255) not null,
16///     val text not null,
17///     primary key (name)
18/// );
19/// ```
20/// 
21/// **Note**:
22/// 1. You can change the table name and the field names.
23/// 2. The `name` field (or whatever you specify) is the primary key of the cache.
24/// 
25/// ## Build
26/// 
27/// Use [`MySqlCacheBuilder`] to build a [`MySqlCache`].
28/// You need to specify the table name and the field names when building.
29/// 
30/// ```rust,ignore
31/// let pool = MySqlPool::connect("mysql://test:123456@127.0.0.1:3306/dev").await?;
32/// let cache = MySqlCacheBuilder::new(pool)
33///     .table("cache")
34///     .key_field("name")
35///     .value_field("val")
36///     .finish();
37/// ```
38/// 
39#[derive(Debug, Clone)]
40pub struct MySqlCache {
41    inner: Arc<Inner>,
42}
43
44#[async_trait::async_trait]
45impl Cache for MySqlCache {
46    async fn get<T: Cacheable + Send + Sync>(&self, key: &str) -> anyhow::Result<Option<T>> {
47        let sql = format!(r#"
48            SELECT {}
49            FROM {}
50            WHERE {} = ?
51            LIMIT 1
52        "#, &self.inner.value_field, &self.inner.table, &self.inner.key_field);
53
54        let value: Option<(String,)> = sqlx::query_as(&sql)
55            .bind(key)
56            .fetch_optional(&self.inner.pool)
57            .await?;
58
59        let result = value.as_ref()
60            .map(|value| &value.0)
61            .map(|value| value.as_str())
62            .map(T::from_hex)
63            .transpose()?;
64
65        Ok(result)
66    }
67
68    async fn set<T: Cacheable + Send + Sync>(&self, key: &str, value: T) -> anyhow::Result<()> {
69        let value = value.to_hex();
70
71        let sql = format!(r#"
72            INSERT INTO {} ({}, {})
73            VALUES (?, ?)
74            ON DUPLICATE KEY UPDATE {} = ?
75        "#,
76            &self.inner.table,
77            &self.inner.key_field,
78            &self.inner.value_field,
79            &self.inner.value_field,
80        );
81
82        sqlx::query(&sql)
83            .bind(key)
84            .bind(&value)
85            .bind(&value)
86            .execute(&self.inner.pool)
87            .await?;
88
89        Ok(())
90    }
91
92    async fn delete(&self, key: &str) -> anyhow::Result<()> {
93        let sql = format!(r#"
94            DELETE FROM {}
95            WHERE {} = ?
96        "#, &self.inner.table, &self.inner.key_field);
97
98        sqlx::query(&sql)
99            .bind(key)
100            .execute(&self.inner.pool)
101            .await?;
102
103        Ok(())
104    }
105
106    async fn len(&self) -> anyhow::Result<usize> {
107        let sql = format!(r#"
108            SELECT COUNT(*)
109            FROM {}
110        "#, &self.inner.table);
111
112        let count: (i64,) = sqlx::query_as(&sql).fetch_optional(&self.inner.pool).await?.unwrap_or_default();
113
114        Ok(count.0 as usize)
115    }
116}
117
118/// [`MySqlCacheBuilder`] is used to build a [`MySqlCache`].
119#[derive(Debug, Clone)]
120pub struct MySqlCacheBuilder {
121    key_field: String,
122    value_field: String,
123    table: String,
124    pool: sqlx::MySqlPool,
125}
126
127impl MySqlCacheBuilder {
128    /// Create a new [`MySqlCacheBuilder`]. You need to specify the [`sqlx::MySqlPool`].
129    pub fn new(pool: sqlx::MySqlPool) -> Self {
130        Self {
131            key_field: String::from("name"),
132            value_field: String::from("val"),
133            table: String::from("cache"),
134            pool,
135        }
136    }
137
138    /// Set the key field.
139    pub fn key_field<S: ToString>(mut self, key: S) -> Self {
140        self.key_field = key.to_string();
141        self
142    }
143
144    /// Set the value field.
145    pub fn value_field<S: ToString>(mut self, value: S) -> Self {
146        self.value_field = value.to_string();
147        self
148    }
149
150    /// Set the table name.
151    pub fn table<S: ToString>(mut self, table: S) -> Self {
152        self.table = table.to_string();
153        self
154    }
155
156    /// Finish and build a [`MySqlCache`].
157    pub fn finish(self) -> MySqlCache {
158        MySqlCache {
159            inner: Arc::new(Inner {
160                key_field: self.key_field,
161                value_field: self.value_field,
162                table: self.table,
163                pool: self.pool,
164            })
165        }
166    }
167}
168
169#[derive(Debug)]
170struct Inner {
171    key_field: String,
172    value_field: String,
173    table: String,
174    pool: sqlx::MySqlPool,
175}
176
177#[cfg(test)]
178mod tests {
179    use sqlx::MySqlPool;
180    use super::*;
181
182    #[tokio::test]
183    async fn test_mysql_cache_builder() -> anyhow::Result<()> {
184        let pool = MySqlPool::connect("mysql://test:123456@127.0.0.1:3306/dev").await?;
185        let cache = MySqlCacheBuilder::new(pool)
186            .table("my_cache")
187            .key_field("name")
188            .value_field("val")
189            .finish();
190
191        assert_eq!(cache.inner.table, String::from("my_cache"));
192        assert_eq!(cache.inner.key_field, String::from("name"));
193        assert_eq!(cache.inner.value_field, String::from("val"));
194
195        let cloned_cache = cache.clone();
196        assert_eq!(cloned_cache.inner.table, String::from("my_cache"));
197        assert_eq!(cloned_cache.inner.key_field, String::from("name"));
198        assert_eq!(cloned_cache.inner.value_field, String::from("val"));
199
200        println!("{:?}", cloned_cache);
201
202        Ok(())
203    }
204
205    #[tokio::test]
206    async fn test_mysql_cache() -> anyhow::Result<()> {
207        // create user test@'%' identified by '123456';
208        // create database dev;
209        // grant all privileges on dev.* to test@'%';
210        //
211        // CREATE TABLE IF NOT EXISTS my_cache (
212        //     name varchar(255) not null,
213        //     val text not null,
214        //     primary key (name)
215        // );
216
217        let pool = MySqlPool::connect("mysql://test:123456@127.0.0.1:3306/dev").await?;
218
219        let cache = MySqlCacheBuilder::new(pool)
220            .table("my_cache")
221            .key_field("name")
222            .value_field("val")
223            .finish();
224
225        cache.set("user_id", 114514).await?;
226        cache.set("username", String::from("jack")).await?;
227
228        let user_id: usize = cache.get("user_id").await?.unwrap();
229        let username: String = cache.get("username").await?.unwrap();
230
231        assert_eq!(user_id, 114514);
232        assert_eq!(username, String::from("jack"));
233
234        cache.delete("user_id").await?;
235        let user_id: Option<()> = cache.get("user_id").await?;
236        assert_eq!(user_id, None);
237
238        let len = cache.len().await?;
239        println!("len = {}", len);
240
241        Ok(())
242    }
243}