pub(crate) const ROW_PREFIX: &str = "vr:";
pub(crate) const LEGACY_ROW_PREFIX: &str = "v:";
const LEN_DIGITS: usize = 8;
pub(crate) fn index_prefix(index_key: &str) -> Vec<u8> {
format!("{ROW_PREFIX}{:08x}{index_key}", index_key.len()).into_bytes()
}
pub(crate) fn key(index_key: &str, doc_id: &str) -> Vec<u8> {
let mut k = index_prefix(index_key);
k.extend_from_slice(doc_id.as_bytes());
k
}
pub(crate) fn parse_key(row_key: &[u8]) -> Option<(&str, &str)> {
let rest = row_key.strip_prefix(ROW_PREFIX.as_bytes())?;
let len_field = std::str::from_utf8(rest.get(..LEN_DIGITS)?).ok()?;
let len = usize::from_str_radix(len_field, 16).ok()?;
let body = rest.get(LEN_DIGITS..)?;
let index_key = std::str::from_utf8(body.get(..len)?).ok()?;
let doc_id = std::str::from_utf8(body.get(len..)?).ok()?;
Some((index_key, doc_id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keys_round_trip_with_separators_in_ids() {
let k = key("chat:emb", "a:b");
assert_eq!(parse_key(&k), Some(("chat:emb", "a:b")));
}
#[test]
fn base_and_named_prefixes_do_not_alias() {
let base = index_prefix("chat");
assert!(!key("chat:emb", "b").starts_with(&base));
assert!(!key("chat2", "b").starts_with(&base));
assert!(key("chat", "emb:b").starts_with(&base));
}
#[test]
fn prefix_is_disjoint_from_other_vector_keys() {
let k = key("entries", "abc");
assert!(!k.starts_with(b"hnsw:"));
assert!(!k.starts_with(LEGACY_ROW_PREFIX.as_bytes()));
assert_ne!(k.as_slice(), b"hnsw_id_map");
assert_eq!(parse_key(b"v:entries:abc"), None);
}
}