use nodedb_types::Surrogate;
use super::lifecycle::VectorCollection;
use crate::error::{VectorError, check_dim};
impl VectorCollection {
pub fn insert(&mut self, vector: Vec<f32>) -> Result<u32, VectorError> {
check_dim(self.dim, vector.len())?;
let id = self.next_id;
match &mut self.ivf {
Some(ivf) => ivf.insert_with_id(id, vector)?,
None => {
self.growing.insert(vector)?;
}
}
self.next_id += 1;
Ok(id)
}
pub fn insert_with_surrogate(
&mut self,
vector: Vec<f32>,
surrogate: Surrogate,
) -> Result<u32, VectorError> {
check_dim(self.dim, vector.len())?;
if surrogate != Surrogate::ZERO
&& let Some(old) = self.surrogate_to_local.get(&surrogate).copied()
{
self.delete_inner(old);
self.surrogate_map.remove(&old);
}
let id = self.insert(vector)?;
if surrogate != Surrogate::ZERO {
self.surrogate_map.insert(id, surrogate);
self.surrogate_to_local.insert(surrogate, id);
}
Ok(id)
}
pub fn insert_batch_with_surrogates(
&mut self,
vectors: &[Vec<f32>],
surrogates: &[Surrogate],
) -> Result<Vec<u32>, VectorError> {
for v in vectors {
check_dim(self.dim, v.len())?;
}
let mut ids = Vec::with_capacity(vectors.len());
for (i, v) in vectors.iter().enumerate() {
let surrogate = surrogates.get(i).copied().unwrap_or(Surrogate::ZERO);
ids.push(self.insert_with_surrogate(v.clone(), surrogate)?);
}
Ok(ids)
}
pub fn insert_multi_vector(
&mut self,
vectors: &[&[f32]],
document_surrogate: Surrogate,
) -> Result<Vec<u32>, VectorError> {
for v in vectors {
check_dim(self.dim, v.len())?;
}
let mut ids = Vec::with_capacity(vectors.len());
for &v in vectors {
let id = self.insert(v.to_vec())?;
if document_surrogate != Surrogate::ZERO {
self.surrogate_map.insert(id, document_surrogate);
}
ids.push(id);
}
if document_surrogate != Surrogate::ZERO {
self.multi_doc_map.insert(document_surrogate, ids.clone());
}
Ok(ids)
}
pub fn delete_multi_vector(&mut self, document_surrogate: Surrogate) -> usize {
let Some(ids) = self.multi_doc_map.remove(&document_surrogate) else {
return 0;
};
let mut deleted = 0;
for id in &ids {
if self.delete(*id) {
deleted += 1;
}
self.surrogate_map.remove(id);
}
self.surrogate_to_local.remove(&document_surrogate);
deleted
}
pub fn get_surrogate(&self, vector_id: u32) -> Option<Surrogate> {
self.surrogate_map.get(&vector_id).copied()
}
pub fn local_for_surrogate(&self, surrogate: Surrogate) -> Option<u32> {
self.surrogate_to_local.get(&surrogate).copied()
}
pub fn delete(&mut self, id: u32) -> bool {
let ok = self.delete_inner(id);
if ok && let Some(s) = self.surrogate_map.remove(&id) {
self.surrogate_to_local.remove(&s);
}
ok
}
pub(super) fn delete_inner(&mut self, id: u32) -> bool {
if let Some(ivf) = &mut self.ivf
&& ivf.contains(id)
{
return ivf.delete(id);
}
if id >= self.growing_base_id {
let local = id - self.growing_base_id;
if (local as usize) < self.growing.len() {
return self.growing.delete(local);
}
}
for seg in &mut self.sealed {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.index.len() {
return seg.index.delete(local);
}
}
}
for seg in &mut self.building {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.flat.len() {
return seg.flat.delete(local);
}
}
}
false
}
pub fn vector_for_id(&self, id: u32) -> Option<Vec<f32>> {
if let Some(ivf) = &self.ivf
&& ivf.contains(id)
{
return ivf.get_vector(id).map(<[f32]>::to_vec);
}
if id >= self.growing_base_id {
let local = id - self.growing_base_id;
if (local as usize) < self.growing.len() {
return self.growing.get_vector(local).map(<[f32]>::to_vec);
}
}
for seg in &self.sealed {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.index.len() {
if seg.index.is_deleted(local) {
return None;
}
return sealed_vector(seg, local);
}
}
}
for seg in &self.building {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.flat.len() {
return seg.flat.get_vector(local).map(<[f32]>::to_vec);
}
}
}
None
}
pub fn vector_for_surrogate(&self, surrogate: Surrogate) -> Option<Vec<f32>> {
self.local_for_surrogate(surrogate)
.and_then(|id| self.vector_for_id(id))
}
pub fn delete_by_surrogate(&mut self, surrogate: Surrogate) -> bool {
let Some(global_id) = self.surrogate_to_local.get(&surrogate).copied() else {
return false;
};
self.delete(global_id)
}
pub fn undelete(&mut self, id: u32) -> bool {
if let Some(ivf) = &mut self.ivf
&& ivf.contains(id)
{
return ivf.undelete(id);
}
if id >= self.growing_base_id {
let local = id - self.growing_base_id;
if (local as usize) < self.growing.len() {
return self.growing.undelete(local);
}
}
for seg in &mut self.sealed {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.index.len() {
return seg.index.undelete(local);
}
}
}
for seg in &mut self.building {
if id >= seg.base_id {
let local = id - seg.base_id;
if (local as usize) < seg.flat.len() {
return seg.flat.undelete(local);
}
}
}
false
}
pub fn undelete_bound(&mut self, id: u32, surrogate: Surrogate) -> bool {
if !self.undelete(id) {
return false;
}
if surrogate != Surrogate::ZERO {
self.surrogate_map.insert(id, surrogate);
self.surrogate_to_local.insert(surrogate, id);
}
true
}
}
pub(super) fn sealed_vector(seg: &super::segment::SealedSegment, local: u32) -> Option<Vec<f32>> {
if let Some(mmap) = &seg.mmap_vectors {
return mmap.get_vector(local).map(<[f32]>::to_vec);
}
#[cfg(not(target_arch = "wasm32"))]
{
seg.index
.get_vector_or_backing(local)
.map(std::borrow::Cow::into_owned)
}
#[cfg(target_arch = "wasm32")]
{
seg.index.get_vector(local).map(<[f32]>::to_vec)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hnsw::HnswParams;
fn collection() -> VectorCollection {
VectorCollection::new(2, HnswParams::default())
}
#[test]
fn re_insert_under_the_same_surrogate_leaves_one_live_node() {
let mut coll = collection();
let s = Surrogate::new(7);
let first = coll.insert_with_surrogate(vec![1.0, 0.0], s).unwrap();
let second = coll.insert_with_surrogate(vec![0.0, 1.0], s).unwrap();
assert_ne!(first, second);
assert_eq!(coll.live_count(), 1, "the old node must be tombstoned");
assert_eq!(coll.local_for_surrogate(s), Some(second));
assert_eq!(coll.get_surrogate(first), None);
assert_eq!(coll.get_surrogate(second), Some(s));
}
#[test]
fn delete_then_insert_under_the_same_surrogate_leaves_one_live_node() {
let mut coll = collection();
let s = Surrogate::new(9);
let first = coll.insert_with_surrogate(vec![1.0, 0.0], s).unwrap();
assert!(coll.delete_by_surrogate(s));
assert_eq!(coll.local_for_surrogate(s), None);
let second = coll.insert_with_surrogate(vec![0.0, 1.0], s).unwrap();
assert_ne!(first, second);
assert_eq!(coll.live_count(), 1);
assert_eq!(coll.local_for_surrogate(s), Some(second));
assert!(!coll.delete(first), "the first node is already gone");
}
#[test]
fn undelete_bound_restores_the_node_and_its_binding() {
let mut coll = collection();
let s = Surrogate::new(5);
let id = coll.insert_with_surrogate(vec![1.0, 0.0], s).unwrap();
assert!(coll.delete(id));
assert_eq!(coll.local_for_surrogate(s), None);
assert!(coll.undelete_bound(id, s));
assert_eq!(coll.live_count(), 1);
assert_eq!(coll.local_for_surrogate(s), Some(id));
assert_eq!(coll.get_surrogate(id), Some(s));
assert!(
!coll.undelete_bound(id, s),
"a live node has no tombstone to clear"
);
}
#[test]
fn delete_by_surrogate_is_idempotent() {
let mut coll = collection();
let s = Surrogate::new(3);
coll.insert_with_surrogate(vec![1.0, 0.0], s).unwrap();
assert!(coll.delete_by_surrogate(s));
assert!(!coll.delete_by_surrogate(s));
assert_eq!(coll.live_count(), 0);
}
#[test]
fn vector_for_surrogate_reads_the_growing_segment_and_hides_deletes() {
let mut coll = collection();
let s = Surrogate::new(11);
coll.insert_with_surrogate(vec![0.5, 0.25], s).unwrap();
assert_eq!(coll.vector_for_surrogate(s), Some(vec![0.5, 0.25]));
assert!(coll.delete_by_surrogate(s));
assert_eq!(coll.vector_for_surrogate(s), None);
assert_eq!(coll.vector_for_id(999), None);
}
#[test]
fn headless_vectors_bind_no_surrogate() {
let mut coll = collection();
let first = coll
.insert_with_surrogate(vec![1.0, 0.0], Surrogate::ZERO)
.unwrap();
let second = coll
.insert_with_surrogate(vec![0.0, 1.0], Surrogate::ZERO)
.unwrap();
coll.insert_multi_vector(&[&[0.5, 0.5]], Surrogate::ZERO)
.unwrap();
assert_ne!(first, second);
assert_eq!(coll.live_count(), 3, "every headless insert stays live");
assert_eq!(coll.local_for_surrogate(Surrogate::ZERO), None);
assert_eq!(coll.get_surrogate(first), None);
assert_eq!(coll.get_surrogate(second), None);
assert!(coll.surrogate_to_local.is_empty());
assert!(coll.multi_doc_map.is_empty());
assert!(
!coll.delete_by_surrogate(Surrogate::ZERO),
"a delete by ZERO reaches no vector"
);
assert_eq!(coll.live_count(), 3);
}
}