mlua_swarm/store/enhance_setting/
sqlite.rs1use super::{EnhanceSetting, EnhanceSettingId, EnhanceSettingStore, EnhanceSettingStoreError};
8use async_trait::async_trait;
9use rusqlite::{params, OptionalExtension};
10use rusqlite_isle::{AsyncIsle, AsyncIsleDriver, IsleError};
11use std::path::Path;
12
13const SCHEMA_SQL: &str = "\
14CREATE TABLE IF NOT EXISTS enhance_settings (\
15 id TEXT PRIMARY KEY, \
16 body_json TEXT NOT NULL\
17);\
18";
19
20pub struct SqliteEnhanceSettingStore {
22 isle: AsyncIsle,
23}
24
25impl SqliteEnhanceSettingStore {
26 pub async fn open(
28 path: impl AsRef<Path>,
29 ) -> Result<(Self, AsyncIsleDriver), EnhanceSettingStoreError> {
30 let (isle, driver) = AsyncIsle::spawn(path.as_ref().to_path_buf(), |conn| {
31 conn.execute_batch(SCHEMA_SQL)
32 })
33 .await
34 .map_err(map_isle_err)?;
35 Ok((Self { isle }, driver))
36 }
37
38 pub async fn open_in_memory() -> Result<(Self, AsyncIsleDriver), EnhanceSettingStoreError> {
40 let (isle, driver) = AsyncIsle::open_in_memory(|conn| conn.execute_batch(SCHEMA_SQL))
41 .await
42 .map_err(map_isle_err)?;
43 Ok((Self { isle }, driver))
44 }
45}
46
47fn map_isle_err(e: IsleError) -> EnhanceSettingStoreError {
48 EnhanceSettingStoreError::Other(format!("sqlite: {e}"))
49}
50
51#[async_trait]
52impl EnhanceSettingStore for SqliteEnhanceSettingStore {
53 fn name(&self) -> &str {
54 "sqlite"
55 }
56
57 async fn get(&self, id: &EnhanceSettingId) -> Result<EnhanceSetting, EnhanceSettingStoreError> {
58 let id_str = id.0.clone();
59 let id_for_notfound = id.clone();
60 let row = self
61 .isle
62 .call(move |conn| {
63 conn.query_row(
64 "SELECT body_json FROM enhance_settings WHERE id = ?1",
65 params![id_str],
66 |row| row.get::<_, String>(0),
67 )
68 .optional()
69 })
70 .await
71 .map_err(map_isle_err)?;
72 match row {
73 Some(json_text) => serde_json::from_str::<EnhanceSetting>(&json_text)
74 .map_err(|e| EnhanceSettingStoreError::Other(format!("decode: {e}"))),
75 None => Err(EnhanceSettingStoreError::NotFound(id_for_notfound)),
76 }
77 }
78
79 async fn put(
80 &self,
81 id: &EnhanceSettingId,
82 setting: EnhanceSetting,
83 ) -> Result<(), EnhanceSettingStoreError> {
84 let id_str = id.0.clone();
85 let json_text = serde_json::to_string(&setting)
86 .map_err(|e| EnhanceSettingStoreError::Other(format!("encode: {e}")))?;
87 self.isle
88 .call(move |conn| {
89 conn.execute(
90 "INSERT INTO enhance_settings (id, body_json) VALUES (?1, ?2) \
91 ON CONFLICT(id) DO UPDATE SET body_json = excluded.body_json",
92 params![id_str, json_text],
93 )
94 .map(|_| ())
95 })
96 .await
97 .map_err(map_isle_err)
98 }
99
100 async fn delete(&self, id: &EnhanceSettingId) -> Result<(), EnhanceSettingStoreError> {
101 let id_str = id.0.clone();
102 let id_for_notfound = id.clone();
103 let n = self
104 .isle
105 .call(move |conn| {
106 conn.execute(
107 "DELETE FROM enhance_settings WHERE id = ?1",
108 params![id_str],
109 )
110 })
111 .await
112 .map_err(map_isle_err)?;
113 if n == 0 {
114 Err(EnhanceSettingStoreError::NotFound(id_for_notfound))
115 } else {
116 Ok(())
117 }
118 }
119
120 async fn list(&self) -> Result<Vec<EnhanceSettingId>, EnhanceSettingStoreError> {
121 let rows = self
122 .isle
123 .call(|conn| {
124 let mut stmt = conn.prepare("SELECT id FROM enhance_settings ORDER BY id ASC")?;
125 let iter = stmt.query_map([], |row| row.get::<_, String>(0))?;
126 let mut out = Vec::new();
127 for r in iter {
128 out.push(r?);
129 }
130 Ok(out)
131 })
132 .await
133 .map_err(map_isle_err)?;
134 Ok(rows.into_iter().map(EnhanceSettingId::new).collect())
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::application::VersionSelector;
142 use crate::blueprint::store::BlueprintId;
143 use crate::enhance::setting::EnhanceSettingMeta;
144
145 fn dummy_setting(id: &str, bp: &str) -> EnhanceSetting {
146 EnhanceSetting {
147 id: id.into(),
148 blueprint_id: BlueprintId::new(bp.to_string()),
149 ttl_secs: 10,
150 version: VersionSelector::default(),
151 verifier_axes: vec!["des".into()],
152 meta: EnhanceSettingMeta::default(),
153 }
154 }
155
156 #[tokio::test]
157 async fn put_then_get_returns_same_setting() {
158 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
159 let id = EnhanceSettingId::new("s1");
160 s.put(&id, dummy_setting("s1", "bp-1")).await.unwrap();
161 let got = s.get(&id).await.unwrap();
162 assert_eq!(got.id, "s1");
163 assert_eq!(got.blueprint_id.as_str(), "bp-1");
164 assert_eq!(got.ttl_secs, 10);
165 drop(s);
166 driver.shutdown().await.unwrap();
167 }
168
169 #[tokio::test]
170 async fn put_overwrites_existing() {
171 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
172 let id = EnhanceSettingId::new("s1");
173 s.put(&id, dummy_setting("s1", "bp-1")).await.unwrap();
174 let mut updated = dummy_setting("s1", "bp-2");
175 updated.ttl_secs = 99;
176 s.put(&id, updated).await.unwrap();
177 let got = s.get(&id).await.unwrap();
178 assert_eq!(got.blueprint_id.as_str(), "bp-2");
179 assert_eq!(got.ttl_secs, 99);
180 drop(s);
181 driver.shutdown().await.unwrap();
182 }
183
184 #[tokio::test]
185 async fn get_missing_returns_not_found() {
186 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
187 let err = s.get(&EnhanceSettingId::new("nope")).await.unwrap_err();
188 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
189 drop(s);
190 driver.shutdown().await.unwrap();
191 }
192
193 #[tokio::test]
194 async fn delete_missing_returns_not_found() {
195 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
196 let err = s.delete(&EnhanceSettingId::new("nope")).await.unwrap_err();
197 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
198 drop(s);
199 driver.shutdown().await.unwrap();
200 }
201
202 #[tokio::test]
203 async fn list_returns_sorted_ids() {
204 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
205 s.put(&EnhanceSettingId::new("b"), dummy_setting("b", "bp"))
206 .await
207 .unwrap();
208 s.put(&EnhanceSettingId::new("a"), dummy_setting("a", "bp"))
209 .await
210 .unwrap();
211 s.put(&EnhanceSettingId::new("c"), dummy_setting("c", "bp"))
212 .await
213 .unwrap();
214 let ids: Vec<_> = s.list().await.unwrap().into_iter().map(|i| i.0).collect();
215 assert_eq!(ids, vec!["a", "b", "c"]);
216 drop(s);
217 driver.shutdown().await.unwrap();
218 }
219
220 #[tokio::test]
221 async fn persists_across_reopen() {
222 let dir = tempfile::tempdir().unwrap();
223 let path = dir.path().join("settings.db");
224
225 {
226 let (s, driver) = SqliteEnhanceSettingStore::open(&path).await.unwrap();
227 s.put(
228 &EnhanceSettingId::new("keep"),
229 dummy_setting("keep", "bp-x"),
230 )
231 .await
232 .unwrap();
233 drop(s);
234 driver.shutdown().await.unwrap();
235 }
236
237 let (s, driver) = SqliteEnhanceSettingStore::open(&path).await.unwrap();
238 let got = s.get(&EnhanceSettingId::new("keep")).await.unwrap();
239 assert_eq!(got.blueprint_id.as_str(), "bp-x");
240 drop(s);
241 driver.shutdown().await.unwrap();
242 }
243}