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}