Skip to main content

SqliteGraph

Struct SqliteGraph 

Source
pub struct SqliteGraph {
    pub pool: PoolManager,
    pub hnsw_indexes: Mutex<RawMutex, HashMap<String, HnswIndex>>,
    /* private fields */
}
Expand description

Embedded SQLite-backed graph database.

Provides a lightweight, deterministic graph database with entity and edge storage, pattern matching, MVCC-lite snapshots, and deterministic indexing.

§Thread Safety

NOT thread-safe for concurrent writes. SqliteGraph uses interior mutability (RefCell) and is not Sync. However, the underlying connection pool allows multiple threads to read concurrently when using separate SqliteGraph instances.

§Connection Pooling

File-based databases use an r2d2 connection pool (default 5 connections) for concurrent access. In-memory databases skip pooling and use a single direct connection.

Fields§

§pool: PoolManager

Connection pool for file-based databases, or direct connection for in-memory (public for CLI access to underlying connection)

§hnsw_indexes: Mutex<RawMutex, HashMap<String, HnswIndex>>

HNSW vector indexes stored by name (public for CLI access). Uses parking_lot::Mutex (not RwLock) because HnswIndex contains Box<dyn VectorStorage> which is !Sync (rusqlite::Connection is !Sync). Mutex<T: Send> is itself Send + Sync, making SqliteGraph thread-safe. parking_lot::Mutex is smaller and faster than std::sync::Mutex, and does not propagate poison on panic.

Implementations§

Source§

impl SqliteGraph

Source§

impl SqliteGraph

Source

pub fn open_with_config<P>( path: P, cfg: &SqliteConfig, ) -> Result<SqliteGraph, SqliteGraphError>
where P: AsRef<Path>,

Open a graph database with custom configuration.

§Arguments
  • path - Path to the SQLite database file
  • cfg - Configuration options (pool size, cache size, PRAGMAs, etc.)
§Example
use sqlitegraph::{SqliteGraph, SqliteConfig};

let cfg = SqliteConfig::new()
    .with_pool_size(10)
    .with_wal_mode();
let graph = SqliteGraph::open_with_config("my_graph.db", &cfg)?;
Source

pub fn open<P>(path: P) -> Result<SqliteGraph, SqliteGraphError>
where P: AsRef<Path>,

Source

pub fn open_without_migrations<P>( path: P, ) -> Result<SqliteGraph, SqliteGraphError>
where P: AsRef<Path>,

Source

pub fn open_in_memory_with_config( cfg: &SqliteConfig, ) -> Result<SqliteGraph, SqliteGraphError>

Open an in-memory database with custom configuration.

Note: Pool size is ignored for in-memory databases since they use a single direct connection (each connection would have isolated data).

§Arguments
  • cfg - Configuration options (cache size, PRAGMAs, etc.)
§Example
use sqlitegraph::{SqliteGraph, SqliteConfig};

let cfg = SqliteConfig::new()
    .with_cache_size(256)
    .with_performance_mode();
let graph = SqliteGraph::open_in_memory_with_config(&cfg)?;
Source

pub fn open_in_memory() -> Result<SqliteGraph, SqliteGraphError>

Source

pub fn open_in_memory_without_migrations() -> Result<SqliteGraph, SqliteGraphError>

Source

pub fn introspect(&self) -> Result<GraphIntrospection, SqliteGraphError>

Get comprehensive introspection data for this graph instance.

This method provides a structured snapshot of the graph state, including node counts, edge counts, cache statistics, and file sizes. The result is JSON-serializable for both human debugging and LLM consumption.

§Example
use sqlitegraph::{open_graph, GraphConfig};

let graph = open_graph("my_graph.db", &GraphConfig::sqlite())?;
let intro = graph.introspect()?;

println!("Backend: {}", intro.backend_type);
println!("Nodes: {}", intro.node_count);
println!("Cache hit ratio: {:.2}%", intro.cache_stats.hit_ratio().unwrap_or(0.0));
Source

pub fn cache_stats(&self) -> CacheStats

Get adjacency cache statistics.

Returns combined statistics from both outgoing and incoming adjacency caches. This is useful for monitoring cache effectiveness and tuning cache size.

§Example
use sqlitegraph::{open_graph, GraphConfig};

let graph = open_graph("my_graph.db", &GraphConfig::sqlite())?;
let stats = graph.cache_stats();

println!("Cache hits: {}", stats.hits);
println!("Cache misses: {}", stats.misses);
println!("Hit ratio: {:.2}%", stats.hit_ratio().unwrap_or(0.0));
Source§

impl SqliteGraph

Source

pub fn insert_edge(&self, edge: &GraphEdge) -> Result<i64, SqliteGraphError>

Source

pub fn insert_edges_bulk( &self, edges: &[GraphEdge], ) -> Result<Vec<i64>, SqliteGraphError>

Insert many edges atomically inside a single transaction.

Returns the rowids of the inserted edges in the same order as the input. Empty input returns an empty vector without opening a transaction. On any error, the transaction is rolled back and the database is left untouched.

Source

pub fn get_edge(&self, id: i64) -> Result<GraphEdge, SqliteGraphError>

Source

pub fn delete_edge(&self, id: i64) -> Result<(), SqliteGraphError>

Source§

impl SqliteGraph

Source

pub fn insert_entity( &self, entity: &GraphEntity, ) -> Result<i64, SqliteGraphError>

Source

pub fn insert_entities_bulk( &self, entities: &[GraphEntity], ) -> Result<Vec<i64>, SqliteGraphError>

Insert many entities atomically inside a single transaction.

Returns the rowids of the inserted entities in the same order as the input. Empty input returns an empty vector without opening a transaction. On any error, the transaction is rolled back and the database is left untouched.

Source

pub fn get_entity(&self, id: i64) -> Result<GraphEntity, SqliteGraphError>

Source

pub fn update_entity( &self, entity: &GraphEntity, ) -> Result<(), SqliteGraphError>

Source

pub fn delete_entity(&self, id: i64) -> Result<(), SqliteGraphError>

Source

pub fn list_entity_ids(&self) -> Result<Vec<i64>, SqliteGraphError>

Source

pub fn find_entities_by_kind( &self, kind: &str, ) -> Result<Vec<GraphEntity>, SqliteGraphError>

Find all entities of a given kind.

Uses the idx_entities_kind index for efficient lookup.

Source

pub fn find_entity_by_kind_and_name( &self, kind: &str, name: &str, ) -> Result<Option<GraphEntity>, SqliteGraphError>

Find a single entity by kind and exact name.

Uses the idx_entities_kind_name composite index for efficient lookup. Returns None if no entity matches.

Source§

impl SqliteGraph

Source§

impl SqliteGraph

Source

pub fn match_triples( &self, pattern: &PatternTriple, ) -> Result<Vec<TripleMatch>, SqliteGraphError>

Match lightweight triple patterns using pattern engine.

This method provides a simple interface for matching single-hop patterns like (start_label)-[edge_type]->(end_label) with optional property filters.

§Arguments
  • pattern - The pattern triple to match
§Returns

A vector of triple matches in deterministic order

Source

pub fn match_triples_fast( &self, pattern: &PatternTriple, ) -> Result<Vec<TripleMatch>, SqliteGraphError>

Match lightweight triple patterns using cache-enabled fast-path.

This method provides an optimized version of pattern matching that:

  • Uses cache as a fast-path where safe
  • Falls back to SQL where pattern requires it
  • Returns IDENTICAL results to match_triples()
  • Maintains deterministic ordering
§Arguments
  • pattern - The pattern triple to match
§Returns

A vector of triple matches in deterministic order

Source§

impl SqliteGraph

Source

pub fn acquire_snapshot(&self) -> Result<GraphSnapshot, SqliteGraphError>

Acquire a deterministic snapshot of the current graph state

Returns a read-only snapshot that provides isolated access to graph data. The snapshot contains cloned adjacency maps and uses a read-only SQLite connection.

§MVCC-lite Snapshot Isolation

Snapshots provide MVCC-lite isolation guarantees:

  • Immutable: Snapshot state never changes after creation
  • Consistent: Snapshot sees a point-in-time view of the graph
  • Isolated: Snapshot unaffected by subsequent writes
  • Cloned Data: Adjacency maps are fully cloned (not shared)
§Cache Requirement

IMPORTANT: Snapshots read from the in-memory adjacency cache, not the database. For accurate snapshots, the cache must be warmed first:

use sqlitegraph::SqliteGraph;

let graph = SqliteGraph::open_in_memory()?;
// ... perform writes ...

// Warm cache before snapshot
let entity_ids = graph.list_entity_ids()?;
for &id in &entity_ids {
    let _ = graph.query().outgoing(id);
    let _ = graph.query().incoming(id);
}

// Now acquire snapshot
let snapshot = graph.acquire_snapshot()?;
assert!(snapshot.node_count() > 0);

Without cache warming, snapshots may appear empty even if the database has data.

§Thread Safety

The underlying SnapshotManager is thread-safe and uses lock-free ArcSwap. However, SqliteGraph itself is NOT thread-safe (contains RefCell, non-Sync types).

For concurrent snapshot acquisition, wrap SqliteGraph in a Mutex or RwLock:

use std::sync::{Arc, Mutex};
use sqlitegraph::SqliteGraph;

let graph = Arc::new(Mutex::new(SqliteGraph::open_in_memory()?));
// Multiple threads can now safely acquire snapshots
§Performance
  • Acquisition: < 1ms typical (Arc::clone overhead)
  • Memory: O(N + E) where N = nodes, E = edges (full copy)
  • Throughput: > 10,000 snapshots/second single-threaded
§Returns

Result containing GraphSnapshot or error

§Errors

Returns error if:

  • Read-only SQLite connection cannot be opened
  • Database connection fails
Source

pub fn snapshot(&self) -> Result<GraphSnapshot, SqliteGraphError>

Convenience alias for acquire_snapshot()

This is a shorter name for acquiring snapshots, equivalent to:

let graph = SqliteGraph::open_in_memory()?;
let snapshot = graph.snapshot()?;

See acquire_snapshot() for full documentation.

Source

pub fn snapshot_node_count(&self) -> usize

Get the number of nodes in the current snapshot

Note: This requires cache warming to return accurate results. See acquire_snapshot() documentation for details.

Source

pub fn snapshot_edge_count(&self) -> usize

Get the number of edges in the current snapshot

Note: This requires cache warming to return accurate results. See acquire_snapshot() documentation for details.

Source

pub fn snapshot_contains_node(&self, node_id: i64) -> bool

Check if a node exists in the current snapshot

Note: This requires cache warming to return accurate results. See acquire_snapshot() documentation for details.

Source§

impl SqliteGraph

Source

pub fn reasoner(&self) -> GraphReasoner<'_>

Source§

impl SqliteGraph

Source

pub fn query(&self) -> GraphQuery<'_>

Source§

impl SqliteGraph

SQLiteGraph extension for HNSW vector search

Source

pub fn hnsw_index( &self, name: &str, config: HnswConfig, ) -> Result<MutexGuard<'_, RawMutex, HashMap<String, HnswIndex>>, SqliteGraphError>

Create or get an HNSW index with the specified name and configuration

§Arguments
  • name - Name to identify this index (for multi-index support)
  • config - HNSW configuration parameters
§Returns

Returns a mutable reference to the HnswIndex ready for vector operations

§Examples
use sqlitegraph::{SqliteGraph, hnsw::{HnswConfigBuilder, DistanceMetric}};

let graph = SqliteGraph::open_in_memory()?;
let config = HnswConfigBuilder::new()
    .dimension(256)
    .distance_metric(DistanceMetric::Cosine)
    .build()?;

let hnsw = graph.hnsw_index("embeddings", config)?;
Source

pub fn hnsw_index_persistent( &self, name: &str, config: HnswConfig, ) -> Result<MutexGuard<'_, RawMutex, HashMap<String, HnswIndex>>, SqliteGraphError>

Create or get an HNSW index with persistent storage (for file-based databases)

This method automatically detects if the database is file-based and creates the index with SQLiteVectorStorage for automatic vector persistence. For in-memory databases, falls back to in-memory storage.

§Arguments
  • name - Name to identify this index
  • config - HNSW configuration parameters
§Returns

Returns a mutable reference to the HnswIndex ready for vector operations

§Examples
use sqlitegraph::{SqliteGraph, hnsw::{HnswConfigBuilder, DistanceMetric}};

let graph = SqliteGraph::open("mydb.db")?;
let config = HnswConfigBuilder::new()
    .dimension(256)
    .distance_metric(DistanceMetric::Cosine)
    .build()?;

let hnsw = graph.hnsw_index_persistent("embeddings", config)?;
// Vectors inserted into this index will persist to the database
Source

pub fn get_hnsw_index( &self, name: &str, ) -> Result<Option<MutexGuard<'_, RawMutex, HashMap<String, HnswIndex>>>, SqliteGraphError>

Get an existing HNSW index by name

§Arguments
  • name - Name of the index to retrieve
§Returns

Returns a mutable reference to the HnswIndex if it exists

Source

pub fn get_hnsw_index_ref<F, R>( &self, name: &str, f: F, ) -> Result<R, SqliteGraphError>
where F: FnOnce(&HnswIndex) -> R,

Get a reference to an HNSW index without locking for write

Source

pub fn get_hnsw_index_mut<F, R>( &self, name: &str, f: F, ) -> Result<R, SqliteGraphError>
where F: FnOnce(&mut HnswIndex) -> R,

Get a mutable reference to an HNSW index for modifications

Source

pub fn list_hnsw_indexes(&self) -> Result<Vec<String>, SqliteGraphError>

List all HNSW index names

Source

pub fn delete_hnsw_index(&self, name: &str) -> Result<(), SqliteGraphError>

Delete an HNSW index by name, removing both the in-memory entry and any persisted vectors / metadata in the SQLite tables.

Source

pub fn delete_hnsw_vector( &self, index_name: &str, vector_id: u64, ) -> Result<(), SqliteGraphError>

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more