mysql/conn/opts/mod.rs
1// Copyright (c) 2020 rust-mysql-simple contributors
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
9use percent_encoding::percent_decode;
10use url::Url;
11
12use std::{
13 borrow::Cow, collections::HashMap, fmt, hash::Hash, net::SocketAddr, path::Path, time::Duration,
14};
15
16use crate::{
17 consts::CapabilityFlags, Compression, LocalInfileHandler, PoolConstraints, PoolOpts, UrlError,
18};
19
20/// Default value for client side per-connection statement cache.
21pub const DEFAULT_STMT_CACHE_SIZE: usize = 32;
22
23mod native_tls_opts;
24mod rustls_opts;
25
26pub mod pool_opts;
27
28#[cfg(feature = "native-tls")]
29pub use native_tls_opts::ClientIdentity;
30
31#[cfg(feature = "rustls")]
32pub use rustls_opts::ClientIdentity;
33
34/// Ssl Options.
35#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
36pub struct SslOpts {
37 #[cfg(any(feature = "native-tls", feature = "rustls"))]
38 client_identity: Option<ClientIdentity>,
39 root_cert_path: Option<Cow<'static, Path>>,
40 skip_domain_validation: bool,
41 accept_invalid_certs: bool,
42}
43
44impl SslOpts {
45 /// Sets the client identity.
46 #[cfg(any(feature = "native-tls", feature = "rustls"))]
47 #[cfg_attr(
48 docsrs,
49 doc(cfg(any(
50 feature = "native-tls",
51 feature = "rustls-tls",
52 feature = "rustls-tls-ring"
53 )))
54 )]
55 pub fn with_client_identity(mut self, identity: Option<ClientIdentity>) -> Self {
56 self.client_identity = identity;
57 self
58 }
59
60 /// Sets path to a certificate of the root that connector will trust.
61 ///
62 /// Supported certificate formats are .der and .pem.
63 /// Multiple certs are allowed in .pem files.
64 pub fn with_root_cert_path<T: Into<Cow<'static, Path>>>(
65 mut self,
66 root_cert_path: Option<T>,
67 ) -> Self {
68 self.root_cert_path = root_cert_path.map(Into::into);
69 self
70 }
71
72 /// The way to not validate the server's domain
73 /// name against its certificate (defaults to `false`).
74 pub fn with_danger_skip_domain_validation(mut self, value: bool) -> Self {
75 self.skip_domain_validation = value;
76 self
77 }
78
79 /// If `true` then client will accept invalid certificate (expired, not trusted, ..)
80 /// (defaults to `false`).
81 pub fn with_danger_accept_invalid_certs(mut self, value: bool) -> Self {
82 self.accept_invalid_certs = value;
83 self
84 }
85
86 #[cfg(any(feature = "native-tls", feature = "rustls"))]
87 #[cfg_attr(
88 docsrs,
89 doc(cfg(any(
90 feature = "native-tls",
91 feature = "rustls-tls",
92 feature = "rustls-tls-ring"
93 )))
94 )]
95 pub fn client_identity(&self) -> Option<&ClientIdentity> {
96 self.client_identity.as_ref()
97 }
98
99 pub fn root_cert_path(&self) -> Option<&Path> {
100 self.root_cert_path.as_ref().map(AsRef::as_ref)
101 }
102
103 pub fn skip_domain_validation(&self) -> bool {
104 self.skip_domain_validation
105 }
106
107 pub fn accept_invalid_certs(&self) -> bool {
108 self.accept_invalid_certs
109 }
110}
111
112/// Options structure is quite large so we'll store it separately.
113#[derive(Debug, Clone, Eq, PartialEq)]
114pub(crate) struct InnerOpts {
115 /// Address of mysql server (defaults to `127.0.0.1`). Host names should also work.
116 ip_or_hostname: url::Host,
117 /// TCP port of mysql server (defaults to `3306`).
118 tcp_port: u16,
119 /// Path to unix socket on unix or pipe name on windows (defaults to `None`).
120 ///
121 /// Can be defined using `socket` connection url parameter.
122 socket: Option<String>,
123 /// User (defaults to `None`).
124 user: Option<String>,
125 /// Password (defaults to `None`).
126 pass: Option<String>,
127 /// Database name (defaults to `None`).
128 db_name: Option<String>,
129
130 /// The timeout for each attempt to read from the server.
131 read_timeout: Option<Duration>,
132
133 /// The timeout for each attempt to write to the server.
134 write_timeout: Option<Duration>,
135
136 /// Prefer socket connection (defaults to `true`).
137 ///
138 /// Will reconnect via socket (or named pipe on windows) after TCP
139 /// connection to `127.0.0.1` if `true`.
140 ///
141 /// Will fall back to TCP on error. Use `socket` option to enforce socket connection.
142 ///
143 /// Can be defined using `prefer_socket` connection url parameter.
144 prefer_socket: bool,
145
146 /// Whether to enable `TCP_NODELAY` (defaults to `true`).
147 ///
148 /// This option disables Nagle's algorithm, which can cause unusually high latency (~40ms) at
149 /// some cost to maximum throughput. See #132.
150 tcp_nodelay: bool,
151
152 /// TCP keep alive time for mysql connection.
153 ///
154 /// Can be defined using `tcp_keepalive_time_ms` connection url parameter.
155 tcp_keepalive_time: Option<u32>,
156
157 /// TCP keep alive interval between subsequent probe for mysql connection.
158 ///
159 /// Can be defined using `tcp_keepalive_probe_interval_secs` connection url parameter.
160 #[cfg(any(target_os = "linux", target_os = "macos"))]
161 tcp_keepalive_probe_interval_secs: Option<u32>,
162
163 /// TCP keep alive probe count for mysql connection.
164 ///
165 /// Can be defined using `tcp_keepalive_probe_count` connection url parameter.
166 #[cfg(any(target_os = "linux", target_os = "macos"))]
167 tcp_keepalive_probe_count: Option<u32>,
168
169 /// TCP_USER_TIMEOUT time for mysql connection.
170 ///
171 /// Can be defined using `tcp_user_timeout_ms` connection url parameter.
172 #[cfg(target_os = "linux")]
173 tcp_user_timeout: Option<u32>,
174
175 /// Commands to execute on each new database connection.
176 init: Vec<String>,
177
178 /// Driver will require SSL connection if this option isn't `None` (default to `None`).
179 ssl_opts: Option<SslOpts>,
180
181 /// Connection pool options (defaults to [`PoolOpts::default`]).
182 pool_opts: PoolOpts,
183
184 /// Callback to handle requests for local files.
185 ///
186 /// These are caused by using `LOAD DATA LOCAL INFILE` queries.
187 /// The callback is passed the filename, and a `Write`able object
188 /// to receive the contents of that file.
189 ///
190 /// If unset, the default callback will read files relative to
191 /// the current directory.
192 local_infile_handler: Option<LocalInfileHandler>,
193
194 /// Tcp connect timeout (defaults to `None`).
195 ///
196 /// Can be defined using `tcp_connect_timeout_ms` connection url parameter.
197 tcp_connect_timeout: Option<Duration>,
198
199 /// Bind address for a client (defaults to `None`).
200 ///
201 /// Use carefully. Will probably make pool unusable because of *address already in use*
202 /// errors.
203 bind_address: Option<SocketAddr>,
204
205 /// Number of prepared statements cached on the client side (per connection).
206 /// Defaults to [`DEFAULT_STMT_CACHE_SIZE`].
207 ///
208 /// Can be defined using `stmt_cache_size` connection url parameter.
209 stmt_cache_size: usize,
210
211 /// If not `None`, then client will ask for compression if server supports it
212 /// (defaults to `None`).
213 ///
214 /// Can be defined using `compress` connection url parameter with values `true`, `fast`, `best`,
215 /// `0`, `1`, ..., `9`.
216 ///
217 /// Note that compression level defined here will affect only outgoing packets.
218 compress: Option<crate::Compression>,
219
220 /// Additional client capabilities to set (defaults to empty).
221 ///
222 /// This value will be OR'ed with other client capabilities during connection initialization.
223 ///
224 /// ### Note
225 ///
226 /// It is a good way to set something like `CLIENT_FOUND_ROWS` but you should note that it
227 /// won't let you to interfere with capabilities managed by other options (like
228 /// `CLIENT_SSL` or `CLIENT_COMPRESS`). Also note that some capabilities are reserved,
229 /// pointless or may broke the connection, so this option should be used with caution.
230 additional_capabilities: CapabilityFlags,
231
232 /// Connect attributes
233 connect_attrs: Option<HashMap<String, String>>,
234
235 /// Disables `mysql_old_password` plugin (defaults to `true`).
236 ///
237 /// Available via `secure_auth` connection url parameter.
238 secure_auth: bool,
239
240 /// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
241 ///
242 /// Enables client to send passwords to the server as cleartext, without hashing or encryption
243 /// (consult MySql documentation for more info).
244 ///
245 /// # Security Notes
246 ///
247 /// Sending passwords as cleartext may be a security problem in some configurations. Please
248 /// consider using TLS or encrypted tunnels for server connection.
249 enable_cleartext_plugin: bool,
250
251 /// Client side `max_allowed_packet` value (defaults to `None`).
252 ///
253 /// By default `Conn` will query this value from the server. One can avoid this step
254 /// by explicitly specifying it.
255 max_allowed_packet: Option<usize>,
256
257 /// For tests only
258 #[cfg(test)]
259 pub injected_socket: Option<String>,
260}
261
262impl Default for InnerOpts {
263 fn default() -> Self {
264 InnerOpts {
265 ip_or_hostname: url::Host::Domain(String::from("localhost")),
266 tcp_port: 3306,
267 socket: None,
268 max_allowed_packet: None,
269 user: None,
270 pass: None,
271 db_name: None,
272 read_timeout: None,
273 write_timeout: None,
274 prefer_socket: true,
275 init: vec![],
276 ssl_opts: None,
277 pool_opts: PoolOpts::default(),
278 tcp_keepalive_time: None,
279 #[cfg(any(target_os = "linux", target_os = "macos",))]
280 tcp_keepalive_probe_interval_secs: None,
281 #[cfg(any(target_os = "linux", target_os = "macos",))]
282 tcp_keepalive_probe_count: None,
283 #[cfg(target_os = "linux")]
284 tcp_user_timeout: None,
285 tcp_nodelay: true,
286 local_infile_handler: None,
287 tcp_connect_timeout: None,
288 bind_address: None,
289 stmt_cache_size: DEFAULT_STMT_CACHE_SIZE,
290 compress: None,
291 additional_capabilities: CapabilityFlags::empty(),
292 connect_attrs: Some(HashMap::new()),
293 secure_auth: true,
294 enable_cleartext_plugin: false,
295 #[cfg(test)]
296 injected_socket: None,
297 }
298 }
299}
300
301impl TryFrom<&'_ str> for Opts {
302 type Error = UrlError;
303
304 fn try_from(url: &'_ str) -> Result<Self, Self::Error> {
305 Opts::from_url(url)
306 }
307}
308
309/// Mysql connection options.
310///
311/// Build one with [`OptsBuilder`](struct.OptsBuilder.html).
312#[derive(Clone, Eq, PartialEq, Debug, Default)]
313pub struct Opts(pub(crate) Box<InnerOpts>);
314
315impl Opts {
316 #[doc(hidden)]
317 pub fn addr_is_loopback(&self) -> bool {
318 match self.0.ip_or_hostname {
319 url::Host::Domain(ref name) => name == "localhost",
320 url::Host::Ipv4(ref addr) => addr.is_loopback(),
321 url::Host::Ipv6(ref addr) => addr.is_loopback(),
322 }
323 }
324
325 pub fn from_url(url: &str) -> Result<Opts, UrlError> {
326 from_url(url)
327 }
328
329 pub(crate) fn get_host(&self) -> url::Host {
330 self.0.ip_or_hostname.clone()
331 }
332
333 /// Address of mysql server (defaults to `127.0.0.1`). Host names should also work.
334 pub fn get_ip_or_hostname(&self) -> Cow<str> {
335 self.0.ip_or_hostname.to_string().into()
336 }
337 /// TCP port of mysql server (defaults to `3306`).
338 pub fn get_tcp_port(&self) -> u16 {
339 self.0.tcp_port
340 }
341 /// Socket path on unix or pipe name on windows (defaults to `None`).
342 pub fn get_socket(&self) -> Option<&str> {
343 self.0.socket.as_deref()
344 }
345 /// Client side `max_allowed_packet` value (defaults to `None`).
346 ///
347 /// By default `Conn` will query this value from the server. One can avoid this step
348 /// by explicitly specifying it. Server side default is 4MB.
349 ///
350 /// Available in connection URL via `max_allowed_packet` parameter.
351 pub fn get_max_allowed_packet(&self) -> Option<usize> {
352 self.0.max_allowed_packet
353 }
354 /// User (defaults to `None`).
355 pub fn get_user(&self) -> Option<&str> {
356 self.0.user.as_deref()
357 }
358 /// Password (defaults to `None`).
359 pub fn get_pass(&self) -> Option<&str> {
360 self.0.pass.as_deref()
361 }
362 /// Database name (defaults to `None`).
363 pub fn get_db_name(&self) -> Option<&str> {
364 self.0.db_name.as_deref()
365 }
366
367 /// The timeout for each attempt to write to the server.
368 pub fn get_read_timeout(&self) -> Option<&Duration> {
369 self.0.read_timeout.as_ref()
370 }
371
372 /// The timeout for each attempt to write to the server.
373 pub fn get_write_timeout(&self) -> Option<&Duration> {
374 self.0.write_timeout.as_ref()
375 }
376
377 /// Prefer socket connection (defaults to `true`).
378 ///
379 /// Will reconnect via socket (or named pipe on windows) after TCP connection
380 /// to `127.0.0.1` if `true`.
381 ///
382 /// Will fall back to TCP on error. Use `socket` option to enforce socket connection.
383 pub fn get_prefer_socket(&self) -> bool {
384 self.0.prefer_socket
385 }
386 // XXX: Wait for keepalive_timeout stabilization
387 /// Commands to execute on each new database connection.
388 pub fn get_init(&self) -> Vec<String> {
389 self.0.init.clone()
390 }
391
392 /// Driver will require SSL connection if this option isn't `None` (default to `None`).
393 pub fn get_ssl_opts(&self) -> Option<&SslOpts> {
394 self.0.ssl_opts.as_ref()
395 }
396
397 /// Connection pool options (defaults to [`Default::default`]).
398 pub fn get_pool_opts(&self) -> &PoolOpts {
399 &self.0.pool_opts
400 }
401
402 /// Whether `TCP_NODELAY` will be set for mysql connection.
403 pub fn get_tcp_nodelay(&self) -> bool {
404 self.0.tcp_nodelay
405 }
406
407 /// TCP keep alive time for mysql connection.
408 pub fn get_tcp_keepalive_time_ms(&self) -> Option<u32> {
409 self.0.tcp_keepalive_time
410 }
411
412 /// TCP keep alive interval between subsequent probes for mysql connection.
413 #[cfg(any(target_os = "linux", target_os = "macos",))]
414 pub fn get_tcp_keepalive_probe_interval_secs(&self) -> Option<u32> {
415 self.0.tcp_keepalive_probe_interval_secs
416 }
417
418 /// TCP keep alive probe count for mysql connection.
419 #[cfg(any(target_os = "linux", target_os = "macos",))]
420 pub fn get_tcp_keepalive_probe_count(&self) -> Option<u32> {
421 self.0.tcp_keepalive_probe_count
422 }
423
424 /// TCP_USER_TIMEOUT time for mysql connection.
425 #[cfg(target_os = "linux")]
426 pub fn get_tcp_user_timeout_ms(&self) -> Option<u32> {
427 self.0.tcp_user_timeout
428 }
429
430 /// Callback to handle requests for local files.
431 pub fn get_local_infile_handler(&self) -> Option<&LocalInfileHandler> {
432 self.0.local_infile_handler.as_ref()
433 }
434
435 /// Tcp connect timeout (defaults to `None`).
436 pub fn get_tcp_connect_timeout(&self) -> Option<Duration> {
437 self.0.tcp_connect_timeout
438 }
439
440 /// Bind address for a client (defaults to `None`).
441 ///
442 /// Use carefully. Will probably make pool unusable because of *address already in use*
443 /// errors.
444 pub fn bind_address(&self) -> Option<&SocketAddr> {
445 self.0.bind_address.as_ref()
446 }
447
448 /// Number of prepared statements cached on the client side (per connection).
449 /// Defaults to [`DEFAULT_STMT_CACHE_SIZE`].
450 ///
451 /// Can be defined using `stmt_cache_size` connection url parameter.
452 pub fn get_stmt_cache_size(&self) -> usize {
453 self.0.stmt_cache_size
454 }
455
456 /// If not `None`, then client will ask for compression if server supports it
457 /// (defaults to `None`).
458 ///
459 /// Can be defined using `compress` connection url parameter with values:
460 /// * `true` - library defined default compression level;
461 /// * `fast` - library defined fast compression level;
462 /// * `best` - library defined best compression level;
463 /// * `0`, `1`, ..., `9` - explicitly defined compression level where `0` stands for
464 /// "no compression";
465 ///
466 /// Note that compression level defined here will affect only outgoing packets.
467 pub fn get_compress(&self) -> Option<crate::Compression> {
468 self.0.compress
469 }
470
471 /// Additional client capabilities to set (defaults to empty).
472 ///
473 /// This value will be OR'ed with other client capabilities during connection initialization.
474 ///
475 /// ### Note
476 ///
477 /// It is a good way to set something like `CLIENT_FOUND_ROWS` but you should note that it
478 /// won't let you to interfere with capabilities managed by other options (like
479 /// `CLIENT_SSL` or `CLIENT_COMPRESS`). Also note that some capabilities are reserved,
480 /// pointless or may broke the connection, so this option should be used with caution.
481 pub fn get_additional_capabilities(&self) -> CapabilityFlags {
482 self.0.additional_capabilities
483 }
484
485 /// Connect attributes (the default connect attributes are sent by default).
486 ///
487 /// This value is sent to the server as custom name-value attributes.
488 /// You can see them from performance_schema tables: [`session_account_connect_attrs`
489 /// and `session_connect_attrs`][attr_tables] when all of the following conditions
490 /// are met.
491 ///
492 /// * The server is MySQL 5.6 or later, or MariaDB 10.0 or later.
493 /// * [`performance_schema`] is on.
494 /// * [`performance_schema_session_connect_attrs_size`] is -1 or big enough
495 /// to store specified attributes.
496 ///
497 /// ### Note
498 ///
499 /// - set `connect_attrs` to `None` to completely remove connect attributes
500 /// - set `connect_attrs` to an empty map to send only the default attributes
501 ///
502 /// #### Warning
503 ///
504 /// > There is a bug in MySql 5.6 that kills COM_CHANGE_USER in the presence of connection
505 /// > attributes so it's better to stick to `None` for mysql < 5.7.
506 ///
507 /// Attribute names that begin with an underscore (`_`) are not set by
508 /// application programs because they are reserved for internal use.
509 ///
510 /// The following default attributes are sent in addition to ones set by programs.
511 ///
512 /// name | value
513 /// ----------------|--------------------------
514 /// _client_name | The client library name (`rust-mysql-simple`)
515 /// _client_version | The client library version
516 /// _os | The operation system (`target_os` cfg feature)
517 /// _pid | The client process ID
518 /// _platform | The machine platform (`target_arch` cfg feature)
519 /// program_name | The first element of `std::env::args` if program_name isn't set by programs.
520 ///
521 /// [attr_tables]: https://dev.mysql.com/doc/refman/en/performance-schema-connection-attribute-tables.html
522 /// [`performance_schema`]: https://dev.mysql.com/doc/refman/8.0/en/performance-schema-system-variables.html#sysvar_performance_schema
523 /// [`performance_schema_session_connect_attrs_size`]: https://dev.mysql.com/doc/refman/en/performance-schema-system-variables.html#sysvar_performance_schema_session_connect_attrs_size
524 ///
525 pub fn get_connect_attrs(&self) -> Option<&HashMap<String, String>> {
526 self.0.connect_attrs.as_ref()
527 }
528
529 /// Disables `mysql_old_password` plugin (defaults to `true`).
530 ///
531 /// Available via `secure_auth` connection url parameter.
532 pub fn get_secure_auth(&self) -> bool {
533 self.0.secure_auth
534 }
535
536 /// Returns `true` if `mysql_clear_password` plugin support is enabled (defaults to `false`).
537 ///
538 /// `mysql_clear_password` enables client to send passwords to the server as cleartext, without
539 /// hashing or encryption (consult MySql documentation for more info).
540 ///
541 /// # Security Notes
542 ///
543 /// Sending passwords as cleartext may be a security problem in some configurations. Please
544 /// consider using TLS or encrypted tunnels for server connection.
545 ///
546 /// # Connection URL
547 ///
548 /// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
549 ///
550 /// ```
551 /// # use mysql::*;
552 /// # fn main() -> Result<()> {
553 /// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
554 /// assert!(opts.get_enable_cleartext_plugin());
555 /// # Ok(()) }
556 /// ```
557 pub fn get_enable_cleartext_plugin(&self) -> bool {
558 self.0.enable_cleartext_plugin
559 }
560}
561
562/// Provides a way to build [`Opts`](struct.Opts.html).
563///
564/// ```ignore
565/// let mut ssl_opts = SslOpts::default();
566/// ssl_opts = ssl_opts.with_pkcs12_path(Some(Path::new("/foo/cert.p12")))
567/// .with_root_ca_path(Some(Path::new("/foo/root_ca.der")));
568///
569/// // You can create new default builder
570/// let mut builder = OptsBuilder::new();
571/// builder = builder.ip_or_hostname(Some("foo"))
572/// .db_name(Some("bar"))
573/// .ssl_opts(Some(ssl_opts));
574///
575/// // Or use existing T: Into<Opts>
576/// let builder = OptsBuilder::from_opts(existing_opts)
577/// .ip_or_hostname(Some("foo"))
578/// .db_name(Some("bar"));
579/// ```
580///
581/// ## Connection URL
582///
583/// `Opts` also could be constructed using connection URL. See docs on `OptsBuilder`'s methods for
584/// the list of options available via URL.
585///
586/// Example:
587///
588/// ```ignore
589/// let connection_opts = mysql::Opts::from_url("mysql://root:password@localhost:3307/mysql?prefer_socket=false").unwrap();
590/// let pool = mysql::Pool::new(connection_opts).unwrap();
591/// ```
592#[derive(Debug, Clone, PartialEq, Default)]
593pub struct OptsBuilder {
594 opts: Opts,
595}
596
597impl OptsBuilder {
598 pub fn new() -> Self {
599 OptsBuilder::default()
600 }
601
602 pub fn from_opts<T: Into<Opts>>(opts: T) -> Self {
603 OptsBuilder { opts: opts.into() }
604 }
605
606 /// Use a HashMap for creating an OptsBuilder instance:
607 /// ```ignore
608 /// OptsBuilder::new().from_hash_map(client);
609 /// ```
610 /// `HashMap` key,value pairs:
611 /// - pool_min = upper bound for [`PoolConstraints`]
612 /// - pool_max = lower bound for [`PoolConstraints`]
613 /// - user = Username
614 /// - password = Password
615 /// - host = Host name or ip address
616 /// - port = Port, default is 3306
617 /// - socket = Unix socket or pipe name(on windows) defaults to `None`
618 /// - db_name = Database name (defaults to `None`).
619 /// - prefer_socket = Prefer socket connection (defaults to `true`)
620 /// - tcp_keepalive_time_ms = TCP keep alive time for mysql connection (defaults to `None`)
621 /// - tcp_keepalive_probe_interval_secs = TCP keep alive interval between probes for mysql connection (defaults to `None`)
622 /// - tcp_keepalive_probe_count = TCP keep alive probe count for mysql connection (defaults to `None`)
623 /// - tcp_user_timeout_ms = TCP_USER_TIMEOUT time for mysql connection (defaults to `None`)
624 /// - compress = Compression level(defaults to `None`)
625 /// - tcp_connect_timeout_ms = Tcp connect timeout (defaults to `None`)
626 /// - stmt_cache_size = Number of prepared statements cached on the client side (per connection)
627 /// - secure_auth = Disable `mysql_old_password` auth plugin
628 ///
629 /// Login .cnf file parsing lib <https://github.com/rjcortese/myloginrs> returns a HashMap for client configs
630 ///
631 /// **Note:** You do **not** have to use myloginrs lib.
632 pub fn from_hash_map(mut self, client: &HashMap<String, String>) -> Result<Self, UrlError> {
633 let mut pool_min = PoolConstraints::DEFAULT.min();
634 let mut pool_max = PoolConstraints::DEFAULT.max();
635
636 for (key, value) in client.iter() {
637 match key.as_str() {
638 "pool_min" => match value.parse::<usize>() {
639 Ok(parsed) => pool_min = parsed,
640 Err(_) => {
641 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
642 }
643 },
644 "pool_max" => match value.parse::<usize>() {
645 Ok(parsed) => pool_max = parsed,
646 Err(_) => {
647 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
648 }
649 },
650 "user" => self.opts.0.user = Some(value.to_string()),
651 "password" => self.opts.0.pass = Some(value.to_string()),
652 "host" => {
653 let host = url::Host::parse(value)
654 .unwrap_or_else(|_| url::Host::Domain(value.to_owned()));
655 self.opts.0.ip_or_hostname = host;
656 }
657 "port" => match value.parse::<u16>() {
658 Ok(parsed) => self.opts.0.tcp_port = parsed,
659 Err(_) => {
660 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
661 }
662 },
663 "socket" => self.opts.0.socket = Some(value.to_string()),
664 "db_name" => self.opts.0.db_name = Some(value.to_string()),
665 "prefer_socket" => {
666 //default to true like standard opts builder method
667 match value.parse::<bool>() {
668 Ok(parsed) => self.opts.0.prefer_socket = parsed,
669 Err(_) => {
670 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
671 }
672 }
673 }
674 "enable_cleartext_plugin" => match value.parse::<bool>() {
675 Ok(parsed) => self.opts.0.enable_cleartext_plugin = parsed,
676 Err(_) => {
677 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
678 }
679 },
680 "secure_auth" => match value.parse::<bool>() {
681 Ok(parsed) => self.opts.0.secure_auth = parsed,
682 Err(_) => {
683 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
684 }
685 },
686 "tcp_keepalive_time_ms" => {
687 //if cannot parse, default to none
688 self.opts.0.tcp_keepalive_time = match value.parse::<u32>() {
689 Ok(val) => Some(val),
690 _ => {
691 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
692 }
693 }
694 }
695 #[cfg(any(target_os = "linux", target_os = "macos",))]
696 "tcp_keepalive_probe_interval_secs" => {
697 //if cannot parse, default to none
698 self.opts.0.tcp_keepalive_probe_interval_secs = match value.parse::<u32>() {
699 Ok(val) => Some(val),
700 _ => {
701 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
702 }
703 }
704 }
705 #[cfg(any(target_os = "linux", target_os = "macos",))]
706 "tcp_keepalive_probe_count" => {
707 //if cannot parse, default to none
708 self.opts.0.tcp_keepalive_probe_count = match value.parse::<u32>() {
709 Ok(val) => Some(val),
710 _ => {
711 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
712 }
713 }
714 }
715 #[cfg(target_os = "linux")]
716 "tcp_user_timeout_ms" => {
717 self.opts.0.tcp_user_timeout = match value.parse::<u32>() {
718 Ok(val) => Some(val),
719 _ => {
720 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
721 }
722 }
723 }
724 "compress" => match value.parse::<u32>() {
725 Ok(val) => self.opts.0.compress = Some(Compression::new(val)),
726 Err(_) => {
727 //not an int
728 match value.as_str() {
729 "fast" => self.opts.0.compress = Some(Compression::fast()),
730 "best" => self.opts.0.compress = Some(Compression::best()),
731 "true" => self.opts.0.compress = Some(Compression::default()),
732 _ => {
733 return Err(UrlError::InvalidValue(
734 key.to_string(),
735 value.to_string(),
736 )); //should not go below this due to catch all
737 }
738 }
739 }
740 },
741 "tcp_connect_timeout_ms" => {
742 self.opts.0.tcp_connect_timeout = match value.parse::<u64>() {
743 Ok(val) => Some(Duration::from_millis(val)),
744 _ => {
745 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
746 }
747 }
748 }
749 "stmt_cache_size" => match value.parse::<usize>() {
750 Ok(parsed) => self.opts.0.stmt_cache_size = parsed,
751 Err(_) => {
752 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
753 }
754 },
755 "reset_connection" => match value.parse::<bool>() {
756 Ok(parsed) => {
757 self.opts.0.pool_opts = self.opts.0.pool_opts.with_reset_connection(parsed)
758 }
759 Err(_) => {
760 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
761 }
762 },
763 "check_health" => match value.parse::<bool>() {
764 Ok(parsed) => {
765 self.opts.0.pool_opts = self.opts.0.pool_opts.with_check_health(parsed)
766 }
767 Err(_) => {
768 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
769 }
770 },
771 "max_allowed_packet" => match value.parse::<usize>() {
772 Ok(parsed) => self.opts.0.max_allowed_packet = Some(parsed),
773 Err(_) => {
774 return Err(UrlError::InvalidValue(key.to_string(), value.to_string()))
775 }
776 },
777 _ => {
778 //throw an error if there is an unrecognized param
779 return Err(UrlError::UnknownParameter(key.to_string()));
780 }
781 }
782 }
783
784 if let Some(pool_constraints) = PoolConstraints::new(pool_min, pool_max) {
785 self.opts.0.pool_opts = self.opts.0.pool_opts.with_constraints(pool_constraints);
786 } else {
787 return Err(UrlError::InvalidPoolConstraints {
788 min: pool_min,
789 max: pool_max,
790 });
791 }
792
793 Ok(self)
794 }
795
796 /// Address of mysql server (defaults to `127.0.0.1`). Host names should also work.
797 ///
798 /// **Note:** IPv6 addresses must be given in square brackets, e.g. `[::1]`.
799 pub fn ip_or_hostname<T: Into<String>>(mut self, ip_or_hostname: Option<T>) -> Self {
800 let new = ip_or_hostname
801 .map(Into::into)
802 .unwrap_or_else(|| "127.0.0.1".into());
803 self.opts.0.ip_or_hostname =
804 url::Host::parse(&new).unwrap_or_else(|_| url::Host::Domain(new.to_owned()));
805 self
806 }
807
808 /// TCP port of mysql server (defaults to `3306`).
809 pub fn tcp_port(mut self, tcp_port: u16) -> Self {
810 self.opts.0.tcp_port = tcp_port;
811 self
812 }
813
814 /// Socket path on unix or pipe name on windows (defaults to `None`).
815 ///
816 /// Can be defined using `socket` connection url parameter.
817 pub fn socket<T: Into<String>>(mut self, socket: Option<T>) -> Self {
818 self.opts.0.socket = socket.map(Into::into);
819 self
820 }
821
822 /// Defines `max_allowed_packet` option. See [`Opts::max_allowed_packet`].
823 ///
824 /// Note that it'll saturate to proper minimum and maximum values
825 /// for this parameter (see MySql documentation).
826 pub fn max_allowed_packet(mut self, max_allowed_packet: Option<usize>) -> Self {
827 self.opts.0.max_allowed_packet = max_allowed_packet.map(|x| x.clamp(1024, 1073741824));
828 self
829 }
830
831 /// User (defaults to `None`).
832 pub fn user<T: Into<String>>(mut self, user: Option<T>) -> Self {
833 self.opts.0.user = user.map(Into::into);
834 self
835 }
836
837 /// Password (defaults to `None`).
838 pub fn pass<T: Into<String>>(mut self, pass: Option<T>) -> Self {
839 self.opts.0.pass = pass.map(Into::into);
840 self
841 }
842
843 /// Database name (defaults to `None`).
844 pub fn db_name<T: Into<String>>(mut self, db_name: Option<T>) -> Self {
845 self.opts.0.db_name = db_name.map(Into::into);
846 self
847 }
848
849 /// The timeout for each attempt to read from the server (defaults to `None`).
850 ///
851 /// Note that named pipe connection will ignore duration's `nanos`, and also note that
852 /// it is an error to pass the zero `Duration` to this method.
853 pub fn read_timeout(mut self, read_timeout: Option<Duration>) -> Self {
854 self.opts.0.read_timeout = read_timeout;
855 self
856 }
857
858 /// The timeout for each attempt to write to the server (defaults to `None`).
859 ///
860 /// Note that named pipe connection will ignore duration's `nanos`, and also note that
861 /// it is likely error to pass the zero `Duration` to this method.
862 pub fn write_timeout(mut self, write_timeout: Option<Duration>) -> Self {
863 self.opts.0.write_timeout = write_timeout;
864 self
865 }
866
867 /// TCP keep alive time for mysql connection (defaults to `None`). Available as
868 /// `tcp_keepalive_time_ms` url parameter.
869 ///
870 /// Can be defined using `tcp_keepalive_time_ms` connection url parameter.
871 pub fn tcp_keepalive_time_ms(mut self, tcp_keepalive_time_ms: Option<u32>) -> Self {
872 self.opts.0.tcp_keepalive_time = tcp_keepalive_time_ms;
873 self
874 }
875
876 /// TCP keep alive interval between probes for mysql connection (defaults to `None`). Available as
877 /// `tcp_keepalive_probe_interval_secs` url parameter.
878 ///
879 /// Can be defined using `tcp_keepalive_probe_interval_secs` connection url parameter.
880 #[cfg(any(target_os = "linux", target_os = "macos",))]
881 pub fn tcp_keepalive_probe_interval_secs(
882 mut self,
883 tcp_keepalive_probe_interval_secs: Option<u32>,
884 ) -> Self {
885 self.opts.0.tcp_keepalive_probe_interval_secs = tcp_keepalive_probe_interval_secs;
886 self
887 }
888
889 /// TCP keep alive probe count for mysql connection (defaults to `None`). Available as
890 /// `tcp_keepalive_probe_count` url parameter.
891 ///
892 /// Can be defined using `tcp_keepalive_probe_count` connection url parameter.
893 #[cfg(any(target_os = "linux", target_os = "macos",))]
894 pub fn tcp_keepalive_probe_count(mut self, tcp_keepalive_probe_count: Option<u32>) -> Self {
895 self.opts.0.tcp_keepalive_probe_count = tcp_keepalive_probe_count;
896 self
897 }
898
899 /// TCP_USER_TIMEOUT for mysql connection (defaults to `None`). Available as
900 /// `tcp_user_timeout_ms` url parameter.
901 ///
902 /// Can be defined using `tcp_user_timeout_ms` connection url parameter.
903 #[cfg(target_os = "linux")]
904 pub fn tcp_user_timeout_ms(mut self, tcp_user_timeout_ms: Option<u32>) -> Self {
905 self.opts.0.tcp_user_timeout = tcp_user_timeout_ms;
906 self
907 }
908
909 /// Set the `TCP_NODELAY` option for the mysql connection (defaults to `true`).
910 ///
911 /// Setting this option to false re-enables Nagle's algorithm, which can cause unusually high
912 /// latency (~40ms) but may increase maximum throughput. See #132.
913 pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
914 self.opts.0.tcp_nodelay = nodelay;
915 self
916 }
917
918 /// Prefer socket connection (defaults to `true`). Available as `prefer_socket` url parameter
919 /// with value `true` or `false`.
920 ///
921 /// Will reconnect via socket (on named pipe on windows) after TCP connection
922 /// to `127.0.0.1` if `true`.
923 ///
924 /// Will fall back to TCP on error. Use `socket` option to enforce socket connection.
925 ///
926 /// Can be defined using `prefer_socket` connection url parameter.
927 pub fn prefer_socket(mut self, prefer_socket: bool) -> Self {
928 self.opts.0.prefer_socket = prefer_socket;
929 self
930 }
931
932 /// Commands to execute on each new database connection.
933 pub fn init<T: Into<String>>(mut self, init: Vec<T>) -> Self {
934 self.opts.0.init = init.into_iter().map(Into::into).collect();
935 self
936 }
937
938 /// Driver will require SSL connection if this option isn't `None` (default to `None`).
939 pub fn ssl_opts<T: Into<Option<SslOpts>>>(mut self, ssl_opts: T) -> Self {
940 self.opts.0.ssl_opts = ssl_opts.into();
941 self
942 }
943
944 /// Connection pool options (see [`Opts::get_pool_opts`]).
945 ///
946 /// Pass `None` to reset to default.
947 pub fn pool_opts<T: Into<Option<PoolOpts>>>(mut self, pool_opts: T) -> Self {
948 self.opts.0.pool_opts = pool_opts.into().unwrap_or_default();
949 self
950 }
951
952 /// Callback to handle requests for local files. These are
953 /// caused by using `LOAD DATA LOCAL INFILE` queries. The
954 /// callback is passed the filename, and a `Write`able object
955 /// to receive the contents of that file.
956 /// If unset, the default callback will read files relative to
957 /// the current directory.
958 pub fn local_infile_handler(mut self, handler: Option<LocalInfileHandler>) -> Self {
959 self.opts.0.local_infile_handler = handler;
960 self
961 }
962
963 /// Tcp connect timeout (defaults to `None`). Available as `tcp_connect_timeout_ms`
964 /// url parameter.
965 ///
966 /// Can be defined using `tcp_connect_timeout_ms` connection url parameter.
967 pub fn tcp_connect_timeout(mut self, timeout: Option<Duration>) -> Self {
968 self.opts.0.tcp_connect_timeout = timeout;
969 self
970 }
971
972 /// Bind address for a client (defaults to `None`).
973 ///
974 /// Use carefully. Will probably make pool unusable because of *address already in use*
975 /// errors.
976 pub fn bind_address<T>(mut self, bind_address: Option<T>) -> Self
977 where
978 T: Into<SocketAddr>,
979 {
980 self.opts.0.bind_address = bind_address.map(Into::into);
981 self
982 }
983
984 /// Number of prepared statements cached on the client side (per connection).
985 /// Defaults to [`DEFAULT_STMT_CACHE_SIZE`].
986 ///
987 /// Can be defined using `stmt_cache_size` connection url parameter.
988 ///
989 /// Call with `None` to reset to default.
990 pub fn stmt_cache_size<T>(mut self, cache_size: T) -> Self
991 where
992 T: Into<Option<usize>>,
993 {
994 self.opts.0.stmt_cache_size = cache_size.into().unwrap_or(128);
995 self
996 }
997
998 /// If not `None`, then client will ask for compression if server supports it
999 /// (defaults to `None`).
1000 ///
1001 /// Can be defined using `compress` connection url parameter with values:
1002 /// * `true` - library defined default compression level;
1003 /// * `fast` - library defined fast compression level;
1004 /// * `best` - library defined best compression level;
1005 /// * `0`, `1`, ..., `9` - explicitly defined compression level where `0` stands for
1006 /// "no compression";
1007 ///
1008 /// Note that compression level defined here will affect only outgoing packets.
1009 pub fn compress(mut self, compress: Option<crate::Compression>) -> Self {
1010 self.opts.0.compress = compress;
1011 self
1012 }
1013
1014 /// Additional client capabilities to set (defaults to empty).
1015 ///
1016 /// This value will be OR'ed with other client capabilities during connection initialization.
1017 ///
1018 /// ### Note
1019 ///
1020 /// It is a good way to set something like `CLIENT_FOUND_ROWS` but you should note that it
1021 /// won't let you to interfere with capabilities managed by other options (like
1022 /// `CLIENT_SSL` or `CLIENT_COMPRESS`). Also note that some capabilities are reserved,
1023 /// pointless or may broke the connection, so this option should be used with caution.
1024 pub fn additional_capabilities(mut self, additional_capabilities: CapabilityFlags) -> Self {
1025 let forbidden_flags: CapabilityFlags = CapabilityFlags::CLIENT_PROTOCOL_41
1026 | CapabilityFlags::CLIENT_SSL
1027 | CapabilityFlags::CLIENT_COMPRESS
1028 | CapabilityFlags::CLIENT_SECURE_CONNECTION
1029 | CapabilityFlags::CLIENT_LONG_PASSWORD
1030 | CapabilityFlags::CLIENT_TRANSACTIONS
1031 | CapabilityFlags::CLIENT_LOCAL_FILES
1032 | CapabilityFlags::CLIENT_MULTI_STATEMENTS
1033 | CapabilityFlags::CLIENT_MULTI_RESULTS
1034 | CapabilityFlags::CLIENT_PS_MULTI_RESULTS;
1035
1036 self.opts.0.additional_capabilities = additional_capabilities & !forbidden_flags;
1037 self
1038 }
1039
1040 /// Connect attributes (the default connect attributes are sent by default).
1041 ///
1042 /// This value is sent to the server as custom name-value attributes.
1043 /// You can see them from performance_schema tables: [`session_account_connect_attrs`
1044 /// and `session_connect_attrs`][attr_tables] when all of the following conditions
1045 /// are met.
1046 ///
1047 /// * The server is MySQL 5.6 or later, or MariaDB 10.0 or later.
1048 /// * [`performance_schema`] is on.
1049 /// * [`performance_schema_session_connect_attrs_size`] is -1 or big enough
1050 /// to store specified attributes.
1051 ///
1052 /// ### Note
1053 ///
1054 /// - set `connect_attrs` to `None` to completely remove connect attributes
1055 /// - set `connect_attrs` to an empty map to send only the default attributes
1056 ///
1057 /// #### Warning
1058 ///
1059 /// > There is a bug in MySql 5.6 that kills COM_CHANGE_USER in the presence of connection
1060 /// > attributes so it's better to stick to `None` for mysql < 5.7.
1061 ///
1062 /// Attribute names that begin with an underscore (`_`) are not set by
1063 /// application programs because they are reserved for internal use.
1064 ///
1065 /// The following default attributes are sent in addition to ones set by programs.
1066 ///
1067 /// name | value
1068 /// ----------------|--------------------------
1069 /// _client_name | The client library name (`rust-mysql-simple`)
1070 /// _client_version | The client library version
1071 /// _os | The operation system (`target_os` cfg feature)
1072 /// _pid | The client process ID
1073 /// _platform | The machine platform (`target_arch` cfg feature)
1074 /// program_name | The first element of `std::env::args` if program_name isn't set by programs.
1075 ///
1076 /// [attr_tables]: https://dev.mysql.com/doc/refman/en/performance-schema-connection-attribute-tables.html
1077 /// [`performance_schema`]: https://dev.mysql.com/doc/refman/8.0/en/performance-schema-system-variables.html#sysvar_performance_schema
1078 /// [`performance_schema_session_connect_attrs_size`]: https://dev.mysql.com/doc/refman/en/performance-schema-system-variables.html#sysvar_performance_schema_session_connect_attrs_size
1079 ///
1080 pub fn connect_attrs<T1: Into<String> + Eq + Hash, T2: Into<String>>(
1081 mut self,
1082 connect_attrs: Option<HashMap<T1, T2>>,
1083 ) -> Self {
1084 if let Some(connect_attrs) = connect_attrs {
1085 let mut attrs = HashMap::with_capacity(connect_attrs.len());
1086 for (name, value) in connect_attrs {
1087 let name = name.into();
1088 if !name.starts_with('_') {
1089 attrs.insert(name, value.into());
1090 }
1091 }
1092 self.opts.0.connect_attrs = Some(attrs);
1093 } else {
1094 self.opts.0.connect_attrs = None;
1095 }
1096 self
1097 }
1098
1099 /// Disables `mysql_old_password` plugin (defaults to `true`).
1100 ///
1101 /// Available via `secure_auth` connection url parameter.
1102 pub fn secure_auth(mut self, secure_auth: bool) -> Self {
1103 self.opts.0.secure_auth = secure_auth;
1104 self
1105 }
1106
1107 /// Enables Client-Side Cleartext Pluggable Authentication (defaults to `false`).
1108 ///
1109 /// Enables client to send passwords to the server as cleartext, without hashing or encryption
1110 /// (consult MySql documentation for more info).
1111 ///
1112 /// # Security Notes
1113 ///
1114 /// Sending passwords as cleartext may be a security problem in some configurations. Please
1115 /// consider using TLS or encrypted tunnels for server connection.
1116 ///
1117 /// # Connection URL
1118 ///
1119 /// Use `enable_cleartext_plugin` URL parameter to set this value. E.g.
1120 ///
1121 /// ```
1122 /// # use mysql::*;
1123 /// # fn main() -> Result<()> {
1124 /// let opts = Opts::from_url("mysql://localhost/db?enable_cleartext_plugin=true")?;
1125 /// assert!(opts.get_enable_cleartext_plugin());
1126 /// # Ok(()) }
1127 /// ```
1128 pub fn enable_cleartext_plugin(mut self, enable_cleartext_plugin: bool) -> Self {
1129 self.opts.0.enable_cleartext_plugin = enable_cleartext_plugin;
1130 self
1131 }
1132}
1133
1134impl From<OptsBuilder> for Opts {
1135 fn from(builder: OptsBuilder) -> Opts {
1136 builder.opts
1137 }
1138}
1139
1140fn get_opts_user_from_url(url: &Url) -> Option<String> {
1141 let user = url.username();
1142 if !user.is_empty() {
1143 Some(
1144 percent_decode(user.as_ref())
1145 .decode_utf8_lossy()
1146 .into_owned(),
1147 )
1148 } else {
1149 None
1150 }
1151}
1152
1153fn get_opts_pass_from_url(url: &Url) -> Option<String> {
1154 url.password().map(|pass| {
1155 percent_decode(pass.as_ref())
1156 .decode_utf8_lossy()
1157 .into_owned()
1158 })
1159}
1160
1161fn get_opts_db_name_from_url(url: &Url) -> Option<String> {
1162 if let Some(mut segments) = url.path_segments() {
1163 segments
1164 .next()
1165 .filter(|&db_name| !db_name.is_empty())
1166 .map(|db_name| {
1167 percent_decode(db_name.as_ref())
1168 .decode_utf8_lossy()
1169 .into_owned()
1170 })
1171 } else {
1172 None
1173 }
1174}
1175
1176fn from_url_basic(url_str: &str) -> Result<(Opts, Vec<(String, String)>), UrlError> {
1177 let url = Url::parse(url_str)?;
1178 if url.scheme() != "mysql" {
1179 return Err(UrlError::UnsupportedScheme(url.scheme().to_string()));
1180 }
1181 if url.cannot_be_a_base() {
1182 return Err(UrlError::BadUrl);
1183 }
1184 let user = get_opts_user_from_url(&url);
1185 let pass = get_opts_pass_from_url(&url);
1186 let ip_or_hostname = url
1187 .host()
1188 .ok_or(UrlError::BadUrl)
1189 .and_then(|host| url::Host::parse(&host.to_string()).map_err(|_| UrlError::BadUrl))?;
1190 let tcp_port = url.port().unwrap_or(3306);
1191 let db_name = get_opts_db_name_from_url(&url);
1192
1193 let query_pairs = url.query_pairs().into_owned().collect();
1194 let opts = Opts(Box::new(InnerOpts {
1195 user,
1196 pass,
1197 ip_or_hostname,
1198 tcp_port,
1199 db_name,
1200 ..InnerOpts::default()
1201 }));
1202
1203 Ok((opts, query_pairs))
1204}
1205
1206fn from_url(url: &str) -> Result<Opts, UrlError> {
1207 let (opts, query_pairs) = from_url_basic(url)?;
1208 let hash_map = query_pairs.into_iter().collect::<HashMap<String, String>>();
1209 OptsBuilder::from_opts(opts)
1210 .from_hash_map(&hash_map)
1211 .map(Into::into)
1212}
1213
1214/// [`COM_CHANGE_USER`][1] options.
1215///
1216/// Connection [`Opts`] are going to be updated accordingly upon `COM_CHANGE_USER`.
1217///
1218/// [`Opts`] won't be updated by default, because default `ChangeUserOpts` will reuse
1219/// connection's `user`, `pass` and `db_name`.
1220///
1221/// [1]: https://dev.mysql.com/doc/c-api/5.7/en/mysql-change-user.html
1222#[derive(Clone, Eq, PartialEq)]
1223pub struct ChangeUserOpts {
1224 user: Option<Option<String>>,
1225 pass: Option<Option<String>>,
1226 db_name: Option<Option<String>>,
1227}
1228
1229impl ChangeUserOpts {
1230 pub const DEFAULT: Self = Self {
1231 user: None,
1232 pass: None,
1233 db_name: None,
1234 };
1235
1236 pub(crate) fn update_opts(self, opts: &mut Opts) {
1237 if self.user.is_none() && self.pass.is_none() && self.db_name.is_none() {
1238 return;
1239 }
1240
1241 let mut builder = OptsBuilder::from_opts(opts.clone());
1242
1243 if let Some(user) = self.user {
1244 builder = builder.user(user);
1245 }
1246
1247 if let Some(pass) = self.pass {
1248 builder = builder.pass(pass);
1249 }
1250
1251 if let Some(db_name) = self.db_name {
1252 builder = builder.db_name(db_name);
1253 }
1254
1255 *opts = Opts::from(builder);
1256 }
1257
1258 /// Creates change user options that'll reuse connection options.
1259 pub fn new() -> Self {
1260 Self {
1261 user: None,
1262 pass: None,
1263 db_name: None,
1264 }
1265 }
1266
1267 /// Set [`Opts::get_user`] to the given value.
1268 pub fn with_user(mut self, user: Option<String>) -> Self {
1269 self.user = Some(user);
1270 self
1271 }
1272
1273 /// Set [`Opts::get_pass`] to the given value.
1274 pub fn with_pass(mut self, pass: Option<String>) -> Self {
1275 self.pass = Some(pass);
1276 self
1277 }
1278
1279 /// Set [`Opts::get_db_name`] to the given value.
1280 pub fn with_db_name(mut self, db_name: Option<String>) -> Self {
1281 self.db_name = Some(db_name);
1282 self
1283 }
1284
1285 /// Returns user.
1286 ///
1287 /// * if `None` then `self` does not meant to change user
1288 /// * if `Some(None)` then `self` will clear user
1289 /// * if `Some(Some(_))` then `self` will change user
1290 pub fn user(&self) -> Option<Option<&str>> {
1291 self.user.as_ref().map(|x| x.as_deref())
1292 }
1293
1294 /// Returns password.
1295 ///
1296 /// * if `None` then `self` does not meant to change password
1297 /// * if `Some(None)` then `self` will clear password
1298 /// * if `Some(Some(_))` then `self` will change password
1299 pub fn pass(&self) -> Option<Option<&str>> {
1300 self.pass.as_ref().map(|x| x.as_deref())
1301 }
1302
1303 /// Returns database name.
1304 ///
1305 /// * if `None` then `self` does not meant to change database name
1306 /// * if `Some(None)` then `self` will clear database name
1307 /// * if `Some(Some(_))` then `self` will change database name
1308 pub fn db_name(&self) -> Option<Option<&str>> {
1309 self.db_name.as_ref().map(|x| x.as_deref())
1310 }
1311}
1312
1313impl Default for ChangeUserOpts {
1314 fn default() -> Self {
1315 Self::new()
1316 }
1317}
1318
1319impl fmt::Debug for ChangeUserOpts {
1320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1321 f.debug_struct("ChangeUserOpts")
1322 .field("user", &self.user)
1323 .field(
1324 "pass",
1325 &self.pass.as_ref().map(|x| x.as_ref().map(|_| "...")),
1326 )
1327 .field("db_name", &self.db_name)
1328 .finish()
1329 }
1330}
1331
1332#[cfg(test)]
1333mod test {
1334 use mysql_common::proto::codec::Compression;
1335 use std::time::Duration;
1336
1337 use super::{InnerOpts, Opts, OptsBuilder};
1338
1339 #[allow(dead_code)]
1340 fn assert_conn_from_url_opts_optsbuilder(url: &str, opts: Opts, opts_builder: OptsBuilder) {
1341 crate::Conn::new(url).unwrap();
1342 crate::Conn::new(opts.clone()).unwrap();
1343 crate::Conn::new(opts_builder.clone()).unwrap();
1344 crate::Pool::new(url).unwrap();
1345 crate::Pool::new(opts).unwrap();
1346 crate::Pool::new(opts_builder).unwrap();
1347 }
1348
1349 #[test]
1350 fn should_report_empty_url_database_as_none() {
1351 let opt = Opts::from_url("mysql://localhost/").unwrap();
1352 assert_eq!(opt.get_db_name(), None);
1353 }
1354
1355 #[test]
1356 fn should_convert_url_into_opts() {
1357 #[cfg(any(target_os = "linux", target_os = "macos",))]
1358 let tcp_keepalive_probe_interval_secs = "&tcp_keepalive_probe_interval_secs=8";
1359 #[cfg(not(any(target_os = "linux", target_os = "macos",)))]
1360 let tcp_keepalive_probe_interval_secs = "";
1361
1362 #[cfg(any(target_os = "linux", target_os = "macos",))]
1363 let tcp_keepalive_probe_count = "&tcp_keepalive_probe_count=5";
1364 #[cfg(not(any(target_os = "linux", target_os = "macos",)))]
1365 let tcp_keepalive_probe_count = "";
1366
1367 #[cfg(target_os = "linux")]
1368 let tcp_user_timeout = "&tcp_user_timeout_ms=6000";
1369 #[cfg(not(target_os = "linux"))]
1370 let tcp_user_timeout = "";
1371
1372 let opts = format!(
1373 "mysql://us%20r:p%20w@localhost:3308/db%2dname?prefer_socket=false&tcp_keepalive_time_ms=5000{}{}{}&socket=%2Ftmp%2Fmysql.sock&compress=8",
1374 tcp_keepalive_probe_interval_secs,
1375 tcp_keepalive_probe_count,
1376 tcp_user_timeout,
1377 );
1378 assert_eq!(
1379 Opts(Box::new(InnerOpts {
1380 user: Some("us r".to_string()),
1381 pass: Some("p w".to_string()),
1382 ip_or_hostname: url::Host::Domain("localhost".to_string()),
1383 tcp_port: 3308,
1384 db_name: Some("db-name".to_string()),
1385 prefer_socket: false,
1386 tcp_keepalive_time: Some(5000),
1387 #[cfg(any(target_os = "linux", target_os = "macos",))]
1388 tcp_keepalive_probe_interval_secs: Some(8),
1389 #[cfg(any(target_os = "linux", target_os = "macos",))]
1390 tcp_keepalive_probe_count: Some(5),
1391 #[cfg(target_os = "linux")]
1392 tcp_user_timeout: Some(6000),
1393 socket: Some("/tmp/mysql.sock".into()),
1394 compress: Some(Compression::new(8)),
1395 ..InnerOpts::default()
1396 })),
1397 Opts::from_url(&opts).unwrap(),
1398 );
1399 }
1400
1401 #[test]
1402 #[should_panic]
1403 fn should_panic_on_invalid_url() {
1404 let opts = "42";
1405 Opts::from_url(opts).unwrap();
1406 }
1407
1408 #[test]
1409 #[should_panic]
1410 fn should_panic_on_invalid_scheme() {
1411 let opts = "postgres://localhost";
1412 Opts::from_url(opts).unwrap();
1413 }
1414
1415 #[test]
1416 #[should_panic]
1417 fn should_panic_on_unknown_query_param() {
1418 let opts = "mysql://localhost/foo?bar=baz";
1419 Opts::from_url(opts).unwrap();
1420 }
1421
1422 #[test]
1423 fn should_read_hashmap_into_opts() {
1424 use crate::OptsBuilder;
1425 macro_rules! map(
1426 { $($key:expr => $value:expr), + }=> {
1427 {
1428 let mut h = std::collections::HashMap::new();
1429 $(
1430 h.insert($key, $value);
1431 )+
1432 h
1433 }
1434 };
1435 );
1436
1437 let mut cnf_map = map! {
1438 "user".to_string() => "test".to_string(),
1439 "password".to_string() => "password".to_string(),
1440 "host".to_string() => "127.0.0.1".to_string(),
1441 "port".to_string() => "8080".to_string(),
1442 "db_name".to_string() => "test_db".to_string(),
1443 "prefer_socket".to_string() => "false".to_string(),
1444 "tcp_keepalive_time_ms".to_string() => "5000".to_string(),
1445 "compress".to_string() => "best".to_string(),
1446 "tcp_connect_timeout_ms".to_string() => "1000".to_string(),
1447 "stmt_cache_size".to_string() => "33".to_string(),
1448 "max_allowed_packet".to_string() => "65536".to_string()
1449 };
1450 #[cfg(any(target_os = "linux", target_os = "macos",))]
1451 cnf_map.insert(
1452 "tcp_keepalive_probe_interval_secs".to_string(),
1453 "8".to_string(),
1454 );
1455 #[cfg(any(target_os = "linux", target_os = "macos",))]
1456 cnf_map.insert("tcp_keepalive_probe_count".to_string(), "5".to_string());
1457
1458 let parsed_opts = OptsBuilder::new().from_hash_map(&cnf_map).unwrap();
1459
1460 assert_eq!(parsed_opts.opts.get_user(), Some("test"));
1461 assert_eq!(parsed_opts.opts.get_pass(), Some("password"));
1462 assert_eq!(parsed_opts.opts.get_ip_or_hostname(), "127.0.0.1");
1463 assert_eq!(parsed_opts.opts.get_tcp_port(), 8080);
1464 assert_eq!(parsed_opts.opts.get_db_name(), Some("test_db"));
1465 assert_eq!(parsed_opts.opts.get_max_allowed_packet(), Some(65536));
1466 assert!(!parsed_opts.opts.get_prefer_socket());
1467 assert_eq!(parsed_opts.opts.get_tcp_keepalive_time_ms(), Some(5000));
1468 #[cfg(any(target_os = "linux", target_os = "macos",))]
1469 assert_eq!(
1470 parsed_opts.opts.get_tcp_keepalive_probe_interval_secs(),
1471 Some(8)
1472 );
1473 #[cfg(any(target_os = "linux", target_os = "macos",))]
1474 assert_eq!(parsed_opts.opts.get_tcp_keepalive_probe_count(), Some(5));
1475 assert_eq!(
1476 parsed_opts.opts.get_compress(),
1477 Some(crate::Compression::best())
1478 );
1479 assert_eq!(
1480 parsed_opts.opts.get_tcp_connect_timeout(),
1481 Some(Duration::from_millis(1000))
1482 );
1483 assert_eq!(parsed_opts.opts.get_stmt_cache_size(), 33);
1484 }
1485
1486 #[test]
1487 fn should_have_url_err() {
1488 use crate::OptsBuilder;
1489 use crate::UrlError;
1490 macro_rules! map(
1491 { $($key:expr => $value:expr), + }=> {
1492 {
1493 let mut h = std::collections::HashMap::new();
1494 $(
1495 h.insert($key, $value);
1496 )+
1497 h
1498 }
1499 };
1500 );
1501
1502 let cnf_map = map! {
1503 "user".to_string() => "test".to_string(),
1504 "password".to_string() => "password".to_string(),
1505 "host".to_string() => "127.0.0.1".to_string(),
1506 "port".to_string() => "NOTAPORT".to_string(),
1507 "db_name".to_string() => "test_db".to_string(),
1508 "prefer_socket".to_string() => "false".to_string(),
1509 "tcp_keepalive_time_ms".to_string() => "5000".to_string(),
1510 "compress".to_string() => "best".to_string(),
1511 "tcp_connect_timeout_ms".to_string() => "1000".to_string(),
1512 "stmt_cache_size".to_string() => "33".to_string()
1513 };
1514
1515 let parsed = OptsBuilder::new().from_hash_map(&cnf_map);
1516 assert_eq!(
1517 parsed,
1518 Err(UrlError::InvalidValue(
1519 "port".to_string(),
1520 "NOTAPORT".to_string()
1521 ))
1522 );
1523 }
1524}