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