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}