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