use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::sync::{Condvar, Mutex, MutexGuard};
use std::time::{Duration, Instant};
use postgres::{Client, Statement};
use crate::error::{Error, Result};
use crate::sql::Q;
pub(crate) type Connector = Box<dyn Fn() -> core::result::Result<Client, postgres::Error> + Send + Sync>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PoolOptions {
max_size: usize,
checkout_timeout: Duration,
}
impl Default for PoolOptions {
fn default() -> PoolOptions {
PoolOptions::new()
}
}
impl PoolOptions {
pub fn new() -> PoolOptions {
PoolOptions { max_size: 8, checkout_timeout: Duration::from_secs(30) }
}
pub fn max_size(mut self, n: usize) -> PoolOptions {
self.max_size = n.max(1);
self
}
pub fn checkout_timeout(mut self, d: Duration) -> PoolOptions {
self.checkout_timeout = d;
self
}
}
pub(crate) struct Conn {
pub(crate) client: Client,
statements: HashMap<Q, Statement>,
}
impl Conn {
pub(crate) fn stmt(&mut self, q: Q) -> core::result::Result<Statement, postgres::Error> {
if let Some(s) = self.statements.get(&q) {
return Ok(s.clone());
}
let s = self.client.prepare_typed(q.sql(), q.types())?;
self.statements.insert(q, s.clone());
Ok(s)
}
}
struct State {
idle: Vec<Conn>,
open: usize,
}
pub(crate) struct Pool {
connect: Connector,
state: Mutex<State>,
freed: Condvar,
options: PoolOptions,
}
const SESSION: &str = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED; \
SET TIME ZONE 'UTC'; SET DateStyle = 'ISO, YMD'";
impl Pool {
pub(crate) fn new(connect: Connector, options: PoolOptions) -> Pool {
Pool { connect, state: Mutex::new(State { idle: Vec::new(), open: 0 }), freed: Condvar::new(), options }
}
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
pub(crate) fn get(&self) -> Result<Pooled<'_>> {
let deadline = Instant::now() + self.options.checkout_timeout;
let mut state = self.lock();
loop {
while let Some(conn) = state.idle.pop() {
if conn.client.is_closed() {
state.open -= 1;
continue;
}
return Ok(Pooled { pool: self, conn: Some(conn) });
}
if state.open < self.options.max_size {
state.open += 1;
drop(state);
return match self.open() {
Ok(conn) => Ok(Pooled { pool: self, conn: Some(conn) }),
Err(e) => {
self.lock().open -= 1;
self.freed.notify_one();
Err(e)
}
};
}
let now = Instant::now();
if now >= deadline {
return Err(Error::PoolTimeout);
}
state = self.freed.wait_timeout(state, deadline - now).unwrap_or_else(|e| e.into_inner()).0;
}
}
fn open(&self) -> Result<Conn> {
let mut client = (self.connect)()?;
client.batch_execute(SESSION)?;
Ok(Conn { client, statements: HashMap::new() })
}
fn put_back(&self, conn: Conn) {
let mut state = self.lock();
if conn.client.is_closed() {
state.open -= 1;
} else {
state.idle.push(conn);
}
drop(state);
self.freed.notify_one();
}
}
pub(crate) struct Pooled<'a> {
pool: &'a Pool,
conn: Option<Conn>,
}
impl Deref for Pooled<'_> {
type Target = Conn;
fn deref(&self) -> &Conn {
self.conn.as_ref().expect("connection present until drop")
}
}
impl DerefMut for Pooled<'_> {
fn deref_mut(&mut self) -> &mut Conn {
self.conn.as_mut().expect("connection present until drop")
}
}
impl Drop for Pooled<'_> {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
self.pool.put_back(conn);
}
}
}