haematite_cli/serve/storage/
embedded.rs1use std::num::NonZeroUsize;
11
12use haematite::tree::Hash;
13use serde::{Deserialize, Serialize};
14
15use super::Failure;
16use super::vectors::{decode, parse_dimension};
17use crate::serve::protocol::{Metric, Version};
18
19pub const COMPANION_PREFIX: &str = "@haem/embed/";
21pub const FORMAT: u8 = 1;
23pub const TAG_BYTES: usize = 16;
24pub const HEADER_BYTES: usize = 1 + 8 + 8 + TAG_BYTES;
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(deny_unknown_fields)]
29pub struct ModelName {
30 pub name: String,
31 pub revision: String,
32 pub manifest_sha256: String,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39pub enum VectorKind {
40 Dense,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(deny_unknown_fields)]
47pub struct Declaration {
48 pub kind: VectorKind,
49 pub source: String,
50 pub model: ModelName,
51 pub dimensions: NonZeroUsize,
52 pub distance: Metric,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum Field {
58 Supplied { dimensions: usize },
61 Embedded(Declaration),
63}
64
65pub fn read_field(bytes: &[u8]) -> Result<Field, Failure> {
74 if bytes.first().is_some_and(u8::is_ascii_digit) {
75 return parse_dimension(bytes).map(|dimensions| Field::Supplied { dimensions });
76 }
77 serde_json::from_slice(bytes)
78 .map(Field::Embedded)
79 .map_err(|error| Failure::new("vector_declaration_corrupt", error.to_string()))
80}
81
82pub fn encode_declaration(declaration: &Declaration) -> Result<Vec<u8>, Failure> {
87 serde_json::to_vec(declaration).map_err(Failure::storage)
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93pub struct ModelTag(pub [u8; TAG_BYTES]);
94
95pub fn model_tag(model: &ModelName) -> Result<ModelTag, Failure> {
100 let named = serde_json::to_vec(model).map_err(Failure::storage)?;
101 let hash = Hash::of(&named);
102 let tag = hash
103 .as_bytes()
104 .first_chunk::<TAG_BYTES>()
105 .ok_or_else(|| Failure::new("embed_tag_short", "the store's hash is under 16 bytes"))?;
106 Ok(ModelTag(*tag))
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub struct Provenance {
112 pub made_from: Version,
114 pub tag: ModelTag,
115}
116
117#[must_use]
120pub fn encode_embedded(provenance: &Provenance, vector: &[f32]) -> Vec<u8> {
121 let capacity = vector.len().saturating_mul(4).saturating_add(HEADER_BYTES);
122 let mut bytes = Vec::with_capacity(capacity);
123 bytes.push(FORMAT);
124 bytes.extend_from_slice(&provenance.made_from.generation.to_be_bytes());
125 bytes.extend_from_slice(&provenance.made_from.sequence.to_be_bytes());
126 bytes.extend_from_slice(&provenance.tag.0);
127 for member in vector {
128 bytes.extend_from_slice(&member.to_le_bytes());
129 }
130 bytes
131}
132
133pub fn decode_embedded(bytes: &[u8], dimensions: usize) -> Result<(Provenance, Vec<f32>), Failure> {
139 let short = || {
140 Failure::new(
141 "vector_corrupt",
142 "an embedded vector is shorter than its header",
143 )
144 };
145 let (&format, rest) = bytes.split_first().ok_or_else(short)?;
146 if format != FORMAT {
147 return Err(Failure::new(
148 "vector_corrupt",
149 format!("an embedded vector has format {format}, not {FORMAT}"),
150 ));
151 }
152 let (generation, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
153 let (sequence, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
154 let (tag, members) = rest.split_first_chunk::<TAG_BYTES>().ok_or_else(short)?;
155 let provenance = Provenance {
156 made_from: Version {
157 generation: u64::from_be_bytes(*generation),
158 sequence: u64::from_be_bytes(*sequence),
159 },
160 tag: ModelTag(*tag),
161 };
162 Ok((provenance, decode(members, dimensions)?))
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
169#[serde(deny_unknown_fields)]
170pub struct Position {
171 pub branch: String,
172 pub through: Option<Version>,
176 pub toward: Option<Toward>,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(deny_unknown_fields)]
184pub struct Toward {
185 pub version: Version,
186 pub after: String,
189}
190
191pub fn read_position(bytes: &[u8]) -> Result<Position, Failure> {
196 serde_json::from_slice(bytes)
197 .map_err(|error| Failure::new("embed_position_corrupt", error.to_string()))
198}
199
200pub fn encode_position(position: &Position) -> Result<Vec<u8>, Failure> {
205 serde_json::to_vec(position).map_err(Failure::storage)
206}
207
208#[must_use]
210pub fn companion_name(branch: &str) -> String {
211 format!("{COMPANION_PREFIX}{branch}")
212}
213
214#[must_use]
216pub fn position_key(collection: &str, field: &str) -> Vec<u8> {
217 format!("e\0{collection}\0{field}").into_bytes()
218}
219
220#[must_use]
223pub fn vector_key(collection: &str, field: &str, id: &str) -> Vec<u8> {
224 format!("v\0{collection}\0{field}\0{id}").into_bytes()
225}
226
227#[must_use]
232pub fn rewalk_key(collection: &str, field: &str) -> Vec<u8> {
233 format!("er\0{collection}\0{field}").into_bytes()
234}
235
236pub const WINDOWS_MOST: u32 = 1024;
239
240#[must_use]
242pub fn window_prefix(collection: &str, field: &str) -> Vec<u8> {
243 format!("vw\0{collection}\0{field}\0").into_bytes()
244}
245
246#[must_use]
252pub fn window_key(collection: &str, field: &str, id: &str, index: u32) -> Vec<u8> {
253 let mut key = window_prefix(collection, field);
254 key.extend_from_slice(id.as_bytes());
255 key.push(0);
256 key.extend_from_slice(&index.to_be_bytes());
257 key
258}
259
260pub fn window_of(prefix: &[u8], key: &[u8]) -> Result<(String, u32), Failure> {
265 let corrupt = || Failure::new("vector_corrupt", "a window key names no record and index");
266 let rest = key.strip_prefix(prefix).ok_or_else(corrupt)?;
267 let (named, index) = rest.split_last_chunk::<4>().ok_or_else(corrupt)?;
268 let id = named.strip_suffix(&[0]).ok_or_else(corrupt)?;
269 let id = String::from_utf8(id.to_vec()).map_err(|_not_utf8| corrupt())?;
270 Ok((id, u32::from_be_bytes(*index)))
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub struct Span {
277 pub count: u32,
278 pub start: u64,
279 pub end: u64,
280}
281
282const SPAN_BYTES: usize = 20;
283
284#[must_use]
287pub fn encode_window(span: Span, provenance: &Provenance, vector: &[f32]) -> Vec<u8> {
288 let embedded = encode_embedded(provenance, vector);
289 let mut bytes = Vec::with_capacity(SPAN_BYTES.saturating_add(embedded.len()));
290 bytes.extend_from_slice(&span.count.to_be_bytes());
291 bytes.extend_from_slice(&span.start.to_be_bytes());
292 bytes.extend_from_slice(&span.end.to_be_bytes());
293 bytes.extend_from_slice(&embedded);
294 bytes
295}
296
297pub fn decode_window(
303 bytes: &[u8],
304 dimensions: usize,
305) -> Result<(Span, Provenance, Vec<f32>), Failure> {
306 let (span, rest) = split_span(bytes)?;
307 let (provenance, vector) = decode_embedded(rest, dimensions)?;
308 Ok((span, provenance, vector))
309}
310
311pub fn window_span(bytes: &[u8]) -> Result<Span, Failure> {
316 split_span(bytes).map(|(span, _)| span)
317}
318
319fn split_span(bytes: &[u8]) -> Result<(Span, &[u8]), Failure> {
320 let short = || {
321 Failure::new(
322 "vector_corrupt",
323 "an embedded window is shorter than its span",
324 )
325 };
326 let (count, rest) = bytes.split_first_chunk::<4>().ok_or_else(short)?;
327 let (start, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
328 let (end, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
329 let span = Span {
330 count: u32::from_be_bytes(*count),
331 start: u64::from_be_bytes(*start),
332 end: u64::from_be_bytes(*end),
333 };
334 if span.count > WINDOWS_MOST || span.end <= span.start {
335 return Err(Failure::new(
336 "vector_corrupt",
337 "an embedded window names no span or too many windows",
338 ));
339 }
340 Ok((span, rest))
341}
342
343#[cfg(test)]
344#[path = "embedded_tests.rs"]
345mod tests;