mysql_async/opts/
mod.rs

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