1use std::sync::Arc;
4use tokio::sync::{Mutex, Semaphore};
5
6use crate::client::{Client, Connection};
7use crate::error::{Error, Result};
8
9pub const DEFAULT_POOL_MAX_SIZE: usize = 16;
11
12pub struct ConnectionPool {
14 client: Client,
15 connections: Arc<Mutex<Vec<Connection>>>,
16 semaphore: Arc<Semaphore>,
17 max_size: usize,
18}
19
20impl ConnectionPool {
21 pub fn new(host: impl Into<String>, port: u16, max_size: usize) -> Self {
28 assert!(
29 max_size > 0,
30 "ConnectionPool max_size must be at least 1 (was 0). \
31 A pool with 0 connections would deadlock on acquire()."
32 );
33 Self {
34 client: Client::new(host, port),
35 connections: Arc::new(Mutex::new(Vec::new())),
36 semaphore: Arc::new(Semaphore::new(max_size)),
37 max_size,
38 }
39 }
40
41 pub fn from_dsn(dsn: &str) -> Result<Self> {
46 Self::from_dsn_with_size(dsn, DEFAULT_POOL_MAX_SIZE)
47 }
48
49 pub fn from_dsn_with_size(dsn: &str, max_size: usize) -> Result<Self> {
57 assert!(max_size > 0, "ConnectionPool max_size must be at least 1");
58 let client = Client::from_dsn(dsn)?;
59 Ok(Self::from_client(client, max_size))
60 }
61
62 pub fn from_client(client: Client, max_size: usize) -> Self {
67 assert!(max_size > 0, "ConnectionPool max_size must be at least 1");
68 Self {
69 client,
70 connections: Arc::new(Mutex::new(Vec::new())),
71 semaphore: Arc::new(Semaphore::new(max_size)),
72 max_size,
73 }
74 }
75
76 pub fn skip_verify(mut self, skip: bool) -> Self {
78 self.client = self.client.skip_verify(skip);
79 self
80 }
81
82 pub fn page_size(mut self, size: usize) -> Self {
84 self.client = self.client.page_size(size);
85 self
86 }
87
88 pub fn username(mut self, username: impl Into<String>) -> Self {
90 self.client = self.client.username(username);
91 self
92 }
93
94 pub fn password(mut self, password: impl Into<String>) -> Self {
96 self.client = self.client.password(password);
97 self
98 }
99
100 pub async fn acquire(&self) -> Result<PooledConnection> {
106 let permit = Arc::clone(&self.semaphore)
107 .acquire_owned()
108 .await
109 .map_err(|_| Error::pool("Connection pool has been closed"))?;
110
111 let connection = loop {
113 let conn = {
114 let mut connections = self.connections.lock().await;
115 connections.pop()
116 };
117
118 match conn {
119 Some(c) if c.is_healthy() => {
120 break c;
122 }
123 Some(_) => {
124 continue;
127 }
128 None => {
129 let client = self.client.clone();
131 break client.connect().await?;
132 }
133 }
134 };
135
136 Ok(PooledConnection {
137 connection: Some(connection),
138 pool: self.connections.clone(),
139 _permit: permit,
140 })
141 }
142
143 pub async fn batch_exec(&self, stmts: &[crate::schema::Statement]) -> Result<()> {
150 if stmts.is_empty() {
151 return Ok(());
152 }
153 let policy = crate::retry::RetryPolicy::default();
154 crate::retry::retry(policy, || async {
155 let mut conn = self.acquire().await?;
156 conn.begin().await?;
157 for (i, stmt) in stmts.iter().enumerate() {
158 if let Err(e) = conn.query(&stmt.query).await {
159 let _ = conn.rollback().await;
160 return Err(Error::query(format!("statement {}: {}", i + 1, e)));
161 }
162 }
163 conn.commit().await
164 })
165 .await
166 }
167
168 pub async fn pipeline_exec(
177 self: &Arc<Self>,
178 stmts: &[crate::schema::Statement],
179 chunk_size: usize,
180 max_workers: usize,
181 ) -> Result<()> {
182 let pool = Arc::clone(self);
183 crate::batch::pipeline_drive(stmts, chunk_size, max_workers, move |chunk| {
184 let pool = Arc::clone(&pool);
185 async move { pool.batch_exec(&chunk).await }
186 })
187 .await
188 }
189
190 pub async fn size(&self) -> usize {
192 self.connections.lock().await.len()
193 }
194
195 pub fn max_size(&self) -> usize {
197 self.max_size
198 }
199}
200
201pub struct PooledConnection {
203 connection: Option<Connection>,
204 pool: Arc<Mutex<Vec<Connection>>>,
205 _permit: tokio::sync::OwnedSemaphorePermit,
206}
207
208impl PooledConnection {
209 pub fn inner(&self) -> &Connection {
217 self.connection
218 .as_ref()
219 .expect("PooledConnection invariant violated: connection was None")
220 }
221}
222
223impl Drop for PooledConnection {
224 fn drop(&mut self) {
225 if let Some(mut conn) = self.connection.take() {
226 if conn.is_healthy() {
229 let pool = self.pool.clone();
230 tokio::spawn(async move {
231 if conn.in_transaction() {
233 let _ = conn.rollback().await;
234 }
235 let mut connections = pool.lock().await;
236 connections.push(conn);
237 });
238 }
239 }
241 }
242}
243
244impl std::ops::Deref for PooledConnection {
245 type Target = Connection;
246
247 fn deref(&self) -> &Self::Target {
248 self.connection
249 .as_ref()
250 .expect("PooledConnection invariant violated: connection was None")
251 }
252}
253
254impl std::ops::DerefMut for PooledConnection {
255 fn deref_mut(&mut self) -> &mut Self::Target {
256 self.connection
257 .as_mut()
258 .expect("PooledConnection invariant violated: connection was None")
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_connection_pool_new() {
268 let pool = ConnectionPool::new("localhost", 3141, 10);
269 assert_eq!(pool.max_size(), 10);
270 }
271
272 #[test]
273 fn test_connection_pool_new_different_host() {
274 let pool = ConnectionPool::new("192.168.1.100", 8443, 5);
275 assert_eq!(pool.max_size(), 5);
276 }
277
278 #[test]
279 fn test_connection_pool_new_string_host() {
280 let host = String::from("geode.example.com");
281 let pool = ConnectionPool::new(host, 3141, 20);
282 assert_eq!(pool.max_size(), 20);
283 }
284
285 #[test]
286 fn test_connection_pool_skip_verify() {
287 let pool = ConnectionPool::new("localhost", 3141, 10).skip_verify(true);
288 assert_eq!(pool.max_size(), 10);
290 }
291
292 #[test]
293 fn test_connection_pool_skip_verify_false() {
294 let pool = ConnectionPool::new("localhost", 3141, 10).skip_verify(false);
295 assert_eq!(pool.max_size(), 10);
296 }
297
298 #[test]
299 fn test_connection_pool_page_size() {
300 let pool = ConnectionPool::new("localhost", 3141, 10).page_size(500);
301 assert_eq!(pool.max_size(), 10);
302 }
303
304 #[test]
305 fn test_connection_pool_chained_config() {
306 let pool = ConnectionPool::new("localhost", 3141, 10)
307 .skip_verify(true)
308 .page_size(1000);
309 assert_eq!(pool.max_size(), 10);
310 }
311
312 #[tokio::test]
313 async fn test_connection_pool_initial_size() {
314 let pool = ConnectionPool::new("localhost", 3141, 10);
315 assert_eq!(pool.size().await, 0);
317 }
318
319 #[test]
320 #[should_panic(expected = "ConnectionPool max_size must be at least 1")]
321 fn test_connection_pool_max_size_zero_panics() {
322 let _pool = ConnectionPool::new("localhost", 3141, 0);
325 }
326
327 #[test]
328 fn test_connection_pool_max_size_one() {
329 let pool = ConnectionPool::new("localhost", 3141, 1);
330 assert_eq!(pool.max_size(), 1);
331 }
332
333 #[test]
334 fn test_connection_pool_max_size_large() {
335 let pool = ConnectionPool::new("localhost", 3141, 1000);
336 assert_eq!(pool.max_size(), 1000);
337 }
338
339 #[test]
348 fn test_semaphore_permits_match_max_size() {
349 let pool = ConnectionPool::new("localhost", 3141, 5);
350 assert_eq!(pool.semaphore.available_permits(), 5);
352 }
353
354 #[test]
355 fn test_connections_vec_initially_empty() {
356 let pool = ConnectionPool::new("localhost", 3141, 10);
357 assert_eq!(pool.max_size(), 10);
360 }
361
362 #[test]
363 fn test_pool_from_dsn() {
364 let pool = ConnectionPool::from_dsn("quic://localhost:3141?insecure=true").unwrap();
365 assert_eq!(pool.max_size(), 16); }
367
368 #[test]
369 fn test_pool_from_dsn_with_size() {
370 let pool = ConnectionPool::from_dsn_with_size("grpc://localhost:50051?tls=0", 4).unwrap();
371 assert_eq!(pool.max_size(), 4);
372 }
373
374 #[test]
375 fn test_pool_from_dsn_invalid() {
376 assert!(ConnectionPool::from_dsn("http://localhost:1").is_err());
377 }
378
379 #[test]
380 fn test_pool_from_client() {
381 let client = Client::new("localhost", 3141).skip_verify(true);
382 let pool = ConnectionPool::from_client(client, 8);
383 assert_eq!(pool.max_size(), 8);
384 }
385
386 #[tokio::test]
387 async fn test_pool_batch_exec_empty_is_noop() {
388 let pool = ConnectionPool::new("localhost", 3141, 4);
389 assert!(pool.batch_exec(&[]).await.is_ok());
391 }
392
393 #[tokio::test]
394 async fn test_pool_pipeline_exec_empty_is_noop() {
395 let pool = Arc::new(ConnectionPool::new("localhost", 3141, 4));
396 assert!(pool.pipeline_exec(&[], 0, 0).await.is_ok());
397 }
398}