weavatrix-search-vector 0.3.1

Persistent, mutable, bounded vector candidate search for Rust and Weavatrix
Documentation
use super::format::{align8, bytes_for, to_u64, to_usize};
use super::{HEADER_LEN, Header, NONE_ENTRY};
use crate::error::SearchError;
use crate::hnsw::NodeLinks;

pub(super) fn validate_offsets(
    offsets: &[u64],
    expected_last: usize,
    label: &'static str,
) -> Result<(), SearchError> {
    if offsets.first() != Some(&0)
        || offsets.windows(2).any(|pair| pair[0] > pair[1])
        || offsets.last().copied() != Some(to_u64(expected_last)?)
    {
        return Err(SearchError::CorruptSnapshot(label));
    }
    Ok(())
}

pub(super) fn validate_owned_graph(
    nodes: &[NodeLinks],
    entry_raw: u64,
    max_level: usize,
    count: usize,
) -> Result<(), SearchError> {
    if count == 0 {
        if entry_raw != NONE_ENTRY {
            return Err(SearchError::CorruptSnapshot(
                "empty graph has an entry node",
            ));
        }
        return Ok(());
    }
    let entry = to_usize(entry_raw)?;
    if entry >= count || nodes[entry].layers.len() <= max_level {
        return Err(SearchError::CorruptSnapshot(
            "graph entry or max level is invalid",
        ));
    }
    for node in nodes {
        if node.layers.is_empty() {
            return Err(SearchError::CorruptSnapshot("graph node has no layer zero"));
        }
        for (level, layer) in node.layers.iter().enumerate() {
            for neighbor in layer {
                let neighbor = *neighbor as usize;
                if neighbor >= count || nodes[neighbor].layers.len() <= level {
                    return Err(SearchError::CorruptSnapshot(
                        "graph neighbor or layer reference is invalid",
                    ));
                }
            }
        }
    }
    Ok(())
}

pub(super) fn validate_canonical_offsets(header: &Header) -> Result<(), SearchError> {
    let count = header.count;
    let vector_count = count
        .checked_mul(header.config.dimensions)
        .ok_or(SearchError::CapacityOverflow)?;
    let expected_vectors = align8(
        HEADER_LEN
            .checked_add(bytes_for::<u64>(count)?)
            .ok_or(SearchError::CapacityOverflow)?,
    )?;
    let expected_codes = align8(
        expected_vectors
            .checked_add(bytes_for::<f32>(vector_count)?)
            .ok_or(SearchError::CapacityOverflow)?,
    )?;
    let expected_routing_positions = align8(
        expected_codes
            .checked_add(bytes_for::<u16>(count)?)
            .ok_or(SearchError::CapacityOverflow)?,
    )?;
    let expected_graphs = align8(
        expected_routing_positions
            .checked_add(bytes_for::<u32>(count)?)
            .ok_or(SearchError::CapacityOverflow)?,
    )?;
    if (
        header.keys_offset,
        header.vectors_offset,
        header.routing_codes_offset,
        header.routing_nodes_offset,
        header.graphs_offset,
    ) != (
        HEADER_LEN,
        expected_vectors,
        expected_codes,
        expected_routing_positions,
        expected_graphs,
    ) {
        return Err(SearchError::CorruptSnapshot(
            "snapshot section offsets do not match canonical layout",
        ));
    }
    Ok(())
}