1#![doc = include_str!("../README.md")]
2
3pub use prolly::{
4 RemoteBatchOp, RemoteManifestUpdate, RemoteNamedRoot, RemoteProllyStore, RemoteRootCondition,
5 RemoteRootWrite, RemoteStoreBackend, RemoteTransactionConflict, RemoteTransactionUpdate,
6};
7
8pub mod mysql {
10 use sqlx::{MySqlPool, Row};
11
12 use crate::{
13 RemoteBatchOp, RemoteManifestUpdate, RemoteNamedRoot, RemoteRootCondition, RemoteRootWrite,
14 RemoteStoreBackend, RemoteTransactionConflict, RemoteTransactionUpdate,
15 };
16
17 pub type MySqlStore = crate::RemoteProllyStore<MySqlBackend>;
19
20 #[derive(Clone, Debug)]
22 pub struct MySqlBackend {
23 pool: MySqlPool,
24 }
25
26 impl MySqlBackend {
27 pub fn new(pool: MySqlPool) -> Self {
29 Self { pool }
30 }
31
32 pub async fn connect(database_url: &str) -> Result<Self, sqlx::Error> {
34 Ok(Self::new(MySqlPool::connect(database_url).await?))
35 }
36
37 pub fn pool(&self) -> &MySqlPool {
39 &self.pool
40 }
41
42 pub async fn initialize_schema(&self) -> Result<(), sqlx::Error> {
44 execute_statements(&self.pool, MYSQL_SCHEMA).await
45 }
46 }
47
48 impl RemoteStoreBackend for MySqlBackend {
49 type Error = sqlx::Error;
50
51 async fn get_node(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
52 sqlx::query("SELECT node FROM prolly_nodes WHERE cid = ?")
53 .bind(key)
54 .fetch_optional(&self.pool)
55 .await?
56 .map(|row| row.try_get("node"))
57 .transpose()
58 }
59
60 async fn put_node(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
61 sqlx::query(
62 "\
63 INSERT INTO prolly_nodes (cid, node) VALUES (?, ?) \
64 ON DUPLICATE KEY UPDATE node = VALUES(node)",
65 )
66 .bind(key)
67 .bind(value)
68 .execute(&self.pool)
69 .await?;
70 Ok(())
71 }
72
73 async fn delete_node(&self, key: &[u8]) -> Result<(), Self::Error> {
74 sqlx::query("DELETE FROM prolly_nodes WHERE cid = ?")
75 .bind(key)
76 .execute(&self.pool)
77 .await?;
78 Ok(())
79 }
80
81 async fn batch_nodes(&self, ops: &[RemoteBatchOp<'_>]) -> Result<(), Self::Error> {
82 let mut tx = self.pool.begin().await?;
83 for op in ops {
84 match op {
85 RemoteBatchOp::Upsert { key, value } => {
86 sqlx::query(
87 "\
88 INSERT INTO prolly_nodes (cid, node) VALUES (?, ?) \
89 ON DUPLICATE KEY UPDATE node = VALUES(node)",
90 )
91 .bind(*key)
92 .bind(*value)
93 .execute(&mut *tx)
94 .await?;
95 }
96 RemoteBatchOp::Delete { key } => {
97 sqlx::query("DELETE FROM prolly_nodes WHERE cid = ?")
98 .bind(*key)
99 .execute(&mut *tx)
100 .await?;
101 }
102 }
103 }
104 tx.commit().await
105 }
106
107 async fn batch_get_nodes_ordered(
108 &self,
109 keys: &[&[u8]],
110 ) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
111 let mut values = Vec::with_capacity(keys.len());
112 for key in keys {
113 values.push(self.get_node(key).await?);
114 }
115 Ok(values)
116 }
117
118 async fn batch_put_nodes(&self, entries: &[(&[u8], &[u8])]) -> Result<(), Self::Error> {
119 let mut tx = self.pool.begin().await?;
120 for (key, value) in entries {
121 sqlx::query(
122 "\
123 INSERT INTO prolly_nodes (cid, node) VALUES (?, ?) \
124 ON DUPLICATE KEY UPDATE node = VALUES(node)",
125 )
126 .bind(*key)
127 .bind(*value)
128 .execute(&mut *tx)
129 .await?;
130 }
131 tx.commit().await
132 }
133
134 async fn list_node_cids(&self) -> Result<Vec<Vec<u8>>, Self::Error> {
135 let rows = sqlx::query("SELECT cid FROM prolly_nodes ORDER BY cid")
136 .fetch_all(&self.pool)
137 .await?;
138 rows.into_iter().map(|row| row.try_get("cid")).collect()
139 }
140
141 fn prefers_batch_reads(&self) -> bool {
142 true
143 }
144
145 fn supports_hints(&self) -> bool {
146 true
147 }
148
149 async fn get_hint(
150 &self,
151 namespace: &[u8],
152 key: &[u8],
153 ) -> Result<Option<Vec<u8>>, Self::Error> {
154 sqlx::query("SELECT value FROM prolly_hints WHERE namespace = ? AND `key` = ?")
155 .bind(namespace)
156 .bind(key)
157 .fetch_optional(&self.pool)
158 .await?
159 .map(|row| row.try_get("value"))
160 .transpose()
161 }
162
163 async fn put_hint(
164 &self,
165 namespace: &[u8],
166 key: &[u8],
167 value: &[u8],
168 ) -> Result<(), Self::Error> {
169 sqlx::query(
170 "\
171 INSERT INTO prolly_hints (namespace, `key`, value) VALUES (?, ?, ?) \
172 ON DUPLICATE KEY UPDATE value = VALUES(value)",
173 )
174 .bind(namespace)
175 .bind(key)
176 .bind(value)
177 .execute(&self.pool)
178 .await?;
179 Ok(())
180 }
181
182 async fn batch_put_nodes_with_hint(
183 &self,
184 entries: &[(&[u8], &[u8])],
185 namespace: &[u8],
186 key: &[u8],
187 value: &[u8],
188 ) -> Result<(), Self::Error> {
189 let mut tx = self.pool.begin().await?;
190 for (key, value) in entries {
191 sqlx::query(
192 "\
193 INSERT INTO prolly_nodes (cid, node) VALUES (?, ?) \
194 ON DUPLICATE KEY UPDATE node = VALUES(node)",
195 )
196 .bind(*key)
197 .bind(*value)
198 .execute(&mut *tx)
199 .await?;
200 }
201 sqlx::query(
202 "\
203 INSERT INTO prolly_hints (namespace, `key`, value) VALUES (?, ?, ?) \
204 ON DUPLICATE KEY UPDATE value = VALUES(value)",
205 )
206 .bind(namespace)
207 .bind(key)
208 .bind(value)
209 .execute(&mut *tx)
210 .await?;
211 tx.commit().await
212 }
213
214 async fn get_root_manifest(&self, name: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
215 sqlx::query("SELECT manifest FROM prolly_roots WHERE name = ?")
216 .bind(name)
217 .fetch_optional(&self.pool)
218 .await?
219 .map(|row| row.try_get("manifest"))
220 .transpose()
221 }
222
223 async fn put_root_manifest(&self, name: &[u8], manifest: &[u8]) -> Result<(), Self::Error> {
224 sqlx::query(
225 "\
226 INSERT INTO prolly_roots (name, manifest) VALUES (?, ?) \
227 ON DUPLICATE KEY UPDATE manifest = VALUES(manifest)",
228 )
229 .bind(name)
230 .bind(manifest)
231 .execute(&self.pool)
232 .await?;
233 Ok(())
234 }
235
236 async fn delete_root_manifest(&self, name: &[u8]) -> Result<(), Self::Error> {
237 sqlx::query("DELETE FROM prolly_roots WHERE name = ?")
238 .bind(name)
239 .execute(&self.pool)
240 .await?;
241 Ok(())
242 }
243
244 async fn compare_and_swap_root_manifest(
245 &self,
246 name: &[u8],
247 expected: Option<&[u8]>,
248 new: Option<&[u8]>,
249 ) -> Result<RemoteManifestUpdate, Self::Error> {
250 let mut tx = self.pool.begin().await?;
251 let applied = match (expected, new) {
252 (None, Some(manifest)) => {
253 sqlx::query("INSERT IGNORE INTO prolly_roots (name, manifest) VALUES (?, ?)")
254 .bind(name)
255 .bind(manifest)
256 .execute(&mut *tx)
257 .await?
258 .rows_affected()
259 == 1
260 }
261 (Some(expected), Some(manifest)) => {
262 sqlx::query(
263 "UPDATE prolly_roots SET manifest = ? WHERE name = ? AND manifest = ?",
264 )
265 .bind(manifest)
266 .bind(name)
267 .bind(expected)
268 .execute(&mut *tx)
269 .await?
270 .rows_affected()
271 == 1
272 }
273 (Some(expected), None) => {
274 sqlx::query("DELETE FROM prolly_roots WHERE name = ? AND manifest = ?")
275 .bind(name)
276 .bind(expected)
277 .execute(&mut *tx)
278 .await?
279 .rows_affected()
280 == 1
281 }
282 (None, None) => {
283 sqlx::query("SELECT manifest FROM prolly_roots WHERE name = ? FOR UPDATE")
284 .bind(name)
285 .fetch_optional(&mut *tx)
286 .await?
287 .is_none()
288 }
289 };
290
291 if applied {
292 tx.commit().await?;
293 return Ok(RemoteManifestUpdate::Applied);
294 }
295
296 let current = sqlx::query("SELECT manifest FROM prolly_roots WHERE name = ?")
297 .bind(name)
298 .fetch_optional(&mut *tx)
299 .await?
300 .map(|row| row.try_get("manifest"))
301 .transpose()?;
302 tx.rollback().await?;
303 Ok(RemoteManifestUpdate::Conflict { current })
304 }
305
306 async fn list_root_manifests(&self) -> Result<Vec<RemoteNamedRoot>, Self::Error> {
307 let rows = sqlx::query("SELECT name, manifest FROM prolly_roots ORDER BY name")
308 .fetch_all(&self.pool)
309 .await?;
310 rows.into_iter()
311 .map(|row| {
312 Ok(RemoteNamedRoot::new(
313 row.try_get("name")?,
314 row.try_get("manifest")?,
315 ))
316 })
317 .collect()
318 }
319
320 fn supports_transactions(&self) -> bool {
321 true
322 }
323
324 async fn commit_transaction(
325 &self,
326 node_writes: &[RemoteBatchOp<'_>],
327 root_conditions: &[RemoteRootCondition],
328 root_writes: &[RemoteRootWrite],
329 ) -> Result<RemoteTransactionUpdate, Self::Error> {
330 let mut tx = self.pool.begin().await?;
331
332 for condition in root_conditions {
333 let current =
334 sqlx::query("SELECT manifest FROM prolly_roots WHERE name = ? FOR UPDATE")
335 .bind(&condition.name)
336 .fetch_optional(&mut *tx)
337 .await?
338 .map(|row| row.try_get("manifest"))
339 .transpose()?;
340 if current != condition.expected {
341 tx.rollback().await?;
342 return Ok(RemoteTransactionUpdate::Conflict(
343 RemoteTransactionConflict::new(
344 condition.name.clone(),
345 condition.expected.clone(),
346 current,
347 ),
348 ));
349 }
350 }
351
352 for write in node_writes {
353 match write {
354 RemoteBatchOp::Upsert { key, value } => {
355 sqlx::query(
356 "\
357 INSERT INTO prolly_nodes (cid, node) VALUES (?, ?) \
358 ON DUPLICATE KEY UPDATE node = VALUES(node)",
359 )
360 .bind(*key)
361 .bind(*value)
362 .execute(&mut *tx)
363 .await?;
364 }
365 RemoteBatchOp::Delete { key } => {
366 sqlx::query("DELETE FROM prolly_nodes WHERE cid = ?")
367 .bind(*key)
368 .execute(&mut *tx)
369 .await?;
370 }
371 }
372 }
373
374 for write in root_writes {
375 match write {
376 RemoteRootWrite::Put { name, manifest } => {
377 sqlx::query(
378 "\
379 INSERT INTO prolly_roots (name, manifest) VALUES (?, ?) \
380 ON DUPLICATE KEY UPDATE manifest = VALUES(manifest)",
381 )
382 .bind(name)
383 .bind(manifest)
384 .execute(&mut *tx)
385 .await?;
386 }
387 RemoteRootWrite::Delete { name } => {
388 sqlx::query("DELETE FROM prolly_roots WHERE name = ?")
389 .bind(name)
390 .execute(&mut *tx)
391 .await?;
392 }
393 }
394 }
395
396 tx.commit().await?;
397 Ok(RemoteTransactionUpdate::Applied)
398 }
399 }
400
401 async fn execute_statements(pool: &MySqlPool, sql: &str) -> Result<(), sqlx::Error> {
402 for statement in sql
403 .split(';')
404 .map(str::trim)
405 .filter(|stmt| !stmt.is_empty())
406 {
407 sqlx::query(statement).execute(pool).await?;
408 }
409 Ok(())
410 }
411
412 pub const MYSQL_SCHEMA: &str = "\
414CREATE TABLE IF NOT EXISTS prolly_nodes (
415 cid VARBINARY(32) PRIMARY KEY,
416 node LONGBLOB NOT NULL
417);
418CREATE TABLE IF NOT EXISTS prolly_hints (
419 namespace VARBINARY(255) NOT NULL,
420 `key` VARBINARY(255) NOT NULL,
421 value LONGBLOB NOT NULL,
422 PRIMARY KEY(namespace, `key`)
423);
424CREATE TABLE IF NOT EXISTS prolly_roots (
425 name VARBINARY(255) PRIMARY KEY,
426 manifest LONGBLOB NOT NULL
427);";
428}
429
430pub use mysql::*;