1use std::ops::{Deref, DerefMut};
2use std::sync::{Arc, Mutex};
3
4use crate::protocol::async_io::ElefantAsyncReadWrite;
5use crate::protocol::PostgresConnection;
6use crate::types::EnumTypeRegistry;
7use crate::{ElefantClientError, PostgresConnectionSettings};
8use tracing::debug;
9
10pub trait ConnectionFactory {
11 type Connection: ElefantAsyncReadWrite;
12
13 fn create_connection(
14 &self,
15 settings: &PostgresConnectionSettings,
16 ) -> impl std::future::Future<Output = Result<Self::Connection, ElefantClientError>>;
17}
18
19pub struct PostgresPool<F: ConnectionFactory>(Arc<PostgresPoolInner<F>>);
20
21struct PostgresPoolInner<F: ConnectionFactory> {
22 factory: F,
23 settings: PostgresConnectionSettings,
24 idle_connections: Mutex<Vec<crate::postgres_client::PostgresClient<F>>>,
25 enum_registry: Arc<EnumTypeRegistry>,
26}
27
28impl<F: ConnectionFactory> Clone for PostgresPool<F> {
29 fn clone(&self) -> Self {
30 PostgresPool(Arc::clone(&self.0))
31 }
32}
33
34impl<F: ConnectionFactory> PostgresPool<F> {
35 pub async fn new(
36 factory: F,
37 settings: PostgresConnectionSettings,
38 ) -> Result<Self, ElefantClientError> {
39 let empty_registry = Arc::new(EnumTypeRegistry::new());
40
41 if settings.enum_type_names().is_empty() {
42 return Ok(PostgresPool(Arc::new(PostgresPoolInner {
43 factory,
44 settings,
45 idle_connections: Mutex::new(Vec::new()),
46 enum_registry: empty_registry,
47 })));
48 }
49
50 let raw_stream = factory.create_connection(&settings).await?;
52 let (connection, channel_binding_data) =
53 PostgresConnection::new_maybe_tls(raw_stream, &settings).await?;
54 let mut client = crate::postgres_client::PostgresClient::new(
55 connection,
56 &settings,
57 empty_registry,
58 channel_binding_data,
59 )
60 .await?;
61
62 let names_sql = settings
64 .enum_type_names()
65 .iter()
66 .map(|n| format!("'{}'", n.replace('\'', "''")))
67 .collect::<Vec<_>>()
68 .join(", ");
69
70 let query = format!(
71 "SELECT t.typname, n.nspname, t.oid::int4, t.typarray::int4 \
72 FROM pg_catalog.pg_type t \
73 JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid \
74 WHERE t.typtype = 'e' AND (t.typname IN ({names_sql}) \
75 OR (n.nspname || '.' || t.typname) IN ({names_sql}))"
76 );
77
78 let mut registry = EnumTypeRegistry::new();
79
80 let mut query_result = client.query_simple(&query).await?;
81 loop {
82 let result_set = query_result.next_result_set().await?;
83 match result_set {
84 crate::postgres_client::QueryResultSet::QueryProcessingComplete => break,
85 crate::postgres_client::QueryResultSet::RowDescriptionReceived(mut row_reader) => {
86 while let Some(row) = row_reader.next_row().await? {
87 let typname: &str = row.get_text(0)?;
88 let nspname: &str = row.get_text(1)?;
89 let oid: i32 = row.get_text(2)?;
90 let typarray: i32 = row.get_text(3)?;
91
92 let qualified_name = format!("{nspname}.{typname}");
93
94 registry.insert(
96 typname.to_string(),
97 crate::types::EnumOidEntry {
98 oid,
99 array_oid: typarray,
100 schema: nspname.to_string(),
101 },
102 );
103
104 registry.insert(
106 qualified_name,
107 crate::types::EnumOidEntry {
108 oid,
109 array_oid: typarray,
110 schema: nspname.to_string(),
111 },
112 );
113 }
114 }
115 }
116 }
117
118 let registry = Arc::new(registry);
119 client.enum_registry = registry.clone();
120
121 Ok(PostgresPool(Arc::new(PostgresPoolInner {
123 factory,
124 settings,
125 idle_connections: Mutex::new(vec![client]),
126 enum_registry: registry,
127 })))
128 }
129
130 pub fn settings(&self) -> &PostgresConnectionSettings {
131 &self.0.settings
132 }
133
134 pub fn enum_registry(&self) -> &Arc<EnumTypeRegistry> {
135 &self.0.enum_registry
136 }
137
138 pub(crate) fn return_connection(&self, mut client: crate::postgres_client::PostgresClient<F>) {
139 client.pool = None;
140 self.0.idle_connections.lock().unwrap().push(client);
141 }
142
143 pub async fn close(&self) -> Result<(), ElefantClientError> {
147 let clients: Vec<_> = self.0.idle_connections.lock().unwrap().drain(..).collect();
148 for client in clients {
149 client.close().await?;
150 }
151 Ok(())
152 }
153
154 pub async fn get_client(&self) -> Result<PoolableClient<F>, ElefantClientError> {
155 loop {
157 let idle_client = { self.0.idle_connections.lock().unwrap().pop() };
158 match idle_client {
159 Some(mut client) => match client.reset().await {
160 Ok(()) => {
161 client.pool = Some(self.clone());
162 client.enum_registry = self.0.enum_registry.clone();
163 return Ok(PoolableClient {
164 client: Some(client),
165 pool: self.clone(),
166 });
167 }
168 Err(e) => {
169 debug!("Idle connection reset failed, discarding: {:?}", e);
170 continue;
171 }
172 },
173 None => break,
174 }
175 }
176
177 let raw_stream = self.0.factory.create_connection(&self.0.settings).await?;
179 let (connection, channel_binding_data) =
180 PostgresConnection::new_maybe_tls(raw_stream, &self.0.settings).await?;
181 let mut client = crate::postgres_client::PostgresClient::new(
182 connection,
183 &self.0.settings,
184 self.0.enum_registry.clone(),
185 channel_binding_data,
186 )
187 .await?;
188 client.pool = Some(self.clone());
189 Ok(PoolableClient {
190 client: Some(client),
191 pool: self.clone(),
192 })
193 }
194}
195
196pub struct PoolableClient<F: ConnectionFactory> {
197 client: Option<crate::postgres_client::PostgresClient<F>>,
198 pool: PostgresPool<F>,
199}
200
201impl<F: ConnectionFactory> Deref for PoolableClient<F> {
202 type Target = crate::postgres_client::PostgresClient<F>;
203
204 fn deref(&self) -> &Self::Target {
205 self.client
206 .as_ref()
207 .expect("client already returned to pool")
208 }
209}
210
211impl<F: ConnectionFactory> DerefMut for PoolableClient<F> {
212 fn deref_mut(&mut self) -> &mut Self::Target {
213 self.client
214 .as_mut()
215 .expect("client already returned to pool")
216 }
217}
218
219impl<F: ConnectionFactory> PoolableClient<F> {
220 pub async fn close(mut self) -> Result<(), ElefantClientError> {
223 match self.client.take() {
224 Some(client) => client.close().await,
225 None => Ok(()),
226 }
227 }
228}
229
230impl<F: ConnectionFactory> Drop for PoolableClient<F> {
231 fn drop(&mut self) {
232 if let Some(client) = self.client.take() {
233 self.pool.return_connection(client);
234 }
235 }
236}
237
238#[cfg(test)]
239impl<F: ConnectionFactory> PostgresPool<F> {
240 pub(crate) fn idle_connection_count(&self) -> usize {
241 self.0.idle_connections.lock().unwrap().len()
242 }
243}
244
245#[cfg(all(test, feature = "tokio"))]
246mod tests {
247 use crate::test_helpers::get_settings;
248 use crate::tokio_connection::{new_client, TokioConnectionFactory, TokioPostgresPool};
249
250 #[tokio::test]
251 async fn pool_reuses_connection_after_drop() {
252 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
253 .await
254 .unwrap();
255
256 let pid1: i32;
257 {
258 let mut client = pool.get_client().await.unwrap();
259 pid1 = client
260 .read_single_value_simple("select pg_backend_pid()")
261 .await;
262 }
263
264 let mut client2 = pool.get_client().await.unwrap();
265 let pid2: i32 = client2
266 .read_single_value_simple("select pg_backend_pid()")
267 .await;
268
269 assert_eq!(pid1, pid2, "Pool should reuse the same connection");
270 }
271
272 #[tokio::test]
273 async fn pool_creates_new_connection_when_all_checked_out() {
274 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
275 .await
276 .unwrap();
277
278 let mut client1 = pool.get_client().await.unwrap();
279 let mut client2 = pool.get_client().await.unwrap();
280
281 let pid1: i32 = client1
282 .read_single_value_simple("select pg_backend_pid()")
283 .await;
284 let pid2: i32 = client2
285 .read_single_value_simple("select pg_backend_pid()")
286 .await;
287
288 assert_ne!(
289 pid1, pid2,
290 "Simultaneously checked-out clients must be different connections"
291 );
292 }
293
294 #[tokio::test]
295 async fn pool_idle_count_grows_on_drop() {
296 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
297 .await
298 .unwrap();
299
300 assert_eq!(pool.idle_connection_count(), 0);
301
302 let client1 = pool.get_client().await.unwrap();
303 let client2 = pool.get_client().await.unwrap();
304 assert_eq!(pool.idle_connection_count(), 0);
305
306 drop(client1);
307 assert_eq!(pool.idle_connection_count(), 1);
308
309 drop(client2);
310 assert_eq!(pool.idle_connection_count(), 2);
311 }
312
313 #[tokio::test]
314 async fn pool_reset_rolls_back_uncommitted_transaction() {
315 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
316 .await
317 .unwrap();
318
319 {
320 let mut client = pool.get_client().await.unwrap();
321 client.execute_non_query_simple("BEGIN").await.unwrap();
322 client
323 .execute_non_query_simple("CREATE TEMP TABLE pool_txn_test (id int)")
324 .await
325 .unwrap();
326 }
328
329 let mut client = pool.get_client().await.unwrap();
331
332 let result = client
334 .execute_non_query_simple("SELECT 1 FROM pool_txn_test")
335 .await;
336 assert!(
337 result.is_err(),
338 "Temp table should not exist after rollback"
339 );
340 }
341
342 #[tokio::test]
343 async fn pool_reset_recovers_from_failed_transaction() {
344 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
345 .await
346 .unwrap();
347
348 {
349 let mut client = pool.get_client().await.unwrap();
350 client.execute_non_query_simple("BEGIN").await.unwrap();
351 let _ = client
353 .execute_non_query_simple("SELECT * FROM nonexistent_table_that_does_not_exist")
354 .await;
355 }
357
358 let mut client = pool.get_client().await.unwrap();
360 let value: i32 = client.read_single_value_simple("select 42").await;
361 assert_eq!(value, 42);
362 }
363
364 #[tokio::test]
365 async fn pool_clone_shares_state() {
366 let pool1 = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
367 .await
368 .unwrap();
369 let pool2 = pool1.clone();
370
371 let pid1: i32;
372 {
373 let mut client = pool1.get_client().await.unwrap();
374 pid1 = client
375 .read_single_value_simple("select pg_backend_pid()")
376 .await;
377 }
378
379 let mut client2 = pool2.get_client().await.unwrap();
380 let pid2: i32 = client2
381 .read_single_value_simple("select pg_backend_pid()")
382 .await;
383
384 assert_eq!(
385 pid1, pid2,
386 "Cloned pool should share the idle connection vec"
387 );
388 }
389
390 #[tokio::test]
391 async fn pool_client_works_across_multiple_reuse_cycles() {
392 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
393 .await
394 .unwrap();
395
396 for i in 0..5 {
397 let mut client = pool.get_client().await.unwrap();
398 let value: i32 = client
399 .read_single_value_simple(&format!("select {}", i + 1))
400 .await;
401 assert_eq!(value, i + 1);
402 }
403 }
404
405 #[tokio::test]
406 async fn pool_close_terminates_connections() {
407 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
408 .await
409 .unwrap();
410
411 let mut client1 = pool.get_client().await.unwrap();
412 let mut client2 = pool.get_client().await.unwrap();
413
414 let pid1: i32 = client1
415 .read_single_value_simple("select pg_backend_pid()")
416 .await;
417 let pid2: i32 = client2
418 .read_single_value_simple("select pg_backend_pid()")
419 .await;
420
421 client1.close().await.unwrap();
423
424 drop(client2);
426 assert_eq!(pool.idle_connection_count(), 1);
427
428 pool.close().await.unwrap();
430 assert_eq!(pool.idle_connection_count(), 0);
431
432 let mut checker = new_client(get_settings()).await.unwrap();
434 let count: i64 = checker
435 .read_single_value_simple(&format!(
436 "select count(*) from pg_stat_activity where pid in ({}, {})",
437 pid1, pid2
438 ))
439 .await;
440 assert_eq!(
441 count, 0,
442 "All connections should be closed after graceful shutdown"
443 );
444 }
445
446 #[tokio::test]
447 async fn pool_drop_closes_all_connections() {
448 let pid1: i32;
449 let pid2: i32;
450
451 {
452 let pool = TokioPostgresPool::new(TokioConnectionFactory, get_settings())
453 .await
454 .unwrap();
455
456 let mut client1 = pool.get_client().await.unwrap();
457 let mut client2 = pool.get_client().await.unwrap();
458
459 pid1 = client1
460 .read_single_value_simple("select pg_backend_pid()")
461 .await;
462 pid2 = client2
463 .read_single_value_simple("select pg_backend_pid()")
464 .await;
465
466 drop(client1);
468 drop(client2);
469 }
470 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
474
475 let mut checker = new_client(get_settings()).await.unwrap();
477 let count: i64 = checker
478 .read_single_value_simple(&format!(
479 "select count(*) from pg_stat_activity where pid in ({}, {})",
480 pid1, pid2
481 ))
482 .await;
483 assert_eq!(
484 count, 0,
485 "All pool connections should be closed after pool drop"
486 );
487 }
488}