//! Connection wrapper providing health checks and metadata //! //! Wraps tokio_postgres::Client with lifecycle tracking and validation capabilities. use crate::error::Result; use std::time::{Duration, Instant}; /// Wrapper around tokio_postgres::Client with lifecycle tracking /// /// Tracks creation time and last usage to enable connection lifecycle /// management (max_lifetime, idle timeout detection). pub struct Connection { client: tokio_postgres::Client, created_at: Instant, last_used: Instant, } impl Connection { /// Create a new Connection wrapper /// /// # Arguments /// * `client` - The underlying tokio_postgres client /// /// # Returns /// A new Connection instance with timestamps initialized to now pub fn new(client: tokio_postgres::Client) -> Self { let now = Instant::now(); Self { client, created_at: now, last_used: now } } /// Check if the connection is still valid /// /// Executes a simple `SELECT 1` query to verify the connection is alive /// and can communicate with the database. /// /// # Returns /// * `Ok(true)` if connection is valid /// * `Ok(false)` if connection failed validation /// * `Err` if validation could not be performed /// /// # Errors /// Returns `PoolError::ConnectionFailed` if the validation query fails pub async fn is_valid(&mut self) -> Result { match self.client.query_one("SELECT 1", &[]).await { Ok(_) => { self.touch(); Ok(true) } Err(_e) => { // Connection failed - this is expected behavior, not an error // We return Ok(false) to indicate validation failed Ok(false) } } } /// Get the age of this connection since creation /// /// # Returns /// Duration since the connection was created pub fn age(&self) -> Duration { self.created_at.elapsed() } /// Get the idle time since last use /// /// # Returns /// Duration since the connection was last used pub fn idle_time(&self) -> Duration { self.last_used.elapsed() } /// Update the last_used timestamp to now /// /// Should be called whenever the connection is checked out or used /// to accurately track idle time. pub fn touch(&mut self) { self.last_used = Instant::now(); } /// Access the underlying tokio_postgres client /// /// # Returns /// Reference to the wrapped Client for executing queries pub fn client(&self) -> &tokio_postgres::Client { &self.client } } impl std::fmt::Debug for Connection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Connection") .field("created_at", &self.created_at) .field("last_used", &self.last_used) .field("age", &self.age()) .field("idle_time", &self.idle_time()) .finish_non_exhaustive() } } #[cfg(test)] mod tests { use super::*; use std::time::Duration; // Note: Connection wraps tokio_postgres::Client, which requires a real // database connection. Full functionality is tested in integration tests // (tests/basic.rs). These unit tests verify the metadata tracking logic. #[test] fn test_instant_elapsed() { // Verify Instant::elapsed() works as expected for age/idle_time let now = Instant::now(); std::thread::sleep(Duration::from_millis(10)); let elapsed = now.elapsed(); assert!(elapsed >= Duration::from_millis(10)); } #[test] fn test_timestamp_comparison() { // Verify that touch() logic would work correctly let t1 = Instant::now(); std::thread::sleep(Duration::from_millis(10)); let t2 = Instant::now(); // Older timestamp has longer elapsed time assert!(t1.elapsed() > t2.elapsed()); } }