2026-02-03 09:39:02 +08:00
|
|
|
//! SQLite storage backend.
|
|
|
|
|
|
|
|
|
|
use rusqlite::Connection;
|
2026-04-03 08:25:26 +08:00
|
|
|
use storage::StorageError;
|
2026-02-03 09:39:02 +08:00
|
|
|
|
2026-04-03 08:25:26 +08:00
|
|
|
use crate::errors::map_rusqlite_error;
|
2026-02-03 09:39:02 +08:00
|
|
|
|
|
|
|
|
/// Configuration for SQLite storage.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub enum StorageConfig {
|
|
|
|
|
/// In-memory database (for testing).
|
|
|
|
|
InMemory,
|
|
|
|
|
/// File-based SQLite database.
|
|
|
|
|
File(String),
|
|
|
|
|
/// SQLCipher encrypted database.
|
|
|
|
|
Encrypted { path: String, key: String },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// SQLite database wrapper.
|
|
|
|
|
///
|
|
|
|
|
/// This provides the core database connection and can be shared
|
|
|
|
|
/// across different domain-specific storage implementations.
|
|
|
|
|
pub struct SqliteDb {
|
|
|
|
|
conn: Connection,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SqliteDb {
|
|
|
|
|
/// Creates a new SQLite database with the given configuration.
|
|
|
|
|
pub fn new(config: StorageConfig) -> Result<Self, StorageError> {
|
|
|
|
|
let conn = match config {
|
2026-04-03 08:25:26 +08:00
|
|
|
StorageConfig::InMemory => Connection::open_in_memory().map_err(map_rusqlite_error)?,
|
|
|
|
|
StorageConfig::File(ref path) => Connection::open(path).map_err(map_rusqlite_error)?,
|
2026-02-03 09:39:02 +08:00
|
|
|
StorageConfig::Encrypted { ref path, ref key } => {
|
2026-04-03 08:25:26 +08:00
|
|
|
let conn = Connection::open(path).map_err(map_rusqlite_error)?;
|
|
|
|
|
conn.pragma_update(None, "key", key)
|
|
|
|
|
.map_err(map_rusqlite_error)?;
|
2026-02-03 09:39:02 +08:00
|
|
|
conn
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Enable foreign keys
|
2026-04-03 08:25:26 +08:00
|
|
|
conn.execute_batch("PRAGMA foreign_keys = ON;")
|
|
|
|
|
.map_err(map_rusqlite_error)?;
|
2026-02-03 09:39:02 +08:00
|
|
|
|
|
|
|
|
Ok(Self { conn })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Returns a reference to the underlying connection.
|
|
|
|
|
///
|
|
|
|
|
/// Use this for domain-specific storage operations.
|
|
|
|
|
pub fn connection(&self) -> &Connection {
|
|
|
|
|
&self.conn
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-25 08:45:22 +08:00
|
|
|
/// Returns a mutable reference to the underlying connection.
|
|
|
|
|
///
|
|
|
|
|
/// Use this for operations that require mutable access, such as transactions.
|
|
|
|
|
pub fn connection_mut(&mut self) -> &mut Connection {
|
|
|
|
|
&mut self.conn
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 09:39:02 +08:00
|
|
|
/// Begins a transaction.
|
|
|
|
|
pub fn transaction(&mut self) -> Result<rusqlite::Transaction<'_>, StorageError> {
|
2026-04-03 08:25:26 +08:00
|
|
|
self.conn.transaction().map_err(map_rusqlite_error)
|
2026-02-03 09:39:02 +08:00
|
|
|
}
|
|
|
|
|
}
|