sqlitegraph 3.9.0

Embedded graph database with explicit SQLite, native-v3, and combined backend modes; HNSW vector search; and graph algorithms
Documentation
use super::*;

pub(super) fn write_all<W: Write>(
    writer: &mut W,
    bytes: &[u8],
    field: &str,
) -> Result<(), IndexPersistenceError> {
    writer
        .write_all(bytes)
        .map_err(|e| IndexPersistenceError::Io(format!("Failed to write {}: {}", field, e)))
}

pub(super) fn write_string_entry<W: Write>(
    writer: &mut W,
    value: &str,
    node_ids: &[i64],
    field: &str,
) -> Result<(), IndexPersistenceError> {
    let value_bytes = value.as_bytes();
    write_all(
        writer,
        &(value_bytes.len() as u32).to_be_bytes(),
        &format!("{} len", field),
    )?;
    write_all(writer, value_bytes, &format!("{} bytes", field))?;
    write_all(writer, &(node_ids.len() as u32).to_be_bytes(), "node count")?;
    for &node_id in node_ids {
        write_all(writer, &node_id.to_be_bytes(), "node ID")?;
    }
    Ok(())
}

pub(super) fn read_array<const N: usize, R: Read>(
    reader: &mut R,
    field: &str,
) -> Result<[u8; N], IndexPersistenceError> {
    let mut bytes = [0u8; N];
    reader.read_exact(&mut bytes).map_err(|e| {
        IndexPersistenceError::Corrupted(format!("Failed to read {}: {}", field, e))
    })?;
    Ok(bytes)
}

pub(super) struct SliceCursor<'a> {
    remaining: &'a [u8],
}

impl<'a> SliceCursor<'a> {
    pub(super) fn new(remaining: &'a [u8]) -> Self {
        Self { remaining }
    }

    fn read_bytes(&mut self, count: usize) -> Result<&'a [u8], IndexPersistenceError> {
        if self.remaining.len() < count {
            return Err(IndexPersistenceError::Corrupted(format!(
                "Unexpected EOF: needed {} bytes, got {}",
                count,
                self.remaining.len()
            )));
        }
        let (head, tail) = self.remaining.split_at(count);
        self.remaining = tail;
        Ok(head)
    }

    pub(super) fn read_u32(&mut self) -> Result<u32, IndexPersistenceError> {
        let bytes = self.read_bytes(4)?;
        Ok(u32::from_be_bytes(
            bytes
                .try_into()
                .expect("invariant: read_bytes guaranteed 4 bytes"),
        ))
    }

    fn read_i64(&mut self) -> Result<i64, IndexPersistenceError> {
        let bytes = self.read_bytes(8)?;
        Ok(i64::from_be_bytes(
            bytes
                .try_into()
                .expect("invariant: read_bytes guaranteed 8 bytes"),
        ))
    }

    fn read_string(&mut self, field: &str) -> Result<String, IndexPersistenceError> {
        let len = self.read_u32()? as usize;
        let bytes = self.read_bytes(len)?;
        let value = std::str::from_utf8(bytes)
            .map_err(|_| IndexPersistenceError::Corrupted(format!("Invalid UTF-8 in {}", field)))?;
        Ok(value.to_string())
    }
}

pub(super) fn read_string_index_entries(
    cursor: &mut SliceCursor<'_>,
    entry_count: usize,
    field: &str,
) -> Result<HashMap<String, Vec<i64>>, IndexPersistenceError> {
    let mut data = HashMap::new();
    for _ in 0..entry_count {
        let key = cursor.read_string(field)?;
        let node_count = cursor.read_u32()? as usize;
        let mut node_ids = Vec::with_capacity(node_count);
        for _ in 0..node_count {
            node_ids.push(cursor.read_i64()?);
        }
        data.insert(key, node_ids);
    }
    Ok(data)
}