use crate::config::AkitaConfig;
use crate::database_err;
use crate::prelude::AkitaError;
pub type PostgresPool = r2d2::Pool<PostgresConnectionManager>;
pub type PostgresConnection = r2d2::PooledConnection<PostgresConnectionManager>;
#[allow(unused)]
#[derive(Clone, Debug)]
pub struct PostgresConnectionManager {
config: postgres::Config,
connection_timeout: std::time::Duration,
application_name: Option<String>,
cfg: AkitaConfig,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum SslMode {
Disable,
Prefer,
Require,
}
impl From<SslMode> for postgres::config::SslMode {
fn from(mode: SslMode) -> Self {
match mode {
SslMode::Disable => postgres::config::SslMode::Disable,
SslMode::Prefer => postgres::config::SslMode::Prefer,
SslMode::Require => postgres::config::SslMode::Require,
}
}
}
impl PostgresConnectionManager {
pub fn new(cfg: &AkitaConfig) -> Result<Self, AkitaError> {
let config = cfg.into();
Ok(Self {
config,
connection_timeout: cfg.get_connection_timeout(),
application_name: None,
cfg: cfg.clone(),
})
}
pub fn from_params(
host: &str,
port: u16,
database: &str,
username: &str,
password: &str,
) -> Self {
let mut config = postgres::Config::new();
config.host(host);
config.port(port);
config.dbname(database);
config.user(username);
config.password(password);
Self {
config,
connection_timeout: std::time::Duration::from_secs(30),
application_name: None,
cfg: Default::default(),
}
}
pub fn with_connection_timeout(mut self, timeout: std::time::Duration) -> Self {
self.connection_timeout = timeout;
self
}
pub fn with_application_name(mut self, name: &str) -> Self {
self.application_name = Some(name.to_string());
self
}
pub fn with_ssl_mode(mut self, mode: SslMode) -> Self {
self.config.ssl_mode(mode.into());
self
}
pub fn with_option(mut self, key: &str, value: &str) -> Self {
self.config.options(&format!("{}={}", key, value));
self
}
}
impl r2d2::ManageConnection for PostgresConnectionManager {
type Connection = postgres::Client;
type Error = postgres::Error;
fn connect(&self) -> std::result::Result<Self::Connection, Self::Error> {
let mut config = self.config.clone();
if let Some(app_name) = &self.application_name {
config.application_name(app_name);
}
config.connect_timeout(self.connection_timeout);
config.connect(postgres::NoTls)
}
fn is_valid(&self, conn: &mut Self::Connection) -> std::result::Result<(), Self::Error> {
conn.simple_query("SELECT 1").map(|_| ())
}
fn has_broken(&self, conn: &mut Self::Connection) -> bool {
conn.is_closed()
}
}
pub fn init_postgres_pool(cfg: AkitaConfig) -> Result<PostgresPool, AkitaError> {
let manager = PostgresConnectionManager::new(&cfg)?;
let pool = r2d2::Pool::builder()
.connection_timeout(cfg.get_connection_timeout())
.min_idle(cfg.get_min_idle().into())
.max_size(cfg.get_max_size())
.idle_timeout(Some(cfg.get_idle_timeout()))
.max_lifetime(Some(cfg.get_max_lifetime()))
.test_on_check_out(cfg.get_test_on_check_out())
.build(manager)
.map_err(|e| {
database_err!(format!(
"Failed to create PostgreSQL connection pool: {}",
e
))
})?;
let mut conn = pool.get()?;
conn.simple_query("SELECT 1")?;
Ok(pool)
}
impl From<AkitaConfig> for postgres::Config {
fn from(v: AkitaConfig) -> Self {
postgres::Config::from(&v)
}
}
impl From<&AkitaConfig> for postgres::Config {
fn from(config: &AkitaConfig) -> Self {
let mut cfg = postgres::Config::new();
if let Ok(Some(host)) = config.get_hostname() {
cfg.host(&host);
}
if let Ok(Some(port)) = config.get_port() {
cfg.port(port);
}
if let Ok(Some(db)) = config.get_database() {
cfg.dbname(&db);
}
if let Ok(Some(username)) = config.get_username() {
cfg.user(&username);
}
if let Ok(Some(password)) = config.get_password() {
cfg.password(&password);
}
cfg.connect_timeout(config.get_connection_timeout());
cfg
}
}