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