use std::time::Duration;
use database_mcp_config::DatabaseConfig;
use database_mcp_sql::Connection;
use database_mcp_sql::SqlError;
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
use tracing::info;
#[derive(Clone)]
pub(crate) struct SqliteConnection {
config: DatabaseConfig,
pool: SqlitePool,
}
impl std::fmt::Debug for SqliteConnection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SqliteConnection").finish_non_exhaustive()
}
}
impl SqliteConnection {
pub(crate) fn new(config: &DatabaseConfig) -> Self {
info!("SQLite lazy connection pool created");
Self {
config: config.clone(),
pool: create_lazy_pool(config),
}
}
#[allow(clippy::unused_async)]
pub(crate) async fn pool(&self, _target: Option<&str>) -> Result<SqlitePool, SqlError> {
Ok(self.pool.clone())
}
}
impl Connection for SqliteConnection {
type DB = sqlx::Sqlite;
async fn pool(&self, target: Option<&str>) -> Result<sqlx::Pool<Self::DB>, SqlError> {
self.pool(target).await
}
fn query_timeout(&self) -> Option<u64> {
self.config.query_timeout
}
}
fn create_lazy_pool(config: &DatabaseConfig) -> SqlitePool {
let conn_ops = SqliteConnectOptions::new().filename(config.name.as_deref().unwrap_or_default());
let mut pool_opts = sqlx::pool::PoolOptions::new()
.max_connections(1)
.min_connections(DatabaseConfig::DEFAULT_MIN_CONNECTIONS)
.idle_timeout(Duration::from_secs(DatabaseConfig::DEFAULT_IDLE_TIMEOUT_SECS))
.max_lifetime(Duration::from_secs(DatabaseConfig::DEFAULT_MAX_LIFETIME_SECS));
if let Some(timeout) = config.connection_timeout {
pool_opts = pool_opts.acquire_timeout(Duration::from_secs(timeout));
}
pool_opts.connect_lazy_with(conn_ops)
}
#[cfg(test)]
mod tests {
use super::*;
use database_mcp_config::DatabaseBackend;
fn base_config() -> DatabaseConfig {
DatabaseConfig {
backend: DatabaseBackend::Sqlite,
name: Some("test.db".into()),
..DatabaseConfig::default()
}
}
#[tokio::test]
async fn create_lazy_pool_returns_idle_pool() {
let pool = create_lazy_pool(&base_config());
assert_eq!(pool.size(), 0, "pool should be lazy (no connections yet)");
}
#[tokio::test]
async fn create_lazy_pool_without_name() {
let pool = create_lazy_pool(&DatabaseConfig {
name: None,
..base_config()
});
assert_eq!(pool.size(), 0);
}
#[tokio::test]
async fn new_creates_lazy_pool() {
let connection = SqliteConnection::new(&base_config());
assert_eq!(connection.pool.size(), 0, "pool should be lazy");
}
#[tokio::test]
async fn pool_returns_single_pool() {
let connection = SqliteConnection::new(&base_config());
connection.pool(None).await.expect("None target should succeed");
connection
.pool(Some("anything"))
.await
.expect("any target should return the same single pool");
}
}