origin_storage_sqlite/
lib.rs1mod schema;
10
11use async_trait::async_trait;
12use origin_domain::{AppError, Result};
13use origin_storage::{Record, Storage, StorageKey};
14use rusqlite::{Connection, OptionalExtension, params};
15use std::path::Path;
16use std::sync::{Arc, Mutex};
17use time::OffsetDateTime;
18use time::format_description::well_known::Rfc3339;
19
20#[derive(Debug, Clone)]
21pub struct SqliteStorage {
22 connection: Arc<Mutex<Connection>>,
23}
24
25impl SqliteStorage {
26 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
28 let connection = Connection::open(path.as_ref()).map_err(to_storage_error)?;
29 Self::from_connection(connection)
30 }
31
32 pub fn in_memory() -> Result<Self> {
34 let connection = Connection::open_in_memory().map_err(to_storage_error)?;
35 Self::from_connection(connection)
36 }
37
38 fn from_connection(connection: Connection) -> Result<Self> {
39 schema::apply(&connection)?;
40 Ok(Self {
41 connection: Arc::new(Mutex::new(connection)),
42 })
43 }
44
45 pub async fn prune_expired(&self, now: OffsetDateTime) -> Result<usize> {
50 let now = encode_time(now)?;
51 self.with_connection(move |connection| {
52 let removed = connection
53 .execute(
54 "DELETE FROM records WHERE expires_at IS NOT NULL AND expires_at <= ?1",
55 params![now],
56 )
57 .map_err(to_storage_error)?;
58 Ok(removed)
59 })
60 .await
61 }
62
63 async fn with_connection<T, F>(&self, operation: F) -> Result<T>
65 where
66 T: Send + 'static,
67 F: FnOnce(&Connection) -> Result<T> + Send + 'static,
68 {
69 let connection = self.connection.clone();
70 tokio::task::spawn_blocking(move || {
71 let guard = connection
72 .lock()
73 .map_err(|_| AppError::storage("sqlite connection poisoned"))?;
74 operation(&guard)
75 })
76 .await
77 .map_err(|error| AppError::storage(format!("storage task failed: {error}")))?
78 }
79}
80
81#[async_trait]
82impl Storage for SqliteStorage {
83 async fn get(&self, key: &StorageKey) -> Result<Option<Record>> {
84 let (namespace, name) = split(key);
85 self.with_connection(move |connection| {
86 connection
87 .query_row(
88 "SELECT value, stored_at, expires_at FROM records \
89 WHERE namespace = ?1 AND key = ?2",
90 params![namespace, name],
91 |row| {
92 Ok((
93 row.get::<_, String>(0)?,
94 row.get::<_, String>(1)?,
95 row.get::<_, Option<String>>(2)?,
96 ))
97 },
98 )
99 .optional()
100 .map_err(to_storage_error)?
101 .map(|(value, stored_at, expires_at)| {
102 let mut record = Record::new(value, decode_time(&stored_at)?);
103 record.expires_at = expires_at.as_deref().map(decode_time).transpose()?;
104 Ok(record)
105 })
106 .transpose()
107 })
108 .await
109 }
110
111 async fn put(&self, key: &StorageKey, record: Record) -> Result<()> {
112 let (namespace, name) = split(key);
113 let stored_at = encode_time(record.stored_at)?;
114 let expires_at = record.expires_at.map(encode_time).transpose()?;
115 let value = record.value;
116
117 self.with_connection(move |connection| {
118 connection
119 .execute(
120 "INSERT INTO records (namespace, key, value, stored_at, expires_at) \
121 VALUES (?1, ?2, ?3, ?4, ?5) \
122 ON CONFLICT(namespace, key) DO UPDATE SET \
123 value = excluded.value, \
124 stored_at = excluded.stored_at, \
125 expires_at = excluded.expires_at",
126 params![namespace, name, value, stored_at, expires_at],
127 )
128 .map_err(to_storage_error)?;
129 Ok(())
130 })
131 .await
132 }
133
134 async fn delete(&self, key: &StorageKey) -> Result<()> {
135 let (namespace, name) = split(key);
136 self.with_connection(move |connection| {
137 connection
138 .execute(
139 "DELETE FROM records WHERE namespace = ?1 AND key = ?2",
140 params![namespace, name],
141 )
142 .map_err(to_storage_error)?;
143 Ok(())
144 })
145 .await
146 }
147
148 async fn keys(&self, namespace: &str) -> Result<Vec<StorageKey>> {
149 let namespace = namespace.to_owned();
150 self.with_connection(move |connection| {
151 let mut statement = connection
152 .prepare("SELECT key FROM records WHERE namespace = ?1")
153 .map_err(to_storage_error)?;
154
155 let keys = statement
156 .query_map(params![namespace], |row| row.get::<_, String>(0))
157 .map_err(to_storage_error)?
158 .collect::<std::result::Result<Vec<_>, _>>()
159 .map_err(to_storage_error)?
160 .into_iter()
161 .map(|key| StorageKey::new(&namespace, key))
162 .collect();
163
164 Ok(keys)
165 })
166 .await
167 }
168
169 async fn clear(&self, namespace: &str) -> Result<()> {
170 let namespace = namespace.to_owned();
171 self.with_connection(move |connection| {
172 connection
173 .execute(
174 "DELETE FROM records WHERE namespace = ?1",
175 params![namespace],
176 )
177 .map_err(to_storage_error)?;
178 Ok(())
179 })
180 .await
181 }
182
183 async fn clear_prefix(&self, prefix: &str) -> Result<usize> {
184 let pattern = format!("{}*", glob_escape(prefix));
187
188 self.with_connection(move |connection| {
189 let removed = connection
190 .execute(
191 "DELETE FROM records WHERE namespace GLOB ?1",
192 params![pattern],
193 )
194 .map_err(to_storage_error)?;
195 Ok(removed)
196 })
197 .await
198 }
199}
200
201fn glob_escape(value: &str) -> String {
203 let mut escaped = String::with_capacity(value.len());
204 for character in value.chars() {
205 match character {
206 '*' | '?' | '[' | ']' => {
207 escaped.push('[');
208 escaped.push(character);
209 escaped.push(']');
210 }
211 other => escaped.push(other),
212 }
213 }
214 escaped
215}
216
217fn split(key: &StorageKey) -> (String, String) {
218 (key.namespace().to_owned(), key.key().to_owned())
219}
220
221fn encode_time(value: OffsetDateTime) -> Result<String> {
222 value
223 .format(&Rfc3339)
224 .map_err(|error| AppError::storage(format!("cannot format timestamp: {error}")))
225}
226
227fn decode_time(value: &str) -> Result<OffsetDateTime> {
228 OffsetDateTime::parse(value, &Rfc3339)
229 .map_err(|error| AppError::storage(format!("cannot parse timestamp {value:?}: {error}")))
230}
231
232fn to_storage_error(error: rusqlite::Error) -> AppError {
235 AppError::storage(error.to_string())
236}