KeiSeiKit-1.0/_primitives/_rust/kei-memory-sqlite/src/store.rs
Parfii-bot 0be354a920 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

95 lines
3.1 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 <author org>
//!
//! SQLite-backed storage layer. The async surface lives in `backend.rs`;
//! this module is sync (rusqlite is sync) and exposes a `SqliteStore`
//! whose Arc-cloned handle is shared by the backend, which wraps the
//! actual blocking calls in `tokio::task::spawn_blocking`.
//!
//! Connection is guarded by `std::sync::Mutex` because rusqlite's
//! `Connection` is not `Sync` on its own. The blocking surface is small
//! (one `lock()` per backend op) and the spawn_blocking thread holds it
//! only for the duration of the SQL.
use crate::error::Result;
use crate::schema::apply_schema;
use rusqlite::Connection;
use std::path::Path;
use std::sync::Mutex;
/// Owned SQLite handle. Cheap to wrap in `Arc` for sharing across
/// `SqliteBackend` clones (see `backend.rs`).
pub struct SqliteStore {
conn: Mutex<Connection>,
}
impl SqliteStore {
/// Open or create a SQLite DB at `path`. Schema is applied
/// idempotently on every open.
pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
let conn = Connection::open(path.as_ref())?;
apply_schema(&conn)?;
Ok(Self {
conn: Mutex::new(conn),
})
}
/// In-memory store for tests / ephemeral fixtures. Schema applied.
pub fn from_memory() -> Result<Self> {
let conn = Connection::open_in_memory()?;
apply_schema(&conn)?;
Ok(Self {
conn: Mutex::new(conn),
})
}
/// Borrow the connection mutex. Backend uses this from inside
/// `spawn_blocking` so the blocking lock is off the async runtime.
pub fn lock(&self) -> std::sync::MutexGuard<'_, Connection> {
// Mutex poisoning aborts here on purpose: a panic mid-transaction
// means the in-memory state is suspect.
self.conn.lock().expect("sqlite connection mutex poisoned")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_memory_opens_and_applies_schema() {
let s = SqliteStore::from_memory().expect("open");
let conn = s.lock();
// Probe the schema: the table must exist.
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='memory_items'",
[],
|r| r.get(0),
)
.unwrap();
assert_eq!(count, 1, "memory_items table must exist");
}
#[test]
fn from_path_creates_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("kei.db");
let _s = SqliteStore::from_path(&path).expect("open");
assert!(path.exists(), "DB file must exist after open");
}
#[test]
fn indexes_present() {
let s = SqliteStore::from_memory().unwrap();
let conn = s.lock();
let n: i64 = conn
.query_row(
"SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name LIKE 'idx_memory_items_%'",
[],
|r| r.get(0),
)
.unwrap();
assert!(n >= 2, "expected idx_memory_items_kind_key and idx_memory_items_created_at");
}
}