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}