nodedb_lite/nodedb/core/
snapshot.rs1use std::path::Path;
6use std::sync::Arc;
7
8use nodedb_types::error::{NodeDbError, NodeDbResult};
9
10use crate::config::LiteConfig;
11use crate::storage::encryption::Encryption;
12use crate::storage::pagedb_storage::PagedbStorageDefault;
13
14use super::types::NodeDbLite;
15
16impl NodeDbLite<PagedbStorageDefault> {
17 pub async fn snapshot_to(&self, path: impl AsRef<Path>) -> NodeDbResult<()> {
23 self.flush().await?;
24 self.storage
25 .export_snapshot(path.as_ref())
26 .await
27 .map_err(NodeDbError::from)
28 }
29
30 pub async fn restore_from(
36 source: impl AsRef<Path>,
37 destination: impl AsRef<Path>,
38 encryption: Encryption,
39 mut config: LiteConfig,
40 ) -> NodeDbResult<Arc<Self>> {
41 config.corruption_policy = crate::storage::corruption::CorruptionPolicy::FailClosed;
42 let destination = destination.as_ref();
43 let storage =
44 PagedbStorageDefault::restore_snapshot(source.as_ref(), destination, encryption)
45 .await
46 .map_err(NodeDbError::from)?;
47 Self::open_with_config(storage, config)
48 .await
49 .map_err(|error| {
50 NodeDbError::storage(format!(
51 "snapshot writable fork remains at {} but Lite reopen returned: {error}",
52 destination.display()
53 ))
54 })
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use nodedb_client::NodeDb;
62
63 #[tokio::test]
64 async fn incomplete_vector_state_stops_snapshot_before_destination_creation() {
65 let dir = tempfile::tempdir().unwrap();
66 let db = NodeDbLite::open_at_path_with_config(
67 dir.path().join("source"),
68 Encryption::Plaintext,
69 LiteConfig {
70 auto_flush_ms: 0,
71 sync_enabled: false,
72 ..LiteConfig::default()
73 },
74 )
75 .await
76 .unwrap();
77 db.vector_insert("vectors", "v1", &[1.0, 0.0], None)
78 .await
79 .unwrap();
80 crate::engine::vector::durable::remove(&*db.storage, "vectors", "v1")
81 .await
82 .unwrap();
83 let destination = dir.path().join("snapshot");
84 assert!(db.snapshot_to(&destination).await.is_err());
85 assert!(!destination.exists());
86 }
87}