sqlx_core/postgres/options/
mod.rs

1use std::borrow::Cow;
2use std::env::var;
3use std::fmt::{Display, Write};
4use std::path::{Path, PathBuf};
5
6mod connect;
7mod parse;
8mod pgpass;
9mod ssl_mode;
10use crate::{connection::LogSettings, net::CertificateInput};
11pub use ssl_mode::PgSslMode;
12
13/// Options and flags which can be used to configure a PostgreSQL connection.
14///
15/// A value of `PgConnectOptions` can be parsed from a connection URL,
16/// as described by [libpq](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING).
17///
18/// The general form for a connection URL is:
19///
20/// ```text
21/// postgresql://[user[:password]@][host][:port][/dbname][?param1=value1&...]
22/// ```
23///
24/// ## Parameters
25///
26/// |Parameter|Default|Description|
27/// |---------|-------|-----------|
28/// | `sslmode` | `prefer` | Determines whether or with what priority a secure SSL TCP/IP connection will be negotiated. See [`PgSslMode`]. |
29/// | `sslrootcert` | `None` | Sets the name of a file containing a list of trusted SSL Certificate Authorities. |
30/// | `statement-cache-capacity` | `100` | The maximum number of prepared statements stored in the cache. Set to `0` to disable. |
31/// | `host` | `None` | Path to the directory containing a PostgreSQL unix domain socket, which will be used instead of TCP if set. |
32/// | `hostaddr` | `None` | Same as `host`, but only accepts IP addresses. |
33/// | `application-name` | `None` | The name will be displayed in the pg_stat_activity view and included in CSV log entries. |
34/// | `user` | result of `whoami` | PostgreSQL user name to connect as. |
35/// | `password` | `None` | Password to be used if the server demands password authentication. |
36/// | `port` | `5432` | Port number to connect to at the server host, or socket file name extension for Unix-domain connections. |
37/// | `dbname` | `None` | The database name. |
38/// | `options` | `None` | The runtime parameters to send to the server at connection start. |
39///
40/// The URL scheme designator can be either `postgresql://` or `postgres://`.
41/// Each of the URL parts is optional.
42///
43/// ```text
44/// postgresql://
45/// postgresql://localhost
46/// postgresql://localhost:5433
47/// postgresql://localhost/mydb
48/// postgresql://user@localhost
49/// postgresql://user:secret@localhost
50/// postgresql://localhost?dbname=mydb&user=postgres&password=postgres
51/// ```
52///
53/// # Example
54///
55/// ```rust,no_run
56/// # use sqlx_core::error::Error;
57/// # use sqlx_core::connection::{Connection, ConnectOptions};
58/// # use sqlx_core::postgres::{PgConnectOptions, PgConnection, PgSslMode};
59/// #
60/// # fn main() {
61/// # #[cfg(feature = "_rt-async-std")]
62/// # sqlx_rt::async_std::task::block_on::<_, Result<(), Error>>(async move {
63/// // URL connection string
64/// let conn = PgConnection::connect("postgres://localhost/mydb").await?;
65///
66/// // Manually-constructed options
67/// let conn = PgConnectOptions::new()
68///     .host("secret-host")
69///     .port(2525)
70///     .username("secret-user")
71///     .password("secret-password")
72///     .ssl_mode(PgSslMode::Require)
73///     .connect().await?;
74/// # Ok(())
75/// # }).unwrap();
76/// # }
77/// ```
78#[derive(Debug, Clone)]
79pub struct PgConnectOptions {
80    pub(crate) host: String,
81    pub(crate) port: u16,
82    pub(crate) socket: Option<PathBuf>,
83    pub(crate) username: String,
84    pub(crate) password: Option<String>,
85    pub(crate) database: Option<String>,
86    pub(crate) ssl_mode: PgSslMode,
87    pub(crate) ssl_root_cert: Option<CertificateInput>,
88    pub(crate) statement_cache_capacity: usize,
89    pub(crate) application_name: Option<String>,
90    pub(crate) log_settings: LogSettings,
91    pub(crate) extra_float_digits: Option<Cow<'static, str>>,
92    pub(crate) options: Option<String>,
93}
94
95impl Default for PgConnectOptions {
96    fn default() -> Self {
97        Self::new_without_pgpass().apply_pgpass()
98    }
99}
100
101impl PgConnectOptions {
102    /// Creates a new, default set of options ready for configuration.
103    ///
104    /// By default, this reads the following environment variables and sets their
105    /// equivalent options.
106    ///
107    ///  * `PGHOST`
108    ///  * `PGPORT`
109    ///  * `PGUSER`
110    ///  * `PGPASSWORD`
111    ///  * `PGDATABASE`
112    ///  * `PGSSLROOTCERT`
113    ///  * `PGSSLMODE`
114    ///  * `PGAPPNAME`
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// # use sqlx_core::postgres::PgConnectOptions;
120    /// let options = PgConnectOptions::new();
121    /// ```
122    pub fn new() -> Self {
123        Self::new_without_pgpass().apply_pgpass()
124    }
125
126    pub fn new_without_pgpass() -> Self {
127        let port = var("PGPORT")
128            .ok()
129            .and_then(|v| v.parse().ok())
130            .unwrap_or(5432);
131
132        let host = var("PGHOST").ok().unwrap_or_else(|| default_host(port));
133
134        // let username = var("PGUSER").ok().unwrap_or_else(whoami::username);
135        let username = var("PGUSER").ok().unwrap_or_else(|| "postgres".into());
136
137        let database = var("PGDATABASE").ok();
138
139        PgConnectOptions {
140            port,
141            host,
142            socket: None,
143            username,
144            password: var("PGPASSWORD").ok(),
145            database,
146            ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
147            ssl_mode: var("PGSSLMODE")
148                .ok()
149                .and_then(|v| v.parse().ok())
150                .unwrap_or_default(),
151            statement_cache_capacity: 100,
152            application_name: var("PGAPPNAME").ok(),
153            extra_float_digits: Some("3".into()),
154            log_settings: Default::default(),
155            options: var("PGOPTIONS").ok(),
156        }
157    }
158
159    pub(crate) fn apply_pgpass(mut self) -> Self {
160        if self.password.is_none() {
161            self.password = pgpass::load_password(
162                &self.host,
163                self.port,
164                &self.username,
165                self.database.as_deref(),
166            );
167        }
168
169        self
170    }
171
172    /// Sets the name of the host to connect to.
173    ///
174    /// If a host name begins with a slash, it specifies
175    /// Unix-domain communication rather than TCP/IP communication; the value is the name of
176    /// the directory in which the socket file is stored.
177    ///
178    /// The default behavior when host is not specified, or is empty,
179    /// is to connect to a Unix-domain socket
180    ///
181    /// # Example
182    ///
183    /// ```rust
184    /// # use sqlx_core::postgres::PgConnectOptions;
185    /// let options = PgConnectOptions::new()
186    ///     .host("localhost");
187    /// ```
188    pub fn host(mut self, host: &str) -> Self {
189        self.host = host.to_owned();
190        self
191    }
192
193    /// Sets the port to connect to at the server host.
194    ///
195    /// The default port for PostgreSQL is `5432`.
196    ///
197    /// # Example
198    ///
199    /// ```rust
200    /// # use sqlx_core::postgres::PgConnectOptions;
201    /// let options = PgConnectOptions::new()
202    ///     .port(5432);
203    /// ```
204    pub fn port(mut self, port: u16) -> Self {
205        self.port = port;
206        self
207    }
208
209    /// Sets a custom path to a directory containing a unix domain socket,
210    /// switching the connection method from TCP to the corresponding socket.
211    ///
212    /// By default set to `None`.
213    pub fn socket(mut self, path: impl AsRef<Path>) -> Self {
214        self.socket = Some(path.as_ref().to_path_buf());
215        self
216    }
217
218    /// Sets the username to connect as.
219    ///
220    /// Defaults to be the same as the operating system name of
221    /// the user running the application.
222    ///
223    /// # Example
224    ///
225    /// ```rust
226    /// # use sqlx_core::postgres::PgConnectOptions;
227    /// let options = PgConnectOptions::new()
228    ///     .username("postgres");
229    /// ```
230    pub fn username(mut self, username: &str) -> Self {
231        self.username = username.to_owned();
232        self
233    }
234
235    /// Sets the password to use if the server demands password authentication.
236    ///
237    /// # Example
238    ///
239    /// ```rust
240    /// # use sqlx_core::postgres::PgConnectOptions;
241    /// let options = PgConnectOptions::new()
242    ///     .username("root")
243    ///     .password("safe-and-secure");
244    /// ```
245    pub fn password(mut self, password: &str) -> Self {
246        self.password = Some(password.to_owned());
247        self
248    }
249
250    /// Sets the database name. Defaults to be the same as the user name.
251    ///
252    /// # Example
253    ///
254    /// ```rust
255    /// # use sqlx_core::postgres::PgConnectOptions;
256    /// let options = PgConnectOptions::new()
257    ///     .database("postgres");
258    /// ```
259    pub fn database(mut self, database: &str) -> Self {
260        self.database = Some(database.to_owned());
261        self
262    }
263
264    /// Get the current database name.
265    ///
266    /// # Example
267    ///
268    /// ```rust
269    /// # use sqlx_core::postgres::PgConnectOptions;
270    /// let options = PgConnectOptions::new()
271    ///     .database("postgres");
272    /// assert!(options.get_database().is_some());
273    /// ```
274    pub fn get_database(&self) -> Option<&str> {
275        self.database.as_deref()
276    }
277
278    /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated
279    /// with the server.
280    ///
281    /// By default, the SSL mode is [`Prefer`](PgSslMode::Prefer), and the client will
282    /// first attempt an SSL connection but fallback to a non-SSL connection on failure.
283    ///
284    /// Ignored for Unix domain socket communication.
285    ///
286    /// # Example
287    ///
288    /// ```rust
289    /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions};
290    /// let options = PgConnectOptions::new()
291    ///     .ssl_mode(PgSslMode::Require);
292    /// ```
293    pub fn ssl_mode(mut self, mode: PgSslMode) -> Self {
294        self.ssl_mode = mode;
295        self
296    }
297
298    /// Sets the name of a file containing SSL certificate authority (CA) certificate(s).
299    /// If the file exists, the server's certificate will be verified to be signed by
300    /// one of these authorities.
301    ///
302    /// # Example
303    ///
304    /// ```rust
305    /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions};
306    /// let options = PgConnectOptions::new()
307    ///     // Providing a CA certificate with less than VerifyCa is pointless
308    ///     .ssl_mode(PgSslMode::VerifyCa)
309    ///     .ssl_root_cert("./ca-certificate.crt");
310    /// ```
311    pub fn ssl_root_cert(mut self, cert: impl AsRef<Path>) -> Self {
312        self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
313        self
314    }
315
316    /// Sets PEM encoded trusted SSL Certificate Authorities (CA).
317    ///
318    /// # Example
319    ///
320    /// ```rust
321    /// # use sqlx_core::postgres::{PgSslMode, PgConnectOptions};
322    /// let options = PgConnectOptions::new()
323    ///     // Providing a CA certificate with less than VerifyCa is pointless
324    ///     .ssl_mode(PgSslMode::VerifyCa)
325    ///     .ssl_root_cert_from_pem(vec![]);
326    /// ```
327    pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec<u8>) -> Self {
328        self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate));
329        self
330    }
331
332    /// Sets the capacity of the connection's statement cache in a number of stored
333    /// distinct statements. Caching is handled using LRU, meaning when the
334    /// amount of queries hits the defined limit, the oldest statement will get
335    /// dropped.
336    ///
337    /// The default cache capacity is 100 statements.
338    pub fn statement_cache_capacity(mut self, capacity: usize) -> Self {
339        self.statement_cache_capacity = capacity;
340        self
341    }
342
343    /// Sets the application name. Defaults to None
344    ///
345    /// # Example
346    ///
347    /// ```rust
348    /// # use sqlx_core::postgres::PgConnectOptions;
349    /// let options = PgConnectOptions::new()
350    ///     .application_name("my-app");
351    /// ```
352    pub fn application_name(mut self, application_name: &str) -> Self {
353        self.application_name = Some(application_name.to_owned());
354        self
355    }
356
357    /// Sets or removes the `extra_float_digits` connection option.
358    ///
359    /// This changes the default precision of floating-point values returned in text mode (when
360    /// not using prepared statements such as calling methods of [`Executor`] directly).
361    ///
362    /// Historically, Postgres would by default round floating-point values to 6 and 15 digits
363    /// for `float4`/`REAL` (`f32`) and `float8`/`DOUBLE` (`f64`), respectively, which would mean
364    /// that the returned value may not be exactly the same as its representation in Postgres.
365    ///
366    /// The nominal range for this value is `-15` to `3`, where negative values for this option
367    /// cause floating-points to be rounded to that many fewer digits than normal (`-1` causes
368    /// `float4` to be rounded to 5 digits instead of six, or 14 instead of 15 for `float8`),
369    /// positive values cause Postgres to emit that many extra digits of precision over default
370    /// (or simply use maximum precision in Postgres 12 and later),
371    /// and 0 means keep the default behavior (or the "old" behavior described above
372    /// as of Postgres 12).
373    ///
374    /// SQLx sets this value to 3 by default, which tells Postgres to return floating-point values
375    /// at their maximum precision in the hope that the parsed value will be identical to its
376    /// counterpart in Postgres. This is also the default in Postgres 12 and later anyway.
377    ///
378    /// However, older versions of Postgres and alternative implementations that talk the Postgres
379    /// protocol may not support this option, or the full range of values.
380    ///
381    /// If you get an error like "unknown option `extra_float_digits`" when connecting, try
382    /// setting this to `None` or consult the manual of your database for the allowed range
383    /// of values.
384    ///
385    /// For more information, see:
386    /// * [Postgres manual, 20.11.2: Client Connection Defaults; Locale and Formatting][20.11.2]
387    /// * [Postgres manual, 8.1.3: Numeric Types; Floating-point Types][8.1.3]
388    ///
389    /// [`Executor`]: crate::executor::Executor
390    /// [20.11.2]: https://www.postgresql.org/docs/current/runtime-config-client.html#RUNTIME-CONFIG-CLIENT-FORMAT
391    /// [8.1.3]: https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT
392    ///
393    /// ### Examples
394    /// ```rust
395    /// # use sqlx_core::postgres::PgConnectOptions;
396    ///
397    /// let mut options = PgConnectOptions::new()
398    ///     // for Redshift and Postgres 10
399    ///     .extra_float_digits(2);
400    ///
401    /// let mut options = PgConnectOptions::new()
402    ///     // don't send the option at all (Postgres 9 and older)
403    ///     .extra_float_digits(None);
404    /// ```
405    pub fn extra_float_digits(mut self, extra_float_digits: impl Into<Option<i8>>) -> Self {
406        self.extra_float_digits = extra_float_digits.into().map(|it| it.to_string().into());
407        self
408    }
409
410    /// Set additional startup options for the connection as a list of key-value pairs.
411    ///
412    /// # Example
413    ///
414    /// ```rust
415    /// # use sqlx_core::postgres::PgConnectOptions;
416    /// let options = PgConnectOptions::new()
417    ///     .options([("geqo", "off"), ("statement_timeout", "5min")]);
418    /// ```
419    pub fn options<K, V, I>(mut self, options: I) -> Self
420    where
421        K: Display,
422        V: Display,
423        I: IntoIterator<Item = (K, V)>,
424    {
425        // Do this in here so `options_str` is only set if we have an option to insert
426        let options_str = self.options.get_or_insert_with(String::new);
427        for (k, v) in options {
428            if !options_str.is_empty() {
429                options_str.push(' ');
430            }
431
432            write!(options_str, "-c {}={}", k, v).expect("failed to write an option to the string");
433        }
434        self
435    }
436
437    /// We try using a socket if hostname starts with `/` or if socket parameter
438    /// is specified.
439    pub(crate) fn fetch_socket(&self) -> Option<String> {
440        match self.socket {
441            Some(ref socket) => {
442                let full_path = format!("{}/.s.PGSQL.{}", socket.display(), self.port);
443                Some(full_path)
444            }
445            None if self.host.starts_with('/') => {
446                let full_path = format!("{}/.s.PGSQL.{}", self.host, self.port);
447                Some(full_path)
448            }
449            _ => None,
450        }
451    }
452}
453
454fn default_host(port: u16) -> String {
455    // try to check for the existence of a unix socket and uses that
456    let socket = format!(".s.PGSQL.{}", port);
457    let candidates = [
458        "/var/run/postgresql", // Debian
459        "/private/tmp",        // OSX (homebrew)
460        "/tmp",                // Default
461    ];
462
463    for candidate in &candidates {
464        if Path::new(candidate).join(&socket).exists() {
465            return candidate.to_string();
466        }
467    }
468
469    // fallback to localhost if no socket was found
470    "localhost".to_owned()
471}
472
473#[test]
474fn test_options_formatting() {
475    let options = PgConnectOptions::new().options([("geqo", "off")]);
476    assert_eq!(options.options, Some("-c geqo=off".to_string()));
477    let options = options.options([("search_path", "sqlx")]);
478    assert_eq!(
479        options.options,
480        Some("-c geqo=off -c search_path=sqlx".to_string())
481    );
482    let options = PgConnectOptions::new().options([("geqo", "off"), ("statement_timeout", "5min")]);
483    assert_eq!(
484        options.options,
485        Some("-c geqo=off -c statement_timeout=5min".to_string())
486    );
487    let options = PgConnectOptions::new();
488    assert_eq!(options.options, None);
489}