grit-core 0.2.4

Embedded, bi-temporal property graph for agent memory: one SQLite file, in-process, deterministic
Documentation
//! The crate-wide `#![deny(unsafe_code)]` is lifted for exactly this module:
//! the sqlite-vec FFI registration (see AGENTS.md "Style"). Nothing else in
//! grit-core may use `unsafe`.
#![allow(unsafe_code)]

use std::sync::Once;

/// Register sqlite-vec as an auto-extension so every subsequently opened
/// connection (writer and readers alike) has the `vec0` module available.
/// Idempotent; safe to call from multiple threads.
pub(crate) fn register_sqlite_vec() {
    static ONCE: Once = Once::new();
    ONCE.call_once(|| {
        type SqliteInitFn = unsafe extern "C" fn(
            *mut rusqlite::ffi::sqlite3,
            *mut *mut std::os::raw::c_char,
            *const rusqlite::ffi::sqlite3_api_routines,
        ) -> std::os::raw::c_int;
        // SAFETY: sqlite3_vec_init has the sqlite3 extension-init ABI that
        // sqlite3_auto_extension expects; the cast is the pattern documented
        // by sqlite-vec itself. Called once, before any connection is opened.
        unsafe {
            rusqlite::ffi::sqlite3_auto_extension(Some(std::mem::transmute::<
                *const (),
                SqliteInitFn,
            >(
                sqlite_vec::sqlite3_vec_init as *const (),
            )));
        }
    });
}

/// View a float slice as little-endian bytes for binding to a `vec0` column.
/// (SQLite blobs are byte-addressed; sqlite-vec reads float32 LE.)
pub(crate) fn f32s_as_bytes(v: &[f32]) -> Vec<u8> {
    let mut out = Vec::with_capacity(v.len() * 4);
    for x in v {
        out.extend_from_slice(&x.to_le_bytes());
    }
    out
}

/// Inverse of [`f32s_as_bytes`]: decode a `vec0` embedding blob back into
/// floats. A trailing partial chunk (impossible for blobs sqlite-vec wrote)
/// is ignored.
pub(crate) fn bytes_as_f32s(bytes: &[u8]) -> Vec<f32> {
    bytes
        .chunks_exact(4)
        .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
        .collect()
}