1use std::collections::VecDeque;
18use std::sync::Arc;
19use std::time::Duration;
20
21use parking_lot::Mutex;
22use tokio::sync::Semaphore;
23use tokio_rusqlite::Connection;
24use tracing::{debug, info, trace, warn};
25
26use crate::config::SqliteConfig;
27use crate::connection::{PooledConnection, SqliteConnection};
28use crate::error::{SqliteError, SqliteResult};
29
30#[derive(Clone)]
46pub struct SqlitePool {
47 config: Arc<SqliteConfig>,
48 semaphore: Arc<Semaphore>,
50 idle_connections: Arc<Mutex<VecDeque<PooledConnection>>>,
52 pool_config: Arc<PoolConfig>,
53 stats: Arc<Mutex<PoolStats>>,
55}
56
57#[derive(Debug, Default, Clone)]
59pub struct PoolStats {
60 pub reuses: u64,
62 pub opens: u64,
64 pub expirations: u64,
66 pub in_use: usize,
75}
76
77impl SqlitePool {
78 pub async fn new(config: SqliteConfig) -> SqliteResult<Self> {
80 Self::with_pool_config(config, PoolConfig::default()).await
81 }
82
83 pub async fn with_pool_config(
85 config: SqliteConfig,
86 pool_config: PoolConfig,
87 ) -> SqliteResult<Self> {
88 info!(
89 path = %config.path_str(),
90 max_connections = %pool_config.max_connections,
91 "SQLite connection pool created"
92 );
93
94 let test_conn = Self::open_connection(&config).await?;
96 drop(test_conn);
97
98 let pool = Self {
99 config: Arc::new(config),
100 semaphore: Arc::new(Semaphore::new(pool_config.max_connections)),
101 idle_connections: Arc::new(Mutex::new(VecDeque::with_capacity(
102 pool_config.max_connections,
103 ))),
104 pool_config: Arc::new(pool_config),
105 stats: Arc::new(Mutex::new(PoolStats::default())),
106 };
107
108 if !pool.config.path.is_memory() && pool.pool_config.min_connections > 0 {
110 debug!(
111 "Pre-warming pool with {} connections",
112 pool.pool_config.min_connections
113 );
114 for _ in 0..pool.pool_config.min_connections {
115 match Self::open_connection(&pool.config).await {
116 Ok(conn) => {
117 let mut idle = pool.idle_connections.lock();
118 idle.push_back(PooledConnection::new(conn));
119 }
120 Err(e) => {
121 warn!(
122 error = %e,
123 "Failed to pre-warm a pooled connection; \
124 continuing with a smaller pool"
125 );
126 }
127 }
128 }
129 }
130
131 Ok(pool)
132 }
133
134 async fn open_connection(config: &SqliteConfig) -> SqliteResult<Connection> {
136 let path = config.path_str().to_string();
137 let init_sql = config.init_sql();
138
139 let conn = if config.path.is_memory() {
140 Connection::open_in_memory().await?
141 } else {
142 Connection::open(&path).await?
143 };
144
145 conn.call(move |conn| {
147 conn.execute_batch(&init_sql)?;
148 Ok(())
149 })
150 .await?;
151
152 #[cfg(feature = "vector")]
166 {
167 use std::sync::Once;
168 static WARN_ONCE: Once = Once::new();
169
170 let _ = conn
171 .call(|conn| {
172 if let Err(e) = crate::vector::register_vector_extension(conn) {
173 WARN_ONCE.call_once(|| {
174 tracing::warn!(
175 error = %e,
176 "sqlite-vector-rs extension could not be registered; \
177 vector SQL functions will be unavailable on this connection. \
178 Build libsqlite_vector_rs.so and set SQLITE_VECTOR_RS_LIB \
179 or place it alongside the test/binary. \
180 (This warning is emitted once per process.)"
181 );
182 });
183 }
184 Ok(())
185 })
186 .await;
187 }
188
189 Ok(conn)
190 }
191
192 pub async fn get(&self) -> SqliteResult<SqliteConnection> {
198 trace!("Acquiring connection from pool");
199
200 let acquire = self.semaphore.clone().acquire_owned();
204 let permit = match self.pool_config.connection_timeout {
205 Some(timeout) => tokio::time::timeout(timeout, acquire)
206 .await
207 .map_err(|_| {
208 SqliteError::timeout(format!(
209 "timed out after {:?} waiting for a connection from the pool",
210 timeout
211 ))
212 })?
213 .map_err(|e| SqliteError::pool(format!("failed to acquire permit: {}", e)))?,
214 None => acquire
215 .await
216 .map_err(|e| SqliteError::pool(format!("failed to acquire permit: {}", e)))?,
217 };
218
219 {
221 let mut stats = self.stats.lock();
222 stats.in_use += 1;
223 }
224
225 if self.config.path.is_memory() {
228 let conn = Self::open_connection(&self.config).await?;
229 {
230 let mut stats = self.stats.lock();
231 stats.opens += 1;
232 }
233 return Ok(SqliteConnection::new_pooled(
234 conn, permit, None, ));
236 }
237
238 let conn: Option<Connection> = {
240 let mut idle = self.idle_connections.lock();
241
242 while let Some(pooled) = idle.pop_front() {
244 let is_expired = if let Some(lifetime) = self.pool_config.max_lifetime {
245 pooled.created_at.elapsed() > lifetime
246 } else {
247 false
248 };
249 let is_idle_expired = if let Some(timeout) = self.pool_config.idle_timeout {
250 pooled.last_used.elapsed() > timeout
251 } else {
252 false
253 };
254
255 if is_expired || is_idle_expired {
256 let mut stats = self.stats.lock();
257 stats.expirations += 1;
258 continue;
260 }
261 let mut stats = self.stats.lock();
263 stats.reuses += 1;
264 return Ok(SqliteConnection::new_pooled(
265 pooled.conn,
266 permit,
267 Some(self.idle_connections.clone()),
268 ));
269 }
270 None
271 };
272
273 if conn.is_none() {
275 debug!("No idle connections, opening new connection");
276 let new_conn = Self::open_connection(&self.config).await?;
277 {
278 let mut stats = self.stats.lock();
279 stats.opens += 1;
280 }
281 return Ok(SqliteConnection::new_pooled(
282 new_conn,
283 permit,
284 Some(self.idle_connections.clone()),
285 ));
286 }
287
288 unreachable!()
289 }
290
291 pub fn config(&self) -> &SqliteConfig {
293 &self.config
294 }
295
296 pub fn pool_config(&self) -> &PoolConfig {
298 &self.pool_config
299 }
300
301 pub fn stats(&self) -> PoolStats {
303 self.stats.lock().clone()
304 }
305
306 pub fn reset_stats(&self) {
308 let mut stats = self.stats.lock();
309 *stats = PoolStats::default();
310 }
311
312 pub async fn is_healthy(&self) -> bool {
314 match Self::open_connection(&self.config).await {
315 Ok(conn) => {
316 let result = conn
317 .call(|conn| {
318 conn.execute("SELECT 1", [])?;
319 Ok(())
320 })
321 .await;
322 result.is_ok()
323 }
324 Err(_) => false,
325 }
326 }
327
328 pub fn available_permits(&self) -> usize {
330 self.semaphore.available_permits()
331 }
332
333 pub fn idle_count(&self) -> usize {
335 self.idle_connections.lock().len()
336 }
337
338 pub fn builder() -> SqlitePoolBuilder {
340 SqlitePoolBuilder::new()
341 }
342}
343
344#[derive(Debug, Clone)]
346pub struct PoolConfig {
347 pub max_connections: usize,
349 pub min_connections: usize,
351 pub connection_timeout: Option<Duration>,
353 pub idle_timeout: Option<Duration>,
355 pub max_lifetime: Option<Duration>,
357}
358
359impl Default for PoolConfig {
360 fn default() -> Self {
361 Self {
362 max_connections: 5, min_connections: 1,
364 connection_timeout: Some(Duration::from_secs(30)),
365 idle_timeout: Some(Duration::from_secs(300)), max_lifetime: Some(Duration::from_secs(1800)), }
368 }
369}
370
371#[derive(Debug, Default)]
373pub struct SqlitePoolBuilder {
374 config: Option<SqliteConfig>,
375 url: Option<String>,
376 pool_config: PoolConfig,
377}
378
379impl SqlitePoolBuilder {
380 pub fn new() -> Self {
382 Self {
383 config: None,
384 url: None,
385 pool_config: PoolConfig::default(),
386 }
387 }
388
389 pub fn url(mut self, url: impl Into<String>) -> Self {
391 self.url = Some(url.into());
392 self
393 }
394
395 pub fn config(mut self, config: SqliteConfig) -> Self {
397 self.config = Some(config);
398 self
399 }
400
401 pub fn max_connections(mut self, n: usize) -> Self {
403 self.pool_config.max_connections = n;
404 self
405 }
406
407 pub fn connection_timeout(mut self, timeout: Duration) -> Self {
409 self.pool_config.connection_timeout = Some(timeout);
410 self
411 }
412
413 pub fn idle_timeout(mut self, timeout: Duration) -> Self {
415 self.pool_config.idle_timeout = Some(timeout);
416 self
417 }
418
419 pub async fn build(self) -> SqliteResult<SqlitePool> {
421 let config = if let Some(config) = self.config {
422 config
423 } else if let Some(url) = self.url {
424 SqliteConfig::from_url(url)?
425 } else {
426 return Err(SqliteError::config("no database URL or config provided"));
427 };
428
429 SqlitePool::with_pool_config(config, self.pool_config).await
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn test_pool_config_default() {
439 let config = PoolConfig::default();
440 assert_eq!(config.max_connections, 5);
441 }
442
443 #[test]
444 fn test_pool_builder() {
445 let builder = SqlitePoolBuilder::new()
446 .url("sqlite::memory:")
447 .max_connections(10);
448
449 assert!(builder.url.is_some());
450 assert_eq!(builder.pool_config.max_connections, 10);
451 }
452
453 #[tokio::test]
454 async fn test_pool_memory() {
455 let pool = SqlitePool::new(SqliteConfig::memory()).await.unwrap();
456 assert!(pool.available_permits() > 0);
459 }
460
461 #[tokio::test]
462 async fn test_pool_get_connection() {
463 let pool = SqlitePool::new(SqliteConfig::memory()).await.unwrap();
464 let conn = pool.get().await;
465 assert!(conn.is_ok());
466 }
467}