cdbc_pg/options/
mod.rs

1use std::env::var;
2use std::fmt::Display;
3use std::path::{Path, PathBuf};
4
5mod connect;
6mod parse;
7mod pgpass;
8mod ssl_mode;
9use cdbc::{ net::CertificateInput};
10pub use ssl_mode::PgSslMode;
11
12/// Options and flags which can be used to configure a PostgreSQL connection.
13///
14/// A value of `PgConnectOptions` can be parsed from a connection URI,
15/// as described by [libpq](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING).
16///
17/// The general form for a connection URI is:
18///
19/// ```text
20/// postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...]
21/// ```
22///
23/// ## Parameters
24///
25/// |Parameter|Default|Description|
26/// |---------|-------|-----------|
27/// | `sslmode` | `prefer` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [`PgSslMode`]. |
28/// | `sslrootcert` | `None` | Sets the name of a file containing a list of trusted SSL Certificate Authorities. |
29/// | `statement-cache-capacity` | `100` | The maximum number of prepared statements stored in the cache. Set to `0` to disable. |
30/// | `host` | `None` | Path to the directory containing a PostgreSQL unix domain socket, which will be used instead of TCP if set. |
31/// | `hostaddr` | `None` | Same as `host`, but only accepts IP addresses. |
32/// | `application-name` | `None` | The name will be displayed in the pg_stat_activity view and included in CSV log entries. |
33/// | `user` | result of `whoami` | PostgreSQL user name to connect as. |
34/// | `password` | `None` | Password to be used if the server demands password authentication. |
35/// | `port` | `5432` | Port number to connect to at the server host, or socket file name extension for Unix-domain connections. |
36/// | `dbname` | `None` | The database name. |
37/// | `options` | `None` | The runtime parameters to send to the server at connection start. |
38///
39/// The URI scheme designator can be either `postgresql://` or `postgres://`.
40/// Each of the URI parts is optional.
41///
42/// ```text
43/// postgresql://
44/// postgresql://localhost
45/// postgresql://localhost:5433
46/// postgresql://localhost/mydb
47/// postgresql://user@localhost
48/// postgresql://user:secret@localhost
49/// postgresql://localhost?dbname=mydb&user=postgres&password=postgres
50/// ```
51///
52/// # Example
53///
54/// ```rust,no_run
55/// # use cdbc::error::Error;
56/// # use cdbc::connection::{Connection, ConnectOptions};
57/// # use cdbc_pg::{PgConnectOptions, PgConnection, PgSslMode};
58/// #
59/// # fn main() {
60/// # #[cfg(feature = "_rt-async-std")]
61/// # sqlx_rt::async_std::task::block_on::<_, Result<(), Error>>(async move {
62/// // URI connection string
63/// let conn = PgConnection::connect("postgres://localhost/mydb").await?;
64///
65/// // Manually-constructed options
66/// let conn = PgConnectOptions::new()
67///     .host("secret-host")
68///     .port(2525)
69///     .username("secret-user")
70///     .password("secret-password")
71///     .ssl_mode(PgSslMode::Require)
72///     .connect().await?;
73/// # Ok(())
74/// # }).unwrap();
75/// # }
76/// ```
77#[derive(Debug, Clone)]
78pub struct PgConnectOptions {
79    pub(crate) host: String,
80    pub(crate) port: u16,
81    pub(crate) socket: Option<PathBuf>,
82    pub(crate) username: String,
83    pub(crate) password: Option<String>,
84    pub(crate) database: Option<String>,
85    pub(crate) ssl_mode: PgSslMode,
86    pub(crate) ssl_root_cert: Option<CertificateInput>,
87    pub(crate) statement_cache_capacity: usize,
88    pub(crate) application_name: Option<String>,
89    pub(crate) options: Option<String>,
90}
91
92impl Default for PgConnectOptions {
93    fn default() -> Self {
94        Self::new_without_pgpass().apply_pgpass()
95    }
96}
97
98impl PgConnectOptions {
99    /// Creates a new, default set of options ready for configuration.
100    ///
101    /// By default, this reads the following environment variables and sets their
102    /// equivalent options.
103    ///
104    ///  * `PGHOST`
105    ///  * `PGPORT`
106    ///  * `PGUSER`
107    ///  * `PGPASSWORD`
108    ///  * `PGDATABASE`
109    ///  * `PGSSLROOTCERT`
110    ///  * `PGSSLMODE`
111    ///  * `PGAPPNAME`
112    ///
113    /// # Example
114    ///
115    /// ```rust
116    /// # use cdbc_pg::PgConnectOptions;
117    /// let options = PgConnectOptions::new();
118    /// ```
119    pub fn new() -> Self {
120        Self::new_without_pgpass().apply_pgpass()
121    }
122
123    pub fn new_without_pgpass() -> Self {
124        let port = var("PGPORT")
125            .ok()
126            .and_then(|v| v.parse().ok())
127            .unwrap_or(5432);
128
129        let host = var("PGHOST").ok().unwrap_or_else(|| default_host(port));
130
131        let username = var("PGUSER").ok().unwrap_or_else(whoami::username);
132
133        let database = var("PGDATABASE").ok();
134
135        PgConnectOptions {
136            port,
137            host,
138            socket: None,
139            username,
140            password: var("PGPASSWORD").ok(),
141            database,
142            ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
143            ssl_mode: var("PGSSLMODE")
144                .ok()
145                .and_then(|v| v.parse().ok())
146                .unwrap_or_default(),
147            statement_cache_capacity: 100,
148            application_name: var("PGAPPNAME").ok(),
149            options: var("PGOPTIONS").ok(),
150        }
151    }
152
153    pub(crate) fn apply_pgpass(mut self) -> Self {
154        if self.password.is_none() {
155            self.password = pgpass::load_password(
156                &self.host,
157                self.port,
158                &self.username,
159                self.database.as_deref(),
160            );
161        }
162
163        self
164    }
165
166    /// Sets the name of the host to connect to.
167    ///
168    /// If a host name begins with a slash, it specifies
169    /// Unix-domain communication rather than TCP/IP communication; the value is the name of
170    /// the directory in which the socket file is stored.
171    ///
172    /// The default behavior when host is not specified, or is empty,
173    /// is to connect to a Unix-domain socket
174    ///
175    /// # Example
176    ///
177    /// ```rust
178    /// # use cdbc_pg::PgConnectOptions;
179    /// let options = PgConnectOptions::new()
180    ///     .host("localhost");
181    /// ```
182    pub fn host(mut self, host: &str) -> Self {
183        self.host = host.to_owned();
184        self
185    }
186
187    /// Sets the port to connect to at the server host.
188    ///
189    /// The default port for PostgreSQL is `5432`.
190    ///
191    /// # Example
192    ///
193    /// ```rust
194    /// # use cdbc_pg::PgConnectOptions;
195    /// let options = PgConnectOptions::new()
196    ///     .port(5432);
197    /// ```
198    pub fn port(mut self, port: u16) -> Self {
199        self.port = port;
200        self
201    }
202
203    /// Sets a custom path to a directory containing a unix domain socket,
204    /// switching the connection method from TCP to the corresponding socket.
205    ///
206    /// By default set to `None`.
207    pub fn socket(mut self, path: impl AsRef<Path>) -> Self {
208        self.socket = Some(path.as_ref().to_path_buf());
209        self
210    }
211
212    /// Sets the username to connect as.
213    ///
214    /// Defaults to be the same as the operating system name of
215    /// the user running the application.
216    ///
217    /// # Example
218    ///
219    /// ```rust
220    /// # use cdbc_pg::PgConnectOptions;
221    /// let options = PgConnectOptions::new()
222    ///     .username("postgres");
223    /// ```
224    pub fn username(mut self, username: &str) -> Self {
225        self.username = username.to_owned();
226        self
227    }
228
229    /// Sets the password to use if the server demands password authentication.
230    ///
231    /// # Example
232    ///
233    /// ```rust
234    /// # use cdbc_pg::PgConnectOptions;
235    /// let options = PgConnectOptions::new()
236    ///     .username("root")
237    ///     .password("safe-and-secure");
238    /// ```
239    pub fn password(mut self, password: &str) -> Self {
240        self.password = Some(password.to_owned());
241        self
242    }
243
244    /// Sets the database name. Defaults to be the same as the user name.
245    ///
246    /// # Example
247    ///
248    /// ```rust
249    /// # use cdbc_pg::PgConnectOptions;
250    /// let options = PgConnectOptions::new()
251    ///     .database("postgres");
252    /// ```
253    pub fn database(mut self, database: &str) -> Self {
254        self.database = Some(database.to_owned());
255        self
256    }
257
258    /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated
259    /// with the server.
260    ///
261    /// By default, the SSL mode is [`Prefer`](PgSslMode::Prefer), and the client will
262    /// first attempt an SSL connection but fallback to a non-SSL connection on failure.
263    ///
264    /// Ignored for Unix domain socket communication.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// # use cdbc_pg::{PgSslMode, PgConnectOptions};
270    /// let options = PgConnectOptions::new()
271    ///     .ssl_mode(PgSslMode::Require);
272    /// ```
273    pub fn ssl_mode(mut self, mode: PgSslMode) -> Self {
274        self.ssl_mode = mode;
275        self
276    }
277
278    /// Sets the name of a file containing SSL certificate authority (CA) certificate(s).
279    /// If the file exists, the server's certificate will be verified to be signed by
280    /// one of these authorities.
281    ///
282    /// # Example
283    ///
284    /// ```rust
285    /// # use cdbc_pg::{PgSslMode, PgConnectOptions};
286    /// let options = PgConnectOptions::new()
287    ///     // Providing a CA certificate with less than VerifyCa is pointless
288    ///     .ssl_mode(PgSslMode::VerifyCa)
289    ///     .ssl_root_cert("./ca-certificate.crt");
290    /// ```
291    pub fn ssl_root_cert(mut self, cert: impl AsRef<Path>) -> Self {
292        self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
293        self
294    }
295
296    /// Sets PEM encoded trusted SSL Certificate Authorities (CA).
297    ///
298    /// # Example
299    ///
300    /// ```rust
301    /// # use cdbc_pg::{PgSslMode, PgConnectOptions};
302    /// let options = PgConnectOptions::new()
303    ///     // Providing a CA certificate with less than VerifyCa is pointless
304    ///     .ssl_mode(PgSslMode::VerifyCa)
305    ///     .ssl_root_cert_from_pem(vec![]);
306    /// ```
307    pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec<u8>) -> Self {
308        self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate));
309        self
310    }
311
312    /// Sets the capacity of the connection's statement cache in a number of stored
313    /// distinct statements. Caching is handled using LRU, meaning when the
314    /// amount of queries hits the defined limit, the oldest statement will get
315    /// dropped.
316    ///
317    /// The default cache capacity is 100 statements.
318    pub fn statement_cache_capacity(mut self, capacity: usize) -> Self {
319        self.statement_cache_capacity = capacity;
320        self
321    }
322
323    /// Sets the application name. Defaults to None
324    ///
325    /// # Example
326    ///
327    /// ```rust
328    /// # use cdbc_pg::PgConnectOptions;
329    /// let options = PgConnectOptions::new()
330    ///     .application_name("my-app");
331    /// ```
332    pub fn application_name(mut self, application_name: &str) -> Self {
333        self.application_name = Some(application_name.to_owned());
334        self
335    }
336
337    /// Set additional startup options for the connection as a list of key-value pairs.
338    ///
339    /// # Example
340    ///
341    /// ```rust
342    /// # use cdbc_pg::PgConnectOptions;
343    /// let options = PgConnectOptions::new()
344    ///     .options([("geqo", "off"), ("statement_timeout", "5min")]);
345    /// ```
346    pub fn options<K, V, I>(mut self, options: I) -> Self
347        where
348            K: Display,
349            V: Display,
350            I: IntoIterator<Item = (K, V)>,
351    {
352        let mut options_str = String::new();
353        for (k, v) in options {
354            options_str += &format!("-c {}={}", k, v);
355        }
356        if let Some(ref mut v) = self.options {
357            v.push(' ');
358            v.push_str(&options_str);
359        } else {
360            self.options = Some(options_str);
361        }
362        self
363    }
364
365    /// We try using a socket if hostname starts with `/` or if socket parameter
366    /// is specified.
367    pub(crate) fn fetch_socket(&self) -> Option<String> {
368        match self.socket {
369            Some(ref socket) => {
370                let full_path = format!("{}/.s.PGSQL.{}", socket.display(), self.port);
371                Some(full_path)
372            }
373            None if self.host.starts_with('/') => {
374                let full_path = format!("{}/.s.PGSQL.{}", self.host, self.port);
375                Some(full_path)
376            }
377            _ => None,
378        }
379    }
380}
381
382fn default_host(port: u16) -> String {
383    // try to check for the existence of a unix socket and uses that
384    let socket = format!(".s.PGSQL.{}", port);
385    let candidates = [
386        "/var/run/postgresql", // Debian
387        "/private/tmp",        // OSX (homebrew)
388        "/tmp",                // Default
389    ];
390
391    for candidate in &candidates {
392        if Path::new(candidate).join(&socket).exists() {
393            return candidate.to_string();
394        }
395    }
396
397    // fallback to localhost if no socket was found
398    "localhost".to_owned()
399}