1mod native_tls_opts;
10mod rustls_opts;
11
12#[cfg(feature = "native-tls-tls")]
13pub use native_tls_opts::ClientIdentity;
14
15#[cfg(feature = "rustls-tls")]
16pub use rustls_opts::ClientIdentity;
17
18use percent_encoding::percent_decode;
19use rand::Rng;
20use tokio::sync::OnceCell;
21use url::{Host, Url};
22
23use std::{
24 borrow::Cow,
25 fmt, io,
26 net::{IpAddr, Ipv4Addr, Ipv6Addr},
27 path::{Path, PathBuf},
28 str::FromStr,
29 sync::Arc,
30 time::{Duration, Instant},
31 vec,
32};
33
34use crate::{
35 consts::CapabilityFlags,
36 error::*,
37 local_infile_handler::{GlobalHandler, GlobalHandlerObject},
38};
39
40pub const DEFAULT_POOL_CONSTRAINTS: PoolConstraints = PoolConstraints { min: 10, max: 100 };
42
43const_assert!(
45 _DEFAULT_POOL_CONSTRAINTS_ARE_CORRECT,
46 DEFAULT_POOL_CONSTRAINTS.min <= DEFAULT_POOL_CONSTRAINTS.max
47 && 0 < DEFAULT_POOL_CONSTRAINTS.max,
48);
49
50pub const DEFAULT_STMT_CACHE_SIZE: usize = 32;
52
53pub const DEFAULT_PORT: u16 = 3306;
55
56pub const DEFAULT_INACTIVE_CONNECTION_TTL: Duration = Duration::from_secs(0);
61
62pub const DEFAULT_TTL_CHECK_INTERVAL: Duration = Duration::from_secs(30);
66
67#[derive(Clone, Eq, PartialEq, Debug)]
70pub(crate) enum HostPortOrUrl {
71 HostPort {
72 host: String,
73 port: u16,
74 resolved_ips: Option<Vec<IpAddr>>,
77 },
78 Url(Url),
79}
80
81impl Default for HostPortOrUrl {
82 fn default() -> Self {
83 HostPortOrUrl::HostPort {
84 host: "127.0.0.1".to_string(),
85 port: DEFAULT_PORT,
86 resolved_ips: None,
87 }
88 }
89}
90
91impl HostPortOrUrl {
92 pub fn get_ip_or_hostname(&self) -> &str {
93 match self {
94 Self::HostPort { host, .. } => host,
95 Self::Url(url) => url.host_str().unwrap_or("127.0.0.1"),
96 }
97 }
98
99 pub fn get_tcp_port(&self) -> u16 {
100 match self {
101 Self::HostPort { port, .. } => *port,
102 Self::Url(url) => url.port().unwrap_or(DEFAULT_PORT),
103 }
104 }
105
106 pub fn get_resolved_ips(&self) -> &Option<Vec<IpAddr>> {
107 match self {
108 Self::HostPort { resolved_ips, .. } => resolved_ips,
109 Self::Url(_) => &None,
110 }
111 }
112
113 pub fn is_loopback(&self) -> bool {
114 match self {
115 Self::HostPort {
116 host, resolved_ips, ..
117 } => {
118 let v4addr: Option<Ipv4Addr> = FromStr::from_str(host).ok();
119 let v6addr: Option<Ipv6Addr> = FromStr::from_str(host).ok();
120 if resolved_ips
121 .as_ref()
122 .is_some_and(|s| s.iter().any(|ip| ip.is_loopback()))
123 {
124 true
125 } else if let Some(addr) = v4addr {
126 addr.is_loopback()
127 } else if let Some(addr) = v6addr {
128 addr.is_loopback()
129 } else {
130 host == "localhost"
131 }
132 }
133 Self::Url(url) => match url.host() {
134 Some(Host::Ipv4(ip)) => ip.is_loopback(),
135 Some(Host::Ipv6(ip)) => ip.is_loopback(),
136 Some(Host::Domain(s)) => s == "localhost",
137 _ => false,
138 },
139 }
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Hash)]
145pub enum PathOrBuf<'a> {
146 Path(Cow<'a, Path>),
147 Buf(Cow<'a, [u8]>),
148}
149
150impl<'a> PathOrBuf<'a> {
151 pub async fn read(&self) -> io::Result<Cow<[u8]>> {
153 match self {
154 PathOrBuf::Path(x) => tokio::fs::read(x.as_ref()).await.map(Cow::Owned),
155 PathOrBuf::Buf(x) => Ok(Cow::Borrowed(x.as_ref())),
156 }
157 }
158
159 pub fn borrow(&self) -> PathOrBuf<'_> {
161 match self {
162 PathOrBuf::Path(path) => PathOrBuf::Path(Cow::Borrowed(path.as_ref())),
163 PathOrBuf::Buf(data) => PathOrBuf::Buf(Cow::Borrowed(data.as_ref())),
164 }
165 }
166}
167
168impl From<PathBuf> for PathOrBuf<'static> {
169 fn from(value: PathBuf) -> Self {
170 Self::Path(Cow::Owned(value))
171 }
172}
173
174impl<'a> From<&'a Path> for PathOrBuf<'a> {
175 fn from(value: &'a Path) -> Self {
176 Self::Path(Cow::Borrowed(value))
177 }
178}
179
180impl From<Vec<u8>> for PathOrBuf<'static> {
181 fn from(value: Vec<u8>) -> Self {
182 Self::Buf(Cow::Owned(value))
183 }
184}
185
186impl<'a> From<&'a [u8]> for PathOrBuf<'a> {
187 fn from(value: &'a [u8]) -> Self {
188 Self::Buf(Cow::Borrowed(value))
189 }
190}
191
192#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
215pub struct SslOpts {
216 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
217 client_identity: Option<ClientIdentity>,
218 root_certs: Vec<PathOrBuf<'static>>,
219 disable_built_in_roots: bool,
220 skip_domain_validation: bool,
221 accept_invalid_certs: bool,
222 tls_hostname_override: Option<Cow<'static, str>>,
223}
224
225impl SslOpts {
226 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
227 pub fn with_client_identity(mut self, identity: Option<ClientIdentity>) -> Self {
228 self.client_identity = identity;
229 self
230 }
231
232 pub fn with_root_certs(mut self, root_certs: Vec<PathOrBuf<'static>>) -> Self {
238 self.root_certs = root_certs;
239 self
240 }
241
242 pub fn with_disable_built_in_roots(mut self, disable_built_in_roots: bool) -> Self {
258 self.disable_built_in_roots = disable_built_in_roots;
259 self
260 }
261
262 pub fn with_danger_skip_domain_validation(mut self, value: bool) -> Self {
278 self.skip_domain_validation = value;
279 self
280 }
281
282 pub fn with_danger_accept_invalid_certs(mut self, value: bool) -> Self {
298 self.accept_invalid_certs = value;
299 self
300 }
301
302 pub fn with_danger_tls_hostname_override<T: Into<Cow<'static, str>>>(
307 mut self,
308 domain: Option<T>,
309 ) -> Self {
310 self.tls_hostname_override = domain.map(Into::into);
311 self
312 }
313
314 #[cfg(any(feature = "native-tls-tls", feature = "rustls-tls"))]
315 pub fn client_identity(&self) -> Option<&ClientIdentity> {
316 self.client_identity.as_ref()
317 }
318
319 pub fn root_certs(&self) -> &[PathOrBuf<'static>] {
320 &self.root_certs
321 }
322
323 pub fn disable_built_in_roots(&self) -> bool {
324 self.disable_built_in_roots
325 }
326
327 pub fn skip_domain_validation(&self) -> bool {
328 self.skip_domain_validation
329 }
330
331 pub fn accept_invalid_certs(&self) -> bool {
332 self.accept_invalid_certs
333 }
334
335 pub fn tls_hostname_override(&self) -> Option<&str> {
336 self.tls_hostname_override.as_deref()
337 }
338}
339
340#[derive(Debug, Clone, Eq, PartialEq, Hash)]
350pub struct PoolOpts {
351 constraints: PoolConstraints,
352 inactive_connection_ttl: Duration,
353 ttl_check_interval: Duration,
354 abs_conn_ttl: Option<Duration>,
355 abs_conn_ttl_jitter: Option<Duration>,
356 reset_connection: bool,
357}
358
359impl PoolOpts {
360 pub fn new() -> Self {
362 Self::default()
363 }
364
365 pub fn with_constraints(mut self, constraints: PoolConstraints) -> Self {
367 self.constraints = constraints;
368 self
369 }
370
371 pub fn constraints(&self) -> PoolConstraints {
373 self.constraints
374 }
375
376 pub fn with_reset_connection(mut self, reset_connection: bool) -> Self {
413 self.reset_connection = reset_connection;
414 self
415 }
416
417 pub fn reset_connection(&self) -> bool {
419 self.reset_connection
420 }
421
422 pub fn with_abs_conn_ttl(mut self, ttl: Option<Duration>) -> Self {
428 self.abs_conn_ttl = ttl;
429 self
430 }
431
432 pub fn with_abs_conn_ttl_jitter(mut self, jitter: Option<Duration>) -> Self {
437 self.abs_conn_ttl_jitter = jitter;
438 self
439 }
440
441 pub fn abs_conn_ttl(&self) -> Option<Duration> {
443 self.abs_conn_ttl
444 }
445
446 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
448 self.abs_conn_ttl_jitter
449 }
450
451 pub(crate) fn new_connection_ttl_deadline(&self) -> Option<Instant> {
453 if let Some(ttl) = self.abs_conn_ttl {
454 let jitter = if let Some(jitter) = self.abs_conn_ttl_jitter {
455 Duration::from_secs(rand::rng().random_range(0..=jitter.as_secs()))
456 } else {
457 Duration::ZERO
458 };
459 Some(Instant::now() + ttl + jitter)
460 } else {
461 None
462 }
463 }
464
465 pub fn with_inactive_connection_ttl(mut self, ttl: Duration) -> Self {
484 self.inactive_connection_ttl = ttl;
485 self
486 }
487
488 pub fn inactive_connection_ttl(&self) -> Duration {
490 self.inactive_connection_ttl
491 }
492
493 pub fn with_ttl_check_interval(mut self, interval: Duration) -> Self {
511 if interval < Duration::from_secs(1) {
512 self.ttl_check_interval = DEFAULT_TTL_CHECK_INTERVAL
513 } else {
514 self.ttl_check_interval = interval;
515 }
516 self
517 }
518
519 pub fn ttl_check_interval(&self) -> Duration {
521 self.ttl_check_interval
522 }
523
524 pub(crate) fn active_bound(&self) -> usize {
537 if self.inactive_connection_ttl > Duration::from_secs(0) {
538 self.constraints.max
539 } else {
540 self.constraints.min
541 }
542 }
543}
544
545impl Default for PoolOpts {
546 fn default() -> Self {
547 Self {
548 constraints: DEFAULT_POOL_CONSTRAINTS,
549 inactive_connection_ttl: DEFAULT_INACTIVE_CONNECTION_TTL,
550 ttl_check_interval: DEFAULT_TTL_CHECK_INTERVAL,
551 abs_conn_ttl: None,
552 abs_conn_ttl_jitter: None,
553 reset_connection: true,
554 }
555 }
556}
557
558#[derive(Clone, Eq, PartialEq, Default, Debug)]
559pub(crate) struct InnerOpts {
560 mysql_opts: MysqlOpts,
561 address: HostPortOrUrl,
562}
563
564#[derive(Clone, Eq, PartialEq, Debug)]
568pub(crate) struct MysqlOpts {
569 user: Option<String>,
571
572 pass: Option<String>,
574
575 db_name: Option<String>,
577
578 tcp_keepalive: Option<u32>,
580
581 tcp_nodelay: bool,
586
587 local_infile_handler: Option<GlobalHandlerObject>,
589
590 pool_opts: PoolOpts,
592
593 conn_ttl: Option<Duration>,
596
597 init: Vec<String>,
599
600 setup: Vec<String>,
603
604 stmt_cache_size: usize,
606
607 ssl_opts: Option<SslOptsAndCachedConnector>,
609
610 prefer_socket: bool,
622
623 socket: Option<String>,
625
626 compression: Option<crate::Compression>,
637
638 max_allowed_packet: Option<usize>,
643
644 wait_timeout: Option<usize>,
649
650 secure_auth: bool,
654
655 client_found_rows: bool,
660
661 enable_cleartext_plugin: bool,
671}
672
673#[derive(Clone, Eq, PartialEq, Debug, Default)]
677pub struct Opts {
678 inner: Arc<InnerOpts>,
679}
680
681impl Opts {
682 #[doc(hidden)]
683 pub fn addr_is_loopback(&self) -> bool {
684 self.inner.address.is_loopback()
685 }
686
687 pub fn from_url(url: &str) -> std::result::Result<Opts, UrlError> {
688 let mut url = Url::parse(url)?;
689
690 if url.port().is_none() {
693 url.set_port(Some(DEFAULT_PORT))
694 .map_err(|_| UrlError::Invalid)?;
695 }
696
697 let mysql_opts = mysqlopts_from_url(&url)?;
698 let address = HostPortOrUrl::Url(url);
699
700 let inner_opts = InnerOpts {
701 mysql_opts,
702 address,
703 };
704
705 Ok(Opts {
706 inner: Arc::new(inner_opts),
707 })
708 }
709
710 pub fn ip_or_hostname(&self) -> &str {
712 self.inner.address.get_ip_or_hostname()
713 }
714
715 pub(crate) fn hostport_or_url(&self) -> &HostPortOrUrl {
716 &self.inner.address
717 }
718
719 pub fn tcp_port(&self) -> u16 {
721 self.inner.address.get_tcp_port()
722 }
723
724 pub fn resolved_ips(&self) -> &Option<Vec<IpAddr>> {
726 self.inner.address.get_resolved_ips()
727 }
728
729 pub fn user(&self) -> Option<&str> {
743 self.inner.mysql_opts.user.as_ref().map(AsRef::as_ref)
744 }
745
746 pub fn pass(&self) -> Option<&str> {
760 self.inner.mysql_opts.pass.as_ref().map(AsRef::as_ref)
761 }
762
763 pub fn db_name(&self) -> Option<&str> {
777 self.inner.mysql_opts.db_name.as_ref().map(AsRef::as_ref)
778 }
779
780 pub fn init(&self) -> &[String] {
782 self.inner.mysql_opts.init.as_ref()
783 }
784
785 pub fn setup(&self) -> &[String] {
791 self.inner.mysql_opts.setup.as_ref()
792 }
793
794 pub fn tcp_keepalive(&self) -> Option<u32> {
808 self.inner.mysql_opts.tcp_keepalive
809 }
810
811 pub fn tcp_nodelay(&self) -> bool {
828 self.inner.mysql_opts.tcp_nodelay
829 }
830
831 pub fn local_infile_handler(&self) -> Option<Arc<dyn GlobalHandler>> {
833 self.inner
834 .mysql_opts
835 .local_infile_handler
836 .as_ref()
837 .map(|x| x.clone_inner())
838 }
839
840 pub fn pool_opts(&self) -> &PoolOpts {
842 &self.inner.mysql_opts.pool_opts
843 }
844
845 pub fn conn_ttl(&self) -> Option<Duration> {
861 self.inner.mysql_opts.conn_ttl
862 }
863
864 pub fn abs_conn_ttl(&self) -> Option<Duration> {
883 self.inner.mysql_opts.pool_opts.abs_conn_ttl
884 }
885
886 pub fn abs_conn_ttl_jitter(&self) -> Option<Duration> {
904 self.inner.mysql_opts.pool_opts.abs_conn_ttl_jitter
905 }
906
907 pub fn stmt_cache_size(&self) -> usize {
929 self.inner.mysql_opts.stmt_cache_size
930 }
931
932 pub fn ssl_opts(&self) -> Option<&SslOpts> {
954 self.inner.mysql_opts.ssl_opts.as_ref().map(|o| &o.ssl_opts)
955 }
956
957 pub fn prefer_socket(&self) -> bool {
981 self.inner.mysql_opts.prefer_socket
982 }
983
984 pub fn socket(&self) -> Option<&str> {
998 self.inner.mysql_opts.socket.as_deref()
999 }
1000
1001 pub fn compression(&self) -> Option<crate::Compression> {
1015 self.inner.mysql_opts.compression
1016 }
1017
1018 pub fn max_allowed_packet(&self) -> Option<usize> {
1025 self.inner.mysql_opts.max_allowed_packet
1026 }
1027
1028 pub fn wait_timeout(&self) -> Option<usize> {
1035 self.inner.mysql_opts.wait_timeout
1036 }
1037
1038 pub fn secure_auth(&self) -> bool {
1042 self.inner.mysql_opts.secure_auth
1043 }
1044
1045 pub fn client_found_rows(&self) -> bool {
1062 self.inner.mysql_opts.client_found_rows
1063 }
1064
1065 pub fn enable_cleartext_plugin(&self) -> bool {
1087 self.inner.mysql_opts.enable_cleartext_plugin
1088 }
1089
1090 pub(crate) fn get_capabilities(&self) -> CapabilityFlags {
1091 let mut out = CapabilityFlags::CLIENT_PROTOCOL_41
1092 | CapabilityFlags::CLIENT_SECURE_CONNECTION
1093 | CapabilityFlags::CLIENT_LONG_PASSWORD
1094 | CapabilityFlags::CLIENT_TRANSACTIONS
1095 | CapabilityFlags::CLIENT_LOCAL_FILES
1096 | CapabilityFlags::CLIENT_MULTI_STATEMENTS
1097 | CapabilityFlags::CLIENT_MULTI_RESULTS
1098 | CapabilityFlags::CLIENT_PS_MULTI_RESULTS
1099 | CapabilityFlags::CLIENT_DEPRECATE_EOF
1100 | CapabilityFlags::CLIENT_PLUGIN_AUTH;
1101
1102 if self.inner.mysql_opts.db_name.is_some() {
1103 out |= CapabilityFlags::CLIENT_CONNECT_WITH_DB;
1104 }
1105 if self.inner.mysql_opts.ssl_opts.is_some() {
1106 out |= CapabilityFlags::CLIENT_SSL;
1107 }
1108 if self.inner.mysql_opts.compression.is_some() {
1109 out |= CapabilityFlags::CLIENT_COMPRESS;
1110 }
1111 if self.client_found_rows() {
1112 out |= CapabilityFlags::CLIENT_FOUND_ROWS;
1113 }
1114
1115 out
1116 }
1117
1118 pub(crate) fn ssl_opts_and_connector(&self) -> Option<&SslOptsAndCachedConnector> {
1119 self.inner.mysql_opts.ssl_opts.as_ref()
1120 }
1121}
1122
1123impl Default for MysqlOpts {
1124 fn default() -> MysqlOpts {
1125 MysqlOpts {
1126 user: None,
1127 pass: None,
1128 db_name: None,
1129 init: vec![],
1130 setup: vec![],
1131 tcp_keepalive: None,
1132 tcp_nodelay: true,
1133 local_infile_handler: None,
1134 pool_opts: Default::default(),
1135 conn_ttl: None,
1136 stmt_cache_size: DEFAULT_STMT_CACHE_SIZE,
1137 ssl_opts: None,
1138 prefer_socket: cfg!(not(target_os = "windows")),
1139 socket: None,
1140 compression: None,
1141 max_allowed_packet: None,
1142 wait_timeout: None,
1143 secure_auth: true,
1144 client_found_rows: false,
1145 enable_cleartext_plugin: false,
1146 }
1147 }
1148}
1149
1150#[derive(Clone)]
1151pub(crate) struct SslOptsAndCachedConnector {
1152 ssl_opts: SslOpts,
1153 tls_connector: Arc<OnceCell<crate::io::TlsConnector>>,
1154}
1155
1156impl fmt::Debug for SslOptsAndCachedConnector {
1157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158 f.debug_struct("SslOptsAndCachedConnector")
1159 .field("ssl_opts", &self.ssl_opts)
1160 .finish()
1161 }
1162}
1163
1164impl SslOptsAndCachedConnector {
1165 fn new(ssl_opts: SslOpts) -> Self {
1166 Self {
1167 ssl_opts,
1168 tls_connector: Arc::new(OnceCell::new()),
1169 }
1170 }
1171
1172 pub(crate) fn ssl_opts(&self) -> &SslOpts {
1173 &self.ssl_opts
1174 }
1175
1176 pub(crate) async fn build_tls_connector(&self) -> Result<crate::io::TlsConnector> {
1177 self.tls_connector
1178 .get_or_try_init(move || self.ssl_opts.build_tls_connector())
1179 .await
1180 .cloned()
1181 }
1182}
1183
1184impl PartialEq for SslOptsAndCachedConnector {
1185 fn eq(&self, other: &Self) -> bool {
1186 self.ssl_opts == other.ssl_opts
1187 }
1188}
1189impl Eq for SslOptsAndCachedConnector {}
1190
1191#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
1195pub struct PoolConstraints {
1196 min: usize,
1197 max: usize,
1198}
1199
1200impl PoolConstraints {
1201 pub const fn new(min: usize, max: usize) -> Option<PoolConstraints> {
1215 if min <= max && max > 0 {
1216 Some(PoolConstraints { min, max })
1217 } else {
1218 None
1219 }
1220 }
1221
1222 pub fn min(&self) -> usize {
1224 self.min
1225 }
1226
1227 pub fn max(&self) -> usize {
1229 self.max
1230 }
1231}
1232
1233impl Default for PoolConstraints {
1234 fn default() -> Self {
1235 DEFAULT_POOL_CONSTRAINTS
1236 }
1237}
1238
1239impl From<PoolConstraints> for (usize, usize) {
1240 fn from(PoolConstraints { min, max }: PoolConstraints) -> Self {
1242 (min, max)
1243 }
1244}
1245
1246#[derive(Debug, Clone, Eq, PartialEq)]
1265pub struct OptsBuilder {
1266 opts: MysqlOpts,
1267 ip_or_hostname: String,
1268 tcp_port: u16,
1269 resolved_ips: Option<Vec<IpAddr>>,
1270}
1271
1272impl Default for OptsBuilder {
1273 fn default() -> Self {
1274 let address = HostPortOrUrl::default();
1275 Self {
1276 opts: MysqlOpts::default(),
1277 ip_or_hostname: address.get_ip_or_hostname().into(),
1278 tcp_port: address.get_tcp_port(),
1279 resolved_ips: None,
1280 }
1281 }
1282}
1283
1284impl OptsBuilder {
1285 pub fn from_opts<T>(opts: T) -> Self
1291 where
1292 Opts: TryFrom<T>,
1293 <Opts as TryFrom<T>>::Error: std::error::Error,
1294 {
1295 let opts = Opts::try_from(opts).unwrap();
1296
1297 OptsBuilder {
1298 tcp_port: opts.inner.address.get_tcp_port(),
1299 ip_or_hostname: opts.inner.address.get_ip_or_hostname().to_string(),
1300 resolved_ips: opts.inner.address.get_resolved_ips().clone(),
1301 opts: opts.inner.mysql_opts.clone(),
1302 }
1303 }
1304
1305 pub fn ip_or_hostname<T: Into<String>>(mut self, ip_or_hostname: T) -> Self {
1307 self.ip_or_hostname = ip_or_hostname.into();
1308 self
1309 }
1310
1311 pub fn tcp_port(mut self, tcp_port: u16) -> Self {
1313 self.tcp_port = tcp_port;
1314 self
1315 }
1316
1317 pub fn resolved_ips<T: Into<Vec<IpAddr>>>(mut self, ips: Option<T>) -> Self {
1321 self.resolved_ips = ips.map(Into::into);
1322 self
1323 }
1324
1325 pub fn user<T: Into<String>>(mut self, user: Option<T>) -> Self {
1327 self.opts.user = user.map(Into::into);
1328 self
1329 }
1330
1331 pub fn pass<T: Into<String>>(mut self, pass: Option<T>) -> Self {
1333 self.opts.pass = pass.map(Into::into);
1334 self
1335 }
1336
1337 pub fn db_name<T: Into<String>>(mut self, db_name: Option<T>) -> Self {
1339 self.opts.db_name = db_name.map(Into::into);
1340 self
1341 }
1342
1343 pub fn init<T: Into<String>>(mut self, init: Vec<T>) -> Self {
1345 self.opts.init = init.into_iter().map(Into::into).collect();
1346 self
1347 }
1348
1349 pub fn setup<T: Into<String>>(mut self, setup: Vec<T>) -> Self {
1351 self.opts.setup = setup.into_iter().map(Into::into).collect();
1352 self
1353 }
1354
1355 pub fn tcp_keepalive<T: Into<u32>>(mut self, tcp_keepalive: Option<T>) -> Self {
1357 self.opts.tcp_keepalive = tcp_keepalive.map(Into::into);
1358 self
1359 }
1360
1361 pub fn tcp_nodelay(mut self, nodelay: bool) -> Self {
1363 self.opts.tcp_nodelay = nodelay;
1364 self
1365 }
1366
1367 pub fn local_infile_handler<T>(mut self, handler: Option<T>) -> Self
1369 where
1370 T: GlobalHandler,
1371 {
1372 self.opts.local_infile_handler = handler.map(GlobalHandlerObject::new);
1373 self
1374 }
1375
1376 pub fn pool_opts<T: Into<Option<PoolOpts>>>(mut self, pool_opts: T) -> Self {
1378 self.opts.pool_opts = pool_opts.into().unwrap_or_default();
1379 self
1380 }
1381
1382 pub fn conn_ttl<T: Into<Option<Duration>>>(mut self, conn_ttl: T) -> Self {
1384 self.opts.conn_ttl = conn_ttl.into();
1385 self
1386 }
1387
1388 pub fn stmt_cache_size<T>(mut self, cache_size: T) -> Self
1390 where
1391 T: Into<Option<usize>>,
1392 {
1393 self.opts.stmt_cache_size = cache_size.into().unwrap_or(DEFAULT_STMT_CACHE_SIZE);
1394 self
1395 }
1396
1397 pub fn ssl_opts<T: Into<Option<SslOpts>>>(mut self, ssl_opts: T) -> Self {
1399 self.opts.ssl_opts = ssl_opts.into().map(SslOptsAndCachedConnector::new);
1400 self
1401 }
1402
1403 pub fn prefer_socket<T: Into<Option<bool>>>(mut self, prefer_socket: T) -> Self {
1405 self.opts.prefer_socket = prefer_socket.into().unwrap_or(true);
1406 self
1407 }
1408
1409 pub fn socket<T: Into<String>>(mut self, socket: Option<T>) -> Self {
1411 self.opts.socket = socket.map(Into::into);
1412 self
1413 }
1414
1415 pub fn compression<T: Into<Option<crate::Compression>>>(mut self, compression: T) -> Self {
1417 self.opts.compression = compression.into();
1418 self
1419 }
1420
1421 pub fn max_allowed_packet(mut self, max_allowed_packet: Option<usize>) -> Self {
1426 self.opts.max_allowed_packet = max_allowed_packet.map(|x| x.clamp(1024, 1073741824));
1427 self
1428 }
1429
1430 pub fn wait_timeout(mut self, wait_timeout: Option<usize>) -> Self {
1435 self.opts.wait_timeout = wait_timeout.map(|x| {
1436 #[cfg(windows)]
1437 let val = std::cmp::min(2147483, x);
1438 #[cfg(not(windows))]
1439 let val = std::cmp::min(31536000, x);
1440
1441 val
1442 });
1443 self
1444 }
1445
1446 pub fn secure_auth(mut self, secure_auth: bool) -> Self {
1450 self.opts.secure_auth = secure_auth;
1451 self
1452 }
1453
1454 pub fn client_found_rows(mut self, client_found_rows: bool) -> Self {
1456 self.opts.client_found_rows = client_found_rows;
1457 self
1458 }
1459
1460 pub fn enable_cleartext_plugin(mut self, enable_cleartext_plugin: bool) -> Self {
1482 self.opts.enable_cleartext_plugin = enable_cleartext_plugin;
1483 self
1484 }
1485}
1486
1487impl From<OptsBuilder> for Opts {
1488 fn from(builder: OptsBuilder) -> Opts {
1489 let address = HostPortOrUrl::HostPort {
1490 host: builder.ip_or_hostname,
1491 port: builder.tcp_port,
1492 resolved_ips: builder.resolved_ips,
1493 };
1494 let inner_opts = InnerOpts {
1495 mysql_opts: builder.opts,
1496 address,
1497 };
1498
1499 Opts {
1500 inner: Arc::new(inner_opts),
1501 }
1502 }
1503}
1504
1505#[derive(Clone, Eq, PartialEq)]
1514pub struct ChangeUserOpts {
1515 user: Option<Option<String>>,
1516 pass: Option<Option<String>>,
1517 db_name: Option<Option<String>>,
1518}
1519
1520impl ChangeUserOpts {
1521 pub(crate) fn update_opts(self, opts: &mut Opts) {
1522 if self.user.is_none() && self.pass.is_none() && self.db_name.is_none() {
1523 return;
1524 }
1525
1526 let mut builder = OptsBuilder::from_opts(opts.clone());
1527
1528 if let Some(user) = self.user {
1529 builder = builder.user(user);
1530 }
1531
1532 if let Some(pass) = self.pass {
1533 builder = builder.pass(pass);
1534 }
1535
1536 if let Some(db_name) = self.db_name {
1537 builder = builder.db_name(db_name);
1538 }
1539
1540 *opts = Opts::from(builder);
1541 }
1542
1543 pub fn new() -> Self {
1545 Self {
1546 user: None,
1547 pass: None,
1548 db_name: None,
1549 }
1550 }
1551
1552 pub fn with_user(mut self, user: Option<String>) -> Self {
1554 self.user = Some(user);
1555 self
1556 }
1557
1558 pub fn with_pass(mut self, pass: Option<String>) -> Self {
1560 self.pass = Some(pass);
1561 self
1562 }
1563
1564 pub fn with_db_name(mut self, db_name: Option<String>) -> Self {
1566 self.db_name = Some(db_name);
1567 self
1568 }
1569
1570 pub fn user(&self) -> Option<Option<&str>> {
1576 self.user.as_ref().map(|x| x.as_deref())
1577 }
1578
1579 pub fn pass(&self) -> Option<Option<&str>> {
1585 self.pass.as_ref().map(|x| x.as_deref())
1586 }
1587
1588 pub fn db_name(&self) -> Option<Option<&str>> {
1594 self.db_name.as_ref().map(|x| x.as_deref())
1595 }
1596}
1597
1598impl Default for ChangeUserOpts {
1599 fn default() -> Self {
1600 Self::new()
1601 }
1602}
1603
1604impl fmt::Debug for ChangeUserOpts {
1605 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1606 f.debug_struct("ChangeUserOpts")
1607 .field("user", &self.user)
1608 .field(
1609 "pass",
1610 &self.pass.as_ref().map(|x| x.as_ref().map(|_| "...")),
1611 )
1612 .field("db_name", &self.db_name)
1613 .finish()
1614 }
1615}
1616
1617fn get_opts_user_from_url(url: &Url) -> Option<String> {
1618 let user = url.username();
1619 if !user.is_empty() {
1620 Some(
1621 percent_decode(user.as_ref())
1622 .decode_utf8_lossy()
1623 .into_owned(),
1624 )
1625 } else {
1626 None
1627 }
1628}
1629
1630fn get_opts_pass_from_url(url: &Url) -> Option<String> {
1631 url.password().map(|pass| {
1632 percent_decode(pass.as_ref())
1633 .decode_utf8_lossy()
1634 .into_owned()
1635 })
1636}
1637
1638fn get_opts_db_name_from_url(url: &Url) -> Option<String> {
1639 if let Some(mut segments) = url.path_segments() {
1640 segments
1641 .next()
1642 .map(|db_name| {
1643 percent_decode(db_name.as_ref())
1644 .decode_utf8_lossy()
1645 .into_owned()
1646 })
1647 .filter(|db| !db.is_empty())
1648 } else {
1649 None
1650 }
1651}
1652
1653fn from_url_basic(url: &Url) -> std::result::Result<(MysqlOpts, Vec<(String, String)>), UrlError> {
1654 if url.scheme() != "mysql" {
1655 return Err(UrlError::UnsupportedScheme {
1656 scheme: url.scheme().to_string(),
1657 });
1658 }
1659 if url.cannot_be_a_base() || !url.has_host() {
1660 return Err(UrlError::Invalid);
1661 }
1662 let user = get_opts_user_from_url(url);
1663 let pass = get_opts_pass_from_url(url);
1664 let db_name = get_opts_db_name_from_url(url);
1665
1666 let query_pairs = url.query_pairs().into_owned().collect();
1667 let opts = MysqlOpts {
1668 user,
1669 pass,
1670 db_name,
1671 ..MysqlOpts::default()
1672 };
1673
1674 Ok((opts, query_pairs))
1675}
1676
1677fn mysqlopts_from_url(url: &Url) -> std::result::Result<MysqlOpts, UrlError> {
1678 let (mut opts, query_pairs): (MysqlOpts, _) = from_url_basic(url)?;
1679 let mut pool_min = DEFAULT_POOL_CONSTRAINTS.min;
1680 let mut pool_max = DEFAULT_POOL_CONSTRAINTS.max;
1681
1682 let mut ssl_opts = None;
1683 let mut skip_domain_validation = false;
1684 let mut accept_invalid_certs = false;
1685 let mut disable_built_in_roots = false;
1686
1687 for (key, value) in query_pairs {
1688 if key == "pool_min" {
1689 match usize::from_str(&value) {
1690 Ok(value) => pool_min = value,
1691 _ => {
1692 return Err(UrlError::InvalidParamValue {
1693 param: "pool_min".into(),
1694 value,
1695 });
1696 }
1697 }
1698 } else if key == "pool_max" {
1699 match usize::from_str(&value) {
1700 Ok(value) => pool_max = value,
1701 _ => {
1702 return Err(UrlError::InvalidParamValue {
1703 param: "pool_max".into(),
1704 value,
1705 });
1706 }
1707 }
1708 } else if key == "inactive_connection_ttl" {
1709 match u64::from_str(&value) {
1710 Ok(value) => {
1711 opts.pool_opts = opts
1712 .pool_opts
1713 .with_inactive_connection_ttl(Duration::from_secs(value))
1714 }
1715 _ => {
1716 return Err(UrlError::InvalidParamValue {
1717 param: "inactive_connection_ttl".into(),
1718 value,
1719 });
1720 }
1721 }
1722 } else if key == "ttl_check_interval" {
1723 match u64::from_str(&value) {
1724 Ok(value) => {
1725 opts.pool_opts = opts
1726 .pool_opts
1727 .with_ttl_check_interval(Duration::from_secs(value))
1728 }
1729 _ => {
1730 return Err(UrlError::InvalidParamValue {
1731 param: "ttl_check_interval".into(),
1732 value,
1733 });
1734 }
1735 }
1736 } else if key == "conn_ttl" {
1737 match u64::from_str(&value) {
1738 Ok(value) => opts.conn_ttl = Some(Duration::from_secs(value)),
1739 _ => {
1740 return Err(UrlError::InvalidParamValue {
1741 param: "conn_ttl".into(),
1742 value,
1743 });
1744 }
1745 }
1746 } else if key == "abs_conn_ttl" {
1747 match u64::from_str(&value) {
1748 Ok(value) => {
1749 opts.pool_opts = opts
1750 .pool_opts
1751 .with_abs_conn_ttl(Some(Duration::from_secs(value)))
1752 }
1753 _ => {
1754 return Err(UrlError::InvalidParamValue {
1755 param: "abs_conn_ttl".into(),
1756 value,
1757 });
1758 }
1759 }
1760 } else if key == "abs_conn_ttl_jitter" {
1761 match u64::from_str(&value) {
1762 Ok(value) => {
1763 opts.pool_opts = opts
1764 .pool_opts
1765 .with_abs_conn_ttl_jitter(Some(Duration::from_secs(value)))
1766 }
1767 _ => {
1768 return Err(UrlError::InvalidParamValue {
1769 param: "abs_conn_ttl_jitter".into(),
1770 value,
1771 });
1772 }
1773 }
1774 } else if key == "tcp_keepalive" {
1775 match u32::from_str(&value) {
1776 Ok(value) => opts.tcp_keepalive = Some(value),
1777 _ => {
1778 return Err(UrlError::InvalidParamValue {
1779 param: "tcp_keepalive_ms".into(),
1780 value,
1781 });
1782 }
1783 }
1784 } else if key == "max_allowed_packet" {
1785 match usize::from_str(&value) {
1786 Ok(value) => opts.max_allowed_packet = Some(value.clamp(1024, 1073741824)),
1787 _ => {
1788 return Err(UrlError::InvalidParamValue {
1789 param: "max_allowed_packet".into(),
1790 value,
1791 });
1792 }
1793 }
1794 } else if key == "wait_timeout" {
1795 match usize::from_str(&value) {
1796 #[cfg(windows)]
1797 Ok(value) => opts.wait_timeout = Some(std::cmp::min(2147483, value)),
1798 #[cfg(not(windows))]
1799 Ok(value) => opts.wait_timeout = Some(std::cmp::min(31536000, value)),
1800 _ => {
1801 return Err(UrlError::InvalidParamValue {
1802 param: "wait_timeout".into(),
1803 value,
1804 });
1805 }
1806 }
1807 } else if key == "enable_cleartext_plugin" {
1808 match bool::from_str(&value) {
1809 Ok(parsed) => opts.enable_cleartext_plugin = parsed,
1810 Err(_) => {
1811 return Err(UrlError::InvalidParamValue {
1812 param: key.to_string(),
1813 value,
1814 });
1815 }
1816 }
1817 } else if key == "reset_connection" {
1818 match bool::from_str(&value) {
1819 Ok(parsed) => opts.pool_opts = opts.pool_opts.with_reset_connection(parsed),
1820 Err(_) => {
1821 return Err(UrlError::InvalidParamValue {
1822 param: key.to_string(),
1823 value,
1824 });
1825 }
1826 }
1827 } else if key == "tcp_nodelay" {
1828 match bool::from_str(&value) {
1829 Ok(value) => opts.tcp_nodelay = value,
1830 _ => {
1831 return Err(UrlError::InvalidParamValue {
1832 param: "tcp_nodelay".into(),
1833 value,
1834 });
1835 }
1836 }
1837 } else if key == "stmt_cache_size" {
1838 match usize::from_str(&value) {
1839 Ok(stmt_cache_size) => {
1840 opts.stmt_cache_size = stmt_cache_size;
1841 }
1842 _ => {
1843 return Err(UrlError::InvalidParamValue {
1844 param: "stmt_cache_size".into(),
1845 value,
1846 });
1847 }
1848 }
1849 } else if key == "prefer_socket" {
1850 match bool::from_str(&value) {
1851 Ok(prefer_socket) => {
1852 opts.prefer_socket = prefer_socket;
1853 }
1854 _ => {
1855 return Err(UrlError::InvalidParamValue {
1856 param: "prefer_socket".into(),
1857 value,
1858 });
1859 }
1860 }
1861 } else if key == "secure_auth" {
1862 match bool::from_str(&value) {
1863 Ok(secure_auth) => {
1864 opts.secure_auth = secure_auth;
1865 }
1866 _ => {
1867 return Err(UrlError::InvalidParamValue {
1868 param: "secure_auth".into(),
1869 value,
1870 });
1871 }
1872 }
1873 } else if key == "client_found_rows" {
1874 match bool::from_str(&value) {
1875 Ok(client_found_rows) => {
1876 opts.client_found_rows = client_found_rows;
1877 }
1878 _ => {
1879 return Err(UrlError::InvalidParamValue {
1880 param: "client_found_rows".into(),
1881 value,
1882 });
1883 }
1884 }
1885 } else if key == "socket" {
1886 opts.socket = Some(value)
1887 } else if key == "compression" {
1888 if value == "fast" {
1889 opts.compression = Some(crate::Compression::fast());
1890 } else if value == "on" || value == "true" {
1891 opts.compression = Some(crate::Compression::default());
1892 } else if value == "best" {
1893 opts.compression = Some(crate::Compression::best());
1894 } else if value.len() == 1 && 0x30 <= value.as_bytes()[0] && value.as_bytes()[0] <= 0x39
1895 {
1896 opts.compression =
1897 Some(crate::Compression::new((value.as_bytes()[0] - 0x30) as u32));
1898 } else {
1899 return Err(UrlError::InvalidParamValue {
1900 param: "compression".into(),
1901 value,
1902 });
1903 }
1904 } else if key == "require_ssl" {
1905 match bool::from_str(&value) {
1906 Ok(x) => {
1907 ssl_opts = x.then(SslOpts::default);
1908 }
1909 _ => {
1910 return Err(UrlError::InvalidParamValue {
1911 param: "require_ssl".into(),
1912 value,
1913 });
1914 }
1915 }
1916 } else if key == "verify_ca" {
1917 match bool::from_str(&value) {
1918 Ok(x) => {
1919 accept_invalid_certs = !x;
1920 }
1921 _ => {
1922 return Err(UrlError::InvalidParamValue {
1923 param: "verify_ca".into(),
1924 value,
1925 });
1926 }
1927 }
1928 } else if key == "verify_identity" {
1929 match bool::from_str(&value) {
1930 Ok(x) => {
1931 skip_domain_validation = !x;
1932 }
1933 _ => {
1934 return Err(UrlError::InvalidParamValue {
1935 param: "verify_identity".into(),
1936 value,
1937 });
1938 }
1939 }
1940 } else if key == "built_in_roots" {
1941 match bool::from_str(&value) {
1942 Ok(x) => {
1943 disable_built_in_roots = !x;
1944 }
1945 _ => {
1946 return Err(UrlError::InvalidParamValue {
1947 param: "built_in_roots".into(),
1948 value,
1949 });
1950 }
1951 }
1952 } else {
1953 return Err(UrlError::UnknownParameter { param: key });
1954 }
1955 }
1956
1957 if let Some(pool_constraints) = PoolConstraints::new(pool_min, pool_max) {
1958 opts.pool_opts = opts.pool_opts.with_constraints(pool_constraints);
1959 } else {
1960 return Err(UrlError::InvalidPoolConstraints {
1961 min: pool_min,
1962 max: pool_max,
1963 });
1964 }
1965
1966 if let Some(ref mut ssl_opts) = ssl_opts {
1967 ssl_opts.accept_invalid_certs = accept_invalid_certs;
1968 ssl_opts.skip_domain_validation = skip_domain_validation;
1969 ssl_opts.disable_built_in_roots = disable_built_in_roots;
1970 }
1971
1972 opts.ssl_opts = ssl_opts.map(SslOptsAndCachedConnector::new);
1973
1974 Ok(opts)
1975}
1976
1977impl FromStr for Opts {
1978 type Err = UrlError;
1979
1980 fn from_str(s: &str) -> std::result::Result<Self, <Self as FromStr>::Err> {
1981 Opts::from_url(s)
1982 }
1983}
1984
1985impl TryFrom<&str> for Opts {
1986 type Error = UrlError;
1987
1988 fn try_from(s: &str) -> std::result::Result<Self, UrlError> {
1989 Opts::from_url(s)
1990 }
1991}
1992
1993#[cfg(test)]
1994mod test {
1995 use super::{HostPortOrUrl, MysqlOpts, Opts, Url};
1996 use crate::{error::UrlError::InvalidParamValue, SslOpts};
1997
1998 use std::{net::IpAddr, net::Ipv4Addr, net::Ipv6Addr, str::FromStr};
1999
2000 #[test]
2001 fn test_builder_eq_url() {
2002 const URL: &str = "mysql://iq-controller@localhost/iq_controller";
2003
2004 let url_opts = super::Opts::from_str(URL).unwrap();
2005 let builder = super::OptsBuilder::default()
2006 .user(Some("iq-controller"))
2007 .ip_or_hostname("localhost")
2008 .db_name(Some("iq_controller"));
2009 let builder_opts = Opts::from(builder);
2010
2011 assert_eq!(url_opts.addr_is_loopback(), builder_opts.addr_is_loopback());
2012 assert_eq!(url_opts.ip_or_hostname(), builder_opts.ip_or_hostname());
2013 assert_eq!(url_opts.tcp_port(), builder_opts.tcp_port());
2014 assert_eq!(url_opts.user(), builder_opts.user());
2015 assert_eq!(url_opts.pass(), builder_opts.pass());
2016 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2017 assert_eq!(url_opts.init(), builder_opts.init());
2018 assert_eq!(url_opts.setup(), builder_opts.setup());
2019 assert_eq!(url_opts.tcp_keepalive(), builder_opts.tcp_keepalive());
2020 assert_eq!(url_opts.tcp_nodelay(), builder_opts.tcp_nodelay());
2021 assert_eq!(url_opts.pool_opts(), builder_opts.pool_opts());
2022 assert_eq!(url_opts.conn_ttl(), builder_opts.conn_ttl());
2023 assert_eq!(url_opts.abs_conn_ttl(), builder_opts.abs_conn_ttl());
2024 assert_eq!(
2025 url_opts.abs_conn_ttl_jitter(),
2026 builder_opts.abs_conn_ttl_jitter()
2027 );
2028 assert_eq!(url_opts.stmt_cache_size(), builder_opts.stmt_cache_size());
2029 assert_eq!(url_opts.ssl_opts(), builder_opts.ssl_opts());
2030 assert_eq!(url_opts.prefer_socket(), builder_opts.prefer_socket());
2031 assert_eq!(url_opts.socket(), builder_opts.socket());
2032 assert_eq!(url_opts.compression(), builder_opts.compression());
2033 assert_eq!(
2034 url_opts.hostport_or_url().get_ip_or_hostname(),
2035 builder_opts.hostport_or_url().get_ip_or_hostname()
2036 );
2037 assert_eq!(
2038 url_opts.hostport_or_url().get_tcp_port(),
2039 builder_opts.hostport_or_url().get_tcp_port()
2040 );
2041 }
2042
2043 #[test]
2044 fn should_convert_url_into_opts() {
2045 let url = "mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true";
2046 let parsed_url =
2047 Url::parse("mysql://usr:pw@192.168.1.1:3309/dbname?prefer_socket=true").unwrap();
2048
2049 let mysql_opts = MysqlOpts {
2050 user: Some("usr".to_string()),
2051 pass: Some("pw".to_string()),
2052 db_name: Some("dbname".to_string()),
2053 prefer_socket: true,
2054 ..MysqlOpts::default()
2055 };
2056 let host = HostPortOrUrl::Url(parsed_url);
2057
2058 let opts = Opts::from_url(url).unwrap();
2059
2060 assert_eq!(opts.inner.mysql_opts, mysql_opts);
2061 assert_eq!(opts.hostport_or_url(), &host);
2062 }
2063
2064 #[test]
2065 fn should_convert_ipv6_url_into_opts() {
2066 let url = "mysql://usr:pw@[::1]:3309/dbname";
2067
2068 let opts = Opts::from_url(url).unwrap();
2069
2070 assert_eq!(opts.ip_or_hostname(), "[::1]");
2071 }
2072
2073 #[test]
2074 fn should_parse_ssl_params() {
2075 const URL1: &str = "mysql://localhost/foo?require_ssl=false";
2076 let opts = Opts::from_url(URL1).unwrap();
2077 assert_eq!(opts.ssl_opts(), None);
2078
2079 const URL2: &str = "mysql://localhost/foo?require_ssl=true";
2080 let opts = Opts::from_url(URL2).unwrap();
2081 assert_eq!(opts.ssl_opts(), Some(&SslOpts::default()));
2082
2083 const URL3: &str = "mysql://localhost/foo?require_ssl=true&verify_ca=false";
2084 let opts = Opts::from_url(URL3).unwrap();
2085 assert_eq!(
2086 opts.ssl_opts(),
2087 Some(&SslOpts::default().with_danger_accept_invalid_certs(true))
2088 );
2089
2090 const URL4: &str =
2091 "mysql://localhost/foo?require_ssl=true&verify_ca=false&verify_identity=false&built_in_roots=false";
2092 let opts = Opts::from_url(URL4).unwrap();
2093 assert_eq!(
2094 opts.ssl_opts(),
2095 Some(
2096 &SslOpts::default()
2097 .with_danger_accept_invalid_certs(true)
2098 .with_danger_skip_domain_validation(true)
2099 .with_disable_built_in_roots(true)
2100 )
2101 );
2102
2103 const URL5: &str =
2104 "mysql://localhost/foo?require_ssl=false&verify_ca=false&verify_identity=false";
2105 let opts = Opts::from_url(URL5).unwrap();
2106 assert_eq!(opts.ssl_opts(), None);
2107 }
2108
2109 #[test]
2110 #[should_panic]
2111 fn should_panic_on_invalid_url() {
2112 let opts = "42";
2113 let _: Opts = Opts::from_str(opts).unwrap();
2114 }
2115
2116 #[test]
2117 #[should_panic]
2118 fn should_panic_on_invalid_scheme() {
2119 let opts = "postgres://localhost";
2120 let _: Opts = Opts::from_str(opts).unwrap();
2121 }
2122
2123 #[test]
2124 #[should_panic]
2125 fn should_panic_on_unknown_query_param() {
2126 let opts = "mysql://localhost/foo?bar=baz";
2127 let _: Opts = Opts::from_str(opts).unwrap();
2128 }
2129
2130 #[test]
2131 fn should_parse_compression() {
2132 let err = Opts::from_url("mysql://localhost/foo?compression=").unwrap_err();
2133 assert_eq!(
2134 err,
2135 InvalidParamValue {
2136 param: "compression".into(),
2137 value: "".into()
2138 }
2139 );
2140
2141 let err = Opts::from_url("mysql://localhost/foo?compression=a").unwrap_err();
2142 assert_eq!(
2143 err,
2144 InvalidParamValue {
2145 param: "compression".into(),
2146 value: "a".into()
2147 }
2148 );
2149
2150 let opts = Opts::from_url("mysql://localhost/foo?compression=fast").unwrap();
2151 assert_eq!(opts.compression(), Some(crate::Compression::fast()));
2152
2153 let opts = Opts::from_url("mysql://localhost/foo?compression=on").unwrap();
2154 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2155
2156 let opts = Opts::from_url("mysql://localhost/foo?compression=true").unwrap();
2157 assert_eq!(opts.compression(), Some(crate::Compression::default()));
2158
2159 let opts = Opts::from_url("mysql://localhost/foo?compression=best").unwrap();
2160 assert_eq!(opts.compression(), Some(crate::Compression::best()));
2161
2162 let opts = Opts::from_url("mysql://localhost/foo?compression=0").unwrap();
2163 assert_eq!(opts.compression(), Some(crate::Compression::new(0)));
2164
2165 let opts = Opts::from_url("mysql://localhost/foo?compression=9").unwrap();
2166 assert_eq!(opts.compression(), Some(crate::Compression::new(9)));
2167 }
2168
2169 #[test]
2170 fn test_builder_eq_url_empty_db() {
2171 let builder = super::OptsBuilder::default();
2172 let builder_opts = Opts::from(builder);
2173
2174 let url: &str = "mysql://iq-controller@localhost";
2175 let url_opts = super::Opts::from_str(url).unwrap();
2176 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2177
2178 let url: &str = "mysql://iq-controller@localhost/";
2179 let url_opts = super::Opts::from_str(url).unwrap();
2180 assert_eq!(url_opts.db_name(), builder_opts.db_name());
2181 }
2182
2183 #[test]
2184 fn test_builder_update_port_host_resolved_ips() {
2185 let builder = super::OptsBuilder::default()
2186 .ip_or_hostname("foo")
2187 .tcp_port(33306);
2188
2189 let resolved = vec![
2190 IpAddr::V4(Ipv4Addr::new(127, 0, 0, 7)),
2191 IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0xffff, 0xc00a, 0x2ff)),
2192 ];
2193 let builder2 = builder
2194 .clone()
2195 .tcp_port(55223)
2196 .resolved_ips(Some(resolved.clone()));
2197
2198 let builder_opts = Opts::from(builder);
2199 assert_eq!(builder_opts.ip_or_hostname(), "foo");
2200 assert_eq!(builder_opts.tcp_port(), 33306);
2201 assert_eq!(
2202 builder_opts.hostport_or_url(),
2203 &HostPortOrUrl::HostPort {
2204 host: "foo".to_string(),
2205 port: 33306,
2206 resolved_ips: None
2207 }
2208 );
2209
2210 let builder_opts2 = Opts::from(builder2);
2211 assert_eq!(builder_opts2.ip_or_hostname(), "foo");
2212 assert_eq!(builder_opts2.tcp_port(), 55223);
2213 assert_eq!(
2214 builder_opts2.hostport_or_url(),
2215 &HostPortOrUrl::HostPort {
2216 host: "foo".to_string(),
2217 port: 55223,
2218 resolved_ips: Some(resolved),
2219 }
2220 );
2221 }
2222}