1#![doc = include_str!("../README.md")]
2
3use std::collections::{hash_map::Entry, HashMap};
4use std::path::Path;
5use std::sync::Arc;
6use std::time::Duration;
7
8use ahash::AHashMap;
9use parking_lot::{Mutex, MutexGuard};
10#[cfg(unix)]
11use rusqlite::OpenFlags;
12use rusqlite::{params, Connection, OptionalExtension, TransactionBehavior};
13
14use prolly::{
15 BatchOp, Cid, Error, ManifestStore, ManifestStoreScan, ManifestUpdate, NamedRootManifest,
16 NodePublication, NodeStoreScan, PublicationOrigin, RootCondition, RootManifest, RootWrite,
17 Store, TransactionConflict, TransactionNodeWrite, TransactionUpdate, TransactionalStore,
18};
19
20struct NodeReadCache {
21 values: AHashMap<Vec<u8>, Arc<[u8]>>,
22 retained_bytes: usize,
23 max_bytes: usize,
24}
25
26impl NodeReadCache {
27 fn new(max_bytes: usize) -> Self {
28 Self {
29 values: AHashMap::new(),
30 retained_bytes: 0,
31 max_bytes,
32 }
33 }
34
35 fn get(&self, key: &[u8]) -> Option<Arc<[u8]>> {
36 self.values.get(key).cloned()
37 }
38
39 fn insert(&mut self, key: &[u8], value: Arc<[u8]>) {
40 if self.max_bytes == 0 || value.len() > self.max_bytes {
41 return;
42 }
43 let previous = self.values.remove(key);
44 self.retained_bytes = self
45 .retained_bytes
46 .saturating_sub(previous.as_ref().map_or(0, |value| value.len()));
47 if self.retained_bytes.saturating_add(value.len()) > self.max_bytes {
48 self.values.clear();
49 self.retained_bytes = 0;
50 }
51 self.retained_bytes = self.retained_bytes.saturating_add(value.len());
52 self.values.insert(key.to_vec(), value);
53 }
54
55 fn insert_immutable(&mut self, key: &[u8], value: Arc<[u8]>) {
56 if self.max_bytes == 0 || value.len() > self.max_bytes {
57 return;
58 }
59 if self.retained_bytes.saturating_add(value.len()) > self.max_bytes {
60 self.values.clear();
61 self.retained_bytes = 0;
62 }
63 if let Entry::Vacant(entry) = self.values.entry(key.to_vec()) {
64 self.retained_bytes = self.retained_bytes.saturating_add(value.len());
65 entry.insert(value);
66 }
67 }
68
69 fn remove(&mut self, key: &[u8]) {
70 if let Some(value) = self.values.remove(key) {
71 self.retained_bytes = self.retained_bytes.saturating_sub(value.len());
72 }
73 }
74}
75
76struct OrderedBatchReadPlan<'a> {
77 unique_keys: Vec<&'a [u8]>,
78 positions: Option<Vec<usize>>,
79}
80
81impl<'a> OrderedBatchReadPlan<'a> {
82 fn new(keys: &[&'a [u8]]) -> Self {
83 let mut unique_indexes = HashMap::with_capacity(keys.len());
84 let mut unique_keys = Vec::with_capacity(keys.len());
85 let mut positions = None;
86 for key in keys {
87 match unique_indexes.entry(*key) {
88 Entry::Occupied(entry) => positions
89 .get_or_insert_with(|| (0..unique_keys.len()).collect::<Vec<_>>())
90 .push(*entry.get()),
91 Entry::Vacant(entry) => {
92 let index = unique_keys.len();
93 unique_keys.push(*key);
94 if let Some(positions) = positions.as_mut() {
95 positions.push(index);
96 }
97 entry.insert(index);
98 }
99 }
100 }
101 Self {
102 unique_keys,
103 positions,
104 }
105 }
106
107 fn unique_keys(&self) -> &[&'a [u8]] {
108 &self.unique_keys
109 }
110
111 fn expand_owned<T: Clone>(&self, values: Vec<Option<T>>) -> Vec<Option<T>> {
112 match &self.positions {
113 Some(positions) => positions
114 .iter()
115 .map(|&index| values[index].clone())
116 .collect(),
117 None => values,
118 }
119 }
120}
121
122fn cid_from_store_key(key: &[u8], context: &str) -> Result<Cid, String> {
123 let bytes: [u8; 32] = key.try_into().map_err(|_| {
124 format!(
125 "{context} key has invalid CID length {}, expected 32",
126 key.len()
127 )
128 })?;
129 Ok(Cid(bytes))
130}
131
132fn sort_cids(cids: &mut [Cid]) {
133 cids.sort_by(|left, right| left.as_bytes().cmp(right.as_bytes()));
134}
135
136fn sort_named_root_manifests(roots: &mut [NamedRootManifest]) {
137 roots.sort_by(|left, right| left.name.cmp(&right.name));
138}
139
140const CREATE_TABLE_SQL: &str = "\
141CREATE TABLE IF NOT EXISTS prolly_nodes (
142 cid BLOB PRIMARY KEY NOT NULL,
143 encoding INTEGER NOT NULL DEFAULT 0,
144 node BLOB NOT NULL
145);";
146
147const CREATE_HINTS_TABLE_SQL: &str = "\
148CREATE TABLE IF NOT EXISTS prolly_hints (
149 namespace BLOB NOT NULL,
150 key BLOB NOT NULL,
151 value BLOB NOT NULL,
152 PRIMARY KEY (namespace, key)
153) WITHOUT ROWID;";
154
155const CREATE_ROOTS_TABLE_SQL: &str = "\
156CREATE TABLE IF NOT EXISTS prolly_roots (
157 name BLOB PRIMARY KEY NOT NULL,
158 manifest BLOB NOT NULL
159) WITHOUT ROWID;";
160
161const SELECT_SQL: &str = "SELECT encoding, node FROM prolly_nodes WHERE cid = ?1";
162const MAX_BATCH_SELECT_KEYS: usize = 256;
163const SELECT_NODE_CIDS_SQL: &str = "SELECT cid FROM prolly_nodes ORDER BY cid";
164const UPSERT_SQL: &str = "\
165INSERT INTO prolly_nodes (cid, encoding, node)
166VALUES (?1, ?2, ?3)
167ON CONFLICT(cid) DO UPDATE SET encoding = excluded.encoding, node = excluded.node";
168const INSERT_IMMUTABLE_SQL: &str = "\
169INSERT OR IGNORE INTO prolly_nodes (cid, encoding, node)
170VALUES (?1, ?2, ?3)";
171const DELETE_SQL: &str = "DELETE FROM prolly_nodes WHERE cid = ?1";
172const UPSERT_HINT_SQL: &str = "\
173INSERT INTO prolly_hints (namespace, key, value)
174VALUES (?1, ?2, ?3)
175ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value";
176const SELECT_ROOT_SQL: &str = "SELECT manifest FROM prolly_roots WHERE name = ?1";
177const SELECT_ROOTS_SQL: &str = "SELECT name, manifest FROM prolly_roots ORDER BY name";
178const UPSERT_ROOT_SQL: &str = "\
179INSERT INTO prolly_roots (name, manifest)
180VALUES (?1, ?2)
181ON CONFLICT(name) DO UPDATE SET manifest = excluded.manifest";
182const DELETE_ROOT_SQL: &str = "DELETE FROM prolly_roots WHERE name = ?1";
183
184#[derive(Debug, Clone)]
186pub struct SqliteStoreConfig {
187 pub busy_timeout_ms: u64,
189 pub enable_wal: bool,
191 pub synchronous_normal: bool,
193 pub page_size_bytes: u32,
197 pub page_cache_size_bytes: u64,
202 pub wal_autocheckpoint_pages: u32,
207 pub node_read_cache_size_bytes: usize,
213 pub node_compression_min_bytes: usize,
218 pub mmap_size_bytes: u64,
223}
224
225impl Default for SqliteStoreConfig {
226 fn default() -> Self {
227 Self {
228 busy_timeout_ms: 5_000,
229 enable_wal: true,
230 synchronous_normal: true,
231 page_size_bytes: 64 * 1024,
232 page_cache_size_bytes: 64 * 1024 * 1024,
233 wal_autocheckpoint_pages: 32 * 1024,
234 node_read_cache_size_bytes: 128 * 1024 * 1024,
235 node_compression_min_bytes: 8 * 1024,
236 mmap_size_bytes: 256 * 1024 * 1024,
237 }
238 }
239}
240
241#[derive(Debug)]
243pub struct SqliteStoreError {
244 message: String,
245 source: Option<rusqlite::Error>,
246}
247
248impl SqliteStoreError {
249 pub fn new(message: impl Into<String>) -> Self {
251 Self {
252 message: message.into(),
253 source: None,
254 }
255 }
256
257 pub fn from_sqlite(err: rusqlite::Error, context: impl Into<String>) -> Self {
259 Self {
260 message: format!("{}: {}", context.into(), err),
261 source: Some(err),
262 }
263 }
264}
265
266impl std::fmt::Display for SqliteStoreError {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 write!(f, "SQLite error: {}", self.message)
269 }
270}
271
272impl std::error::Error for SqliteStoreError {
273 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
274 self.source
275 .as_ref()
276 .map(|e| e as &(dyn std::error::Error + 'static))
277 }
278}
279
280impl From<rusqlite::Error> for SqliteStoreError {
281 fn from(err: rusqlite::Error) -> Self {
282 Self {
283 message: err.to_string(),
284 source: Some(err),
285 }
286 }
287}
288
289pub struct SqliteStore {
294 conn: Mutex<Connection>,
295 node_read_cache: Mutex<NodeReadCache>,
296 node_compression_min_bytes: usize,
297}
298
299#[derive(Clone, Copy, Debug, Eq, PartialEq)]
301pub struct SqliteMainFileIdentity {
302 pub device: u64,
304 pub inode: u64,
306 pub length: u64,
308}
309
310impl SqliteStore {
311 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, SqliteStoreError> {
313 Self::open_with_config(path, SqliteStoreConfig::default())
314 }
315
316 pub fn open_with_config<P: AsRef<Path>>(
318 path: P,
319 config: SqliteStoreConfig,
320 ) -> Result<Self, SqliteStoreError> {
321 let conn = Connection::open(path.as_ref()).map_err(|e| {
322 SqliteStoreError::from_sqlite(
323 e,
324 format!("Failed to open database at {:?}", path.as_ref()),
325 )
326 })?;
327 Self::from_connection(conn, config)
328 }
329
330 pub fn open_existing<P: AsRef<Path>>(path: P) -> Result<Self, SqliteStoreError> {
336 Self::open_existing_verified(path, |_| Ok(()))
337 }
338
339 #[cfg(unix)]
342 pub fn open_existing_verified<P, F>(path: P, verifier: F) -> Result<Self, SqliteStoreError>
343 where
344 P: AsRef<Path>,
345 F: FnOnce(SqliteMainFileIdentity) -> Result<(), SqliteStoreError>,
346 {
347 let conn = Connection::open_with_flags(
348 path.as_ref(),
349 OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_NO_MUTEX,
350 )
351 .map_err(|error| {
352 SqliteStoreError::from_sqlite(
353 error,
354 format!("Failed to open existing database at {:?}", path.as_ref()),
355 )
356 })?;
357 verifier(sqlite_main_file_identity(&conn)?)?;
358 Self::from_existing_connection(conn, SqliteStoreConfig::default())
359 }
360
361 #[cfg(not(unix))]
364 pub fn open_existing_verified<P, F>(_path: P, _verifier: F) -> Result<Self, SqliteStoreError>
365 where
366 P: AsRef<Path>,
367 F: FnOnce(SqliteMainFileIdentity) -> Result<(), SqliteStoreError>,
368 {
369 Err(SqliteStoreError::new(
370 "verified existing SQLite opens are unsupported on this platform",
371 ))
372 }
373
374 pub fn open_in_memory() -> Result<Self, SqliteStoreError> {
376 let conn = Connection::open_in_memory()
377 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to open in-memory database"))?;
378 Self::from_connection(conn, SqliteStoreConfig::default())
379 }
380
381 fn from_connection(
382 conn: Connection,
383 config: SqliteStoreConfig,
384 ) -> Result<Self, SqliteStoreError> {
385 if !(512..=65_536).contains(&config.page_size_bytes)
386 || !config.page_size_bytes.is_power_of_two()
387 {
388 return Err(SqliteStoreError::new(
389 "page_size_bytes must be a power of two from 512 through 65536",
390 ));
391 }
392 conn.pragma_update(None, "page_size", config.page_size_bytes)
395 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set page_size"))?;
396 Self::apply_runtime_config(&conn, &config)?;
397 conn.execute_batch(CREATE_TABLE_SQL)
398 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to initialize schema"))?;
399 ensure_node_encoding_column(&conn)?;
400 conn.execute_batch(CREATE_HINTS_TABLE_SQL)
401 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to initialize hint schema"))?;
402 conn.execute_batch(CREATE_ROOTS_TABLE_SQL)
403 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to initialize root schema"))?;
404 Ok(Self {
405 conn: Mutex::new(conn),
406 node_read_cache: Mutex::new(NodeReadCache::new(config.node_read_cache_size_bytes)),
407 node_compression_min_bytes: config.node_compression_min_bytes,
408 })
409 }
410
411 fn from_existing_connection(
412 conn: Connection,
413 config: SqliteStoreConfig,
414 ) -> Result<Self, SqliteStoreError> {
415 Self::apply_runtime_config(&conn, &config)?;
416 Ok(Self {
417 conn: Mutex::new(conn),
418 node_read_cache: Mutex::new(NodeReadCache::new(config.node_read_cache_size_bytes)),
419 node_compression_min_bytes: config.node_compression_min_bytes,
420 })
421 }
422
423 fn apply_runtime_config(
424 conn: &Connection,
425 config: &SqliteStoreConfig,
426 ) -> Result<(), SqliteStoreError> {
427 conn.busy_timeout(Duration::from_millis(config.busy_timeout_ms))
428 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set busy timeout"))?;
429
430 if config.enable_wal {
431 conn.pragma_update(None, "journal_mode", "WAL")
432 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to enable WAL mode"))?;
433 }
434 if config.synchronous_normal {
435 conn.pragma_update(None, "synchronous", "NORMAL")
436 .map_err(|e| {
437 SqliteStoreError::from_sqlite(e, "Failed to set synchronous=NORMAL")
438 })?;
439 }
440 conn.pragma_update(None, "temp_store", "MEMORY")
441 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set temp_store=MEMORY"))?;
442 let cache_size_kib = config
443 .page_cache_size_bytes
444 .div_ceil(1024)
445 .min(i64::MAX as u64);
446 let cache_size_kib = -(cache_size_kib as i64);
447 conn.pragma_update(None, "cache_size", cache_size_kib)
448 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set cache_size"))?;
449 conn.pragma_update(None, "wal_autocheckpoint", config.wal_autocheckpoint_pages)
450 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set WAL autocheckpoint"))?;
451 conn.pragma_update(None, "mmap_size", config.mmap_size_bytes)
452 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to set mmap_size"))?;
453 Ok(())
454 }
455
456 fn connection(&self) -> Result<MutexGuard<'_, Connection>, SqliteStoreError> {
457 Ok(self.conn.lock())
458 }
459
460 fn node_read_cache(&self) -> Result<MutexGuard<'_, NodeReadCache>, SqliteStoreError> {
461 Ok(self.node_read_cache.lock())
462 }
463
464}
465
466fn ensure_node_encoding_column(conn: &Connection) -> Result<(), SqliteStoreError> {
467 let has_encoding = {
468 let mut stmt = conn
469 .prepare("PRAGMA table_info(prolly_nodes)")
470 .map_err(|error| {
471 SqliteStoreError::from_sqlite(error, "Failed to inspect node schema")
472 })?;
473 let columns = stmt
474 .query_map([], |row| row.get::<_, String>(1))
475 .map_err(|error| SqliteStoreError::from_sqlite(error, "Failed to query node schema"))?;
476 let mut found = false;
477 for column in columns {
478 if column.map_err(|error| {
479 SqliteStoreError::from_sqlite(error, "Failed to read node schema")
480 })? == "encoding"
481 {
482 found = true;
483 break;
484 }
485 }
486 found
487 };
488 if !has_encoding {
489 conn.execute(
490 "ALTER TABLE prolly_nodes ADD COLUMN encoding INTEGER NOT NULL DEFAULT 0",
491 [],
492 )
493 .map_err(|error| {
494 SqliteStoreError::from_sqlite(error, "Failed to migrate node encoding schema")
495 })?;
496 }
497 Ok(())
498}
499
500const NODE_ENCODING_RAW: i64 = 0;
501const NODE_ENCODING_LZ4: i64 = 1;
502fn encode_stored_node(node: &[u8], min_compressible_bytes: usize) -> (i64, Vec<u8>) {
503 let mut scratch = Vec::new();
504 let (encoding, stored) =
505 encode_stored_node_into(node, &mut scratch, min_compressible_bytes);
506 (encoding, stored.to_vec())
507}
508
509fn encode_stored_node_into<'a>(
510 node: &'a [u8],
511 scratch: &'a mut Vec<u8>,
512 min_compressible_bytes: usize,
513) -> (i64, &'a [u8]) {
514 if node.len() < min_compressible_bytes || node.len() > u32::MAX as usize {
515 return (NODE_ENCODING_RAW, node);
516 }
517 let maximum = 4 + lz4_flex::block::get_maximum_output_size(node.len());
518 scratch.clear();
519 scratch.resize(maximum, 0);
520 scratch[..4].copy_from_slice(&(node.len() as u32).to_le_bytes());
521 let compressed_len = lz4_flex::block::compress_into(node, &mut scratch[4..])
522 .expect("maximum LZ4 output size is sufficient");
523 let stored_len = 4 + compressed_len;
524 if stored_len >= node.len() {
525 return (NODE_ENCODING_RAW, node);
526 }
527 scratch.truncate(stored_len);
528 (NODE_ENCODING_LZ4, scratch)
529}
530
531fn decode_stored_node_ref(encoding: i64, node: &[u8]) -> Result<Vec<u8>, SqliteStoreError> {
532 match encoding {
533 NODE_ENCODING_RAW => Ok(node.to_vec()),
534 NODE_ENCODING_LZ4 => lz4_flex::decompress_size_prepended(node)
535 .map_err(|error| SqliteStoreError::new(format!("failed to decompress node: {error}"))),
536 other => Err(SqliteStoreError::new(format!(
537 "unsupported node encoding {other}"
538 ))),
539 }
540}
541
542fn select_nodes_ordered_unique(
543 conn: &Connection,
544 keys: &[&[u8]],
545) -> Result<Vec<Option<Vec<u8>>>, SqliteStoreError> {
546 if keys.is_empty() {
547 return Ok(Vec::new());
548 }
549 if keys.len() == 1 {
550 let mut stmt = conn.prepare_cached(SELECT_SQL).map_err(|error| {
551 SqliteStoreError::from_sqlite(error, "Failed to prepare point read")
552 })?;
553 let mut rows = stmt
554 .query(params![keys[0]])
555 .map_err(|error| SqliteStoreError::from_sqlite(error, "Failed to read key"))?;
556 let value = match rows
557 .next()
558 .map_err(|error| SqliteStoreError::from_sqlite(error, "Failed to read key"))?
559 {
560 Some(row) => {
561 let encoding = row.get::<_, i64>(0).map_err(|error| {
562 SqliteStoreError::from_sqlite(error, "Failed to read node encoding")
563 })?;
564 let node_value = row.get_ref(1).map_err(|error| {
565 SqliteStoreError::from_sqlite(error, "Failed to borrow node bytes")
566 })?;
567 let node = node_value.as_blob().map_err(|error| {
568 SqliteStoreError::new(format!("Failed to borrow node bytes: {error}"))
569 })?;
570 Some(decode_stored_node_ref(encoding, node)?)
571 }
572 None => None,
573 };
574 return Ok(vec![value]);
575 }
576
577 let positions = keys
578 .iter()
579 .enumerate()
580 .map(|(index, key)| (*key, index))
581 .collect::<HashMap<_, _>>();
582 let mut values = vec![None; keys.len()];
583
584 for chunk in keys.chunks(MAX_BATCH_SELECT_KEYS) {
585 let placeholders = (1..=chunk.len())
586 .map(|index| format!("?{index}"))
587 .collect::<Vec<_>>()
588 .join(",");
589 let sql =
590 format!("SELECT cid, encoding, node FROM prolly_nodes WHERE cid IN ({placeholders})");
591 let mut stmt = conn.prepare_cached(&sql).map_err(|error| {
592 SqliteStoreError::from_sqlite(error, "Failed to prepare multi-key read")
593 })?;
594 let mut rows = stmt
595 .query(rusqlite::params_from_iter(chunk.iter().copied()))
596 .map_err(|error| {
597 SqliteStoreError::from_sqlite(error, "Failed to execute multi-key read")
598 })?;
599 while let Some(row) = rows.next().map_err(|error| {
600 SqliteStoreError::from_sqlite(error, "Failed to read multi-key result")
601 })? {
602 let key_value = row.get_ref(0).map_err(|error| {
603 SqliteStoreError::from_sqlite(error, "Failed to read multi-key result")
604 })?;
605 let key = key_value.as_blob().map_err(|error| {
606 SqliteStoreError::new(format!("Failed to borrow node key: {error}"))
607 })?;
608 let encoding = row.get::<_, i64>(1).map_err(|error| {
609 SqliteStoreError::from_sqlite(error, "Failed to read node encoding")
610 })?;
611 let node_value = row.get_ref(2).map_err(|error| {
612 SqliteStoreError::from_sqlite(error, "Failed to borrow node bytes")
613 })?;
614 let node = node_value.as_blob().map_err(|error| {
615 SqliteStoreError::new(format!("Failed to borrow node bytes: {error}"))
616 })?;
617 let index = positions.get(key).copied().ok_or_else(|| {
618 SqliteStoreError::new("multi-key read returned an unrequested key")
619 })?;
620 values[index] = Some(decode_stored_node_ref(encoding, node)?);
621 }
622 }
623
624 Ok(values)
625}
626
627#[cfg(unix)]
628pub fn sqlite_main_file_identity(
630 conn: &Connection,
631) -> Result<SqliteMainFileIdentity, SqliteStoreError> {
632 use std::ffi::{c_int, c_void};
633 use std::fs::File;
634 use std::os::fd::BorrowedFd;
635 use std::os::unix::fs::MetadataExt;
636
637 #[repr(C)]
641 struct UnixFilePrefix {
642 methods: *const rusqlite::ffi::sqlite3_io_methods,
643 vfs: *mut rusqlite::ffi::sqlite3_vfs,
644 inode: *mut c_void,
645 fd: c_int,
646 }
647
648 let mut sqlite_file: *mut rusqlite::ffi::sqlite3_file = std::ptr::null_mut();
649 let rc = unsafe {
652 rusqlite::ffi::sqlite3_file_control(
653 conn.handle(),
654 c"main".as_ptr(),
655 rusqlite::ffi::SQLITE_FCNTL_FILE_POINTER,
656 (&mut sqlite_file as *mut *mut rusqlite::ffi::sqlite3_file).cast(),
657 )
658 };
659 if rc != rusqlite::ffi::SQLITE_OK || sqlite_file.is_null() {
660 return Err(SqliteStoreError::new(format!(
661 "SQLite did not expose its main-file handle (code {rc})"
662 )));
663 }
664 let fd = unsafe { (*(sqlite_file.cast::<UnixFilePrefix>())).fd };
667 let borrowed = unsafe { BorrowedFd::borrow_raw(fd) };
670 let owned = borrowed.try_clone_to_owned().map_err(|error| {
671 SqliteStoreError::new(format!(
672 "failed to duplicate SQLite main-file handle: {error}"
673 ))
674 })?;
675 let metadata = File::from(owned).metadata().map_err(|error| {
676 SqliteStoreError::new(format!("failed to stat SQLite main-file handle: {error}"))
677 })?;
678 Ok(SqliteMainFileIdentity {
679 device: metadata.dev(),
680 inode: metadata.ino(),
681 length: metadata.len(),
682 })
683}
684
685impl Store for SqliteStore {
686 type Error = SqliteStoreError;
687
688 fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
689 if let Some(value) = self.node_read_cache()?.get(key) {
690 return Ok(Some(value.as_ref().to_vec()));
691 }
692 let conn = self.connection()?;
693 let mut stmt = conn
694 .prepare_cached(SELECT_SQL)
695 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to prepare point read"))?;
696 let mut rows = stmt
697 .query(params![key])
698 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read key"))?;
699 let Some(row) = rows
700 .next()
701 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read key"))?
702 else {
703 return Ok(None);
704 };
705 let encoding = row.get::<_, i64>(0).map_err(|error| {
706 SqliteStoreError::from_sqlite(error, "Failed to read node encoding")
707 })?;
708 let node_value = row
709 .get_ref(1)
710 .map_err(|error| SqliteStoreError::from_sqlite(error, "Failed to borrow node bytes"))?;
711 let node = node_value.as_blob().map_err(|error| {
712 SqliteStoreError::new(format!("Failed to borrow node bytes: {error}"))
713 })?;
714 let decoded: Arc<[u8]> = Arc::from(decode_stored_node_ref(encoding, node)?);
715 self.node_read_cache()?.insert(key, decoded.clone());
716 Ok(Some(decoded.as_ref().to_vec()))
717 }
718
719 fn get_shared(&self, key: &[u8]) -> Result<Option<Arc<[u8]>>, Self::Error> {
720 if let Some(value) = self.node_read_cache()?.get(key) {
721 return Ok(Some(value));
722 }
723 let conn = self.connection()?;
724 let mut stmt = conn.prepare_cached(SELECT_SQL).map_err(|e| {
725 SqliteStoreError::from_sqlite(e, "Failed to prepare shared point read")
726 })?;
727 let mut rows = stmt
728 .query(params![key])
729 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read shared key"))?;
730 let Some(row) = rows
731 .next()
732 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read shared key"))?
733 else {
734 return Ok(None);
735 };
736 let encoding = row.get::<_, i64>(0).map_err(|error| {
737 SqliteStoreError::from_sqlite(error, "Failed to read node encoding")
738 })?;
739 let node_value = row
740 .get_ref(1)
741 .map_err(|error| SqliteStoreError::from_sqlite(error, "Failed to borrow node bytes"))?;
742 let node = node_value.as_blob().map_err(|error| {
743 SqliteStoreError::new(format!("Failed to borrow node bytes: {error}"))
744 })?;
745 let decoded: Arc<[u8]> = Arc::from(decode_stored_node_ref(encoding, node)?);
746 self.node_read_cache()?.insert(key, decoded.clone());
747 Ok(Some(decoded))
748 }
749
750 fn has_native_shared_reads(&self) -> bool {
751 true
752 }
753
754 fn put(&self, key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
755 let conn = self.connection()?;
756 let (encoding, stored) =
757 encode_stored_node(value, self.node_compression_min_bytes);
758 conn.execute(UPSERT_SQL, params![key, encoding, stored])
759 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to write key"))?;
760 self.node_read_cache()?.insert(key, Arc::from(value));
761 Ok(())
762 }
763
764 fn delete(&self, key: &[u8]) -> Result<(), Self::Error> {
765 let conn = self.connection()?;
766 conn.execute(DELETE_SQL, params![key])
767 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to delete key"))?;
768 self.node_read_cache()?.remove(key);
769 Ok(())
770 }
771
772 fn batch(&self, ops: &[BatchOp]) -> Result<(), Self::Error> {
773 let mut conn = self.connection()?;
774 let tx = conn
775 .transaction_with_behavior(TransactionBehavior::Immediate)
776 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to start transaction"))?;
777
778 {
779 let mut upsert = tx
780 .prepare_cached(UPSERT_SQL)
781 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to prepare batch write"))?;
782 let mut delete = tx
783 .prepare_cached(DELETE_SQL)
784 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to prepare batch delete"))?;
785 let mut compression_scratch = Vec::new();
786
787 for op in ops {
788 match op {
789 BatchOp::Upsert { key, value } => {
790 let (encoding, value) = encode_stored_node_into(
791 value,
792 &mut compression_scratch,
793 self.node_compression_min_bytes,
794 );
795 upsert.execute(params![key, encoding, value]).map_err(|e| {
796 SqliteStoreError::from_sqlite(e, "Failed to write key in batch")
797 })?;
798 }
799 BatchOp::Delete { key } => {
800 delete.execute(params![key]).map_err(|e| {
801 SqliteStoreError::from_sqlite(e, "Failed to delete key in batch")
802 })?;
803 }
804 }
805 }
806 }
807
808 tx.commit()
809 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to commit transaction"))?;
810 let mut cache = self.node_read_cache()?;
811 for op in ops {
812 match op {
813 BatchOp::Upsert { key, .. } | BatchOp::Delete { key } => cache.remove(key),
814 }
815 }
816 Ok(())
817 }
818
819 fn batch_get(&self, keys: &[&[u8]]) -> Result<HashMap<Vec<u8>, Vec<u8>>, Self::Error> {
820 let plan = OrderedBatchReadPlan::new(keys);
821 let values = self.batch_get_shared_ordered_unique(plan.unique_keys())?;
822 let mut results = HashMap::with_capacity(plan.unique_keys().len());
823 for (key, value) in plan.unique_keys().iter().zip(values) {
824 if let Some(value) = value {
825 results.insert(key.to_vec(), value.as_ref().to_vec());
826 }
827 }
828
829 Ok(results)
830 }
831
832 fn batch_get_ordered(&self, keys: &[&[u8]]) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
833 let plan = OrderedBatchReadPlan::new(keys);
834 let unique_values = self
835 .batch_get_shared_ordered_unique(plan.unique_keys())?
836 .into_iter()
837 .map(|value| value.map(|value| value.as_ref().to_vec()))
838 .collect();
839 Ok(plan.expand_owned(unique_values))
840 }
841
842 fn batch_get_ordered_unique(
843 &self,
844 keys: &[&[u8]],
845 ) -> Result<Vec<Option<Vec<u8>>>, Self::Error> {
846 self.batch_get_shared_ordered_unique(keys).map(|values| {
847 values
848 .into_iter()
849 .map(|value| value.map(|value| value.as_ref().to_vec()))
850 .collect()
851 })
852 }
853
854 fn batch_get_shared_ordered_unique(
855 &self,
856 keys: &[&[u8]],
857 ) -> Result<Vec<Option<Arc<[u8]>>>, Self::Error> {
858 let mut values = vec![None; keys.len()];
859 let mut missing = Vec::new();
860 {
861 let cache = self.node_read_cache()?;
862 for (position, key) in keys.iter().enumerate() {
863 match cache.get(key) {
864 Some(value) => values[position] = Some(value),
865 None => missing.push((position, *key)),
866 }
867 }
868 }
869 if missing.is_empty() {
870 return Ok(values);
871 }
872 let missing_keys = missing.iter().map(|(_, key)| *key).collect::<Vec<_>>();
873 let conn = self.connection()?;
874 let loaded = select_nodes_ordered_unique(&conn, &missing_keys)?;
875 let mut cache = self.node_read_cache()?;
876 for ((position, key), loaded) in missing.into_iter().zip(loaded) {
877 if let Some(loaded) = loaded {
878 let loaded: Arc<[u8]> = Arc::from(loaded);
879 cache.insert(key, loaded.clone());
880 values[position] = Some(loaded);
881 }
882 }
883 Ok(values)
884 }
885
886 fn prefers_batch_reads(&self) -> bool {
887 true
888 }
889
890 fn batch_put(&self, entries: &[(&[u8], &[u8])]) -> Result<(), Self::Error> {
891 let mut conn = self.connection()?;
892 let tx = conn
893 .transaction_with_behavior(TransactionBehavior::Immediate)
894 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to start transaction"))?;
895
896 {
897 let mut stmt = tx.prepare_cached(UPSERT_SQL).map_err(|e| {
898 SqliteStoreError::from_sqlite(e, "Failed to prepare batch_put write")
899 })?;
900 let mut ordered = entries.iter().collect::<Vec<_>>();
901 ordered.sort_by(|left, right| left.0.cmp(right.0));
902 let mut compression_scratch = Vec::new();
903 for &&(key, value) in &ordered {
904 let (encoding, value) = encode_stored_node_into(
905 value,
906 &mut compression_scratch,
907 self.node_compression_min_bytes,
908 );
909 stmt.execute(params![key, encoding, value]).map_err(|e| {
910 SqliteStoreError::from_sqlite(e, "Failed to write key in batch_put")
911 })?;
912 }
913 }
914
915 tx.commit()
916 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to commit transaction"))?;
917 let mut cache = self.node_read_cache()?;
918 for &(key, _) in entries {
919 cache.remove(key);
920 }
921 Ok(())
922 }
923
924 fn supports_hints(&self) -> bool {
925 true
926 }
927
928 fn get_hint(&self, namespace: &[u8], key: &[u8]) -> Result<Option<Vec<u8>>, Self::Error> {
929 let conn = self.connection()?;
930 conn.query_row(
931 "SELECT value FROM prolly_hints WHERE namespace = ?1 AND key = ?2",
932 params![namespace, key],
933 |row| row.get(0),
934 )
935 .optional()
936 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read hint"))
937 }
938
939 fn put_hint(&self, namespace: &[u8], key: &[u8], value: &[u8]) -> Result<(), Self::Error> {
940 let conn = self.connection()?;
941 conn.execute(
942 "\
943 INSERT INTO prolly_hints (namespace, key, value) \
944 VALUES (?1, ?2, ?3) \
945 ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value",
946 params![namespace, key, value],
947 )
948 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to write hint"))?;
949 Ok(())
950 }
951
952 fn batch_put_with_hint(
953 &self,
954 entries: &[(&[u8], &[u8])],
955 namespace: &[u8],
956 key: &[u8],
957 value: &[u8],
958 ) -> Result<(), Self::Error> {
959 let mut conn = self.connection()?;
960 let tx = conn
961 .transaction_with_behavior(TransactionBehavior::Immediate)
962 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to start transaction"))?;
963
964 {
965 let mut upsert_node = tx.prepare_cached(UPSERT_SQL).map_err(|e| {
966 SqliteStoreError::from_sqlite(e, "Failed to prepare batch_put write")
967 })?;
968 let mut ordered = entries.iter().collect::<Vec<_>>();
969 ordered.sort_by(|left, right| left.0.cmp(right.0));
970 let mut compression_scratch = Vec::new();
971 for &&(key, value) in &ordered {
972 let (encoding, value) = encode_stored_node_into(
973 value,
974 &mut compression_scratch,
975 self.node_compression_min_bytes,
976 );
977 upsert_node
978 .execute(params![key, encoding, value])
979 .map_err(|e| {
980 SqliteStoreError::from_sqlite(e, "Failed to write key in batch_put")
981 })?;
982 }
983 }
984
985 tx.execute(
986 "\
987 INSERT INTO prolly_hints (namespace, key, value) \
988 VALUES (?1, ?2, ?3) \
989 ON CONFLICT(namespace, key) DO UPDATE SET value = excluded.value",
990 params![namespace, key, value],
991 )
992 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to write hint in batch_put"))?;
993
994 tx.commit()
995 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to commit transaction"))?;
996 let mut cache = self.node_read_cache()?;
997 for &(key, _) in entries {
998 cache.remove(key);
999 }
1000 Ok(())
1001 }
1002
1003 fn publish_nodes(&self, publication: NodePublication<'_>) -> Result<(), Self::Error> {
1004 let mut conn = self.connection()?;
1005 let tx = conn
1006 .transaction_with_behavior(TransactionBehavior::Immediate)
1007 .map_err(|error| {
1008 SqliteStoreError::from_sqlite(error, "Failed to start node publication")
1009 })?;
1010 {
1011 let mut insert = tx.prepare_cached(INSERT_IMMUTABLE_SQL).map_err(|error| {
1015 SqliteStoreError::from_sqlite(error, "Failed to prepare node publication")
1016 })?;
1017 let mut ordered = publication.entries().iter().collect::<Vec<_>>();
1018 ordered.sort_by(|left, right| left.0.cmp(right.0));
1019 let mut compression_scratch = Vec::new();
1020 for &&(key, value) in &ordered {
1021 let (encoding, value) = encode_stored_node_into(
1022 value,
1023 &mut compression_scratch,
1024 self.node_compression_min_bytes,
1025 );
1026 insert
1027 .execute(params![key, encoding, value])
1028 .map_err(|error| {
1029 SqliteStoreError::from_sqlite(error, "Failed to publish immutable node")
1030 })?;
1031 }
1032 }
1033 if let Some(hint) = publication.hint() {
1034 tx.execute(
1035 UPSERT_HINT_SQL,
1036 params![hint.namespace(), hint.key(), hint.value()],
1037 )
1038 .map_err(|error| {
1039 SqliteStoreError::from_sqlite(error, "Failed to write publication hint")
1040 })?;
1041 }
1042 tx.commit().map_err(|error| {
1043 SqliteStoreError::from_sqlite(error, "Failed to commit node publication")
1044 })?;
1045 if !matches!(
1049 publication.origin(),
1050 PublicationOrigin::TreeBuild | PublicationOrigin::Merge
1051 ) {
1052 let mut cache = self.node_read_cache()?;
1053 for &(key, value) in publication.entries() {
1054 cache.insert_immutable(key, Arc::from(value));
1055 }
1056 }
1057 Ok(())
1058 }
1059}
1060
1061impl NodeStoreScan for SqliteStore {
1062 type Error = SqliteStoreError;
1063
1064 fn list_node_cids(&self) -> Result<Vec<Cid>, Self::Error> {
1065 let conn = self.connection()?;
1066 let mut stmt = conn
1067 .prepare_cached(SELECT_NODE_CIDS_SQL)
1068 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to prepare node CID listing"))?;
1069 let rows = stmt
1070 .query_map([], |row| row.get::<_, Vec<u8>>(0))
1071 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to list node CIDs"))?;
1072
1073 let mut cids = Vec::new();
1074 for row in rows {
1075 let key = row
1076 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read listed node CID"))?;
1077 cids.push(cid_from_store_key(&key, "SQLite node").map_err(SqliteStoreError::new)?);
1078 }
1079 sort_cids(&mut cids);
1080 Ok(cids)
1081 }
1082}
1083
1084impl ManifestStore for SqliteStore {
1085 type Error = SqliteStoreError;
1086
1087 fn get_root(&self, name: &[u8]) -> Result<Option<RootManifest>, Self::Error> {
1088 let conn = self.connection()?;
1089 let bytes = conn
1090 .query_row(SELECT_ROOT_SQL, params![name], |row| row.get(0))
1091 .optional()
1092 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read root manifest"))?;
1093 decode_root_manifest(bytes)
1094 }
1095
1096 fn put_root(&self, name: &[u8], manifest: &RootManifest) -> Result<(), Self::Error> {
1097 let conn = self.connection()?;
1098 let bytes = encode_root_manifest(manifest)?;
1099 conn.execute(UPSERT_ROOT_SQL, params![name, bytes])
1100 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to write root manifest"))?;
1101 Ok(())
1102 }
1103
1104 fn delete_root(&self, name: &[u8]) -> Result<(), Self::Error> {
1105 let conn = self.connection()?;
1106 conn.execute(DELETE_ROOT_SQL, params![name])
1107 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to delete root manifest"))?;
1108 Ok(())
1109 }
1110
1111 fn compare_and_swap_root(
1112 &self,
1113 name: &[u8],
1114 expected: Option<&RootManifest>,
1115 new: Option<&RootManifest>,
1116 ) -> Result<ManifestUpdate, Self::Error> {
1117 let expected_bytes = expected.map(encode_root_manifest).transpose()?;
1118 let new_bytes = new.map(encode_root_manifest).transpose()?;
1119
1120 let mut conn = self.connection()?;
1121 let tx = conn
1122 .transaction_with_behavior(TransactionBehavior::Immediate)
1123 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to start root transaction"))?;
1124
1125 let current_bytes = tx
1126 .query_row(SELECT_ROOT_SQL, params![name], |row| row.get(0))
1127 .optional()
1128 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to read root manifest"))?;
1129
1130 if current_bytes.as_deref() != expected_bytes.as_deref() {
1131 return Ok(ManifestUpdate::Conflict {
1132 current: decode_root_manifest(current_bytes)?,
1133 });
1134 }
1135
1136 match new_bytes {
1137 Some(bytes) => {
1138 tx.execute(UPSERT_ROOT_SQL, params![name, bytes])
1139 .map_err(|e| {
1140 SqliteStoreError::from_sqlite(e, "Failed to write root manifest")
1141 })?;
1142 }
1143 None => {
1144 tx.execute(DELETE_ROOT_SQL, params![name]).map_err(|e| {
1145 SqliteStoreError::from_sqlite(e, "Failed to delete root manifest")
1146 })?;
1147 }
1148 }
1149
1150 tx.commit()
1151 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to commit root transaction"))?;
1152 Ok(ManifestUpdate::Applied)
1153 }
1154}
1155
1156impl ManifestStoreScan for SqliteStore {
1157 fn list_roots(&self) -> Result<Vec<NamedRootManifest>, Self::Error> {
1158 let conn = self.connection()?;
1159 let mut stmt = conn.prepare_cached(SELECT_ROOTS_SQL).map_err(|e| {
1160 SqliteStoreError::from_sqlite(e, "Failed to prepare root manifest listing")
1161 })?;
1162 let rows = stmt
1163 .query_map([], |row| {
1164 Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?))
1165 })
1166 .map_err(|e| SqliteStoreError::from_sqlite(e, "Failed to list root manifests"))?;
1167
1168 let mut roots = Vec::new();
1169 for row in rows {
1170 let (name, bytes) = row.map_err(|e| {
1171 SqliteStoreError::from_sqlite(e, "Failed to read listed root manifest")
1172 })?;
1173 let manifest = RootManifest::from_bytes(&bytes)
1174 .map_err(|err| SqliteStoreError::new(err.to_string()))?;
1175 roots.push(NamedRootManifest::new(name, manifest));
1176 }
1177 sort_named_root_manifests(&mut roots);
1178 Ok(roots)
1179 }
1180}
1181
1182impl TransactionalStore for SqliteStore {
1183 fn supports_transactions(&self) -> bool {
1184 true
1185 }
1186
1187 fn commit_transaction(
1188 &self,
1189 node_writes: &[TransactionNodeWrite],
1190 root_conditions: &[RootCondition],
1191 root_writes: &[RootWrite],
1192 ) -> Result<TransactionUpdate, Error> {
1193 let mut conn = self
1194 .connection()
1195 .map_err(|err| Error::Store(Box::new(err)))?;
1196 let tx = conn
1197 .transaction_with_behavior(TransactionBehavior::Immediate)
1198 .map_err(|err| {
1199 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1200 err,
1201 "Failed to start transaction commit",
1202 )))
1203 })?;
1204
1205 for condition in root_conditions {
1206 let current_bytes = tx
1207 .query_row(SELECT_ROOT_SQL, params![condition.name], |row| row.get(0))
1208 .optional()
1209 .map_err(|err| {
1210 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1211 err,
1212 "Failed to read root manifest during transaction commit",
1213 )))
1214 })?;
1215 let current =
1216 decode_root_manifest(current_bytes).map_err(|err| Error::Store(Box::new(err)))?;
1217 if current != condition.expected {
1218 return Ok(TransactionUpdate::Conflict(Box::new(
1219 TransactionConflict::new(
1220 condition.name.clone(),
1221 condition.expected.clone(),
1222 current,
1223 ),
1224 )));
1225 }
1226 }
1227
1228 {
1229 let mut upsert_node = tx.prepare_cached(UPSERT_SQL).map_err(|err| {
1230 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1231 err,
1232 "Failed to prepare transaction node write",
1233 )))
1234 })?;
1235 let mut delete_node = tx.prepare_cached(DELETE_SQL).map_err(|err| {
1236 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1237 err,
1238 "Failed to prepare transaction node delete",
1239 )))
1240 })?;
1241 for write in node_writes {
1242 match write {
1243 TransactionNodeWrite::Upsert { key, value } => {
1244 let (encoding, value) =
1245 encode_stored_node(value, self.node_compression_min_bytes);
1246 upsert_node
1247 .execute(params![key, encoding, value])
1248 .map_err(|err| {
1249 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1250 err,
1251 "Failed to write node during transaction commit",
1252 )))
1253 })?;
1254 }
1255 TransactionNodeWrite::Delete { key } => {
1256 delete_node.execute(params![key]).map_err(|err| {
1257 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1258 err,
1259 "Failed to delete node during transaction commit",
1260 )))
1261 })?;
1262 }
1263 }
1264 }
1265 }
1266
1267 {
1268 let mut upsert_root = tx.prepare_cached(UPSERT_ROOT_SQL).map_err(|err| {
1269 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1270 err,
1271 "Failed to prepare transaction root write",
1272 )))
1273 })?;
1274 let mut delete_root = tx.prepare_cached(DELETE_ROOT_SQL).map_err(|err| {
1275 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1276 err,
1277 "Failed to prepare transaction root delete",
1278 )))
1279 })?;
1280 for write in root_writes {
1281 match write {
1282 RootWrite::Put { name, manifest } => {
1283 let bytes = encode_root_manifest(manifest)
1284 .map_err(|err| Error::Store(Box::new(err)))?;
1285 upsert_root.execute(params![name, bytes]).map_err(|err| {
1286 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1287 err,
1288 "Failed to write root during transaction commit",
1289 )))
1290 })?;
1291 }
1292 RootWrite::Delete { name } => {
1293 delete_root.execute(params![name]).map_err(|err| {
1294 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1295 err,
1296 "Failed to delete root during transaction commit",
1297 )))
1298 })?;
1299 }
1300 }
1301 }
1302 }
1303
1304 tx.commit().map_err(|err| {
1305 Error::Store(Box::new(SqliteStoreError::from_sqlite(
1306 err,
1307 "Failed to commit transaction",
1308 )))
1309 })?;
1310 let mut cache = self
1311 .node_read_cache()
1312 .map_err(|err| Error::Store(Box::new(err)))?;
1313 for write in node_writes {
1314 match write {
1315 TransactionNodeWrite::Upsert { key, .. } | TransactionNodeWrite::Delete { key } => {
1316 cache.remove(key)
1317 }
1318 }
1319 }
1320 Ok(TransactionUpdate::Applied {
1321 nodes_written: node_writes.len(),
1322 roots_written: root_writes.len(),
1323 })
1324 }
1325}
1326
1327fn encode_root_manifest(manifest: &RootManifest) -> Result<Vec<u8>, SqliteStoreError> {
1328 manifest
1329 .to_bytes()
1330 .map_err(|e| SqliteStoreError::new(format!("failed to encode root manifest: {e}")))
1331}
1332
1333fn decode_root_manifest(bytes: Option<Vec<u8>>) -> Result<Option<RootManifest>, SqliteStoreError> {
1334 bytes
1335 .as_deref()
1336 .map(RootManifest::from_bytes)
1337 .transpose()
1338 .map_err(|e| SqliteStoreError::new(format!("failed to decode root manifest: {e}")))
1339}
1340
1341#[cfg(test)]
1342mod tests {
1343 use super::*;
1344
1345 #[test]
1346 fn sqlite_store_put_get_delete() {
1347 let store = SqliteStore::open_in_memory().unwrap();
1348
1349 store.put(b"key", b"value").unwrap();
1350 assert_eq!(store.get(b"key").unwrap(), Some(b"value".to_vec()));
1351
1352 store.delete(b"key").unwrap();
1353 assert_eq!(store.get(b"key").unwrap(), None);
1354 }
1355
1356 #[test]
1357 fn sqlite_store_reuses_shared_node_reads() {
1358 let store = SqliteStore::open_in_memory().unwrap();
1359 store.put(b"key", b"value").unwrap();
1360
1361 let first = store.get_shared(b"key").unwrap().unwrap();
1362 let second = store.get_shared(b"key").unwrap().unwrap();
1363
1364 assert!(Arc::ptr_eq(&first, &second));
1365 }
1366
1367 #[test]
1368 fn sqlite_store_applies_publication_pragmas_and_rowid_schema() {
1369 let store = SqliteStore::open_in_memory().unwrap();
1370 let conn = store.connection().unwrap();
1371 let page_size: u32 = conn
1372 .query_row("PRAGMA page_size", [], |row| row.get(0))
1373 .unwrap();
1374 let cache_size: i64 = conn
1375 .query_row("PRAGMA cache_size", [], |row| row.get(0))
1376 .unwrap();
1377 let wal_autocheckpoint: u32 = conn
1378 .query_row("PRAGMA wal_autocheckpoint", [], |row| row.get(0))
1379 .unwrap();
1380 let schema: String = conn
1381 .query_row(
1382 "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'prolly_nodes'",
1383 [],
1384 |row| row.get(0),
1385 )
1386 .unwrap();
1387
1388 assert_eq!(page_size, 64 * 1024);
1389 assert_eq!(cache_size, -64 * 1024);
1390 assert_eq!(wal_autocheckpoint, 32 * 1024);
1391 assert!(!schema.to_ascii_uppercase().contains("WITHOUT ROWID"));
1392 }
1393
1394 #[test]
1395 fn sqlite_store_rejects_invalid_page_size() {
1396 let config = SqliteStoreConfig {
1397 page_size_bytes: 1_000,
1398 ..SqliteStoreConfig::default()
1399 };
1400 let error = SqliteStore::from_connection(Connection::open_in_memory().unwrap(), config)
1401 .err()
1402 .expect("invalid page size must fail");
1403 assert!(error.to_string().contains("page_size_bytes"));
1404 }
1405
1406 #[test]
1407 fn sqlite_store_batch_is_order_preserving_for_reads() {
1408 let store = SqliteStore::open_in_memory().unwrap();
1409 let ops = vec![
1410 BatchOp::Upsert {
1411 key: b"a",
1412 value: b"1",
1413 },
1414 BatchOp::Upsert {
1415 key: b"b",
1416 value: b"2",
1417 },
1418 BatchOp::Upsert {
1419 key: b"c",
1420 value: b"3",
1421 },
1422 ];
1423
1424 store.batch(&ops).unwrap();
1425
1426 let keys: Vec<&[u8]> = vec![b"c", b"missing", b"a", b"c", b"missing", b"b"];
1427 assert_eq!(
1428 store.batch_get_ordered(&keys).unwrap(),
1429 vec![
1430 Some(b"3".to_vec()),
1431 None,
1432 Some(b"1".to_vec()),
1433 Some(b"3".to_vec()),
1434 None,
1435 Some(b"2".to_vec())
1436 ]
1437 );
1438 }
1439
1440 #[test]
1441 fn sqlite_store_batch_put_updates_existing_keys() {
1442 let store = SqliteStore::open_in_memory().unwrap();
1443
1444 store.put(b"a", b"old").unwrap();
1445 store
1446 .batch_put(&[(b"a".as_slice(), b"new".as_slice()), (b"b", b"2")])
1447 .unwrap();
1448
1449 assert_eq!(store.get(b"a").unwrap(), Some(b"new".to_vec()));
1450 assert_eq!(store.get(b"b").unwrap(), Some(b"2".to_vec()));
1451 }
1452
1453 #[test]
1454 fn sqlite_store_persists_hints_separately_from_nodes() {
1455 let store = SqliteStore::open_in_memory().unwrap();
1456
1457 store.put_hint(b"rightmost", b"root", b"hint-v1").unwrap();
1458 assert_eq!(
1459 store.get_hint(b"rightmost", b"root").unwrap(),
1460 Some(b"hint-v1".to_vec())
1461 );
1462 assert_eq!(store.get_hint(b"rightmost", b"missing").unwrap(), None);
1463 assert_eq!(store.get(b"root").unwrap(), None);
1464
1465 store.put_hint(b"rightmost", b"root", b"hint-v2").unwrap();
1466 assert_eq!(
1467 store.get_hint(b"rightmost", b"root").unwrap(),
1468 Some(b"hint-v2".to_vec())
1469 );
1470 }
1471
1472 #[test]
1473 fn sqlite_store_compresses_repetitive_nodes_transparently() {
1474 let store = SqliteStore::open_in_memory().unwrap();
1475 let node = vec![b'x'; 16 * 1024];
1476
1477 store.put(b"compressed", &node).unwrap();
1478
1479 let conn = store.connection().unwrap();
1480 let (encoding, stored_bytes): (i64, usize) = conn
1481 .query_row(
1482 "SELECT encoding, length(node) FROM prolly_nodes WHERE cid = ?1",
1483 params![b"compressed"],
1484 |row| Ok((row.get(0)?, row.get(1)?)),
1485 )
1486 .unwrap();
1487 drop(conn);
1488 assert_eq!(encoding, NODE_ENCODING_LZ4);
1489 assert!(stored_bytes < node.len());
1490 assert_eq!(store.get(b"compressed").unwrap(), Some(node));
1491 }
1492
1493 #[test]
1494 fn sqlite_store_honors_node_compression_threshold() {
1495 let config = SqliteStoreConfig {
1496 node_compression_min_bytes: 32 * 1024,
1497 ..SqliteStoreConfig::default()
1498 };
1499 let store = SqliteStore::from_connection(Connection::open_in_memory().unwrap(), config)
1500 .unwrap();
1501 let node = vec![b'x'; 16 * 1024];
1502
1503 store.put(b"raw", &node).unwrap();
1504
1505 let conn = store.connection().unwrap();
1506 let encoding: i64 = conn
1507 .query_row(
1508 "SELECT encoding FROM prolly_nodes WHERE cid = ?1",
1509 params![b"raw"],
1510 |row| row.get(0),
1511 )
1512 .unwrap();
1513 assert_eq!(encoding, NODE_ENCODING_RAW);
1514 }
1515
1516 #[test]
1517 fn sqlite_store_migrates_legacy_raw_node_tables() {
1518 let conn = Connection::open_in_memory().unwrap();
1519 conn.execute_batch(
1520 "CREATE TABLE prolly_nodes (
1521 cid BLOB PRIMARY KEY NOT NULL,
1522 node BLOB NOT NULL
1523 ) WITHOUT ROWID;
1524 INSERT INTO prolly_nodes (cid, node) VALUES (x'6c6567616379', x'726177');",
1525 )
1526 .unwrap();
1527
1528 let store = SqliteStore::from_connection(conn, SqliteStoreConfig::default()).unwrap();
1529
1530 assert_eq!(store.get(b"legacy").unwrap(), Some(b"raw".to_vec()));
1531 let conn = store.connection().unwrap();
1532 let encoding: i64 = conn
1533 .query_row(
1534 "SELECT encoding FROM prolly_nodes WHERE cid = ?1",
1535 params![b"legacy"],
1536 |row| row.get(0),
1537 )
1538 .unwrap();
1539 assert_eq!(encoding, NODE_ENCODING_RAW);
1540 }
1541}