use std::num::NonZeroUsize;
use haematite::tree::Hash;
use serde::{Deserialize, Serialize};
use super::Failure;
use super::vectors::{decode, parse_dimension};
use crate::serve::protocol::{Metric, Version};
pub const COMPANION_PREFIX: &str = "@haem/embed/";
pub const FORMAT: u8 = 1;
pub const TAG_BYTES: usize = 16;
pub const HEADER_BYTES: usize = 1 + 8 + 8 + TAG_BYTES;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ModelName {
pub name: String,
pub revision: String,
pub manifest_sha256: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VectorKind {
Dense,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Declaration {
pub kind: VectorKind,
pub source: String,
pub model: ModelName,
pub dimensions: NonZeroUsize,
pub distance: Metric,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Field {
Supplied { dimensions: usize },
Embedded(Declaration),
}
pub fn read_field(bytes: &[u8]) -> Result<Field, Failure> {
if bytes.first().is_some_and(u8::is_ascii_digit) {
return parse_dimension(bytes).map(|dimensions| Field::Supplied { dimensions });
}
serde_json::from_slice(bytes)
.map(Field::Embedded)
.map_err(|error| Failure::new("vector_declaration_corrupt", error.to_string()))
}
pub fn encode_declaration(declaration: &Declaration) -> Result<Vec<u8>, Failure> {
serde_json::to_vec(declaration).map_err(Failure::storage)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModelTag(pub [u8; TAG_BYTES]);
pub fn model_tag(model: &ModelName) -> Result<ModelTag, Failure> {
let named = serde_json::to_vec(model).map_err(Failure::storage)?;
let hash = Hash::of(&named);
let tag = hash
.as_bytes()
.first_chunk::<TAG_BYTES>()
.ok_or_else(|| Failure::new("embed_tag_short", "the store's hash is under 16 bytes"))?;
Ok(ModelTag(*tag))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Provenance {
pub made_from: Version,
pub tag: ModelTag,
}
#[must_use]
pub fn encode_embedded(provenance: &Provenance, vector: &[f32]) -> Vec<u8> {
let capacity = vector.len().saturating_mul(4).saturating_add(HEADER_BYTES);
let mut bytes = Vec::with_capacity(capacity);
bytes.push(FORMAT);
bytes.extend_from_slice(&provenance.made_from.generation.to_be_bytes());
bytes.extend_from_slice(&provenance.made_from.sequence.to_be_bytes());
bytes.extend_from_slice(&provenance.tag.0);
for member in vector {
bytes.extend_from_slice(&member.to_le_bytes());
}
bytes
}
pub fn decode_embedded(bytes: &[u8], dimensions: usize) -> Result<(Provenance, Vec<f32>), Failure> {
let short = || {
Failure::new(
"vector_corrupt",
"an embedded vector is shorter than its header",
)
};
let (&format, rest) = bytes.split_first().ok_or_else(short)?;
if format != FORMAT {
return Err(Failure::new(
"vector_corrupt",
format!("an embedded vector has format {format}, not {FORMAT}"),
));
}
let (generation, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
let (sequence, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
let (tag, members) = rest.split_first_chunk::<TAG_BYTES>().ok_or_else(short)?;
let provenance = Provenance {
made_from: Version {
generation: u64::from_be_bytes(*generation),
sequence: u64::from_be_bytes(*sequence),
},
tag: ModelTag(*tag),
};
Ok((provenance, decode(members, dimensions)?))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Position {
pub branch: String,
pub through: Option<Version>,
pub toward: Option<Toward>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Toward {
pub version: Version,
pub after: String,
}
pub fn read_position(bytes: &[u8]) -> Result<Position, Failure> {
serde_json::from_slice(bytes)
.map_err(|error| Failure::new("embed_position_corrupt", error.to_string()))
}
pub fn encode_position(position: &Position) -> Result<Vec<u8>, Failure> {
serde_json::to_vec(position).map_err(Failure::storage)
}
#[must_use]
pub fn companion_name(branch: &str) -> String {
format!("{COMPANION_PREFIX}{branch}")
}
#[must_use]
pub fn position_key(collection: &str, field: &str) -> Vec<u8> {
format!("e\0{collection}\0{field}").into_bytes()
}
#[must_use]
pub fn vector_key(collection: &str, field: &str, id: &str) -> Vec<u8> {
format!("v\0{collection}\0{field}\0{id}").into_bytes()
}
#[must_use]
pub fn rewalk_key(collection: &str, field: &str) -> Vec<u8> {
format!("er\0{collection}\0{field}").into_bytes()
}
pub const WINDOWS_MOST: u32 = 1024;
#[must_use]
pub fn window_prefix(collection: &str, field: &str) -> Vec<u8> {
format!("vw\0{collection}\0{field}\0").into_bytes()
}
#[must_use]
pub fn window_key(collection: &str, field: &str, id: &str, index: u32) -> Vec<u8> {
let mut key = window_prefix(collection, field);
key.extend_from_slice(id.as_bytes());
key.push(0);
key.extend_from_slice(&index.to_be_bytes());
key
}
pub fn window_of(prefix: &[u8], key: &[u8]) -> Result<(String, u32), Failure> {
let corrupt = || Failure::new("vector_corrupt", "a window key names no record and index");
let rest = key.strip_prefix(prefix).ok_or_else(corrupt)?;
let (named, index) = rest.split_last_chunk::<4>().ok_or_else(corrupt)?;
let id = named.strip_suffix(&[0]).ok_or_else(corrupt)?;
let id = String::from_utf8(id.to_vec()).map_err(|_not_utf8| corrupt())?;
Ok((id, u32::from_be_bytes(*index)))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub count: u32,
pub start: u64,
pub end: u64,
}
const SPAN_BYTES: usize = 20;
#[must_use]
pub fn encode_window(span: Span, provenance: &Provenance, vector: &[f32]) -> Vec<u8> {
let embedded = encode_embedded(provenance, vector);
let mut bytes = Vec::with_capacity(SPAN_BYTES.saturating_add(embedded.len()));
bytes.extend_from_slice(&span.count.to_be_bytes());
bytes.extend_from_slice(&span.start.to_be_bytes());
bytes.extend_from_slice(&span.end.to_be_bytes());
bytes.extend_from_slice(&embedded);
bytes
}
pub fn decode_window(
bytes: &[u8],
dimensions: usize,
) -> Result<(Span, Provenance, Vec<f32>), Failure> {
let (span, rest) = split_span(bytes)?;
let (provenance, vector) = decode_embedded(rest, dimensions)?;
Ok((span, provenance, vector))
}
pub fn window_span(bytes: &[u8]) -> Result<Span, Failure> {
split_span(bytes).map(|(span, _)| span)
}
fn split_span(bytes: &[u8]) -> Result<(Span, &[u8]), Failure> {
let short = || {
Failure::new(
"vector_corrupt",
"an embedded window is shorter than its span",
)
};
let (count, rest) = bytes.split_first_chunk::<4>().ok_or_else(short)?;
let (start, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
let (end, rest) = rest.split_first_chunk::<8>().ok_or_else(short)?;
let span = Span {
count: u32::from_be_bytes(*count),
start: u64::from_be_bytes(*start),
end: u64::from_be_bytes(*end),
};
if span.count > WINDOWS_MOST || span.end <= span.start {
return Err(Failure::new(
"vector_corrupt",
"an embedded window names no span or too many windows",
));
}
Ok((span, rest))
}
#[cfg(test)]
#[path = "embedded_tests.rs"]
mod tests;