mysql_async/opts/mod.rs
1// Copyright (c) 2016 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9mod native_tls_opts;
10mod rustls_opts;
11
12#[cfg(feature = "native-tls-tls")]
13pub use native_tls_opts::ClientIdentity;
14
15#[cfg(feature = "rustls-tls")]
16pub use rustls_opts::ClientIdentity;
17
18use percent_encoding::percent_decode;
19use rand::Rng;
20use url::{Host, Url};
21
22use std::{
23 borrow::Cow,
24 fmt, io,
25 net::{IpAddr, Ipv4Addr, Ipv6Addr},
26 path::{Path, PathBuf},
27 str::FromStr,
28 sync::Arc,
29 time::{Duration, Instant},
30 vec,
31};
32
33use crate::{
34 consts::CapabilityFlags,
35 error::*,
36 local_infile_handler::{GlobalHandler, GlobalHandlerObject},
37};
38
39/// Default pool constraints.
40pub const DEFAULT_POOL_CONSTRAINTS: PoolConstraints = PoolConstraints { min: 10, max: 100 };
41
42//
43const_assert!(
44 _DEFAULT_POOL_CONSTRAINTS_ARE_CORRECT,
45 DEFAULT_POOL_CONSTRAINTS.min <= DEFAULT_POOL_CONSTRAINTS.max,
46);
47
48/// Each connection will cache up to this number of statements by default.
49pub const DEFAULT_STMT_CACHE_SIZE: usize = 32;
50
51/// Default server port.
52pub const DEFAULT_PORT: u16 = 3306;
53
54/// Default `inactive_connection_ttl` of a pool.
55///
56/// `0` value means, that connection will be dropped immediately
57/// if it is outside of the pool's lower bound.
58pub const DEFAULT_INACTIVE_CONNECTION_TTL: Duration = Duration::from_secs(0);
59
60/// Default `ttl_check_interval` of a pool.
61///
62/// It isn't used if `inactive_connection_ttl` is `0`.
63pub const DEFAULT_TTL_CHECK_INTERVAL: Duration = Duration::from_secs(30);
64
65/// Represents information about a host and port combination that can be converted
66/// into socket addresses using to_socket_addrs.
67#[derive(Clone, Eq, PartialEq, Debug)]
68pub(crate) enum HostPortOrUrl {
69 HostPort {
70 host: String,
71 port: u16,
72 /// The resolved IP addresses to use for the TCP connection. If empty,
73 /// DNS resolution of `host` will be performed.
74 resolved_ips: Option<Vec<IpAddr>>,
75 },
76 Url(Url),
77}
78
79impl Default for HostPortOrUrl {
80 fn default() -> Self {
81 HostPortOrUrl::HostPort {
82 host: "127.0.0.1".to_string(),
83 port: DEFAULT_PORT,
84 resolved_ips: None,
85 }
86 }
87}
88
89impl HostPortOrUrl {
90 pub fn get_ip_or_hostname(&self) -> &str {
91 match self {
92 Self::HostPort { host, .. } => host,
93 Self::Url(url) => url.host_str().unwrap_or("127.0.0.1"),
94 }
95 }
96
97 pub fn get_tcp_port(&self) -> u16 {
98 match self {
99 Self::HostPort { port, .. } => *port,
100 Self::Url(url) => url.port().unwrap_or(DEFAULT_PORT),
101 }
102 }
103
104 pub fn get_resolved_ips(&self) -> &Option<Vec<IpAddr>> {
105 match self {
106 Self::HostPort { resolved_ips, .. } => resolved_ips,
107 Self::Url(_) => &None,
108 }
109 }
110
111 pub fn is_loopback(&self) -> bool {
112 match self {
113 Self::HostPort {
114 host, resolved_ips, ..
115 } => {
116 let v4addr: Option<Ipv4Addr> = FromStr::from_str(host).ok();
117 let v6addr: Option<Ipv6Addr> = FromStr::from_str(host).ok();
118 if resolved_ips
119 .as_ref()
120 .is_some_and(|s| s.iter().any(|ip| ip.is_loopback()))
121 {
122 true
123 } else if let Some(addr) = v4addr {
124 addr.is_loopback()
125 } else if let Some(addr) = v6addr {
126 addr.is_loopback()
127 } else {
128 host == "localhost"
129 }
130 }
131 Self::Url(url) => match url.host() {
132 Some(Host::Ipv4(ip)) => ip.is_loopback(),
133 Some(Host::Ipv6(ip)) => ip.is_loopback(),
134 Some(Host::Domain(s)) => s == "localhost",
135 _ => false,
136 },
137 }
138 }
139}
140
141/// Represents data that is either on-disk or in the buffer.
142#[derive(Debug, Clone, PartialEq, Eq, Hash)]
143pub enum PathOrBuf<'a> {
144 Path(Cow<'a, Path>),
145 Buf(Cow<'a, [u8]>),
146}
147
148impl<'a> PathOrBuf<'a> {
149 /// Will either read data from disk or return the buffered data.
150 pub async fn read(&self) -> io::Result<Cow<[u8]>> {
151 match self {
152 PathOrBuf::Path(x) => tokio::fs::read(x.as_ref()).await.map(Cow::Owned),
153 PathOrBuf::Buf(x) => Ok(Cow::Borrowed(x.as_ref())),
154 }
155 }
156
157 /// Borrows `self`.
158 pub fn borrow(&self) -> PathOrBuf<'_> {
159 match self {
160 PathOrBuf::Path(path) => PathOrBuf::Path(Cow::Borrowed(path.as_ref())),
161 PathOrBuf::Buf(data) => PathOrBuf::Buf(Cow::Borrowed(data.as_ref())),
162 }
163 }
164}
165
166impl From<PathBuf> for PathOrBuf<'static> {
167 fn from(value: PathBuf) -> Self {
168 Self::Path(Cow::Owned(value))
169 }
170}
171
172impl<'a> From<&'a Path> for PathOrBuf<'a> {
173 fn from(value: &'a Path) -> Self {
174 Self::Path(Cow::Borrowed(value))
175 }
176}
177
178impl From<Vec<u8>> for PathOrBuf<'static> {
179 fn from(value: Vec<u8>) -> Self {
180 Self::Buf(Cow::Owned(value))
181 }
182}
183
184impl<'a> From<&'a [u8]> for PathOrBuf<'a> {
185 fn from(value: &'a [u8]) -> Self {
186 Self::Buf(Cow::Borrowed(value))
187 }
188}
189
190/// Ssl Options.
191///
192/// ```
193/// # use mysql_async::SslOpts;
194/// # use std::path::Path;
195/// # #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
196/// # use mysql_async::ClientIdentity;
197/// // With native-tls
198/// # #[cfg(feature = "native-tls-tls")]
199/// let ssl_opts = SslOpts::default()
200/// .with_client_identity(Some(ClientIdentity::new(Path::new("/path").into())
201/// .with_password("******")
202/// ));
203///
204/// // With rustls
205/// # #[cfg(feature = "rustls-tls")]
206/// let ssl_opts = SslOpts::default()
207/// .with_client_identity(Some(ClientIdentity::new(
208/// Path::new("/path/to/chain").into(),
209/// Path::new("/path/to/priv_key").into(),
210/// )));
211/// ```
212#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
213pub struct SslOpts {
214 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
215 client_identity: Option<ClientIdentity>,
216 root_certs: Vec<PathOrBuf<'static>>,
217 disable_built_in_roots: bool,
218 skip_domain_validation: bool,
219 accept_invalid_certs: bool,
220 tls_hostname_override: Option<Cow<'static, str>>,
221}
222
223impl SslOpts {
224 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
225 pub fn with_client_identity(mut self, identity: Option<ClientIdentity>) -> Self {
226 self.client_identity = identity;
227 self
228 }
229
230 /// Sets path to a `pem` or `der` certificate of the root that connector will trust.
231 ///
232 /// Multiple certs are allowed in .pem files.
233 ///
234 /// All the elements in `root_certs` will be merged.
235 pub fn with_root_certs(mut self, root_certs: Vec<PathOrBuf<'static>>) -> Self {
236 self.root_certs = root_certs;
237 self
238 }
239
240 /// If `true`, use only the root certificates configured via [`SslOpts::with_root_certs`],
241 /// not any system or built-in certs. By default system built-in certs _will be_ used.
242 ///
243 /// # Connection URL
244 ///
245 /// Use `built_in_roots` URL parameter to set this value:
246 ///
247 /// ```
248 /// # use mysql_async::*;
249 /// # use std::time::Duration;
250 /// # fn main() -> Result<()> {
251 /// let opts = Opts::from_url("mysql://localhost/db?require_ssl=true&built_in_roots=false")?;
252 /// assert_eq!(opts.ssl_opts().unwrap().disable_built_in_roots(), true);
253 /// # Ok(()) }
254 /// ```
255 pub fn with_disable_built_in_roots(mut self, disable_built_in_roots: bool) -> Self {
256 self.disable_built_in_roots = disable_built_in_roots;
257 self
258 }
259
260 /// The way to not validate the server's domain name against its certificate.
261 /// By default domain name _will be_ validated.
262 ///
263 /// # Connection URL
264 ///
265 /// Use `verify_identity` URL parameter to set this value:
266 ///
267 /// ```
268 /// # use mysql_async::*;
269 /// # use std::time::Duration;
270 /// # fn main() -> Result<()> {
271 /// let opts = Opts::from_url("mysql://localhost/db?require_ssl=true&verify_identity=false")?;
272 /// assert_eq!(opts.ssl_opts().unwrap().skip_domain_validation(), true);
273 /// # Ok(()) }
274 /// ```
275 pub fn with_danger_skip_domain_validation(mut self, value: bool) -> Self {
276 self.skip_domain_validation = value;
277 self
278 }
279
280 /// If `true` then client will accept invalid certificate (expired, not trusted, ..).
281 /// Invalid certificates _won't get_ accepted by default.
282 ///
283 /// # Connection URL
284 ///
285 /// Use `verify_ca` URL parameter to set this value:
286 ///
287 /// ```
288 /// # use mysql_async::*;
289 /// # use std::time::Duration;
290 /// # fn main() -> Result<()> {
291 /// let opts = Opts::from_url("mysql://localhost/db?require_ssl=true&verify_ca=false")?;
292 /// assert_eq!(opts.ssl_opts().unwrap().accept_invalid_certs(), true);
293 /// # Ok(()) }
294 /// ```
295 pub fn with_danger_accept_invalid_certs(mut self, value: bool) -> Self {
296 self.accept_invalid_certs = value;
297 self
298 }
299
300 /// If set, will override the hostname used to verify the server's certificate.
301 ///
302 /// This is useful when connecting to a server via a tunnel, where the server hostname is
303 /// different from the hostname used to connect to the tunnel.
304 pub fn with_danger_tls_hostname_override<T: Into<Cow<'static, str>>>(
305 mut self,
306 domain: Option<T>,
307 ) -> Self {
308 self.tls_hostname_override = domain.map(Into::into);
309 self
310 }
311
312 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
313 pub fn client_identity(&self) -> Option<&ClientIdentity> {
314 self.client_identity.as_ref()
315 }
316
317 pub fn root_certs(&self) -> &[PathOrBuf<'static>] {
318 &self.root_certs
319 }
320
321 pub fn disable_built_in_roots(&self) -> bool {
322 self.disable_built_in_roots
323 }
324
325 pub fn skip_domain_validation(&self) -> bool {
326 self.skip_domain_validation
327 }
328
329 pub fn accept_invalid_certs(&self) -> bool {
330 self.accept_invalid_certs
331 }
332
333 pub fn tls_hostname_override(&self) -> Option<&str> {
334 self.tls_hostname_override.as_deref()
335 }
336}
337
338/// Connection pool options.
339///
340/// ```
341/// # use mysql_async::{PoolOpts, PoolConstraints};
342/// # use std::time::Duration;
343/// let pool_opts = PoolOpts::default()
344/// .with_constraints(PoolConstraints::new(15, 30).unwrap())
345/// .with_inactive_connection_ttl(Duration::from_secs(60));
346/// ```
347#[derive(Debug, Clone, Eq, PartialEq, Hash)]
348pub struct PoolOpts {
349 constraints: PoolConstraints,
350 inactive_connection_ttl: Duration,
351 ttl_check_interval: Duration,
352 abs_conn_ttl: Option<Duration>,
353 abs_conn_ttl_jitter: Option<Duration>,
354 reset_connection: bool,
355}
356
357impl PoolOpts {
358 /// Calls `Self::default`.
359 pub fn new() -> Self {
360 Self::default()
361 }
362
363 /// Creates the default [`PoolOpts`] with the given constraints.
364 pub fn with_constraints(mut self, constraints: PoolConstraints) -> Self {
365 self.constraints = constraints;
366 self
367 }
368
369 /// Returns pool constraints.
370 pub fn constraints(&self) -> PoolConstraints {
371 self.constraints
372 }
373
374 /// Sets whether to reset connection upon returning it to a pool (defaults to `true`).
375 ///
376 /// Default behavior increases reliability but comes with cons:
377 ///
378 /// * reset procedure removes all prepared statements, i.e. kills prepared statements cache
379 /// * connection reset is quite fast but requires additional client-server roundtrip
380 /// (might also requires requthentication for older servers)
381 ///
382 /// The purpose of the reset procedure is to:
383 ///
384 /// * rollback any opened transactions (`mysql_async` is able to do this without explicit reset)
385 /// * reset transaction isolation level
386 /// * reset session variables
387 /// * delete user variables
388 /// * remove temporary tables
389 /// * remove all PREPARE statement (this action kills prepared statements cache)
390 ///
391 /// So to encrease overall performance you can safely opt-out of the default behavior
392 /// if you are not willing to change the session state in an unpleasant way.
393 ///
394 /// It is also possible to selectively opt-in/out using [`Conn::reset_connection`][1].
395 ///
396 /// # Connection URL
397 ///
398 /// You can use `reset_connection` URL parameter to set this value. E.g.
399 ///
400 /// ```
401 /// # use mysql_async::*;
402 /// # use std::time::Duration;
403 /// # fn main() -> Result<()> {
404 /// let opts = Opts::from_url("mysql://localhost/db?reset_connection=false")?;
405 /// assert_eq!(opts.pool_opts().reset_connection(), false);
406 /// # Ok(()) }
407 /// ```
408 ///
409 /// [1]: crate::Conn::reset_connection
410 pub fn with_reset_connection(mut self, reset_connection: bool) -> Self {
411 self.reset_connection = reset_connection;
412 self
413 }
414
415 /// Returns the `reset_connection` value (see [`PoolOpts::with_reset_connection`]).
416 pub fn reset_connection(&self) -> bool {
417 self.reset_connection
418 }
419
420 /// Sets an absolute TTL after which a connection is removed from the pool.
421 /// This may push the pool below the requested minimum pool size and is indepedent of the
422 /// idle TTL.
423 /// The absolute TTL is disabled by default.
424 /// Fractions of seconds are ignored.
425 pub fn with_abs_conn_ttl(mut self, ttl: Option<Duration>) -> Self {
426 self.abs_conn_ttl = ttl;
427 self
428 }
429
430 /// Optionally, the absolute TTL can be extended by a per-connection random amount
431 /// bounded by `jitter`.
432 /// Setting `abs_conn_ttl_jitter` without `abs_conn_ttl` has no effect.
433 /// Fractions of seconds are ignored.
434 pub fn with_abs_conn_ttl_jitter(mut self, jitter: Option<Duration>) -> Self {
435 self.abs_conn_ttl_jitter = jitter;
436 self
437 }
438
439 /// Returns the absolute TTL, if set.
440 pub fn abs_conn_ttl(&self) -> Option<Duration> {
441 self.abs_conn_ttl
442 }
443
444 /// Returns the absolute TTL's jitter bound, if set.
445 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
446 self.abs_conn_ttl_jitter
447 }
448
449 /// Returns a new deadline that's TTL (+ random jitter) in the future.
450 pub(crate) fn new_connection_ttl_deadline(&self) -> Option<Instant> {
451 if let Some(ttl) = self.abs_conn_ttl {
452 let jitter = if let Some(jitter) = self.abs_conn_ttl_jitter {
453 Duration::from_secs(rand::thread_rng().gen_range(0..=jitter.as_secs()))
454 } else {
455 Duration::ZERO
456 };
457 Some(Instant::now() + ttl + jitter)
458 } else {
459 None
460 }
461 }
462
463 /// Pool will recycle inactive connection if it is outside of the lower bound of the pool
464 /// and if it is idling longer than this value (defaults to
465 /// [`DEFAULT_INACTIVE_CONNECTION_TTL`]).
466 ///
467 /// Note that it may, actually, idle longer because of [`PoolOpts::ttl_check_interval`].
468 ///
469 /// # Connection URL
470 ///
471 /// You can use `inactive_connection_ttl` URL parameter to set this value (in seconds). E.g.
472 ///
473 /// ```
474 /// # use mysql_async::*;
475 /// # use std::time::Duration;
476 /// # fn main() -> Result<()> {
477 /// let opts = Opts::from_url("mysql://localhost/db?inactive_connection_ttl=60")?;
478 /// assert_eq!(opts.pool_opts().inactive_connection_ttl(), Duration::from_secs(60));
479 /// # Ok(()) }
480 /// ```
481 pub fn with_inactive_connection_ttl(mut self, ttl: Duration) -> Self {
482 self.inactive_connection_ttl = ttl;
483 self
484 }
485
486 /// Returns a `inactive_connection_ttl` value.
487 pub fn inactive_connection_ttl(&self) -> Duration {
488 self.inactive_connection_ttl
489 }
490
491 /// Pool will check idling connection for expiration with this interval
492 /// (defaults to [`DEFAULT_TTL_CHECK_INTERVAL`]).
493 ///
494 /// If `interval` is less than one second, then [`DEFAULT_TTL_CHECK_INTERVAL`] will be used.
495 ///
496 /// # Connection URL
497 ///
498 /// You can use `ttl_check_interval` URL parameter to set this value (in seconds). E.g.
499 ///
500 /// ```
501 /// # use mysql_async::*;
502 /// # use std::time::Duration;
503 /// # fn main() -> Result<()> {
504 /// let opts = Opts::from_url("mysql://localhost/db?ttl_check_interval=60")?;
505 /// assert_eq!(opts.pool_opts().ttl_check_interval(), Duration::from_secs(60));
506 /// # Ok(()) }
507 /// ```
508 pub fn with_ttl_check_interval(mut self, interval: Duration) -> Self {
509 if interval < Duration::from_secs(1) {
510 self.ttl_check_interval = DEFAULT_TTL_CHECK_INTERVAL
511 } else {
512 self.ttl_check_interval = interval;
513 }
514 self
515 }
516
517 /// Returns a `ttl_check_interval` value.
518 pub fn ttl_check_interval(&self) -> Duration {
519 self.ttl_check_interval
520 }
521
522 /// Returns active bound for this `PoolOpts`.
523 ///
524 /// This value controls how many connections will be returned to an idle queue of a pool.
525 ///
526 /// Active bound is either:
527 /// * `min` bound of the pool constraints, if this [`PoolOpts`] defines
528 /// `inactive_connection_ttl` to be `0`. This means, that pool will hold no more than `min`
529 /// number of idling connections and other connections will be immediately disconnected.
530 /// * `max` bound of the pool constraints, if this [`PoolOpts`] defines
531 /// `inactive_connection_ttl` to be non-zero. This means, that pool will hold up to `max`
532 /// number of idling connections and this number will be eventually reduced to `min`
533 /// by a handler of `ttl_check_interval`.
534 pub(crate) fn active_bound(&self) -> usize {
535 if self.inactive_connection_ttl > Duration::from_secs(0) {
536 self.constraints.max
537 } else {
538 self.constraints.min
539 }
540 }
541}
542
543impl Default for PoolOpts {
544 fn default() -> Self {
545 Self {
546 constraints: DEFAULT_POOL_CONSTRAINTS,
547 inactive_connection_ttl: DEFAULT_INACTIVE_CONNECTION_TTL,
548 ttl_check_interval: DEFAULT_TTL_CHECK_INTERVAL,
549 abs_conn_ttl: None,
550 abs_conn_ttl_jitter: None,
551 reset_connection: true,
552 }
553 }
554}
555
556#[derive(Clone, Eq, PartialEq, Default, Debug)]
557pub(crate) struct InnerOpts {
558 mysql_opts: MysqlOpts,
559 address: HostPortOrUrl,
560}
561
562/// Mysql connection options.
563///
564/// Build one with [`OptsBuilder`].
565#[derive(Clone, Eq, PartialEq, Debug)]
566pub(crate) struct MysqlOpts {
567 /// User (defaults to `None`).
568 user: Option<String>,
569
570 /// Password (defaults to `None`).
571 pass: Option<String>,
572
573 /// Database name (defaults to `None`).
574 db_name: Option<String>,
575
576 /// TCP keep alive timeout in milliseconds (defaults to `None`).
577 tcp_keepalive: Option<u32>,
578
579 /// Whether to enable `TCP_NODELAY` (defaults to `true`).
580 ///
581 /// This option disables Nagle's algorithm, which can cause unusually high latency (~40ms) at
582 /// some cost to maximum throughput. See blackbeam/rust-mysql-simple#132.
583 tcp_nodelay: bool,
584
585 /// Local infile handler
586 local_infile_handler: Option<GlobalHandlerObject>,
587
588 /// Connection pool options (defaults to [`PoolOpts::default`]).
589 pool_opts: PoolOpts,
590
591 /// Pool will close a connection if time since last IO exceeds this number of seconds
592 /// (defaults to `wait_timeout`).
593 conn_ttl: Option<Duration>,
594
595 /// Commands to execute once new connection is established.
596 init: Vec<String>,
597
598 /// Commands to execute on new connection and every time
599 /// [`Conn::reset`] or [`Conn::change_user`] is invoked.
600 setup: Vec<String>,
601
602 /// Number of prepared statements cached on the client side (per connection). Defaults to `10`.
603 stmt_cache_size: usize,
604
605 /// Driver will require SSL connection if this option isn't `None` (default to `None`).
606 ssl_opts: Option<SslOpts>,
607
608 /// Prefer socket connection (defaults to `true`).
609 ///
610 /// Will reconnect via socket (or named pipe on Windows) after TCP connection to `127.0.0.1`
611 /// if `true`.
612 ///
613 /// Will fall back to TCP on error. Use `socket` option to enforce socket connection.
614 ///
615 /// # Note
616 ///
617 /// Library will query the `@@socket` server variable to get socket address,
618 /// and this address may be incorrect in some cases (i.e. docker).
619 prefer_socket: bool,
620
621 /// Path to unix socket (or named pipe on Windows) (defaults to `None`).
622 socket: Option<String>,
623
624 /// If not `None`, then client will ask for compression if server supports it
625 /// (defaults to `None`).
626 ///
627 /// Can be defined using `compress` connection url parameter with values:
628 /// * `fast` - for compression level 1;
629 /// * `best` - for compression level 9;
630 /// * `on`, `true` - for default compression level;
631 /// * `0`, ..., `9`.
632 ///
633 /// Note that compression level defined here will affect only outgoing packets.
634 compression: Option<crate::Compression>,
635
636 /// Client side `max_allowed_packet` value (defaults to `None`).
637 ///
638 /// By default `Conn` will query this value from the server. One can avoid this step
639 /// by explicitly specifying it.
640 max_allowed_packet: Option<usize>,
641
642 /// Client side `wait_timeout` value (defaults to `None`).
643 ///
644 /// By default `Conn` will query this value from the server. One can avoid this step
645 /// by explicitly specifying it.
646 wait_timeout: Option<usize>,
647
648 /// Disables `mysql_old_password` plugin (defaults to `true`).
649 ///
650 /// Available via `secure_auth` connection url parameter.
651 secure_auth: bool,
652
653 /// Enables `CLIENT_FOUND_ROWS` capability (defaults to `false`).
654 ///
655 /// Changes the behavior of the affected count returned for writes (UPDATE/INSERT etc).
656 /// It makes MySQL return the FOUND rows instead of the AFFECTED rows.
657 client_found_rows: bool,
658
659 /// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
660 ///
661 /// Enables client to send passwords to the server as cleartext, without hashing or encryption
662 /// (consult MySql documentation for more info).
663 ///
664 /// # Security Notes
665 ///
666 /// Sending passwords as cleartext may be a security problem in some configurations. Please
667 /// consider using TLS or encrypted tunnels for server connection.
668 enable_cleartext_plugin: bool,
669}
670
671/// Mysql connection options.
672///
673/// Build one with [`OptsBuilder`].
674#[derive(Clone, Eq, PartialEq, Debug, Default)]
675pub struct Opts {
676 inner: Arc<InnerOpts>,
677}
678
679impl Opts {
680 #[doc(hidden)]
681 pub fn addr_is_loopback(&self) -> bool {
682 self.inner.address.is_loopback()
683 }
684
685 pub fn from_url(url: &str) -> std::result::Result<Opts, UrlError> {
686 let mut url = Url::parse(url)?;
687
688 // We use the URL for socket address resolution later, so make
689 // sure it has a port set.
690 if url.port().is_none() {
691 url.set_port(Some(DEFAULT_PORT))
692 .map_err(|_| UrlError::Invalid)?;
693 }
694
695 let mysql_opts = mysqlopts_from_url(&url)?;
696 let address = HostPortOrUrl::Url(url);
697
698 let inner_opts = InnerOpts {
699 mysql_opts,
700 address,
701 };
702
703 Ok(Opts {
704 inner: Arc::new(inner_opts),
705 })
706 }
707
708 /// Address of mysql server (defaults to `127.0.0.1`). Hostnames should also work.
709 pub fn ip_or_hostname(&self) -> &str {
710 self.inner.address.get_ip_or_hostname()
711 }
712
713 pub(crate) fn hostport_or_url(&self) -> &HostPortOrUrl {
714 &self.inner.address
715 }
716
717 /// TCP port of mysql server (defaults to `3306`).
718 pub fn tcp_port(&self) -> u16 {
719 self.inner.address.get_tcp_port()
720 }
721
722 /// The resolved IPs for the mysql server, if provided.
723 pub fn resolved_ips(&self) -> &Option<Vec<IpAddr>> {
724 self.inner.address.get_resolved_ips()
725 }
726
727 /// User (defaults to `None`).
728 ///
729 /// # Connection URL
730 ///
731 /// Can be defined in connection URL. E.g.
732 ///
733 /// ```
734 /// # use mysql_async::*;
735 /// # fn main() -> Result<()> {
736 /// let opts = Opts::from_url("mysql://user@localhost/database_name")?;
737 /// assert_eq!(opts.user(), Some("user"));
738 /// # Ok(()) }
739 /// ```
740 pub fn user(&self) -> Option<&str> {
741 self.inner.mysql_opts.user.as_ref().map(AsRef::as_ref)
742 }
743
744 /// Password (defaults to `None`).
745 ///
746 /// # Connection URL
747 ///
748 /// Can be defined in connection URL. E.g.
749 ///
750 /// ```
751 /// # use mysql_async::*;
752 /// # fn main() -> Result<()> {
753 /// let opts = Opts::from_url("mysql://user:pass%20word@localhost/database_name")?;
754 /// assert_eq!(opts.pass(), Some("pass word"));
755 /// # Ok(()) }
756 /// ```
757 pub fn pass(&self) -> Option<&str> {
758 self.inner.mysql_opts.pass.as_ref().map(AsRef::as_ref)
759 }
760
761 /// Database name (defaults to `None`).
762 ///
763 /// # Connection URL
764 ///
765 /// Database name can be defined in connection URL. E.g.
766 ///
767 /// ```
768 /// # use mysql_async::*;
769 /// # fn main() -> Result<()> {
770 /// let opts = Opts::from_url("mysql://localhost/database_name")?;
771 /// assert_eq!(opts.db_name(), Some("database_name"));
772 /// # Ok(()) }
773 /// ```
774 pub fn db_name(&self) -> Option<&str> {
775 self.inner.mysql_opts.db_name.as_ref().map(AsRef::as_ref)
776 }
777
778 /// Commands to execute once new connection is established.
779 pub fn init(&self) -> &[String] {
780 self.inner.mysql_opts.init.as_ref()
781 }
782
783 /// Commands to execute on new connection and every time
784 /// [`Conn::reset`][1] or [`Conn::change_user`][2] is invoked.
785 ///
786 /// [1]: crate::Conn::reset
787 /// [2]: crate::Conn::change_user
788 pub fn setup(&self) -> &[String] {
789 self.inner.mysql_opts.setup.as_ref()
790 }
791
792 /// TCP keep alive timeout in milliseconds (defaults to `None`).
793 ///
794 /// # Connection URL
795 ///
796 /// You can use `tcp_keepalive` URL parameter to set this value (in milliseconds). E.g.
797 ///
798 /// ```
799 /// # use mysql_async::*;
800 /// # fn main() -> Result<()> {
801 /// let opts = Opts::from_url("mysql://localhost/db?tcp_keepalive=10000")?;
802 /// assert_eq!(opts.tcp_keepalive(), Some(10_000));
803 /// # Ok(()) }
804 /// ```
805 pub fn tcp_keepalive(&self) -> Option<u32> {
806 self.inner.mysql_opts.tcp_keepalive
807 }
808
809 /// Set the `TCP_NODELAY` option for the mysql connection (defaults to `true`).
810 ///
811 /// Setting this option to false re-enables Nagle's algorithm, which can cause unusually high
812 /// latency (~40ms) but may increase maximum throughput. See #132.
813 ///
814 /// # Connection URL
815 ///
816 /// You can use `tcp_nodelay` URL parameter to set this value. E.g.
817 ///
818 /// ```
819 /// # use mysql_async::*;
820 /// # fn main() -> Result<()> {
821 /// let opts = Opts::from_url("mysql://localhost/db?tcp_nodelay=false")?;
822 /// assert_eq!(opts.tcp_nodelay(), false);
823 /// # Ok(()) }
824 /// ```
825 pub fn tcp_nodelay(&self) -> bool {
826 self.inner.mysql_opts.tcp_nodelay
827 }
828
829 /// Handler for local infile requests (defaults to `None`).
830 pub fn local_infile_handler(&self) -> Option<Arc<dyn GlobalHandler>> {
831 self.inner
832 .mysql_opts
833 .local_infile_handler
834 .as_ref()
835 .map(|x| x.clone_inner())
836 }
837
838 /// Connection pool options (defaults to [`Default::default`]).
839 pub fn pool_opts(&self) -> &PoolOpts {
840 &self.inner.mysql_opts.pool_opts
841 }
842
843 /// Pool will close connection if time since last IO exceeds this number of seconds
844 /// (defaults to `wait_timeout`. `None` to reset to default).
845 ///
846 /// # Connection URL
847 ///
848 /// You can use `conn_ttl` URL parameter to set this value (in seconds). E.g.
849 ///
850 /// ```
851 /// # use mysql_async::*;
852 /// # use std::time::Duration;
853 /// # fn main() -> Result<()> {
854 /// let opts = Opts::from_url("mysql://localhost/db?conn_ttl=360")?;
855 /// assert_eq!(opts.conn_ttl(), Some(Duration::from_secs(360)));
856 /// # Ok(()) }
857 /// ```
858 pub fn conn_ttl(&self) -> Option<Duration> {
859 self.inner.mysql_opts.conn_ttl
860 }
861
862 /// The pool will close a connection when this absolute TTL has elapsed.
863 /// Disabled by default.
864 ///
865 /// Enables forced recycling and migration of connections in a guaranteed timeframe.
866 /// This TTL bypasses pool constraints and an idle pool can go below the min size.
867 ///
868 /// # Connection URL
869 ///
870 /// You can use `abs_conn_ttl` URL parameter to set this value (in seconds). E.g.
871 ///
872 /// ```
873 /// # use mysql_async::*;
874 /// # use std::time::Duration;
875 /// # fn main() -> Result<()> {
876 /// let opts = Opts::from_url("mysql://localhost/db?abs_conn_ttl=86400")?;
877 /// assert_eq!(opts.abs_conn_ttl(), Some(Duration::from_secs(24 * 60 * 60)));
878 /// # Ok(()) }
879 /// ```
880 pub fn abs_conn_ttl(&self) -> Option<Duration> {
881 self.inner.mysql_opts.pool_opts.abs_conn_ttl
882 }
883
884 /// Upper bound of a random value added to the absolute TTL, if enabled.
885 /// Disabled by default.
886 ///
887 /// Should be used to prevent connections from closing at the same time.
888 ///
889 /// # Connection URL
890 ///
891 /// You can use `abs_conn_ttl_jitter` URL parameter to set this value (in seconds). E.g.
892 ///
893 /// ```
894 /// # use mysql_async::*;
895 /// # use std::time::Duration;
896 /// # fn main() -> Result<()> {
897 /// let opts = Opts::from_url("mysql://localhost/db?abs_conn_ttl=7200&abs_conn_ttl_jitter=3600")?;
898 /// assert_eq!(opts.abs_conn_ttl_jitter(), Some(Duration::from_secs(60 * 60)));
899 /// # Ok(()) }
900 /// ```
901 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
902 self.inner.mysql_opts.pool_opts.abs_conn_ttl_jitter
903 }
904
905 /// Number of prepared statements cached on the client side (per connection). Defaults to
906 /// [`DEFAULT_STMT_CACHE_SIZE`].
907 ///
908 /// Call with `None` to reset to default. Set to `0` to disable statement cache.
909 ///
910 /// # Caveats
911 ///
912 /// If statement cache is disabled (`stmt_cache_size` is `0`), then you must close statements
913 /// manually.
914 ///
915 /// # Connection URL
916 ///
917 /// You can use `stmt_cache_size` URL parameter to set this value. E.g.
918 ///
919 /// ```
920 /// # use mysql_async::*;
921 /// # fn main() -> Result<()> {
922 /// let opts = Opts::from_url("mysql://localhost/db?stmt_cache_size=128")?;
923 /// assert_eq!(opts.stmt_cache_size(), 128);
924 /// # Ok(()) }
925 /// ```
926 pub fn stmt_cache_size(&self) -> usize {
927 self.inner.mysql_opts.stmt_cache_size
928 }
929
930 /// Driver will require SSL connection if this opts isn't `None` (defaults to `None`).
931 ///
932 /// # Connection URL parameters
933 ///
934 /// Note that for securty reasons:
935 ///
936 /// * CA and IDENTITY verifications are opt-out
937 /// * there is no way to give an idenity or root certs via query URL
938 ///
939 /// URL Parameters:
940 ///
941 /// * `require_ssl: bool` (defaults to `false`) – requires SSL with default [`SslOpts`]
942 /// * `verify_ca: bool` (defaults to `true`) – requires server Certificate Authority (CA)
943 /// certificate validation against the configured CA certificates.
944 /// Makes no sence if `require_ssl` equals `false`.
945 /// * `verify_identity: bool` (defaults to `true`) – perform host name identity verification
946 /// by checking the host name the client uses for connecting to the server against
947 /// the identity in the certificate that the server sends to the client.
948 /// Makes no sence if `require_ssl` equals `false`.
949 ///
950 ///
951 pub fn ssl_opts(&self) -> Option<&SslOpts> {
952 self.inner.mysql_opts.ssl_opts.as_ref()
953 }
954
955 /// Prefer socket connection (defaults to `true` **temporary `false` on Windows platform**).
956 ///
957 /// Will reconnect via socket (or named pipe on Windows) after TCP connection to `127.0.0.1`
958 /// if `true`.
959 ///
960 /// Will fall back to TCP on error. Use `socket` option to enforce socket connection.
961 ///
962 /// # Note
963 ///
964 /// Library will query the `@@socket` server variable to get socket address,
965 /// and this address may be incorrect in some cases (e.g. docker).
966 ///
967 /// # Connection URL
968 ///
969 /// You can use `prefer_socket` URL parameter to set this value. E.g.
970 ///
971 /// ```
972 /// # use mysql_async::*;
973 /// # fn main() -> Result<()> {
974 /// let opts = Opts::from_url("mysql://localhost/db?prefer_socket=false")?;
975 /// assert_eq!(opts.prefer_socket(), false);
976 /// # Ok(()) }
977 /// ```
978 pub fn prefer_socket(&self) -> bool {
979 self.inner.mysql_opts.prefer_socket
980 }
981
982 /// Path to unix socket (or named pipe on Windows) (defaults to `None`).
983 ///
984 /// # Connection URL
985 ///
986 /// You can use `socket` URL parameter to set this value. E.g.
987 ///
988 /// ```
989 /// # use mysql_async::*;
990 /// # fn main() -> Result<()> {
991 /// let opts = Opts::from_url("mysql://localhost/db?socket=%2Fpath%2Fto%2Fsocket")?;
992 /// assert_eq!(opts.socket(), Some("/path/to/socket"));
993 /// # Ok(()) }
994 /// ```
995 pub fn socket(&self) -> Option<&str> {
996 self.inner.mysql_opts.socket.as_deref()
997 }
998
999 /// If not `None`, then client will ask for compression if server supports it
1000 /// (defaults to `None`).
1001 ///
1002 /// # Connection URL
1003 ///
1004 /// You can use `compression` URL parameter to set this value:
1005 ///
1006 /// * `fast` - for compression level 1;
1007 /// * `best` - for compression level 9;
1008 /// * `on`, `true` - for default compression level;
1009 /// * `0`, ..., `9`.
1010 ///
1011 /// Note that compression level defined here will affect only outgoing packets.
1012 pub fn compression(&self) -> Option<crate::Compression> {
1013 self.inner.mysql_opts.compression
1014 }
1015
1016 /// Client side `max_allowed_packet` value (defaults to `None`).
1017 ///
1018 /// By default `Conn` will query this value from the server. One can avoid this step
1019 /// by explicitly specifying it. Server side default is 4MB.
1020 ///
1021 /// Available in connection URL via `max_allowed_packet` parameter.
1022 pub fn max_allowed_packet(&self) -> Option<usize> {
1023 self.inner.mysql_opts.max_allowed_packet
1024 }
1025
1026 /// Client side `wait_timeout` value (defaults to `None`).
1027 ///
1028 /// By default `Conn` will query this value from the server. One can avoid this step
1029 /// by explicitly specifying it. Server side default is 28800.
1030 ///
1031 /// Available in connection URL via `wait_timeout` parameter.
1032 pub fn wait_timeout(&self) -> Option<usize> {
1033 self.inner.mysql_opts.wait_timeout
1034 }
1035
1036 /// Disables `mysql_old_password` plugin (defaults to `true`).
1037 ///
1038 /// Available via `secure_auth` connection url parameter.
1039 pub fn secure_auth(&self) -> bool {
1040 self.inner.mysql_opts.secure_auth
1041 }
1042
1043 /// Returns `true` if `CLIENT_FOUND_ROWS` capability is enabled (defaults to `false`).
1044 ///
1045 /// `CLIENT_FOUND_ROWS` changes the behavior of the affected count returned for writes
1046 /// (UPDATE/INSERT etc). It makes MySQL return the FOUND rows instead of the AFFECTED rows.
1047 ///
1048 /// # Connection URL
1049 ///
1050 /// Use `client_found_rows` URL parameter to set this value. E.g.
1051 ///
1052 /// ```
1053 /// # use mysql_async::*;
1054 /// # fn main() -> Result<()> {
1055 /// let opts = Opts::from_url("mysql://localhost/db?client_found_rows=true")?;
1056 /// assert!(opts.client_found_rows());
1057 /// # Ok(()) }
1058 /// ```
1059 pub fn client_found_rows(&self) -> bool {
1060 self.inner.mysql_opts.client_found_rows
1061 }
1062
1063 /// Returns `true` if `mysql_clear_password` plugin support is enabled (defaults to `false`).
1064 ///
1065 /// `mysql_clear_password` enables client to send passwords to the server as cleartext, without
1066 /// hashing or encryption (consult MySql documentation for more info).
1067 ///
1068 /// # Security Notes
1069 ///
1070 /// Sending passwords as cleartext may be a security problem in some configurations. Please
1071 /// consider using TLS or encrypted tunnels for server connection.
1072 ///
1073 /// # Connection URL
1074 ///
1075 /// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
1076 ///
1077 /// ```
1078 /// # use mysql_async::*;
1079 /// # fn main() -> Result<()> {
1080 /// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
1081 /// assert!(opts.enable_cleartext_plugin());
1082 /// # Ok(()) }
1083 /// ```
1084 pub fn enable_cleartext_plugin(&self) -> bool {
1085 self.inner.mysql_opts.enable_cleartext_plugin
1086 }
1087
1088 pub(crate) fn get_capabilities(&self) -> CapabilityFlags {
1089 let mut out = CapabilityFlags::CLIENT_PROTOCOL_41
1090 | CapabilityFlags::CLIENT_SECURE_CONNECTION
1091 | CapabilityFlags::CLIENT_LONG_PASSWORD
1092 | CapabilityFlags::CLIENT_TRANSACTIONS
1093 | CapabilityFlags::CLIENT_LOCAL_FILES
1094 | CapabilityFlags::CLIENT_MULTI_STATEMENTS
1095 | CapabilityFlags::CLIENT_MULTI_RESULTS
1096 | CapabilityFlags::CLIENT_PS_MULTI_RESULTS
1097 | CapabilityFlags::CLIENT_DEPRECATE_EOF
1098 | CapabilityFlags::CLIENT_PLUGIN_AUTH;
1099
1100 if self.inner.mysql_opts.db_name.is_some() {
1101 out |= CapabilityFlags::CLIENT_CONNECT_WITH_DB;
1102 }
1103 if self.inner.mysql_opts.ssl_opts.is_some() {
1104 out |= CapabilityFlags::CLIENT_SSL;
1105 }
1106 if self.inner.mysql_opts.compression.is_some() {
1107 out |= CapabilityFlags::CLIENT_COMPRESS;
1108 }
1109 if self.client_found_rows() {
1110 out |= CapabilityFlags::CLIENT_FOUND_ROWS;
1111 }
1112
1113 out
1114 }
1115}
1116
1117impl Default for MysqlOpts {
1118 fn default() -> MysqlOpts {
1119 MysqlOpts {
1120 user: None,
1121 pass: None,
1122 db_name: None,
1123 init: vec![],
1124 setup: vec![],
1125 tcp_keepalive: None,
1126 tcp_nodelay: true,
1127 local_infile_handler: None,
1128 pool_opts: Default::default(),
1129 conn_ttl: None,
1130 stmt_cache_size: DEFAULT_STMT_CACHE_SIZE,
1131 ssl_opts: None,
1132 prefer_socket: cfg!(not(target_os = "windows")),
1133 socket: None,
1134 compression: None,
1135 max_allowed_packet: None,
1136 wait_timeout: None,
1137 secure_auth: true,
1138 client_found_rows: false,
1139 enable_cleartext_plugin: false,
1140 }
1141 }
1142}
1143
1144/// Connection pool constraints.
1145///
1146/// This type stores `min` and `max` constraints for [`crate::Pool`] and ensures that `min <= max`.
1147#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1148pub struct PoolConstraints {
1149 min: usize,
1150 max: usize,
1151}
1152
1153impl PoolConstraints {
1154 /// Creates new [`PoolConstraints`] if constraints are valid (`min <= max`).
1155 ///
1156 /// # Connection URL
1157 ///
1158 /// You can use `pool_min` and `pool_max` URL parameters to define pool constraints.
1159 ///
1160 /// ```
1161 /// # use mysql_async::*;
1162 /// # fn main() -> Result<()> {
1163 /// let opts = Opts::from_url("mysql://localhost/db?pool_min=0&pool_max=151")?;
1164 /// assert_eq!(opts.pool_opts().constraints(), PoolConstraints::new(0, 151).unwrap());
1165 /// # Ok(()) }
1166 /// ```
1167 pub fn new(min: usize, max: usize) -> Option<PoolConstraints> {
1168 if min <= max {
1169 Some(PoolConstraints { min, max })
1170 } else {
1171 None
1172 }
1173 }
1174
1175 /// Lower bound of this pool constraints.
1176 pub fn min(&self) -> usize {
1177 self.min
1178 }
1179
1180 /// Upper bound of this pool constraints.
1181 pub fn max(&self) -> usize {
1182 self.max
1183 }
1184}
1185
1186impl Default for PoolConstraints {
1187 fn default() -> Self {
1188 DEFAULT_POOL_CONSTRAINTS
1189 }
1190}
1191
1192impl From<PoolConstraints> for (usize, usize) {
1193 /// Transforms constraints to a pair of `(min, max)`.
1194 fn from(PoolConstraints { min, max }: PoolConstraints) -> Self {
1195 (min, max)
1196 }
1197}
1198
1199/// Provides a way to build [`Opts`].
1200///
1201/// ```
1202/// # use mysql_async::OptsBuilder;
1203/// // You can use the default builder
1204/// let existing_opts = OptsBuilder::default()
1205/// .ip_or_hostname("foo")
1206/// .db_name(Some("bar"))
1207/// // ..
1208/// # ;
1209///
1210/// // Or use existing T: Into<Opts>
1211/// let builder = OptsBuilder::from(existing_opts)
1212/// .ip_or_hostname("baz")
1213/// .tcp_port(33306)
1214/// // ..
1215/// # ;
1216/// ```
1217#[derive(Debug, Clone, Eq, PartialEq)]
1218pub struct OptsBuilder {
1219 opts: MysqlOpts,
1220 ip_or_hostname: String,
1221 tcp_port: u16,
1222 resolved_ips: Option<Vec<IpAddr>>,
1223}
1224
1225impl Default for OptsBuilder {
1226 fn default() -> Self {
1227 let address = HostPortOrUrl::default();
1228 Self {
1229 opts: MysqlOpts::default(),
1230 ip_or_hostname: address.get_ip_or_hostname().into(),
1231 tcp_port: address.get_tcp_port(),
1232 resolved_ips: None,
1233 }
1234 }
1235}
1236
1237impl OptsBuilder {
1238 /// Creates new builder from the given `Opts`.
1239 ///
1240 /// # Panic
1241 ///
1242 /// It'll panic if `Opts::try_from(opts)` returns error.
1243 pub fn from_opts<T>(opts: T) -> Self
1244 where
1245 Opts: TryFrom<T>,
1246 <Opts as TryFrom<T>>::Error: std::error::Error,
1247 {
1248 let opts = Opts::try_from(opts).unwrap();
1249
1250 OptsBuilder {
1251 tcp_port: opts.inner.address.get_tcp_port(),
1252 ip_or_hostname: opts.inner.address.get_ip_or_hostname().to_string(),
1253 resolved_ips: opts.inner.address.get_resolved_ips().clone(),
1254 opts: opts.inner.mysql_opts.clone(),
1255 }
1256 }
1257
1258 /// Defines server IP or hostname. See [`Opts::ip_or_hostname`].
1259 pub fn ip_or_hostname<T: Into<String>>(mut self, ip_or_hostname: T) -> Self {
1260 self.ip_or_hostname = ip_or_hostname.into();
1261 self
1262 }
1263
1264 /// Defines TCP port. See [`Opts::tcp_port`].
1265 pub fn tcp_port(mut self, tcp_port: u16) -> Self {
1266 self.tcp_port = tcp_port;
1267 self
1268 }
1269
1270 /// Defines already-resolved IPs to use for the connection. When provided
1271 /// the connection will not perform DNS resolution and the hostname will be
1272 /// used only for TLS identity verification purposes.
1273 pub fn resolved_ips<T: Into<Vec<IpAddr>>>(mut self, ips: Option<T>) -> Self {
1274 self.resolved_ips = ips.map(Into::into);
1275 self
1276 }
1277
1278 /// Defines user name. See [`Opts::user`].
1279 pub fn user<T: Into<String>>(mut self, user: Option<T>) -> Self {
1280 self.opts.user = user.map(Into::into);
1281 self
1282 }
1283
1284 /// Defines password. See [`Opts::pass`].
1285 pub fn pass<T: Into<String>>(mut self, pass: Option<T>) -> Self {
1286 self.opts.pass = pass.map(Into::into);
1287 self
1288 }
1289
1290 /// Defines database name. See [`Opts::db_name`].
1291 pub fn db_name<T: Into<String>>(mut self, db_name: Option<T>) -> Self {
1292 self.opts.db_name = db_name.map(Into::into);
1293 self
1294 }
1295
1296 /// Defines initial queries. See [`Opts::init`].
1297 pub fn init<T: Into<String>>(mut self, init: Vec<T>) -> Self {
1298 self.opts.init = init.into_iter().map(Into::into).collect();
1299 self
1300 }
1301
1302 /// Defines setup queries. See [`Opts::setup`].
1303 pub fn setup<T: Into<String>>(mut self, setup: Vec<T>) -> Self {
1304 self.opts.setup = setup.into_iter().map(Into::into).collect();
1305 self
1306 }
1307
1308 /// Defines `tcp_keepalive` option. See [`Opts::tcp_keepalive`].
1309 pub fn tcp_keepalive<T: Into<u32>>(mut self, tcp_keepalive: Option<T>) -> Self {
1310 self.opts.tcp_keepalive = tcp_keepalive.map(Into::into);
1311 self
1312 }
1313
1314 /// Defines `tcp_nodelay` option. See [`Opts::tcp_nodelay`].
1315 pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
1316 self.opts.tcp_nodelay = nodelay;
1317 self
1318 }
1319
1320 /// Defines _global_ LOCAL INFILE handler (see crate-level docs).
1321 pub fn local_infile_handler<T>(mut self, handler: Option<T>) -> Self
1322 where
1323 T: GlobalHandler,
1324 {
1325 self.opts.local_infile_handler = handler.map(GlobalHandlerObject::new);
1326 self
1327 }
1328
1329 /// Defines pool options. See [`Opts::pool_opts`].
1330 pub fn pool_opts<T: Into<Option<PoolOpts>>>(mut self, pool_opts: T) -> Self {
1331 self.opts.pool_opts = pool_opts.into().unwrap_or_default();
1332 self
1333 }
1334
1335 /// Defines connection TTL. See [`Opts::conn_ttl`].
1336 pub fn conn_ttl<T: Into<Option<Duration>>>(mut self, conn_ttl: T) -> Self {
1337 self.opts.conn_ttl = conn_ttl.into();
1338 self
1339 }
1340
1341 /// Defines statement cache size. See [`Opts::stmt_cache_size`].
1342 pub fn stmt_cache_size<T>(mut self, cache_size: T) -> Self
1343 where
1344 T: Into<Option<usize>>,
1345 {
1346 self.opts.stmt_cache_size = cache_size.into().unwrap_or(DEFAULT_STMT_CACHE_SIZE);
1347 self
1348 }
1349
1350 /// Defines SSL options. See [`Opts::ssl_opts`].
1351 pub fn ssl_opts<T: Into<Option<SslOpts>>>(mut self, ssl_opts: T) -> Self {
1352 self.opts.ssl_opts = ssl_opts.into();
1353 self
1354 }
1355
1356 /// Defines `prefer_socket` option. See [`Opts::prefer_socket`].
1357 pub fn prefer_socket<T: Into<Option<bool>>>(mut self, prefer_socket: T) -> Self {
1358 self.opts.prefer_socket = prefer_socket.into().unwrap_or(true);
1359 self
1360 }
1361
1362 /// Defines socket path. See [`Opts::socket`].
1363 pub fn socket<T: Into<String>>(mut self, socket: Option<T>) -> Self {
1364 self.opts.socket = socket.map(Into::into);
1365 self
1366 }
1367
1368 /// Defines compression. See [`Opts::compression`].
1369 pub fn compression<T: Into<Option<crate::Compression>>>(mut self, compression: T) -> Self {
1370 self.opts.compression = compression.into();
1371 self
1372 }
1373
1374 /// Defines `max_allowed_packet` option. See [`Opts::max_allowed_packet`].
1375 ///
1376 /// Note that it'll saturate to proper minimum and maximum values
1377 /// for this parameter (see MySql documentation).
1378 pub fn max_allowed_packet(mut self, max_allowed_packet: Option<usize>) -> Self {
1379 self.opts.max_allowed_packet = max_allowed_packet.map(|x| x.clamp(1024, 1073741824));
1380 self
1381 }
1382
1383 /// Defines `wait_timeout` option. See [`Opts::wait_timeout`].
1384 ///
1385 /// Note that it'll saturate to proper minimum and maximum values
1386 /// for this parameter (see MySql documentation).
1387 pub fn wait_timeout(mut self, wait_timeout: Option<usize>) -> Self {
1388 self.opts.wait_timeout = wait_timeout.map(|x| {
1389 #[cfg(windows)]
1390 let val = std::cmp::min(2147483, x);
1391 #[cfg(not(windows))]
1392 let val = std::cmp::min(31536000, x);
1393
1394 val
1395 });
1396 self
1397 }
1398
1399 /// Disables `mysql_old_password` plugin (defaults to `true`).
1400 ///
1401 /// Available via `secure_auth` connection url parameter.
1402 pub fn secure_auth(mut self, secure_auth: bool) -> Self {
1403 self.opts.secure_auth = secure_auth;
1404 self
1405 }
1406
1407 /// Enables or disables `CLIENT_FOUND_ROWS` capability. See [`Opts::client_found_rows`].
1408 pub fn client_found_rows(mut self, client_found_rows: bool) -> Self {
1409 self.opts.client_found_rows = client_found_rows;
1410 self
1411 }
1412
1413 /// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
1414 ///
1415 /// Enables client to send passwords to the server as cleartext, without hashing or encryption
1416 /// (consult MySql documentation for more info).
1417 ///
1418 /// # Security Notes
1419 ///
1420 /// Sending passwords as cleartext may be a security problem in some configurations. Please
1421 /// consider using TLS or encrypted tunnels for server connection.
1422 ///
1423 /// # Connection URL
1424 ///
1425 /// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
1426 ///
1427 /// ```
1428 /// # use mysql_async::*;
1429 /// # fn main() -> Result<()> {
1430 /// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
1431 /// assert!(opts.enable_cleartext_plugin());
1432 /// # Ok(()) }
1433 /// ```
1434 pub fn enable_cleartext_plugin(mut self, enable_cleartext_plugin: bool) -> Self {
1435 self.opts.enable_cleartext_plugin = enable_cleartext_plugin;
1436 self
1437 }
1438}
1439
1440impl From<OptsBuilder> for Opts {
1441 fn from(builder: OptsBuilder) -> Opts {
1442 let address = HostPortOrUrl::HostPort {
1443 host: builder.ip_or_hostname,
1444 port: builder.tcp_port,
1445 resolved_ips: builder.resolved_ips,
1446 };
1447 let inner_opts = InnerOpts {
1448 mysql_opts: builder.opts,
1449 address,
1450 };
1451
1452 Opts {
1453 inner: Arc::new(inner_opts),
1454 }
1455 }
1456}
1457
1458/// [`COM_CHANGE_USER`][1] options.
1459///
1460/// Connection [`Opts`] are going to be updated accordingly upon `COM_CHANGE_USER`.
1461///
1462/// [`Opts`] won't be updated by default, because default `ChangeUserOpts` will reuse
1463/// connection's `user`, `pass` and `db_name`.
1464///
1465/// [1]: https://dev.mysql.com/doc/c-api/5.7/en/mysql-change-user.html
1466#[derive(Clone, Eq, PartialEq)]
1467pub struct ChangeUserOpts {
1468 user: Option<Option<String>>,
1469 pass: Option<Option<String>>,
1470 db_name: Option<Option<String>>,
1471}
1472
1473impl ChangeUserOpts {
1474 pub(crate) fn update_opts(self, opts: &mut Opts) {
1475 if self.user.is_none() && self.pass.is_none() && self.db_name.is_none() {
1476 return;
1477 }
1478
1479 let mut builder = OptsBuilder::from_opts(opts.clone());
1480
1481 if let Some(user) = self.user {
1482 builder = builder.user(user);
1483 }
1484
1485 if let Some(pass) = self.pass {
1486 builder = builder.pass(pass);
1487 }
1488
1489 if let Some(db_name) = self.db_name {
1490 builder = builder.db_name(db_name);
1491 }
1492
1493 *opts = Opts::from(builder);
1494 }
1495
1496 /// Creates change user options that'll reuse connection options.
1497 pub fn new() -> Self {
1498 Self {
1499 user: None,
1500 pass: None,
1501 db_name: None,
1502 }
1503 }
1504
1505 /// Set [`Opts::user`] to the given value.
1506 pub fn with_user(mut self, user: Option<String>) -> Self {
1507 self.user = Some(user);
1508 self
1509 }
1510
1511 /// Set [`Opts::pass`] to the given value.
1512 pub fn with_pass(mut self, pass: Option<String>) -> Self {
1513 self.pass = Some(pass);
1514 self
1515 }
1516
1517 /// Set [`Opts::db_name`] to the given value.
1518 pub fn with_db_name(mut self, db_name: Option<String>) -> Self {
1519 self.db_name = Some(db_name);
1520 self
1521 }
1522
1523 /// Returns user.
1524 ///
1525 /// * if `None` then `self` does not meant to change user
1526 /// * if `Some(None)` then `self` will clear user
1527 /// * if `Some(Some(_))` then `self` will change user
1528 pub fn user(&self) -> Option<Option<&str>> {
1529 self.user.as_ref().map(|x| x.as_deref())
1530 }
1531
1532 /// Returns password.
1533 ///
1534 /// * if `None` then `self` does not meant to change password
1535 /// * if `Some(None)` then `self` will clear password
1536 /// * if `Some(Some(_))` then `self` will change password
1537 pub fn pass(&self) -> Option<Option<&str>> {
1538 self.pass.as_ref().map(|x| x.as_deref())
1539 }
1540
1541 /// Returns database name.
1542 ///
1543 /// * if `None` then `self` does not meant to change database name
1544 /// * if `Some(None)` then `self` will clear database name
1545 /// * if `Some(Some(_))` then `self` will change database name
1546 pub fn db_name(&self) -> Option<Option<&str>> {
1547 self.db_name.as_ref().map(|x| x.as_deref())
1548 }
1549}
1550
1551impl Default for ChangeUserOpts {
1552 fn default() -> Self {
1553 Self::new()
1554 }
1555}
1556
1557impl fmt::Debug for ChangeUserOpts {
1558 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1559 f.debug_struct("ChangeUserOpts")
1560 .field("user", &self.user)
1561 .field(
1562 "pass",
1563 &self.pass.as_ref().map(|x| x.as_ref().map(|_| "...")),
1564 )
1565 .field("db_name", &self.db_name)
1566 .finish()
1567 }
1568}
1569
1570fn get_opts_user_from_url(url: &Url) -> Option<String> {
1571 let user = url.username();
1572 if !user.is_empty() {
1573 Some(
1574 percent_decode(user.as_ref())
1575 .decode_utf8_lossy()
1576 .into_owned(),
1577 )
1578 } else {
1579 None
1580 }
1581}
1582
1583fn get_opts_pass_from_url(url: &Url) -> Option<String> {
1584 url.password().map(|pass| {
1585 percent_decode(pass.as_ref())
1586 .decode_utf8_lossy()
1587 .into_owned()
1588 })
1589}
1590
1591fn get_opts_db_name_from_url(url: &Url) -> Option<String> {
1592 if let Some(mut segments) = url.path_segments() {
1593 segments
1594 .next()
1595 .map(|db_name| {
1596 percent_decode(db_name.as_ref())
1597 .decode_utf8_lossy()
1598 .into_owned()
1599 })
1600 .filter(|db| !db.is_empty())
1601 } else {
1602 None
1603 }
1604}
1605
1606fn from_url_basic(url: &Url) -> std::result::Result<(MysqlOpts, Vec<(String, String)>), UrlError> {
1607 if url.scheme() != "mysql" {
1608 return Err(UrlError::UnsupportedScheme {
1609 scheme: url.scheme().to_string(),
1610 });
1611 }
1612 if url.cannot_be_a_base() || !url.has_host() {
1613 return Err(UrlError::Invalid);
1614 }
1615 let user = get_opts_user_from_url(url);
1616 let pass = get_opts_pass_from_url(url);
1617 let db_name = get_opts_db_name_from_url(url);
1618
1619 let query_pairs = url.query_pairs().into_owned().collect();
1620 let opts = MysqlOpts {
1621 user,
1622 pass,
1623 db_name,
1624 ..MysqlOpts::default()
1625 };
1626
1627 Ok((opts, query_pairs))
1628}
1629
1630fn mysqlopts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
1631 let (mut opts, query_pairs): (MysqlOpts, _) = from_url_basic(url)?;
1632 let mut pool_min = DEFAULT_POOL_CONSTRAINTS.min;
1633 let mut pool_max = DEFAULT_POOL_CONSTRAINTS.max;
1634
1635 let mut skip_domain_validation = false;
1636 let mut accept_invalid_certs = false;
1637 let mut disable_built_in_roots = false;
1638
1639 for (key, value) in query_pairs {
1640 if key == "pool_min" {
1641 match usize::from_str(&value) {
1642 Ok(value) => pool_min = value,
1643 _ => {
1644 return Err(UrlError::InvalidParamValue {
1645 param: "pool_min".into(),
1646 value,
1647 });
1648 }
1649 }
1650 } else if key == "pool_max" {
1651 match usize::from_str(&value) {
1652 Ok(value) => pool_max = value,
1653 _ => {
1654 return Err(UrlError::InvalidParamValue {
1655 param: "pool_max".into(),
1656 value,
1657 });
1658 }
1659 }
1660 } else if key == "inactive_connection_ttl" {
1661 match u64::from_str(&value) {
1662 Ok(value) => {
1663 opts.pool_opts = opts
1664 .pool_opts
1665 .with_inactive_connection_ttl(Duration::from_secs(value))
1666 }
1667 _ => {
1668 return Err(UrlError::InvalidParamValue {
1669 param: "inactive_connection_ttl".into(),
1670 value,
1671 });
1672 }
1673 }
1674 } else if key == "ttl_check_interval" {
1675 match u64::from_str(&value) {
1676 Ok(value) => {
1677 opts.pool_opts = opts
1678 .pool_opts
1679 .with_ttl_check_interval(Duration::from_secs(value))
1680 }
1681 _ => {
1682 return Err(UrlError::InvalidParamValue {
1683 param: "ttl_check_interval".into(),
1684 value,
1685 });
1686 }
1687 }
1688 } else if key == "conn_ttl" {
1689 match u64::from_str(&value) {
1690 Ok(value) => opts.conn_ttl = Some(Duration::from_secs(value)),
1691 _ => {
1692 return Err(UrlError::InvalidParamValue {
1693 param: "conn_ttl".into(),
1694 value,
1695 });
1696 }
1697 }
1698 } else if key == "abs_conn_ttl" {
1699 match u64::from_str(&value) {
1700 Ok(value) => {
1701 opts.pool_opts = opts
1702 .pool_opts
1703 .with_abs_conn_ttl(Some(Duration::from_secs(value)))
1704 }
1705 _ => {
1706 return Err(UrlError::InvalidParamValue {
1707 param: "abs_conn_ttl".into(),
1708 value,
1709 });
1710 }
1711 }
1712 } else if key == "abs_conn_ttl_jitter" {
1713 match u64::from_str(&value) {
1714 Ok(value) => {
1715 opts.pool_opts = opts
1716 .pool_opts
1717 .with_abs_conn_ttl_jitter(Some(Duration::from_secs(value)))
1718 }
1719 _ => {
1720 return Err(UrlError::InvalidParamValue {
1721 param: "abs_conn_ttl_jitter".into(),
1722 value,
1723 });
1724 }
1725 }
1726 } else if key == "tcp_keepalive" {
1727 match u32::from_str(&value) {
1728 Ok(value) => opts.tcp_keepalive = Some(value),
1729 _ => {
1730 return Err(UrlError::InvalidParamValue {
1731 param: "tcp_keepalive_ms".into(),
1732 value,
1733 });
1734 }
1735 }
1736 } else if key == "max_allowed_packet" {
1737 match usize::from_str(&value) {
1738 Ok(value) => opts.max_allowed_packet = Some(value.clamp(1024, 1073741824)),
1739 _ => {
1740 return Err(UrlError::InvalidParamValue {
1741 param: "max_allowed_packet".into(),
1742 value,
1743 });
1744 }
1745 }
1746 } else if key == "wait_timeout" {
1747 match usize::from_str(&value) {
1748 #[cfg(windows)]
1749 Ok(value) => opts.wait_timeout = Some(std::cmp::min(2147483, value)),
1750 #[cfg(not(windows))]
1751 Ok(value) => opts.wait_timeout = Some(std::cmp::min(31536000, value)),
1752 _ => {
1753 return Err(UrlError::InvalidParamValue {
1754 param: "wait_timeout".into(),
1755 value,
1756 });
1757 }
1758 }
1759 } else if key == "enable_cleartext_plugin" {
1760 match bool::from_str(&value) {
1761 Ok(parsed) => opts.enable_cleartext_plugin = parsed,
1762 Err(_) => {
1763 return Err(UrlError::InvalidParamValue {
1764 param: key.to_string(),
1765 value,
1766 });
1767 }
1768 }
1769 } else if key == "reset_connection" {
1770 match bool::from_str(&value) {
1771 Ok(parsed) => opts.pool_opts = opts.pool_opts.with_reset_connection(parsed),
1772 Err(_) => {
1773 return Err(UrlError::InvalidParamValue {
1774 param: key.to_string(),
1775 value,
1776 });
1777 }
1778 }
1779 } else if key == "tcp_nodelay" {
1780 match bool::from_str(&value) {
1781 Ok(value) => opts.tcp_nodelay = value,
1782 _ => {
1783 return Err(UrlError::InvalidParamValue {
1784 param: "tcp_nodelay".into(),
1785 value,
1786 });
1787 }
1788 }
1789 } else if key == "stmt_cache_size" {
1790 match usize::from_str(&value) {
1791 Ok(stmt_cache_size) => {
1792 opts.stmt_cache_size = stmt_cache_size;
1793 }
1794 _ => {
1795 return Err(UrlError::InvalidParamValue {
1796 param: "stmt_cache_size".into(),
1797 value,
1798 });
1799 }
1800 }
1801 } else if key == "prefer_socket" {
1802 match bool::from_str(&value) {
1803 Ok(prefer_socket) => {
1804 opts.prefer_socket = prefer_socket;
1805 }
1806 _ => {
1807 return Err(UrlError::InvalidParamValue {
1808 param: "prefer_socket".into(),
1809 value,
1810 });
1811 }
1812 }
1813 } else if key == "secure_auth" {
1814 match bool::from_str(&value) {
1815 Ok(secure_auth) => {
1816 opts.secure_auth = secure_auth;
1817 }
1818 _ => {
1819 return Err(UrlError::InvalidParamValue {
1820 param: "secure_auth".into(),
1821 value,
1822 });
1823 }
1824 }
1825 } else if key == "client_found_rows" {
1826 match bool::from_str(&value) {
1827 Ok(client_found_rows) => {
1828 opts.client_found_rows = client_found_rows;
1829 }
1830 _ => {
1831 return Err(UrlError::InvalidParamValue {
1832 param: "client_found_rows".into(),
1833 value,
1834 });
1835 }
1836 }
1837 } else if key == "socket" {
1838 opts.socket = Some(value)
1839 } else if key == "compression" {
1840 if value == "fast" {
1841 opts.compression = Some(crate::Compression::fast());
1842 } else if value == "on" || value == "true" {
1843 opts.compression = Some(crate::Compression::default());
1844 } else if value == "best" {
1845 opts.compression = Some(crate::Compression::best());
1846 } else if value.len() == 1 && 0x30 <= value.as_bytes()[0] && value.as_bytes()[0] <= 0x39
1847 {
1848 opts.compression =
1849 Some(crate::Compression::new((value.as_bytes()[0] - 0x30) as u32));
1850 } else {
1851 return Err(UrlError::InvalidParamValue {
1852 param: "compression".into(),
1853 value,
1854 });
1855 }
1856 } else if key == "require_ssl" {
1857 match bool::from_str(&value) {
1858 Ok(x) => opts.ssl_opts = x.then(SslOpts::default),
1859 _ => {
1860 return Err(UrlError::InvalidParamValue {
1861 param: "require_ssl".into(),
1862 value,
1863 });
1864 }
1865 }
1866 } else if key == "verify_ca" {
1867 match bool::from_str(&value) {
1868 Ok(x) => {
1869 accept_invalid_certs = !x;
1870 }
1871 _ => {
1872 return Err(UrlError::InvalidParamValue {
1873 param: "verify_ca".into(),
1874 value,
1875 });
1876 }
1877 }
1878 } else if key == "verify_identity" {
1879 match bool::from_str(&value) {
1880 Ok(x) => {
1881 skip_domain_validation = !x;
1882 }
1883 _ => {
1884 return Err(UrlError::InvalidParamValue {
1885 param: "verify_identity".into(),
1886 value,
1887 });
1888 }
1889 }
1890 } else if key == "built_in_roots" {
1891 match bool::from_str(&value) {
1892 Ok(x) => {
1893 disable_built_in_roots = !x;
1894 }
1895 _ => {
1896 return Err(UrlError::InvalidParamValue {
1897 param: "built_in_roots".into(),
1898 value,
1899 });
1900 }
1901 }
1902 } else {
1903 return Err(UrlError::UnknownParameter { param: key });
1904 }
1905 }
1906
1907 if let Some(pool_constraints) = PoolConstraints::new(pool_min, pool_max) {
1908 opts.pool_opts = opts.pool_opts.with_constraints(pool_constraints);
1909 } else {
1910 return Err(UrlError::InvalidPoolConstraints {
1911 min: pool_min,
1912 max: pool_max,
1913 });
1914 }
1915
1916 if let Some(ref mut ssl_opts) = opts.ssl_opts.as_mut() {
1917 ssl_opts.accept_invalid_certs = accept_invalid_certs;
1918 ssl_opts.skip_domain_validation = skip_domain_validation;
1919 ssl_opts.disable_built_in_roots = disable_built_in_roots;
1920 }
1921
1922 Ok(opts)
1923}
1924
1925impl FromStr for Opts {
1926 type Err = UrlError;
1927
1928 fn from_str(s: &str) -> std::result::Result<Self, <Self as FromStr>::Err> {
1929 Opts::from_url(s)
1930 }
1931}
1932
1933impl TryFrom<&str> for Opts {
1934 type Error = UrlError;
1935
1936 fn try_from(s: &str) -> std::result::Result<Self, UrlError> {
1937 Opts::from_url(s)
1938 }
1939}
1940
1941#[cfg(test)]
1942mod test {
1943 use super::{HostPortOrUrl, MysqlOpts, Opts, Url};
1944 use crate::{error::UrlError::InvalidParamValue, SslOpts};
1945
1946 use std::{net::IpAddr, net::Ipv4Addr, net::Ipv6Addr, str::FromStr};
1947
1948 #[test]
1949 fn test_builder_eq_url() {
1950 const URL: &str = "mysql://iq-controller@localhost/iq_controller";
1951
1952 let url_opts = super::Opts::from_str(URL).unwrap();
1953 let builder = super::OptsBuilder::default()
1954 .user(Some("iq-controller"))
1955 .ip_or_hostname("localhost")
1956 .db_name(Some("iq_controller"));
1957 let builder_opts = Opts::from(builder);
1958
1959 assert_eq!(url_opts.addr_is_loopback(), builder_opts.addr_is_loopback());
1960 assert_eq!(url_opts.ip_or_hostname(), builder_opts.ip_or_hostname());
1961 assert_eq!(url_opts.tcp_port(), builder_opts.tcp_port());
1962 assert_eq!(url_opts.user(), builder_opts.user());
1963 assert_eq!(url_opts.pass(), builder_opts.pass());
1964 assert_eq!(url_opts.db_name(), builder_opts.db_name());
1965 assert_eq!(url_opts.init(), builder_opts.init());
1966 assert_eq!(url_opts.setup(), builder_opts.setup());
1967 assert_eq!(url_opts.tcp_keepalive(), builder_opts.tcp_keepalive());
1968 assert_eq!(url_opts.tcp_nodelay(), builder_opts.tcp_nodelay());
1969 assert_eq!(url_opts.pool_opts(), builder_opts.pool_opts());
1970 assert_eq!(url_opts.conn_ttl(), builder_opts.conn_ttl());
1971 assert_eq!(url_opts.abs_conn_ttl(), builder_opts.abs_conn_ttl());
1972 assert_eq!(
1973 url_opts.abs_conn_ttl_jitter(),
1974 builder_opts.abs_conn_ttl_jitter()
1975 );
1976 assert_eq!(url_opts.stmt_cache_size(), builder_opts.stmt_cache_size());
1977 assert_eq!(url_opts.ssl_opts(), builder_opts.ssl_opts());
1978 assert_eq!(url_opts.prefer_socket(), builder_opts.prefer_socket());
1979 assert_eq!(url_opts.socket(), builder_opts.socket());
1980 assert_eq!(url_opts.compression(), builder_opts.compression());
1981 assert_eq!(
1982 url_opts.hostport_or_url().get_ip_or_hostname(),
1983 builder_opts.hostport_or_url().get_ip_or_hostname()
1984 );
1985 assert_eq!(
1986 url_opts.hostport_or_url().get_tcp_port(),
1987 builder_opts.hostport_or_url().get_tcp_port()
1988 );
1989 }
1990
1991 #[test]
1992 fn should_convert_url_into_opts() {
1993 let url = "mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true";
1994 let parsed_url =
1995 Url::parse("mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true").unwrap();
1996
1997 let mysql_opts = MysqlOpts {
1998 user: Some("usr".to_string()),
1999 pass: Some("pw".to_string()),
2000 db_name: Some("dbname".to_string()),
2001 prefer_socket: true,
2002 ..MysqlOpts::default()
2003 };
2004 let host = HostPortOrUrl::Url(parsed_url);
2005
2006 let opts = Opts::from_url(url).unwrap();
2007
2008 assert_eq!(opts.inner.mysql_opts, mysql_opts);
2009 assert_eq!(opts.hostport_or_url(), &host);
2010 }
2011
2012 #[test]
2013 fn should_convert_ipv6_url_into_opts() {
2014 let url = "mysql://usr:pw@[::1]:3309/dbname";
2015
2016 let opts = Opts::from_url(url).unwrap();
2017
2018 assert_eq!(opts.ip_or_hostname(), "[::1]");
2019 }
2020
2021 #[test]
2022 fn should_parse_ssl_params() {
2023 const URL1: &str = "mysql://localhost/foo?require_ssl=false";
2024 let opts = Opts::from_url(URL1).unwrap();
2025 assert_eq!(opts.ssl_opts(), None);
2026
2027 const URL2: &str = "mysql://localhost/foo?require_ssl=true";
2028 let opts = Opts::from_url(URL2).unwrap();
2029 assert_eq!(opts.ssl_opts(), Some(&SslOpts::default()));
2030
2031 const URL3: &str = "mysql://localhost/foo?require_ssl=true&verify_ca=false";
2032 let opts = Opts::from_url(URL3).unwrap();
2033 assert_eq!(
2034 opts.ssl_opts(),
2035 Some(&SslOpts::default().with_danger_accept_invalid_certs(true))
2036 );
2037
2038 const URL4: &str =
2039 "mysql://localhost/foo?require_ssl=true&verify_ca=false&verify_identity=false&built_in_roots=false";
2040 let opts = Opts::from_url(URL4).unwrap();
2041 assert_eq!(
2042 opts.ssl_opts(),
2043 Some(
2044 &SslOpts::default()
2045 .with_danger_accept_invalid_certs(true)
2046 .with_danger_skip_domain_validation(true)
2047 .with_disable_built_in_roots(true)
2048 )
2049 );
2050
2051 const URL5: &str =
2052 "mysql://localhost/foo?require_ssl=false&verify_ca=false&verify_identity=false";
2053 let opts = Opts::from_url(URL5).unwrap();
2054 assert_eq!(opts.ssl_opts(), None);
2055 }
2056
2057 #[test]
2058 #[should_panic]
2059 fn should_panic_on_invalid_url() {
2060 let opts = "42";
2061 let _: Opts = Opts::from_str(opts).unwrap();
2062 }
2063
2064 #[test]
2065 #[should_panic]
2066 fn should_panic_on_invalid_scheme() {
2067 let opts = "postgres://localhost";
2068 let _: Opts = Opts::from_str(opts).unwrap();
2069 }
2070
2071 #[test]
2072 #[should_panic]
2073 fn should_panic_on_unknown_query_param() {
2074 let opts = "mysql://localhost/foo?bar=baz";
2075 let _: Opts = Opts::from_str(opts).unwrap();
2076 }
2077
2078 #[test]
2079 fn should_parse_compression() {
2080 let err = Opts::from_url("mysql://localhost/foo?compression=").unwrap_err();
2081 assert_eq!(
2082 err,
2083 InvalidParamValue {
2084 param: "compression".into(),
2085 value: "".into()
2086 }
2087 );
2088
2089 let err = Opts::from_url("mysql://localhost/foo?compression=a").unwrap_err();
2090 assert_eq!(
2091 err,
2092 InvalidParamValue {
2093 param: "compression".into(),
2094 value: "a".into()
2095 }
2096 );
2097
2098 let opts = Opts::from_url("mysql://localhost/foo?compression=fast").unwrap();
2099 assert_eq!(opts.compression(), Some(crate::Compression::fast()));
2100
2101 let opts = Opts::from_url("mysql://localhost/foo?compression=on").unwrap();
2102 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2103
2104 let opts = Opts::from_url("mysql://localhost/foo?compression=true").unwrap();
2105 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2106
2107 let opts = Opts::from_url("mysql://localhost/foo?compression=best").unwrap();
2108 assert_eq!(opts.compression(), Some(crate::Compression::best()));
2109
2110 let opts = Opts::from_url("mysql://localhost/foo?compression=0").unwrap();
2111 assert_eq!(opts.compression(), Some(crate::Compression::new(0)));
2112
2113 let opts = Opts::from_url("mysql://localhost/foo?compression=9").unwrap();
2114 assert_eq!(opts.compression(), Some(crate::Compression::new(9)));
2115 }
2116
2117 #[test]
2118 fn test_builder_eq_url_empty_db() {
2119 let builder = super::OptsBuilder::default();
2120 let builder_opts = Opts::from(builder);
2121
2122 let url: &str = "mysql://iq-controller@localhost";
2123 let url_opts = super::Opts::from_str(url).unwrap();
2124 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2125
2126 let url: &str = "mysql://iq-controller@localhost/";
2127 let url_opts = super::Opts::from_str(url).unwrap();
2128 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2129 }
2130
2131 #[test]
2132 fn test_builder_update_port_host_resolved_ips() {
2133 let builder = super::OptsBuilder::default()
2134 .ip_or_hostname("foo")
2135 .tcp_port(33306);
2136
2137 let resolved = vec![
2138 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)),
2139 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff)),
2140 ];
2141 let builder2 = builder
2142 .clone()
2143 .tcp_port(55223)
2144 .resolved_ips(Some(resolved.clone()));
2145
2146 let builder_opts = Opts::from(builder);
2147 assert_eq!(builder_opts.ip_or_hostname(), "foo");
2148 assert_eq!(builder_opts.tcp_port(), 33306);
2149 assert_eq!(
2150 builder_opts.hostport_or_url(),
2151 &HostPortOrUrl::HostPort {
2152 host: "foo".to_string(),
2153 port: 33306,
2154 resolved_ips: None
2155 }
2156 );
2157
2158 let builder_opts2 = Opts::from(builder2);
2159 assert_eq!(builder_opts2.ip_or_hostname(), "foo");
2160 assert_eq!(builder_opts2.tcp_port(), 55223);
2161 assert_eq!(
2162 builder_opts2.hostport_or_url(),
2163 &HostPortOrUrl::HostPort {
2164 host: "foo".to_string(),
2165 port: 55223,
2166 resolved_ips: Some(resolved),
2167 }
2168 );
2169 }
2170}