uqa-storage 0.1.6

Document store, inverted index, IVF/HNSW vectors, B-tree, R*Tree, catalog
Documentation
//
// Unified Query Algebra
//
// Copyright (c) 2023-2026 Cognica, Inc.
//

//! Checked document-id, ordinal, and vector-blob encoding.

use super::{DocId, SQLiteError, SQLiteResult};

pub(super) fn i64_to_usize(field: &str, value: i64) -> SQLiteResult<usize> {
    value.try_into().map_err(|_| {
        SQLiteError::StorageBackend(format!("invalid {field}: {value} does not fit in usize"))
    })
}

pub(super) fn encode_doc_id(doc_id: DocId) -> SQLiteResult<i64> {
    i64::try_from(doc_id).map_err(|_| {
        SQLiteError::StorageBackend(format!(
            "document id {doc_id} does not fit in SQLite INTEGER"
        ))
    })
}

pub(super) fn decode_doc_id(doc_id: i64) -> SQLiteResult<DocId> {
    DocId::try_from(doc_id).map_err(|_| {
        SQLiteError::StorageBackend(format!(
            "invalid negative document id {doc_id} in persisted vector index"
        ))
    })
}

pub(super) fn vector_to_blob(v: &[f32]) -> SQLiteResult<Vec<u8>> {
    let capacity = v
        .len()
        .checked_mul(std::mem::size_of::<f32>())
        .ok_or_else(|| SQLiteError::StorageBackend("vector payload size overflow".into()))?;
    let mut buf = Vec::with_capacity(capacity);
    for x in v {
        buf.extend_from_slice(&x.to_le_bytes());
    }
    Ok(buf)
}

pub(super) fn usize_to_u64(field: &str, value: usize) -> SQLiteResult<u64> {
    u64::try_from(value).map_err(|_| {
        SQLiteError::StorageBackend(format!("{field} does not fit in the u64 counter range"))
    })
}

pub(super) fn validate_vector_ordinal_count(count: u64) -> SQLiteResult<()> {
    if count > u64::from(u32::MAX) + 1 {
        return Err(SQLiteError::StorageBackend(
            "vector ordinal exceeds the u32 index format".into(),
        ));
    }
    Ok(())
}

pub(super) fn validate_persisted_ordinal_sequence(
    rows: &[(DocId, u32, Vec<f32>)],
) -> SQLiteResult<()> {
    let mut current_doc = None;
    let mut expected = 0_u64;
    for (doc_id, ordinal, _) in rows {
        if current_doc != Some(*doc_id) {
            current_doc = Some(*doc_id);
            expected = 0;
        }
        if u64::from(*ordinal) != expected {
            return Err(SQLiteError::StorageBackend(format!(
                "invalid persisted vector ordinal sequence for document {doc_id}: expected {expected}, found {ordinal}"
            )));
        }
        expected = expected.checked_add(1).ok_or_else(|| {
            SQLiteError::StorageBackend("persisted vector ordinal sequence overflow".into())
        })?;
    }
    Ok(())
}

pub(super) fn blob_to_vector(blob: &[u8]) -> SQLiteResult<Vec<f32>> {
    if !blob.len().is_multiple_of(4) {
        return Err(SQLiteError::StorageBackend(
            "invalid vector payload".to_string(),
        ));
    }
    Ok(blob
        .chunks_exact(4)
        .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
        .collect())
}