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