database_mcp_sqlite/
connection.rs1use std::time::Duration;
8
9use database_mcp_config::DatabaseConfig;
10use database_mcp_sql::Connection;
11use database_mcp_sql::SqlError;
12use sqlx::sqlite::{SqliteConnectOptions, SqlitePool};
13use tracing::info;
14
15#[derive(Clone)]
17pub(crate) struct SqliteConnection {
18 config: DatabaseConfig,
19 pool: SqlitePool,
20}
21
22impl std::fmt::Debug for SqliteConnection {
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("SqliteConnection").finish_non_exhaustive()
25 }
26}
27
28impl SqliteConnection {
29 pub(crate) fn new(config: &DatabaseConfig) -> Self {
31 info!("SQLite lazy connection pool created");
32
33 Self {
34 config: config.clone(),
35 pool: create_lazy_pool(config),
36 }
37 }
38
39 #[allow(clippy::unused_async)]
44 pub(crate) async fn pool(&self, _target: Option<&str>) -> Result<SqlitePool, SqlError> {
45 Ok(self.pool.clone())
46 }
47}
48
49impl Connection for SqliteConnection {
50 type DB = sqlx::Sqlite;
51
52 async fn pool(&self, target: Option<&str>) -> Result<sqlx::Pool<Self::DB>, SqlError> {
53 self.pool(target).await
54 }
55
56 fn query_timeout(&self) -> Option<u64> {
57 self.config.query_timeout
58 }
59}
60
61fn create_lazy_pool(config: &DatabaseConfig) -> SqlitePool {
65 let conn_ops = SqliteConnectOptions::new().filename(config.name.as_deref().unwrap_or_default());
66 let mut pool_opts = sqlx::pool::PoolOptions::new()
67 .max_connections(1)
68 .min_connections(DatabaseConfig::DEFAULT_MIN_CONNECTIONS)
69 .idle_timeout(Duration::from_secs(DatabaseConfig::DEFAULT_IDLE_TIMEOUT_SECS))
70 .max_lifetime(Duration::from_secs(DatabaseConfig::DEFAULT_MAX_LIFETIME_SECS));
71
72 if let Some(timeout) = config.connection_timeout {
73 pool_opts = pool_opts.acquire_timeout(Duration::from_secs(timeout));
74 }
75
76 pool_opts.connect_lazy_with(conn_ops)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use database_mcp_config::DatabaseBackend;
83
84 fn base_config() -> DatabaseConfig {
85 DatabaseConfig {
86 backend: DatabaseBackend::Sqlite,
87 name: Some("test.db".into()),
88 ..DatabaseConfig::default()
89 }
90 }
91
92 #[tokio::test]
93 async fn create_lazy_pool_returns_idle_pool() {
94 let pool = create_lazy_pool(&base_config());
95 assert_eq!(pool.size(), 0, "pool should be lazy (no connections yet)");
96 }
97
98 #[tokio::test]
99 async fn create_lazy_pool_without_name() {
100 let pool = create_lazy_pool(&DatabaseConfig {
101 name: None,
102 ..base_config()
103 });
104 assert_eq!(pool.size(), 0);
105 }
106
107 #[tokio::test]
108 async fn new_creates_lazy_pool() {
109 let connection = SqliteConnection::new(&base_config());
110 assert_eq!(connection.pool.size(), 0, "pool should be lazy");
111 }
112
113 #[tokio::test]
114 async fn pool_returns_single_pool() {
115 let connection = SqliteConnection::new(&base_config());
116 connection.pool(None).await.expect("None target should succeed");
117 connection
118 .pool(Some("anything"))
119 .await
120 .expect("any target should return the same single pool");
121 }
122}