use database_mcp_backend::error::AppError;
use database_mcp_config::DatabaseConfig;
use sqlx::SqlitePool;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
use tracing::info;
#[derive(Clone)]
pub struct SqliteBackend {
pub(crate) pool: SqlitePool,
pub read_only: bool,
}
impl std::fmt::Debug for SqliteBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SqliteBackend")
.field("read_only", &self.read_only)
.finish_non_exhaustive()
}
}
impl SqliteBackend {
pub async fn new(config: &DatabaseConfig) -> Result<Self, AppError> {
let name = config.name.as_deref().unwrap_or_default();
let pool = SqlitePoolOptions::new()
.max_connections(1) .connect_with(connect_options(config))
.await
.map_err(|e| AppError::Connection(format!("Failed to open SQLite: {e}")))?;
info!("SQLite connection initialized: {name}");
Ok(Self {
pool,
read_only: config.read_only,
})
}
pub(crate) fn quote_identifier(name: &str) -> String {
database_mcp_backend::identifier::quote_identifier(name, '"')
}
}
fn connect_options(config: &DatabaseConfig) -> SqliteConnectOptions {
let name = config.name.as_deref().unwrap_or_default();
SqliteConnectOptions::new().filename(name)
}
#[cfg(test)]
mod tests {
use super::*;
use database_mcp_config::DatabaseBackend;
#[test]
fn try_from_sets_filename() {
let config = DatabaseConfig {
backend: DatabaseBackend::Sqlite,
name: Some("test.db".into()),
..DatabaseConfig::default()
};
let opts = connect_options(&config);
assert_eq!(opts.get_filename().to_str().expect("valid path"), "test.db");
}
#[test]
fn try_from_empty_name_defaults() {
let config = DatabaseConfig {
backend: DatabaseBackend::Sqlite,
name: None,
..DatabaseConfig::default()
};
let opts = connect_options(&config);
assert_eq!(opts.get_filename().to_str().expect("valid path"), "");
}
}