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("DELETE FROM enhance_settings WHERE id = ?1", params![id_str])
107 })
108 .await
109 .map_err(map_isle_err)?;
110 if n == 0 {
111 Err(EnhanceSettingStoreError::NotFound(id_for_notfound))
112 } else {
113 Ok(())
114 }
115 }
116
117 async fn list(&self) -> Result<Vec<EnhanceSettingId>, EnhanceSettingStoreError> {
118 let rows = self
119 .isle
120 .call(|conn| {
121 let mut stmt = conn.prepare("SELECT id FROM enhance_settings ORDER BY id ASC")?;
122 let iter = stmt.query_map([], |row| row.get::<_, String>(0))?;
123 let mut out = Vec::new();
124 for r in iter {
125 out.push(r?);
126 }
127 Ok(out)
128 })
129 .await
130 .map_err(map_isle_err)?;
131 Ok(rows.into_iter().map(EnhanceSettingId::new).collect())
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::application::VersionSelector;
139 use crate::blueprint::store::BlueprintId;
140 use crate::enhance::setting::EnhanceSettingMeta;
141
142 fn dummy_setting(id: &str, bp: &str) -> EnhanceSetting {
143 EnhanceSetting {
144 id: id.into(),
145 blueprint_id: BlueprintId::new(bp.to_string()),
146 ttl_secs: 10,
147 version: VersionSelector::default(),
148 verifier_axes: vec!["des".into()],
149 meta: EnhanceSettingMeta::default(),
150 }
151 }
152
153 #[tokio::test]
154 async fn put_then_get_returns_same_setting() {
155 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
156 let id = EnhanceSettingId::new("s1");
157 s.put(&id, dummy_setting("s1", "bp-1")).await.unwrap();
158 let got = s.get(&id).await.unwrap();
159 assert_eq!(got.id, "s1");
160 assert_eq!(got.blueprint_id.as_str(), "bp-1");
161 assert_eq!(got.ttl_secs, 10);
162 drop(s);
163 driver.shutdown().await.unwrap();
164 }
165
166 #[tokio::test]
167 async fn put_overwrites_existing() {
168 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
169 let id = EnhanceSettingId::new("s1");
170 s.put(&id, dummy_setting("s1", "bp-1")).await.unwrap();
171 let mut updated = dummy_setting("s1", "bp-2");
172 updated.ttl_secs = 99;
173 s.put(&id, updated).await.unwrap();
174 let got = s.get(&id).await.unwrap();
175 assert_eq!(got.blueprint_id.as_str(), "bp-2");
176 assert_eq!(got.ttl_secs, 99);
177 drop(s);
178 driver.shutdown().await.unwrap();
179 }
180
181 #[tokio::test]
182 async fn get_missing_returns_not_found() {
183 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
184 let err = s.get(&EnhanceSettingId::new("nope")).await.unwrap_err();
185 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
186 drop(s);
187 driver.shutdown().await.unwrap();
188 }
189
190 #[tokio::test]
191 async fn delete_missing_returns_not_found() {
192 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
193 let err = s.delete(&EnhanceSettingId::new("nope")).await.unwrap_err();
194 assert!(matches!(err, EnhanceSettingStoreError::NotFound(_)));
195 drop(s);
196 driver.shutdown().await.unwrap();
197 }
198
199 #[tokio::test]
200 async fn list_returns_sorted_ids() {
201 let (s, driver) = SqliteEnhanceSettingStore::open_in_memory().await.unwrap();
202 s.put(&EnhanceSettingId::new("b"), dummy_setting("b", "bp"))
203 .await
204 .unwrap();
205 s.put(&EnhanceSettingId::new("a"), dummy_setting("a", "bp"))
206 .await
207 .unwrap();
208 s.put(&EnhanceSettingId::new("c"), dummy_setting("c", "bp"))
209 .await
210 .unwrap();
211 let ids: Vec<_> = s.list().await.unwrap().into_iter().map(|i| i.0).collect();
212 assert_eq!(ids, vec!["a", "b", "c"]);
213 drop(s);
214 driver.shutdown().await.unwrap();
215 }
216
217 #[tokio::test]
218 async fn persists_across_reopen() {
219 let dir = tempfile::tempdir().unwrap();
220 let path = dir.path().join("settings.db");
221
222 {
223 let (s, driver) = SqliteEnhanceSettingStore::open(&path).await.unwrap();
224 s.put(&EnhanceSettingId::new("keep"), dummy_setting("keep", "bp-x"))
225 .await
226 .unwrap();
227 drop(s);
228 driver.shutdown().await.unwrap();
229 }
230
231 let (s, driver) = SqliteEnhanceSettingStore::open(&path).await.unwrap();
232 let got = s.get(&EnhanceSettingId::new("keep")).await.unwrap();
233 assert_eq!(got.blueprint_id.as_str(), "bp-x");
234 drop(s);
235 driver.shutdown().await.unwrap();
236 }
237}