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 postgres {
10 use std::collections::{BTreeMap, BTreeSet, HashMap};
11 use std::num::NonZeroUsize;
12
13 use sqlx::{PgConnection, PgPool, Row};
14
15 use crate::{
16 RemoteBatchOp, RemoteManifestUpdate, RemoteNamedRoot, RemoteRootCondition, RemoteRootWrite,
17 RemoteStoreBackend, RemoteTransactionConflict, RemoteTransactionUpdate,
18 };
19
20 pub type PostgresStore = crate::RemoteProllyStore<PostgresBackend>;
22
23 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
25 pub struct PostgresBackendOptions {
26 max_batch_items: NonZeroUsize,
27 }
28
29 impl PostgresBackendOptions {
30 pub const fn new(max_batch_items: NonZeroUsize) -> Self {
32 Self { max_batch_items }
33 }
34
35 pub const fn max_batch_items(self) -> usize {
37 self.max_batch_items.get()
38 }
39 }
40
41 impl Default for PostgresBackendOptions {
42 fn default() -> Self {
43 Self::new(NonZeroUsize::new(1_024).expect("1024 is nonzero"))
44 }
45 }
46
47 #[derive(Clone, Debug)]
49 pub struct PostgresBackend {
50 pool: PgPool,
51 options: PostgresBackendOptions,
52 }
53
54 impl PostgresBackend {
55 pub fn new(pool: PgPool) -> Self {
57 Self::new_with_options(pool, PostgresBackendOptions::default())
58 }
59
60 pub fn new_with_options(pool: PgPool, options: PostgresBackendOptions) -> Self {
62 Self { pool, options }
63 }
64
65 pub async fn connect(database_url: &str) -> Result<Self, sqlx::Error> {
67 Self::connect_with_options(database_url, PostgresBackendOptions::default()).await
68 }
69
70 pub async fn connect_with_options(
72 database_url: &str,
73 options: PostgresBackendOptions,
74 ) -> Result<Self, sqlx::Error> {
75 Ok(Self::new_with_options(
76 PgPool::connect(database_url).await?,
77 options,
78 ))
79 }
80
81 pub fn pool(&self) -> &PgPool {
83 &self.pool
84 }
85
86 pub const fn options(&self) -> PostgresBackendOptions {
88 self.options
89 }
90
91 pub async fn initialize_schema(&self) -> Result<(), sqlx::Error> {
93 execute_statements(&self.pool, POSTGRES_SCHEMA).await
94 }
95 }
96
97 impl RemoteStoreBackend for PostgresBackend {
98 type Error = sqlx::Error;
99
100 async fn get_node(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
101 sqlx::query("SELECT node FROM prolly_nodes WHERE cid = $1")
102 .bind(key)
103 .fetch_optional(&self.pool)
104 .await?
105 .map(|row| row.try_get("node"))
106 .transpose()
107 }
108
109 async fn put_node(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
110 sqlx::query(
111 "\
112 INSERT INTO prolly_nodes (cid, node) VALUES ($1, $2) \
113 ON CONFLICT(cid) DO UPDATE SET node = excluded.node",
114 )
115 .bind(key)
116 .bind(value)
117 .execute(&self.pool)
118 .await?;
119 Ok(())
120 }
121
122 async fn delete_node(&self, key: &[u8]) -> Result<(), Self::Error> {
123 sqlx::query("DELETE FROM prolly_nodes WHERE cid = $1")
124 .bind(key)
125 .execute(&self.pool)
126 .await?;
127 Ok(())
128 }
129
130 async fn batch_nodes(&self, ops: &[RemoteBatchOp<'_>]) -> Result<(), Self::Error> {
131 if ops.is_empty() {
132 return Ok(());
133 }
134 let mut final_ops = HashMap::<&[u8], Option<&[u8]>>::with_capacity(ops.len());
135 for op in ops {
136 match op {
137 RemoteBatchOp::Upsert { key, value } => {
138 final_ops.insert(key, Some(value));
139 }
140 RemoteBatchOp::Delete { key } => {
141 final_ops.insert(key, None);
142 }
143 }
144 }
145 let deletes = final_ops
146 .iter()
147 .filter_map(|(key, value)| value.is_none().then_some(*key))
148 .collect::<Vec<_>>();
149 let upserts = final_ops
150 .iter()
151 .filter_map(|(key, value)| value.map(|value| (*key, value)))
152 .collect::<Vec<_>>();
153 let mut tx = self.pool.begin().await?;
154 delete_node_chunks(&mut tx, &deletes, self.options.max_batch_items()).await?;
155 upsert_node_chunks(&mut tx, &upserts, self.options.max_batch_items()).await?;
156 tx.commit().await
157 }
158
159 async fn batch_get_nodes_ordered(
160 &self,
161 keys: &[&[u8]],
162 ) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
163 if keys.is_empty() {
164 return Ok(Vec::new());
165 }
166 let mut values = Vec::with_capacity(keys.len());
167 for chunk in keys.chunks(self.options.max_batch_items()) {
168 let requested = chunk.iter().map(|key| (*key).to_vec()).collect::<Vec<_>>();
169 let rows = sqlx::query(
170 "\
171 SELECT requested.ord, nodes.node \
172 FROM unnest($1::bytea[]) WITH ORDINALITY AS requested(cid, ord) \
173 LEFT JOIN prolly_nodes AS nodes ON nodes.cid = requested.cid \
174 ORDER BY requested.ord",
175 )
176 .bind(requested)
177 .fetch_all(&self.pool)
178 .await?;
179 for row in rows {
180 values.push(row.try_get::<Option<Vec<u8>>, _>("node")?);
181 }
182 }
183 Ok(values)
184 }
185
186 async fn batch_put_nodes(&self, entries: &[(&[u8], &[u8])]) -> Result<(), Self::Error> {
187 if entries.is_empty() {
188 return Ok(());
189 }
190 let entries = deduplicate_entries(entries);
191 let entries = entries.into_iter().collect::<Vec<_>>();
192 let mut tx = self.pool.begin().await?;
193 upsert_node_chunks(&mut tx, &entries, self.options.max_batch_items()).await?;
194 tx.commit().await
195 }
196
197 async fn list_node_cids(&self) -> Result<Vec<Vec<u8>>, Self::Error> {
198 let rows = sqlx::query("SELECT cid FROM prolly_nodes ORDER BY cid")
199 .fetch_all(&self.pool)
200 .await?;
201 rows.into_iter().map(|row| row.try_get("cid")).collect()
202 }
203
204 fn prefers_batch_reads(&self) -> bool {
205 true
206 }
207
208 fn supports_hints(&self) -> bool {
209 true
210 }
211
212 async fn get_hint(
213 &self,
214 namespace: &[u8],
215 key: &[u8],
216 ) -> Result<Option<Vec<u8>>, Self::Error> {
217 sqlx::query("SELECT value FROM prolly_hints WHERE namespace = $1 AND key = $2")
218 .bind(namespace)
219 .bind(key)
220 .fetch_optional(&self.pool)
221 .await?
222 .map(|row| row.try_get("value"))
223 .transpose()
224 }
225
226 async fn put_hint(
227 &self,
228 namespace: &[u8],
229 key: &[u8],
230 value: &[u8],
231 ) -> Result<(), Self::Error> {
232 sqlx::query(
233 "\
234 INSERT INTO prolly_hints (namespace, key, value) VALUES ($1, $2, $3) \
235 ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value",
236 )
237 .bind(namespace)
238 .bind(key)
239 .bind(value)
240 .execute(&self.pool)
241 .await?;
242 Ok(())
243 }
244
245 async fn batch_put_nodes_with_hint(
246 &self,
247 entries: &[(&[u8], &[u8])],
248 namespace: &[u8],
249 key: &[u8],
250 value: &[u8],
251 ) -> Result<(), Self::Error> {
252 let entries = deduplicate_entries(entries);
253 let entries = entries.into_iter().collect::<Vec<_>>();
254 let mut tx = self.pool.begin().await?;
255 upsert_node_chunks(&mut tx, &entries, self.options.max_batch_items()).await?;
256 sqlx::query(
257 "\
258 INSERT INTO prolly_hints (namespace, key, value) VALUES ($1, $2, $3) \
259 ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value",
260 )
261 .bind(namespace)
262 .bind(key)
263 .bind(value)
264 .execute(&mut *tx)
265 .await?;
266 tx.commit().await
267 }
268
269 async fn get_root_manifest(&self, name: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
270 sqlx::query("SELECT manifest FROM prolly_roots WHERE name = $1")
271 .bind(name)
272 .fetch_optional(&self.pool)
273 .await?
274 .map(|row| row.try_get("manifest"))
275 .transpose()
276 }
277
278 async fn put_root_manifest(&self, name: &[u8], manifest: &[u8]) -> Result<(), Self::Error> {
279 let mut tx = self.pool.begin().await?;
280 lock_root_names(&mut tx, &[name.to_vec()]).await?;
281 upsert_root_chunks(&mut tx, &[(name, manifest)], self.options.max_batch_items())
282 .await?;
283 tx.commit().await
284 }
285
286 async fn delete_root_manifest(&self, name: &[u8]) -> Result<(), Self::Error> {
287 let mut tx = self.pool.begin().await?;
288 lock_root_names(&mut tx, &[name.to_vec()]).await?;
289 delete_root_chunks(&mut tx, &[name], self.options.max_batch_items()).await?;
290 tx.commit().await
291 }
292
293 async fn compare_and_swap_root_manifest(
294 &self,
295 name: &[u8],
296 expected: Option<&[u8]>,
297 new: Option<&[u8]>,
298 ) -> Result<RemoteManifestUpdate, Self::Error> {
299 let mut tx = self.pool.begin().await?;
300 lock_root_names(&mut tx, &[name.to_vec()]).await?;
301
302 let current = sqlx::query("SELECT manifest FROM prolly_roots WHERE name = $1")
303 .bind(name)
304 .fetch_optional(&mut *tx)
305 .await?
306 .map(|row| row.try_get("manifest"))
307 .transpose()?;
308 if current.as_deref() != expected {
309 tx.rollback().await?;
310 return Ok(RemoteManifestUpdate::Conflict { current });
311 }
312
313 match new {
314 Some(manifest) => {
315 upsert_root_chunks(
316 &mut tx,
317 &[(name, manifest)],
318 self.options.max_batch_items(),
319 )
320 .await?;
321 }
322 None => {
323 delete_root_chunks(&mut tx, &[name], self.options.max_batch_items()).await?;
324 }
325 }
326
327 tx.commit().await?;
328 Ok(RemoteManifestUpdate::Applied)
329 }
330
331 async fn list_root_manifests(&self) -> Result<Vec<RemoteNamedRoot>, Self::Error> {
332 let rows = sqlx::query("SELECT name, manifest FROM prolly_roots ORDER BY name")
333 .fetch_all(&self.pool)
334 .await?;
335 rows.into_iter()
336 .map(|row| {
337 Ok(RemoteNamedRoot::new(
338 row.try_get("name")?,
339 row.try_get("manifest")?,
340 ))
341 })
342 .collect()
343 }
344
345 fn supports_transactions(&self) -> bool {
346 true
347 }
348
349 async fn commit_transaction(
350 &self,
351 node_writes: &[RemoteBatchOp<'_>],
352 root_conditions: &[RemoteRootCondition],
353 root_writes: &[RemoteRootWrite],
354 ) -> Result<RemoteTransactionUpdate, Self::Error> {
355 let mut tx = self.pool.begin().await?;
356 let root_names = root_names(root_conditions, root_writes);
357 lock_root_names(&mut tx, &root_names).await?;
358 let current_roots = read_root_manifests(&mut tx, &root_names).await?;
359
360 for condition in root_conditions {
361 let current = current_roots.get(&condition.name).cloned().unwrap_or(None);
362 if current != condition.expected {
363 tx.rollback().await?;
364 return Ok(RemoteTransactionUpdate::Conflict(
365 RemoteTransactionConflict::new(
366 condition.name.clone(),
367 condition.expected.clone(),
368 current,
369 ),
370 ));
371 }
372 }
373
374 let mut final_nodes =
375 HashMap::<&[u8], Option<&[u8]>>::with_capacity(node_writes.len());
376 for write in node_writes {
377 match write {
378 RemoteBatchOp::Upsert { key, value } => {
379 final_nodes.insert(key, Some(value));
380 }
381 RemoteBatchOp::Delete { key } => {
382 final_nodes.insert(key, None);
383 }
384 };
385 }
386 let node_deletes = final_nodes
387 .iter()
388 .filter_map(|(key, value)| value.is_none().then_some(*key))
389 .collect::<Vec<_>>();
390 let node_upserts = final_nodes
391 .iter()
392 .filter_map(|(key, value)| value.map(|value| (*key, value)))
393 .collect::<Vec<_>>();
394 delete_node_chunks(&mut tx, &node_deletes, self.options.max_batch_items()).await?;
395 upsert_node_chunks(&mut tx, &node_upserts, self.options.max_batch_items()).await?;
396
397 let mut final_roots = BTreeMap::<Vec<u8>, Option<Vec<u8>>>::new();
398 for write in root_writes {
399 match write {
400 RemoteRootWrite::Put { name, manifest } => {
401 final_roots.insert(name.clone(), Some(manifest.clone()));
402 }
403 RemoteRootWrite::Delete { name } => {
404 final_roots.insert(name.clone(), None);
405 }
406 }
407 }
408 let root_deletes = final_roots
409 .iter()
410 .filter_map(|(name, manifest)| manifest.is_none().then_some(name.as_slice()))
411 .collect::<Vec<_>>();
412 let root_upserts = final_roots
413 .iter()
414 .filter_map(|(name, manifest)| {
415 manifest
416 .as_deref()
417 .map(|manifest| (name.as_slice(), manifest))
418 })
419 .collect::<Vec<_>>();
420 delete_root_chunks(&mut tx, &root_deletes, self.options.max_batch_items()).await?;
421 upsert_root_chunks(&mut tx, &root_upserts, self.options.max_batch_items()).await?;
422
423 tx.commit().await?;
424 Ok(RemoteTransactionUpdate::Applied)
425 }
426 }
427
428 async fn upsert_node_chunks(
429 connection: &mut PgConnection,
430 entries: &[(&[u8], &[u8])],
431 max_batch_items: usize,
432 ) -> Result<(), sqlx::Error> {
433 for chunk in entries.chunks(max_batch_items) {
434 let keys = chunk
435 .iter()
436 .map(|(key, _)| (*key).to_vec())
437 .collect::<Vec<_>>();
438 let values = chunk
439 .iter()
440 .map(|(_, value)| (*value).to_vec())
441 .collect::<Vec<_>>();
442 sqlx::query(
443 "\
444 INSERT INTO prolly_nodes (cid, node) \
445 SELECT input.cid, input.node \
446 FROM unnest($1::bytea[], $2::bytea[]) AS input(cid, node) \
447 ON CONFLICT(cid) DO UPDATE SET node = excluded.node",
448 )
449 .bind(keys)
450 .bind(values)
451 .execute(&mut *connection)
452 .await?;
453 }
454 Ok(())
455 }
456
457 fn deduplicate_entries<'a>(entries: &[(&'a [u8], &'a [u8])]) -> HashMap<&'a [u8], &'a [u8]> {
458 entries.iter().copied().collect()
459 }
460
461 async fn delete_node_chunks(
462 connection: &mut PgConnection,
463 keys: &[&[u8]],
464 max_batch_items: usize,
465 ) -> Result<(), sqlx::Error> {
466 for chunk in keys.chunks(max_batch_items) {
467 let keys = chunk.iter().map(|key| (*key).to_vec()).collect::<Vec<_>>();
468 sqlx::query("DELETE FROM prolly_nodes WHERE cid = ANY($1::bytea[])")
469 .bind(keys)
470 .execute(&mut *connection)
471 .await?;
472 }
473 Ok(())
474 }
475
476 async fn upsert_root_chunks(
477 connection: &mut PgConnection,
478 entries: &[(&[u8], &[u8])],
479 max_batch_items: usize,
480 ) -> Result<(), sqlx::Error> {
481 for chunk in entries.chunks(max_batch_items) {
482 let names = chunk
483 .iter()
484 .map(|(name, _)| (*name).to_vec())
485 .collect::<Vec<_>>();
486 let manifests = chunk
487 .iter()
488 .map(|(_, manifest)| (*manifest).to_vec())
489 .collect::<Vec<_>>();
490 sqlx::query(
491 "\
492 INSERT INTO prolly_roots (name, manifest) \
493 SELECT input.name, input.manifest \
494 FROM unnest($1::bytea[], $2::bytea[]) AS input(name, manifest) \
495 ON CONFLICT(name) DO UPDATE SET manifest = excluded.manifest",
496 )
497 .bind(names)
498 .bind(manifests)
499 .execute(&mut *connection)
500 .await?;
501 }
502 Ok(())
503 }
504
505 async fn delete_root_chunks(
506 connection: &mut PgConnection,
507 names: &[&[u8]],
508 max_batch_items: usize,
509 ) -> Result<(), sqlx::Error> {
510 for chunk in names.chunks(max_batch_items) {
511 let names = chunk
512 .iter()
513 .map(|name| (*name).to_vec())
514 .collect::<Vec<_>>();
515 sqlx::query("DELETE FROM prolly_roots WHERE name = ANY($1::bytea[])")
516 .bind(names)
517 .execute(&mut *connection)
518 .await?;
519 }
520 Ok(())
521 }
522
523 fn root_names(conditions: &[RemoteRootCondition], writes: &[RemoteRootWrite]) -> Vec<Vec<u8>> {
524 let mut names = BTreeSet::new();
525 names.extend(conditions.iter().map(|condition| condition.name.clone()));
526 names.extend(writes.iter().map(|write| match write {
527 RemoteRootWrite::Put { name, .. } | RemoteRootWrite::Delete { name } => name.clone(),
528 }));
529 names.into_iter().collect()
530 }
531
532 async fn lock_root_names(
533 connection: &mut PgConnection,
534 names: &[Vec<u8>],
535 ) -> Result<(), sqlx::Error> {
536 for name in names {
537 sqlx::query(
538 "\
539 SELECT pg_advisory_xact_lock( \
540 hashtextextended('prolly-root-v1:' || encode($1::bytea, 'hex'), 0) \
541 )",
542 )
543 .bind(name)
544 .execute(&mut *connection)
545 .await?;
546 }
547 Ok(())
548 }
549
550 async fn read_root_manifests(
551 connection: &mut PgConnection,
552 names: &[Vec<u8>],
553 ) -> Result<BTreeMap<Vec<u8>, Option<Vec<u8>>>, sqlx::Error> {
554 if names.is_empty() {
555 return Ok(BTreeMap::new());
556 }
557 let rows = sqlx::query(
558 "\
559 SELECT requested.name, roots.manifest \
560 FROM unnest($1::bytea[]) AS requested(name) \
561 LEFT JOIN prolly_roots AS roots ON roots.name = requested.name",
562 )
563 .bind(names)
564 .fetch_all(&mut *connection)
565 .await?;
566 rows.into_iter()
567 .map(|row| {
568 Ok((
569 row.try_get::<Vec<u8>, _>("name")?,
570 row.try_get::<Option<Vec<u8>>, _>("manifest")?,
571 ))
572 })
573 .collect()
574 }
575
576 async fn execute_statements(pool: &PgPool, sql: &str) -> Result<(), sqlx::Error> {
577 for statement in sql
578 .split(';')
579 .map(str::trim)
580 .filter(|stmt| !stmt.is_empty())
581 {
582 sqlx::query(statement).execute(pool).await?;
583 }
584 Ok(())
585 }
586
587 pub const POSTGRES_SCHEMA: &str = "\
589CREATE TABLE IF NOT EXISTS prolly_nodes (
590 cid bytea PRIMARY KEY,
591 node bytea NOT NULL
592);
593CREATE TABLE IF NOT EXISTS prolly_hints (
594 namespace bytea NOT NULL,
595 key bytea NOT NULL,
596 value bytea NOT NULL,
597 PRIMARY KEY(namespace, key)
598);
599CREATE TABLE IF NOT EXISTS prolly_roots (
600 name bytea PRIMARY KEY,
601 manifest bytea NOT NULL
602);";
603}
604
605pub use postgres::*;