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