1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use super::decoder::decode_snapshot;
use super::writer::{write_snapshot, write_snapshot_stream};
use super::{FORMAT_VERSION, HEADER_LEN, Header, MappedVectorIndex, SnapshotMetadata};
use crate::error::SearchError;
use crate::hnsw::VectorIndex;
use std::io::{Cursor, Read, Seek, Write};
use std::path::Path;
impl VectorIndex {
/// Persists normalized vectors, routing data, and every HNSW replica.
///
/// The snapshot is written through a temporary sibling and flushed before
/// replacement.
///
/// # Errors
///
/// Returns a typed storage, allocation, or capacity error.
pub fn save(&self, path: impl AsRef<Path>) -> Result<(), SearchError> {
write_snapshot(self, path.as_ref())
}
/// Loads a persisted index into owned memory without rebuilding HNSW.
///
/// # Errors
///
/// Returns a typed storage, version, integrity, allocation, or config
/// error.
pub fn load(path: impl AsRef<Path>) -> Result<Self, SearchError> {
MappedVectorIndex::open(path)?.to_owned()
}
/// Serializes the canonical snapshot into an owned byte buffer.
///
/// # Errors
///
/// Returns a typed storage, allocation, or capacity error.
pub fn to_bytes(&self) -> Result<Vec<u8>, SearchError> {
let mut cursor = Cursor::new(Vec::new());
write_snapshot_stream(self, &mut cursor)?;
Ok(cursor.into_inner())
}
/// Restores an owned index directly from a canonical snapshot buffer.
///
/// # Errors
///
/// Returns a typed version, integrity, allocation, or config error.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SearchError> {
decode_snapshot(bytes)
}
/// Writes a canonical snapshot to a seekable stream.
///
/// The caller should provide an empty or truncated stream.
///
/// # Errors
///
/// Returns a typed stream, allocation, or capacity error.
pub fn write_to<W: Write + Seek>(&self, writer: &mut W) -> Result<(), SearchError> {
write_snapshot_stream(self, writer)
}
/// Reads a complete canonical snapshot from a stream.
///
/// # Errors
///
/// Returns a typed stream, version, integrity, allocation, or config
/// error.
pub fn read_from<R: Read>(reader: &mut R) -> Result<Self, SearchError> {
let mut bytes = Vec::new();
reader
.read_to_end(&mut bytes)
.map_err(|error| SearchError::storage("read snapshot stream", &error))?;
Self::from_bytes(&bytes)
}
/// Reads snapshot metadata without decoding vectors or graphs.
///
/// # Errors
///
/// Returns a typed storage, version, or header error.
pub fn read_metadata(path: impl AsRef<Path>) -> Result<SnapshotMetadata, SearchError> {
let mut file = std::fs::File::open(path)
.map_err(|error| SearchError::storage("open snapshot metadata", &error))?;
let mut header = [0_u8; HEADER_LEN];
file.read_exact(&mut header)
.map_err(|error| SearchError::storage("read snapshot metadata", &error))?;
SnapshotMetadata::from_bytes(&header)
}
}
impl SnapshotMetadata {
/// Parses the fixed snapshot header from a full or header-only buffer.
///
/// # Errors
///
/// Returns a typed version or structural error.
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SearchError> {
let header = Header::parse(bytes)?;
Ok(Self {
format_version: FORMAT_VERSION,
vector_count: header.count,
serialized_bytes: header.file_len,
config: header.config,
})
}
}