1use std::collections::HashMap;
6use std::ops::{Deref, DerefMut};
7use std::sync::{Condvar, Mutex, MutexGuard};
8use std::time::{Duration, Instant};
9
10use postgres::{Client, Statement};
11use rustls::pki_types::pem::PemObject;
12use rustls::pki_types::CertificateDer;
13use rustls::RootCertStore;
14
15use crate::error::{Db, Error, Result};
16use crate::sql::Q;
17
18pub(crate) type Connector = Box<dyn Fn() -> core::result::Result<Client, postgres::Error> + Send + Sync>;
20
21#[derive(Clone, PartialEq, Eq)]
25pub struct PoolOptions {
26 max_size: usize,
27 checkout_timeout: Duration,
28 pub(crate) webpki_roots: bool,
29 pub(crate) tls_roots: Vec<CertificateDer<'static>>,
30}
31
32impl std::fmt::Debug for PoolOptions {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("PoolOptions")
35 .field("max_size", &self.max_size)
36 .field("checkout_timeout", &self.checkout_timeout)
37 .field("webpki_roots", &self.webpki_roots)
38 .field("tls_root_certificates", &self.tls_roots.len())
39 .finish()
40 }
41}
42
43impl Default for PoolOptions {
44 fn default() -> PoolOptions {
45 PoolOptions::new()
46 }
47}
48
49impl PoolOptions {
50 pub fn new() -> PoolOptions {
53 PoolOptions {
54 max_size: 8,
55 checkout_timeout: Duration::from_secs(30),
56 webpki_roots: true,
57 tls_roots: Vec::new(),
58 }
59 }
60
61 pub fn max_size(mut self, n: usize) -> PoolOptions {
63 self.max_size = n.max(1);
64 self
65 }
66
67 pub fn checkout_timeout(mut self, d: Duration) -> PoolOptions {
70 self.checkout_timeout = d;
71 self
72 }
73
74 pub fn tls_root_certificates_pem(mut self, pem: &[u8]) -> Result<PoolOptions> {
79 let mut check = RootCertStore::empty();
80 let mut n = 0;
81 for cert in CertificateDer::pem_slice_iter(pem) {
82 n += 1;
83 let cert = cert.map_err(|e| Error::Config(format!("root certificate {n} of the PEM bundle: {e}")))?;
84 check
85 .add(cert.clone())
86 .map_err(|e| Error::Config(format!("root certificate {n} of the PEM bundle: {e}")))?;
87 self.tls_roots.push(cert);
88 }
89 if n == 0 {
90 return Err(Error::Config("the PEM bundle holds no certificate".into()));
91 }
92 Ok(self)
93 }
94
95 pub fn tls_webpki_roots(mut self, yes: bool) -> PoolOptions {
99 self.webpki_roots = yes;
100 self
101 }
102}
103
104pub(crate) struct Conn {
106 pub(crate) client: Client,
107 statements: HashMap<Q, Statement>,
108}
109
110impl Conn {
111 pub(crate) fn stmt(&mut self, q: Q) -> Result<Statement> {
113 if let Some(s) = self.statements.get(&q) {
114 return Ok(s.clone());
115 }
116 let s = self.client.prepare_typed(q.sql(), q.types()).db()?;
117 self.statements.insert(q, s.clone());
118 Ok(s)
119 }
120}
121
122struct State {
123 idle: Vec<Conn>,
124 open: usize,
125}
126
127pub(crate) struct Pool {
128 connect: Connector,
129 state: Mutex<State>,
130 freed: Condvar,
131 options: PoolOptions,
132}
133
134const SESSION: &str = "SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED; \
136 SET TIME ZONE 'UTC'; SET DateStyle = 'ISO, YMD'";
137
138impl Pool {
139 pub(crate) fn new(connect: Connector, options: PoolOptions) -> Pool {
140 Pool { connect, state: Mutex::new(State { idle: Vec::new(), open: 0 }), freed: Condvar::new(), options }
141 }
142
143 fn lock(&self) -> MutexGuard<'_, State> {
144 self.state.lock().unwrap_or_else(|e| e.into_inner())
147 }
148
149 pub(crate) fn get(&self) -> Result<Pooled<'_>> {
152 let deadline = Instant::now() + self.options.checkout_timeout;
153 let mut state = self.lock();
154 loop {
155 while let Some(conn) = state.idle.pop() {
156 if conn.client.is_closed() {
157 state.open -= 1;
158 continue;
159 }
160 return Ok(Pooled { pool: self, conn: Some(conn) });
161 }
162 if state.open < self.options.max_size {
163 state.open += 1;
164 drop(state);
165 return match self.open() {
166 Ok(conn) => Ok(Pooled { pool: self, conn: Some(conn) }),
167 Err(e) => {
168 self.lock().open -= 1;
169 self.freed.notify_one();
170 Err(e)
171 }
172 };
173 }
174 let now = Instant::now();
175 if now >= deadline {
176 return Err(Error::PoolTimeout);
177 }
178 state = self.freed.wait_timeout(state, deadline - now).unwrap_or_else(|e| e.into_inner()).0;
179 }
180 }
181
182 fn open(&self) -> Result<Conn> {
183 let mut client = (self.connect)().db()?;
184 client.batch_execute(SESSION).db()?;
185 Ok(Conn { client, statements: HashMap::new() })
186 }
187
188 fn put_back(&self, conn: Conn) {
189 let mut state = self.lock();
190 if conn.client.is_closed() {
191 state.open -= 1;
192 } else {
193 state.idle.push(conn);
194 }
195 drop(state);
196 self.freed.notify_one();
197 }
198}
199
200pub(crate) struct Pooled<'a> {
203 pool: &'a Pool,
204 conn: Option<Conn>,
205}
206
207impl Deref for Pooled<'_> {
208 type Target = Conn;
209 fn deref(&self) -> &Conn {
210 self.conn.as_ref().expect("connection present until drop")
212 }
213}
214
215impl DerefMut for Pooled<'_> {
216 fn deref_mut(&mut self) -> &mut Conn {
217 self.conn.as_mut().expect("connection present until drop")
218 }
219}
220
221impl Drop for Pooled<'_> {
222 fn drop(&mut self) {
223 if let Some(conn) = self.conn.take() {
224 self.pool.put_back(conn);
225 }
226 }
227}