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