Skip to main content

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