Skip to main content

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