use anyhow::Result;
use chrono::{DateTime, TimeZone, Utc};
pub(crate) fn ms_to_utc(ms: i64) -> rusqlite::Result<DateTime<Utc>> {
Utc.timestamp_millis_opt(ms).single().ok_or_else(|| {
rusqlite::Error::FromSqlConversionFailure(
0,
rusqlite::types::Type::Integer,
format!("invalid timestamp ms {ms}").into(),
)
})
}
pub(crate) fn pack_embedding_blob(embedding: &[f32]) -> Vec<u8> {
embedding.iter().flat_map(|f| f.to_le_bytes()).collect()
}
pub(crate) fn decode_signature_blob(
blob: Option<Vec<u8>>,
dim: i64,
label: &str,
) -> Result<Option<Vec<f32>>> {
let Some(bytes) = blob else {
return Ok(None);
};
if dim < 0 {
anyhow::bail!("{label} has negative dimension {dim}");
}
if !bytes.len().is_multiple_of(4) {
anyhow::bail!("{label} blob length {} not a multiple of 4", bytes.len());
}
let floats: Vec<f32> = bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
if floats.len() != dim as usize {
anyhow::bail!(
"embedding dimension mismatch: dim column says {dim}, blob contains {} floats",
floats.len()
);
}
Ok(Some(floats))
}