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) host_addr: Option<String>,
20    pub(crate) port: u16,
21    pub(crate) socket: Option<PathBuf>,
22    pub(crate) username: String,
23    pub(crate) password: Option<String>,
24    pub(crate) database: Option<String>,
25    pub(crate) ssl_mode: PgSslMode,
26    pub(crate) ssl_root_cert: Option<CertificateInput>,
27    pub(crate) ssl_client_cert: Option<CertificateInput>,
28    pub(crate) ssl_client_key: Option<CertificateInput>,
29    pub(crate) statement_cache_capacity: usize,
30    pub(crate) application_name: Option<String>,
31    pub(crate) log_settings: LogSettings,
32    pub(crate) extra_float_digits: Option<Cow<'static, str>>,
33    pub(crate) options: Option<String>,
34}
35
36impl Default for PgConnectOptions {
37    fn default() -> Self {
38        Self::with_libpq_defaults()
39    }
40}
41
42impl PgConnectOptions {
43    /// Create a default set of connection options populated from the current environment.
44    ///
45    /// This behaves as if parsed from the connection string `postgres://`
46    ///
47    /// See the type-level documentation for details.
48    ///
49    /// # Deprecated
50    /// This method is deprecated. Use [`with_libpq_defaults()`](Self::with_libpq_defaults) instead.
51    #[deprecated(
52        since = "0.9.0",
53        note = "Use `with_libpq_defaults()` instead to make the behavior more explicit"
54    )]
55    pub fn new() -> Self {
56        Self::with_libpq_defaults()
57    }
58
59    /// Create a default set of connection options populated from the current environment,
60    /// mimicking libpq's default behavior.
61    ///
62    /// This reads environment variables (`PGHOST`, `PGPORT`, `PGUSER`, etc.) and `.pgpass` file
63    /// to populate connection options, similar to how libpq behaves.
64    ///
65    /// This behaves as if parsed from the connection string `postgres://`
66    ///
67    /// See the type-level documentation for details.
68    pub fn with_libpq_defaults() -> Self {
69        Self::default_without_env_internal().apply_env_and_pgpass()
70    }
71
72    /// Create a default set of connection options _without_ reading from `passfile`.
73    ///
74    /// Equivalent to [`PgConnectOptions::with_libpq_defaults()`] but `passfile` is ignored.
75    ///
76    /// See the type-level documentation for details.
77    ///
78    /// # Deprecated
79    /// This method is deprecated. Use [`default_without_env()`](Self::default_without_env) instead.
80    #[deprecated(
81        since = "0.9.0",
82        note = "Use `default_without_env()` for explicit defaults without environment variables"
83    )]
84    pub fn new_without_pgpass() -> Self {
85        Self::default_without_env_internal().apply_env()
86    }
87
88    /// Create connection options with sensible defaults without reading environment variables.
89    ///
90    /// This method provides a predictable baseline for connection options that doesn't depend
91    /// on the environment. Useful for developer tools, third-party libraries, or any context
92    /// where environment variables cannot be relied upon.
93    ///
94    /// The defaults are:
95    /// - `host`: `"localhost"` (or Unix socket path if available)
96    /// - `port`: `5432`
97    /// - `username`: `"postgres"`
98    /// - `ssl_mode`: [`PgSslMode::Prefer`]
99    /// - `statement_cache_capacity`: `100`
100    /// - `extra_float_digits`: `Some("2")`
101    ///
102    /// All other fields are set to `None`.
103    ///
104    /// Does not respect any `PG*` environment variables or `.pgpass` files.
105    ///
106    /// See the type-level documentation for details.
107    pub fn default_without_env() -> Self {
108        let port = 5432;
109        let host = default_host(port);
110        let username = "postgres".to_string();
111
112        PgConnectOptions {
113            host,
114            host_addr: None,
115            port,
116            socket: None,
117            username,
118            password: None,
119            database: None,
120            ssl_mode: PgSslMode::Prefer,
121            ssl_root_cert: None,
122            ssl_client_cert: None,
123            ssl_client_key: None,
124            statement_cache_capacity: 100,
125            application_name: None,
126            extra_float_digits: Some("2".into()),
127            log_settings: Default::default(),
128            options: None,
129        }
130    }
131
132    /// Internal method that reads environment variables (libpq-style).
133    ///
134    /// This is used internally by `with_libpq_defaults()` and the deprecated `new_without_pgpass()`.
135    fn default_without_env_internal() -> Self {
136        let port = var("PGPORT")
137            .ok()
138            .and_then(|v| v.parse().ok())
139            .unwrap_or(5432);
140
141        let host = var("PGHOST").ok().unwrap_or_else(|| default_host(port));
142
143        let host_addr = var("PGHOSTADDR").ok();
144
145        let username = if let Ok(username) = var("PGUSER") {
146            username
147        } else if let Ok(username) = whoami::username() {
148            username
149        } else {
150            // keep the same fallback as previous version
151            "unknown".to_string()
152        };
153
154        let database = var("PGDATABASE").ok();
155
156        let ssl_mode = var("PGSSLMODE")
157            .ok()
158            .and_then(|v| v.parse().ok())
159            .unwrap_or_default();
160
161        PgConnectOptions {
162            host,
163            host_addr,
164            port,
165            socket: None,
166            username,
167            password: var("PGPASSWORD").ok(),
168            database,
169            ssl_mode,
170            ssl_root_cert: var("PGSSLROOTCERT").ok().map(CertificateInput::from),
171            ssl_client_cert: var("PGSSLCERT").ok().map(CertificateInput::from),
172            // As of writing, the implementation of `From<String>` only looks for
173            // `-----BEGIN CERTIFICATE-----` and so will not attempt to parse
174            // a PEM-encoded private key.
175            ssl_client_key: var("PGSSLKEY").ok().map(CertificateInput::from),
176            statement_cache_capacity: 100,
177            application_name: var("PGAPPNAME").ok(),
178            extra_float_digits: Some("2".into()),
179            log_settings: Default::default(),
180            options: var("PGOPTIONS").ok(),
181        }
182    }
183
184    pub(crate) fn apply_pgpass(mut self) -> Self {
185        if self.password.is_none() {
186            self.password = pgpass::load_password(
187                &self.host,
188                self.port,
189                &self.username,
190                self.database.as_deref(),
191            );
192        }
193
194        self
195    }
196
197    /// Apply environment variables only (no pgpass).
198    ///
199    /// This is used internally for the deprecated `new_without_pgpass()`.
200    fn apply_env(self) -> Self {
201        // Environment variables are already applied in default_without_env_internal()
202        // This method exists for backwards compatibility but doesn't do anything
203        self
204    }
205
206    /// Apply both environment variables and pgpass.
207    ///
208    /// This is used internally for `with_libpq_defaults()`.
209    fn apply_env_and_pgpass(self) -> Self {
210        // Environment variables are already applied in default_without_env_internal()
211        // We just need to apply pgpass
212        self.apply_pgpass()
213    }
214
215    /// Sets the name of the host to connect to.
216    ///
217    /// If a host name begins with a slash, it specifies
218    /// Unix-domain communication rather than TCP/IP communication; the value is the name of
219    /// the directory in which the socket file is stored.
220    ///
221    /// The default behavior when host is not specified, or is empty,
222    /// is to connect to a Unix-domain socket
223    ///
224    /// # Example
225    ///
226    /// ```rust
227    /// # use sqlx_postgres::PgConnectOptions;
228    /// let options = PgConnectOptions::new()
229    ///     .host("localhost");
230    /// ```
231    pub fn host(mut self, host: &str) -> Self {
232        host.clone_into(&mut self.host);
233        self
234    }
235
236    /// Sets the host address to connect to.
237    ///
238    /// This is different to the host parameter as it overwrites DNS lookups for TCP/IP
239    /// communication. This is particuarly useful when the DB port has to be
240    /// proxied to localhost for security reasons.
241    ///
242    /// # Example
243    ///
244    /// ```rust
245    /// # use sqlx_postgres::PgConnectOptions;
246    /// let options = PgConnectOptions::new()
247    ///     .host("example.com");
248    ///
249    /// // Initially, host_addr should be None (unless PGHOSTADDR env var is set)
250    /// // For this test, we assume it's not set
251    /// assert_eq!(options.get_host_addr(), None);
252    ///
253    /// let options = options.host_addr("127.0.0.1");
254    ///
255    /// // After setting, host_addr should contain the specified value
256    /// assert_eq!(options.get_host_addr(), Some("127.0.0.1"));
257    /// ```
258    pub fn host_addr(mut self, host_addr: &str) -> Self {
259        self.host_addr = Some(host_addr.to_string());
260        self
261    }
262
263    /// Sets the port to connect to at the server host.
264    ///
265    /// The default port for PostgreSQL is `5432`.
266    ///
267    /// # Example
268    ///
269    /// ```rust
270    /// # use sqlx_postgres::PgConnectOptions;
271    /// let options = PgConnectOptions::new()
272    ///     .port(5432);
273    /// ```
274    pub fn port(mut self, port: u16) -> Self {
275        self.port = port;
276        self
277    }
278
279    /// Sets a custom path to a directory containing a unix domain socket,
280    /// switching the connection method from TCP to the corresponding socket.
281    ///
282    /// By default set to `None`.
283    pub fn socket(mut self, path: impl AsRef<Path>) -> Self {
284        self.socket = Some(path.as_ref().to_path_buf());
285        self
286    }
287
288    /// Sets the username to connect as.
289    ///
290    /// Defaults to be the same as the operating system name of
291    /// the user running the application.
292    ///
293    /// # Example
294    ///
295    /// ```rust
296    /// # use sqlx_postgres::PgConnectOptions;
297    /// let options = PgConnectOptions::new()
298    ///     .username("postgres");
299    /// ```
300    pub fn username(mut self, username: &str) -> Self {
301        username.clone_into(&mut self.username);
302        self
303    }
304
305    /// Sets the password to use if the server demands password authentication.
306    ///
307    /// # Example
308    ///
309    /// ```rust
310    /// # use sqlx_postgres::PgConnectOptions;
311    /// let options = PgConnectOptions::new()
312    ///     .username("root")
313    ///     .password("safe-and-secure");
314    /// ```
315    pub fn password(mut self, password: &str) -> Self {
316        self.password = Some(password.to_owned());
317        self
318    }
319
320    /// Sets the database name. Defaults to be the same as the user name.
321    ///
322    /// # Example
323    ///
324    /// ```rust
325    /// # use sqlx_postgres::PgConnectOptions;
326    /// let options = PgConnectOptions::new()
327    ///     .database("postgres");
328    /// ```
329    pub fn database(mut self, database: &str) -> Self {
330        self.database = Some(database.to_owned());
331        self
332    }
333
334    /// Sets whether or with what priority a secure SSL TCP/IP connection will be negotiated
335    /// with the server.
336    ///
337    /// By default, the SSL mode is [`Prefer`](PgSslMode::Prefer), and the client will
338    /// first attempt an SSL connection but fallback to a non-SSL connection on failure.
339    ///
340    /// Ignored for Unix domain socket communication.
341    ///
342    /// # Example
343    ///
344    /// ```rust
345    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
346    /// let options = PgConnectOptions::new()
347    ///     .ssl_mode(PgSslMode::Require);
348    /// ```
349    pub fn ssl_mode(mut self, mode: PgSslMode) -> Self {
350        self.ssl_mode = mode;
351        self
352    }
353
354    /// Sets the name of a file containing SSL certificate authority (CA) certificate(s).
355    /// If the file exists, the server's certificate will be verified to be signed by
356    /// one of these authorities.
357    ///
358    /// # Example
359    ///
360    /// ```rust
361    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
362    /// let options = PgConnectOptions::new()
363    ///     // Providing a CA certificate with less than VerifyCa is pointless
364    ///     .ssl_mode(PgSslMode::VerifyCa)
365    ///     .ssl_root_cert("./ca-certificate.crt");
366    /// ```
367    pub fn ssl_root_cert(mut self, cert: impl AsRef<Path>) -> Self {
368        self.ssl_root_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
369        self
370    }
371
372    /// Sets the name of a file containing SSL client certificate.
373    ///
374    /// # Example
375    ///
376    /// ```rust
377    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
378    /// let options = PgConnectOptions::new()
379    ///     // Providing a CA certificate with less than VerifyCa is pointless
380    ///     .ssl_mode(PgSslMode::VerifyCa)
381    ///     .ssl_client_cert("./client.crt");
382    /// ```
383    pub fn ssl_client_cert(mut self, cert: impl AsRef<Path>) -> Self {
384        self.ssl_client_cert = Some(CertificateInput::File(cert.as_ref().to_path_buf()));
385        self
386    }
387
388    /// Sets the SSL client certificate as a PEM-encoded byte slice.
389    ///
390    /// This should be an ASCII-encoded blob that starts with `-----BEGIN CERTIFICATE-----`.
391    ///
392    /// # Example
393    /// Note: embedding SSL certificates and keys in the binary is not advised.
394    /// This is for illustration purposes only.
395    ///
396    /// ```rust
397    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
398    ///
399    /// const CERT: &[u8] = b"\
400    /// -----BEGIN CERTIFICATE-----
401    /// <Certificate data here.>
402    /// -----END CERTIFICATE-----";
403    ///    
404    /// let options = PgConnectOptions::new()
405    ///     // Providing a CA certificate with less than VerifyCa is pointless
406    ///     .ssl_mode(PgSslMode::VerifyCa)
407    ///     .ssl_client_cert_from_pem(CERT);
408    /// ```
409    pub fn ssl_client_cert_from_pem(mut self, cert: impl AsRef<[u8]>) -> Self {
410        self.ssl_client_cert = Some(CertificateInput::Inline(cert.as_ref().to_vec()));
411        self
412    }
413
414    /// Sets the name of a file containing SSL client key.
415    ///
416    /// # Example
417    ///
418    /// ```rust
419    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
420    /// let options = PgConnectOptions::new()
421    ///     // Providing a CA certificate with less than VerifyCa is pointless
422    ///     .ssl_mode(PgSslMode::VerifyCa)
423    ///     .ssl_client_key("./client.key");
424    /// ```
425    pub fn ssl_client_key(mut self, key: impl AsRef<Path>) -> Self {
426        self.ssl_client_key = Some(CertificateInput::File(key.as_ref().to_path_buf()));
427        self
428    }
429
430    /// Sets the SSL client key as a PEM-encoded byte slice.
431    ///
432    /// This should be an ASCII-encoded blob that starts with `-----BEGIN PRIVATE KEY-----`.
433    ///
434    /// # Example
435    /// Note: embedding SSL certificates and keys in the binary is not advised.
436    /// This is for illustration purposes only.
437    ///
438    /// ```rust
439    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
440    ///
441    /// const KEY: &[u8] = b"\
442    /// -----BEGIN PRIVATE KEY-----
443    /// <Private key data here.>
444    /// -----END PRIVATE KEY-----";
445    ///
446    /// let options = PgConnectOptions::new()
447    ///     // Providing a CA certificate with less than VerifyCa is pointless
448    ///     .ssl_mode(PgSslMode::VerifyCa)
449    ///     .ssl_client_key_from_pem(KEY);
450    /// ```
451    pub fn ssl_client_key_from_pem(mut self, key: impl AsRef<[u8]>) -> Self {
452        self.ssl_client_key = Some(CertificateInput::Inline(key.as_ref().to_vec()));
453        self
454    }
455
456    /// Sets PEM encoded trusted SSL Certificate Authorities (CA).
457    ///
458    /// # Example
459    ///
460    /// ```rust
461    /// # use sqlx_postgres::{PgSslMode, PgConnectOptions};
462    /// let options = PgConnectOptions::new()
463    ///     // Providing a CA certificate with less than VerifyCa is pointless
464    ///     .ssl_mode(PgSslMode::VerifyCa)
465    ///     .ssl_root_cert_from_pem(vec![]);
466    /// ```
467    pub fn ssl_root_cert_from_pem(mut self, pem_certificate: Vec<u8>) -> Self {
468        self.ssl_root_cert = Some(CertificateInput::Inline(pem_certificate));
469        self
470    }
471
472    /// Sets the capacity of the connection's statement cache in a number of stored
473    /// distinct statements. Caching is handled using LRU, meaning when the
474    /// amount of queries hits the defined limit, the oldest statement will get
475    /// dropped.
476    ///
477    /// The default cache capacity is 100 statements.
478    pub fn statement_cache_capacity(mut self, capacity: usize) -> Self {
479        self.statement_cache_capacity = capacity;
480        self
481    }
482
483    /// Sets the application name. Defaults to None
484    ///
485    /// # Example
486    ///
487    /// ```rust
488    /// # use sqlx_postgres::PgConnectOptions;
489    /// let options = PgConnectOptions::new()
490    ///     .application_name("my-app");
491    /// ```
492    pub fn application_name(mut self, application_name: &str) -> Self {
493        self.application_name = Some(application_name.to_owned());
494        self
495    }
496
497    /// Sets or removes the `extra_float_digits` connection option.
498    ///
499    /// This changes the default precision of floating-point values returned in text mode (when
500    /// not using prepared statements such as calling methods of [`Executor`] directly).
501    ///
502    /// Historically, Postgres would by default round floating-point values to 6 and 15 digits
503    /// for `float4`/`REAL` (`f32`) and `float8`/`DOUBLE` (`f64`), respectively, which would mean
504    /// that the returned value may not be exactly the same as its representation in Postgres.
505    ///
506    /// The nominal range for this value is `-15` to `3`, where negative values for this option
507    /// cause floating-points to be rounded to that many fewer digits than normal (`-1` causes
508    /// `float4` to be rounded to 5 digits instead of six, or 14 instead of 15 for `float8`),
509    /// positive values cause Postgres to emit that many extra digits of precision over default
510    /// (or simply use maximum precision in Postgres 12 and later),
511    /// and 0 means keep the default behavior (or the "old" behavior described above
512    /// as of Postgres 12).
513    ///
514    /// SQLx sets this value to 3 by default, which tells Postgres to return floating-point values
515    /// at their maximum precision in the hope that the parsed value will be identical to its
516    /// counterpart in Postgres. This is also the default in Postgres 12 and later anyway.
517    ///
518    /// However, older versions of Postgres and alternative implementations that talk the Postgres
519    /// protocol may not support this option, or the full range of values.
520    ///
521    /// If you get an error like "unknown option `extra_float_digits`" when connecting, try
522    /// setting this to `None` or consult the manual of your database for the allowed range
523    /// of values.
524    ///
525    /// For more information, see:
526    /// * [Postgres manual, 20.11.2: Client Connection Defaults; Locale and Formatting][20.11.2]
527    /// * [Postgres manual, 8.1.3: Numeric Types; Floating-point Types][8.1.3]
528    ///
529    /// [`Executor`]: crate::executor::Executor
530    /// [20.11.2]: https://www.postgresql.org/docs/current/runtime-config-client.html#RUNTIME-CONFIG-CLIENT-FORMAT
531    /// [8.1.3]: https://www.postgresql.org/docs/current/datatype-numeric.html#DATATYPE-FLOAT
532    ///
533    /// ### Examples
534    /// ```rust
535    /// # use sqlx_postgres::PgConnectOptions;
536    ///
537    /// let mut options = PgConnectOptions::new()
538    ///     // for Redshift and Postgres 10
539    ///     .extra_float_digits(2);
540    ///
541    /// let mut options = PgConnectOptions::new()
542    ///     // don't send the option at all (Postgres 9 and older)
543    ///     .extra_float_digits(None);
544    /// ```
545    pub fn extra_float_digits(mut self, extra_float_digits: impl Into<Option<i8>>) -> Self {
546        self.extra_float_digits = extra_float_digits.into().map(|it| it.to_string().into());
547        self
548    }
549
550    /// Set additional startup options for the connection as a list of key-value pairs.
551    ///
552    /// Escapes the options’ backslash and space characters as per
553    /// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
554    ///
555    /// # Example
556    ///
557    /// ```rust
558    /// # use sqlx_postgres::PgConnectOptions;
559    /// let options = PgConnectOptions::new()
560    ///     .options([("geqo", "off"), ("statement_timeout", "5min")]);
561    /// ```
562    pub fn options<K, V, I>(mut self, options: I) -> Self
563    where
564        K: Display,
565        V: Display,
566        I: IntoIterator<Item = (K, V)>,
567    {
568        // Do this in here so `options_str` is only set if we have an option to insert
569        let options_str = self.options.get_or_insert_with(String::new);
570        for (k, v) in options {
571            if !options_str.is_empty() {
572                options_str.push(' ');
573            }
574
575            options_str.push_str("-c ");
576            write!(PgOptionsWriteEscaped(options_str), "{k}={v}").ok();
577        }
578        self
579    }
580
581    /// We try using a socket if hostname starts with `/` or if socket parameter
582    /// is specified.
583    pub(crate) fn fetch_socket(&self) -> Option<String> {
584        match self.socket {
585            Some(ref socket) => {
586                let full_path = format!("{}/.s.PGSQL.{}", socket.display(), self.port);
587                Some(full_path)
588            }
589            None if self.host.starts_with('/') => {
590                let full_path = format!("{}/.s.PGSQL.{}", self.host, self.port);
591                Some(full_path)
592            }
593            _ => None,
594        }
595    }
596}
597
598impl PgConnectOptions {
599    /// Get the current host.
600    ///
601    /// # Example
602    ///
603    /// ```rust
604    /// # use sqlx_postgres::PgConnectOptions;
605    /// let options = PgConnectOptions::new()
606    ///     .host("127.0.0.1");
607    /// assert_eq!(options.get_host(), "127.0.0.1");
608    /// ```
609    pub fn get_host(&self) -> &str {
610        &self.host
611    }
612
613    /// Get the current host addr.
614    ///
615    /// # Example
616    ///
617    /// ```rust
618    /// # use sqlx_postgres::PgConnectOptions;
619    /// let options = PgConnectOptions::new()
620    ///     .host("example.com");
621    ///
622    /// // Initially, host_addr should be None (unless PGHOSTADDR env var is set)
623    /// // For this test, we assume it's not set
624    /// assert_eq!(options.get_host_addr(), None);
625    ///
626    /// let options = options.host_addr("127.0.0.1");
627    ///
628    /// // After setting host_addr, it should return the configured value
629    /// assert_eq!(options.get_host_addr(), Some("127.0.0.1"));
630    /// ```
631    pub fn get_host_addr(&self) -> Option<&str> {
632        self.host_addr.as_deref()
633    }
634
635    /// Get the server's port.
636    ///
637    /// # Example
638    ///
639    /// ```rust
640    /// # use sqlx_postgres::PgConnectOptions;
641    /// let options = PgConnectOptions::new()
642    ///     .port(6543);
643    /// assert_eq!(options.get_port(), 6543);
644    /// ```
645    pub fn get_port(&self) -> u16 {
646        self.port
647    }
648
649    /// Get the socket path.
650    ///
651    /// # Example
652    ///
653    /// ```rust
654    /// # use sqlx_postgres::PgConnectOptions;
655    /// let options = PgConnectOptions::new()
656    ///     .socket("/tmp");
657    /// assert!(options.get_socket().is_some());
658    /// ```
659    pub fn get_socket(&self) -> Option<&PathBuf> {
660        self.socket.as_ref()
661    }
662
663    /// Get the server's port.
664    ///
665    /// # Example
666    ///
667    /// ```rust
668    /// # use sqlx_postgres::PgConnectOptions;
669    /// let options = PgConnectOptions::new()
670    ///     .username("foo");
671    /// assert_eq!(options.get_username(), "foo");
672    /// ```
673    pub fn get_username(&self) -> &str {
674        &self.username
675    }
676
677    /// Get the current database name.
678    ///
679    /// # Example
680    ///
681    /// ```rust
682    /// # use sqlx_postgres::PgConnectOptions;
683    /// let options = PgConnectOptions::new()
684    ///     .database("postgres");
685    /// assert!(options.get_database().is_some());
686    /// ```
687    pub fn get_database(&self) -> Option<&str> {
688        self.database.as_deref()
689    }
690
691    /// Get the SSL mode.
692    ///
693    /// # Example
694    ///
695    /// ```rust
696    /// # use sqlx_postgres::{PgConnectOptions, PgSslMode};
697    /// let options = PgConnectOptions::new();
698    /// assert!(matches!(options.get_ssl_mode(), PgSslMode::Prefer));
699    /// ```
700    pub fn get_ssl_mode(&self) -> PgSslMode {
701        self.ssl_mode
702    }
703
704    /// Get the application name.
705    ///
706    /// # Example
707    ///
708    /// ```rust
709    /// # use sqlx_postgres::PgConnectOptions;
710    /// let options = PgConnectOptions::new()
711    ///     .application_name("service");
712    /// assert!(options.get_application_name().is_some());
713    /// ```
714    pub fn get_application_name(&self) -> Option<&str> {
715        self.application_name.as_deref()
716    }
717
718    /// Get the options.
719    ///
720    /// # Example
721    ///
722    /// ```rust
723    /// # use sqlx_postgres::PgConnectOptions;
724    /// let options = PgConnectOptions::new()
725    ///     .options([("foo", "bar")]);
726    /// assert!(options.get_options().is_some());
727    /// ```
728    pub fn get_options(&self) -> Option<&str> {
729        self.options.as_deref()
730    }
731}
732
733fn default_host(port: u16) -> String {
734    // try to check for the existence of a unix socket and uses that
735    let socket = format!(".s.PGSQL.{port}");
736    let candidates = [
737        "/var/run/postgresql", // Debian
738        "/private/tmp",        // OSX (homebrew)
739        "/tmp",                // Default
740    ];
741
742    for candidate in &candidates {
743        if Path::new(candidate).join(&socket).exists() {
744            return candidate.to_string();
745        }
746    }
747
748    // fallback to localhost if no socket was found
749    "localhost".to_owned()
750}
751
752/// Writer that escapes passed-in PostgreSQL options.
753///
754/// Escapes backslashes and spaces with an additional backslash according to
755/// https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
756#[derive(Debug)]
757struct PgOptionsWriteEscaped<'a>(&'a mut String);
758
759impl Write for PgOptionsWriteEscaped<'_> {
760    fn write_str(&mut self, s: &str) -> fmt::Result {
761        let mut span_start = 0;
762
763        for (span_end, matched) in s.match_indices([' ', '\\']) {
764            write!(self.0, r"{}\{matched}", &s[span_start..span_end])?;
765            span_start = span_end + matched.len();
766        }
767
768        // Write the rest of the string after the last match, or all of it if no matches
769        self.0.push_str(&s[span_start..]);
770
771        Ok(())
772    }
773
774    fn write_char(&mut self, ch: char) -> fmt::Result {
775        if matches!(ch, ' ' | '\\') {
776            self.0.push('\\');
777        }
778
779        self.0.push(ch);
780
781        Ok(())
782    }
783}
784
785#[test]
786fn test_options_formatting() {
787    let options = PgConnectOptions::default_without_env().options([("geqo", "off")]);
788    assert_eq!(options.options, Some("-c geqo=off".to_string()));
789    let options = options.options([("search_path", "sqlx")]);
790    assert_eq!(
791        options.options,
792        Some("-c geqo=off -c search_path=sqlx".to_string())
793    );
794    let options = PgConnectOptions::default_without_env()
795        .options([("geqo", "off"), ("statement_timeout", "5min")]);
796    assert_eq!(
797        options.options,
798        Some("-c geqo=off -c statement_timeout=5min".to_string())
799    );
800    // https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNECT-OPTIONS
801    let options = PgConnectOptions::default_without_env()
802        .options([("application_name", r"/back\slash/ and\ spaces")]);
803    assert_eq!(
804        options.options,
805        Some(r"-c application_name=/back\\slash/\ and\\\ spaces".to_string())
806    );
807    let options = PgConnectOptions::default_without_env();
808    assert_eq!(options.options, None);
809}
810
811#[test]
812fn test_pg_write_escaped() {
813    let mut buf = String::new();
814    let mut x = PgOptionsWriteEscaped(&mut buf);
815    x.write_str("x").unwrap();
816    x.write_str("").unwrap();
817    x.write_char('\\').unwrap();
818    x.write_str("y \\").unwrap();
819    x.write_char(' ').unwrap();
820    x.write_char('z').unwrap();
821    assert_eq!(buf, r"x\\y\ \\\ z");
822}
823
824#[test]
825#[allow(deprecated)]
826fn test_deprecated_api_backwards_compatibility() {
827    // Test that deprecated methods still work correctly for backwards compatibility
828
829    // Test deprecated new() method
830    let options = PgConnectOptions::new();
831    assert_eq!(options.port, 5432);
832    assert_eq!(options.statement_cache_capacity, 100);
833    assert!(options.extra_float_digits.is_some());
834
835    // Test deprecated new_without_pgpass() method
836    let options = PgConnectOptions::new_without_pgpass();
837    assert_eq!(options.port, 5432);
838    assert_eq!(options.statement_cache_capacity, 100);
839
840    // Verify the deprecated methods can be chained with builder methods
841    let options = PgConnectOptions::new()
842        .host("example.com")
843        .port(5433)
844        .username("testuser")
845        .database("testdb");
846
847    assert_eq!(options.get_host(), "example.com");
848    assert_eq!(options.get_port(), 5433);
849    assert_eq!(options.get_username(), "testuser");
850    assert_eq!(options.get_database(), Some("testdb"));
851
852    // Verify new_without_pgpass() works with builder pattern
853    let options = PgConnectOptions::new_without_pgpass()
854        .host("localhost")
855        .username("postgres");
856
857    assert_eq!(options.get_host(), "localhost");
858    assert_eq!(options.get_username(), "postgres");
859}
860
861#[test]
862fn test_new_api_without_environment() {
863    // Test the new API methods that don't read environment variables
864
865    // Test default_without_env() provides hardcoded defaults
866    let options = PgConnectOptions::default_without_env();
867    assert_eq!(options.port, 5432);
868    assert_eq!(options.username, "postgres"); // Hardcoded, not OS username
869    assert_eq!(options.ssl_mode, PgSslMode::Prefer);
870    assert_eq!(options.statement_cache_capacity, 100);
871    assert!(options.extra_float_digits.is_some());
872    assert!(options.password.is_none());
873    assert!(options.database.is_none());
874
875    // Test builder pattern with default_without_env()
876    let options = PgConnectOptions::default_without_env()
877        .host("example.com")
878        .port(5433)
879        .username("myuser")
880        .database("mydb")
881        .password("mypass");
882
883    assert_eq!(options.get_host(), "example.com");
884    assert_eq!(options.get_port(), 5433);
885    assert_eq!(options.get_username(), "myuser");
886    assert_eq!(options.get_database(), Some("mydb"));
887
888    // Test with_libpq_defaults()
889    let options = PgConnectOptions::with_libpq_defaults();
890    assert_eq!(options.port, 5432);
891    assert_eq!(options.statement_cache_capacity, 100);
892}