sqlx_postgres/options/
mod.rs

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