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 async fn acquire(&self) -> Result<PooledConnection> {
94 let permit = Arc::clone(&self.semaphore)
95 .acquire_owned()
96 .await
97 .map_err(|_| Error::pool("Connection pool has been closed"))?;
98
99 let connection = loop {
101 let conn = {
102 let mut connections = self.connections.lock().await;
103 connections.pop()
104 };
105
106 match conn {
107 Some(c) if c.is_healthy() => {
108 break c;
110 }
111 Some(_) => {
112 continue;
115 }
116 None => {
117 let client = self.client.clone();
119 break client.connect().await?;
120 }
121 }
122 };
123
124 Ok(PooledConnection {
125 connection: Some(connection),
126 pool: self.connections.clone(),
127 _permit: permit,
128 })
129 }
130
131 pub async fn batch_exec(&self, stmts: &[crate::schema::Statement]) -> Result<()> {
138 if stmts.is_empty() {
139 return Ok(());
140 }
141 let policy = crate::retry::RetryPolicy::default();
142 crate::retry::retry(policy, || async {
143 let mut conn = self.acquire().await?;
144 conn.begin().await?;
145 for (i, stmt) in stmts.iter().enumerate() {
146 if let Err(e) = conn.query(&stmt.query).await {
147 let _ = conn.rollback().await;
148 return Err(Error::query(format!("statement {}: {}", i + 1, e)));
149 }
150 }
151 conn.commit().await
152 })
153 .await
154 }
155
156 pub async fn pipeline_exec(
165 self: &Arc<Self>,
166 stmts: &[crate::schema::Statement],
167 chunk_size: usize,
168 max_workers: usize,
169 ) -> Result<()> {
170 let pool = Arc::clone(self);
171 crate::batch::pipeline_drive(stmts, chunk_size, max_workers, move |chunk| {
172 let pool = Arc::clone(&pool);
173 async move { pool.batch_exec(&chunk).await }
174 })
175 .await
176 }
177
178 pub async fn size(&self) -> usize {
180 self.connections.lock().await.len()
181 }
182
183 pub fn max_size(&self) -> usize {
185 self.max_size
186 }
187}
188
189pub struct PooledConnection {
191 connection: Option<Connection>,
192 pool: Arc<Mutex<Vec<Connection>>>,
193 _permit: tokio::sync::OwnedSemaphorePermit,
194}
195
196impl PooledConnection {
197 pub fn inner(&self) -> &Connection {
205 self.connection
206 .as_ref()
207 .expect("PooledConnection invariant violated: connection was None")
208 }
209}
210
211impl Drop for PooledConnection {
212 fn drop(&mut self) {
213 if let Some(mut conn) = self.connection.take() {
214 if conn.is_healthy() {
217 let pool = self.pool.clone();
218 tokio::spawn(async move {
219 if conn.in_transaction() {
221 let _ = conn.rollback().await;
222 }
223 let mut connections = pool.lock().await;
224 connections.push(conn);
225 });
226 }
227 }
229 }
230}
231
232impl std::ops::Deref for PooledConnection {
233 type Target = Connection;
234
235 fn deref(&self) -> &Self::Target {
236 self.connection
237 .as_ref()
238 .expect("PooledConnection invariant violated: connection was None")
239 }
240}
241
242impl std::ops::DerefMut for PooledConnection {
243 fn deref_mut(&mut self) -> &mut Self::Target {
244 self.connection
245 .as_mut()
246 .expect("PooledConnection invariant violated: connection was None")
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn test_connection_pool_new() {
256 let pool = ConnectionPool::new("localhost", 3141, 10);
257 assert_eq!(pool.max_size(), 10);
258 }
259
260 #[test]
261 fn test_connection_pool_new_different_host() {
262 let pool = ConnectionPool::new("192.168.1.100", 8443, 5);
263 assert_eq!(pool.max_size(), 5);
264 }
265
266 #[test]
267 fn test_connection_pool_new_string_host() {
268 let host = String::from("geode.example.com");
269 let pool = ConnectionPool::new(host, 3141, 20);
270 assert_eq!(pool.max_size(), 20);
271 }
272
273 #[test]
274 fn test_connection_pool_skip_verify() {
275 let pool = ConnectionPool::new("localhost", 3141, 10).skip_verify(true);
276 assert_eq!(pool.max_size(), 10);
278 }
279
280 #[test]
281 fn test_connection_pool_skip_verify_false() {
282 let pool = ConnectionPool::new("localhost", 3141, 10).skip_verify(false);
283 assert_eq!(pool.max_size(), 10);
284 }
285
286 #[test]
287 fn test_connection_pool_page_size() {
288 let pool = ConnectionPool::new("localhost", 3141, 10).page_size(500);
289 assert_eq!(pool.max_size(), 10);
290 }
291
292 #[test]
293 fn test_connection_pool_chained_config() {
294 let pool = ConnectionPool::new("localhost", 3141, 10)
295 .skip_verify(true)
296 .page_size(1000);
297 assert_eq!(pool.max_size(), 10);
298 }
299
300 #[tokio::test]
301 async fn test_connection_pool_initial_size() {
302 let pool = ConnectionPool::new("localhost", 3141, 10);
303 assert_eq!(pool.size().await, 0);
305 }
306
307 #[test]
308 #[should_panic(expected = "ConnectionPool max_size must be at least 1")]
309 fn test_connection_pool_max_size_zero_panics() {
310 let _pool = ConnectionPool::new("localhost", 3141, 0);
313 }
314
315 #[test]
316 fn test_connection_pool_max_size_one() {
317 let pool = ConnectionPool::new("localhost", 3141, 1);
318 assert_eq!(pool.max_size(), 1);
319 }
320
321 #[test]
322 fn test_connection_pool_max_size_large() {
323 let pool = ConnectionPool::new("localhost", 3141, 1000);
324 assert_eq!(pool.max_size(), 1000);
325 }
326
327 #[test]
336 fn test_semaphore_permits_match_max_size() {
337 let pool = ConnectionPool::new("localhost", 3141, 5);
338 assert_eq!(pool.semaphore.available_permits(), 5);
340 }
341
342 #[test]
343 fn test_connections_vec_initially_empty() {
344 let pool = ConnectionPool::new("localhost", 3141, 10);
345 assert_eq!(pool.max_size(), 10);
348 }
349
350 #[test]
351 fn test_pool_from_dsn() {
352 let pool = ConnectionPool::from_dsn("quic://localhost:3141?insecure=true").unwrap();
353 assert_eq!(pool.max_size(), 16); }
355
356 #[test]
357 fn test_pool_from_dsn_with_size() {
358 let pool = ConnectionPool::from_dsn_with_size("grpc://localhost:50051?tls=0", 4).unwrap();
359 assert_eq!(pool.max_size(), 4);
360 }
361
362 #[test]
363 fn test_pool_from_dsn_invalid() {
364 assert!(ConnectionPool::from_dsn("http://localhost:1").is_err());
365 }
366
367 #[test]
368 fn test_pool_from_client() {
369 let client = Client::new("localhost", 3141).skip_verify(true);
370 let pool = ConnectionPool::from_client(client, 8);
371 assert_eq!(pool.max_size(), 8);
372 }
373}