Skip to main content

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