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